When Notion shipped Formulas 2.0, it included support for Lists (or Arrays in programming parlance). One of the functions included for working with lists is the sort
function. Unlike JavaScript’s sort
function, Notion’s sort
initially only sorted the list in a very specific way.
[3, 1, 2].sort() /* => [1, 2, 3] */
The rules for sorting in Notion are a bit complex, but here’s the general gist (note that this definitely differs from how things are sorted in JavaScript!):
- Smaller numbers will come before larger ones
- Earlier dates will come before later ones
- In general strings are sorted alphabetically, but there are some confusing rules here (see a couple examples below)
"[A-Z]"
(uppercase) comes after "[a-z]"
(lowercase), in alphabetical order.
This one’s a bit confusing. Here’s an example to showcase:
["B", "A", "b", "a"].sort() /* => ["a", "A", "b", "B"] */
If you’re coming from JavaScript, this is going to seem a bit off to you. Here’s the same call in JavaScript. Note the difference:
["B", "A", "b", "a"].sort() /* => ['A', 'B', 'a', 'b'] */
In Notion “a’s” come before “b’s”, no matter the case. In JavaScript, uppers come before lowers. You can imagine how this might bite you in the bum if you’re a .js dev and expecting similar in Notion.
"0"
(the string representation of the number 0
) is treated as a number:
["a", "B", "0", "A", 1, 2].sort() /* => ["0", 1, 2, "a", "A", "B"]
If you mix property types, you may have some pretty unexpected results with sorting. I think some of these behaviours may be bugs, so heads up! In general you mostly want to only sort lists of the same type (numbers only or letters only).
Since by default the lists were sorted by the above rules, this meant that in order to sort a list in the opposite direction, we had to do something like this:
[3, 1, 2].sort().reverse() /* => [3, 2, 1] */
However, as of early December, you can now use an expression passed to sort
to modify your sorting. In this case, sort
receives what’s referred to as the “current context” which is an expression in which you can utilize two variables:
current
— the current item being iterated over in the listindex
— the index of the item being iterated over in the list starting from0
and counting up
The code inside the ()
will be run on each item in the list, meaning that, in the following example, current
will be first 3, then 1, and finally, 2.
[3, 1, 2].sort(current * -1) /* [3, 2, 1] */
In the above example, we’re multiplying each item in the list by -1
, and that return value is what is used to compare and sort the original list. In effect, this means that the resulting list is sorted in descending order.
Sorting Relations
Originally, when sort
was used with a relation property (which also happen to be lists), the relation would be sorted alphabetically using the primary Name
field of the page as comparison. But what if you wanted to sort by a property defined on the relation?
Early on, the Notion community figured out a way to work around this. You can see an early example of this I worked on with Thomas Frank in this Sort by Relation property solve. He’s since iterated on this example to a wild conclusion, but here’s the original simple example that sorts the related Videos
related to a Channel page by a Views
property defined on the Videos database.
/* Start with the related videos */
prop("Videos")
/* Find all the views for each video */
.map(current.prop("Views"))
/* Sort them... */
.sort()
/* ...in descending order */
.reverse()
/* Look up the Video page by view */
.map(
/* Set a temporary "views" variable to avoid "current" clash */
lets(
views,
current,
prop("Videos").find(current.prop("Views") == views)
)
)
To make the cool styled visual code shown on the right (and further expressed in Thomas’s tweet), Thomas used the same code as in Sorted Videos
and added the replace
calls to the views to format the number with commas (Wouldn’t it be nice if Notion added a formatter to formulas?! :))
This allows you to style
the number itself, highlighting the video with the most views.
/* Previous code */
prop("Videos")
.map(current.prop("Views"))
.sort()
.reverse()
.map(
lets(
views,
current,
prop("Videos").find(current.prop("Views") == views)
)
)
/* New code */
.map(
/* Use "red" as the color if it's the first item in the list */
lets(color, index == 0 ? "red" : "",
/* Add the name of the video and open paranthesis */
current + " (" +
/* Use "replace" to format the number with commas */
current.prop("Views")
.replace("(\d{1})(\d{3})$","$1,$2")
.replace("(\d{1})(\d{3},\d{3})$","$1,$2")
/* Style the number red if first item */
.style("b", color) +
/* Closing paranthesis */
")"
)
)
Again, here’s how this looks. In the left version, you’ll note that since we just have a list of pages, they are displayed looking similar to a relation property.
Whereas on the right, we see that they have been turned into “text” and represented as a link to the page. In both cases, they still link to the page. Anytime we use a page reference as text like this (for example, concatenating it together with a string), it will automatically be converted into an inline link.
Of course, these formulas are clunky. But! Now that we can pass an expression to sort
and not have to rely on that map
/find
combo, this is wildly simplified!
Actually sorting the Video relation by Views descending is now as simple as:
prop("Videos").sort(current.prop("Views") * -1)
Wowza! 🤩
Here we’re using the same method to sort a [3, 1, 2]
array by multiplying by -1
, except in this case, we’re multiplying the current
page’s View
property by -1
. The original list is organized by this number in turn. Here we can see the same outcome as our previous versions with much less code.
Pretty sweet! It’s nice to see Notion continuing to iterate to make Formulas 2.0 feel more polished.
Formula Fundamentals
Thanks for reading along! If you want to go deeper on topics like this, you can check out the Formula Fundamentals course (which of course I just updated to document the new sort
functionality).
We also just added a new Changelog to the course where we’ll be posting updates to the course as well as notes about what’s changed about Formulas over time in general.