Skip to content
notionsolutions

A practical library for building,
organizing and working in Notion.

Formula guides

Notion prop() in Formulas 2.0: Tokens, Relations and current.prop()

prop() still works in Formulas 2.0: the editor shows it as a property token and copies it back out as prop() text. What each property type hands a formula, seven relation patterns with current.prop(), when a rollup is enough, and fixes for the list and automation errors prop() runs into.

Published
One blue frosted-glass tile in a steel holder joined by five steel rods to five white ceramic plates in a graphite rack, each plate topped with a small blue glass bead
One prop() reference reads one property; through a relation it reaches the same property on every related page.

prop() still works in Notion Formulas 2.0. Type prop("Due Date") into the formula editor and it turns into a Due Date property token; copy a formula out of the editor and the tokens come back as prop() text. On a relation, prop("Tasks") hands the formula a list of related pages rather than their values, so prop("Tasks").map(current.prop("Status")) returns every related task's status and prop("Tasks").first().prop("Status") returns the first one's.

While you build, I'd pick properties from the editor's list instead of typing their names, because Notion matches names exactly and a selected property can't be misspelled. When a formula leaves the editor for a doc, a template note or a message, paste the prop() version, because that's the text that survives copy and paste. Both forms point at the same property: Notion's formula syntax reference writes a project's task count as prop("Tasks").length(), and its formulas help page writes the same count as Tasks.length().

prop() or a token: two ways to write the same reference

A property gets into a formula in one of three ways. Thomas Frank's page on referencing properties in formulas describes the first two: select the property from the Properties menu in the formula editor, or start typing its name to filter that menu and press Enter when the right one is highlighted. The third is to type prop("Name") yourself, with the property name in quotes.

Whichever route you take, the editor shows a token. Ben Borowski's walkthrough of everything new in Formulas 2.0 on Notion Mastery describes property tokens rendered with a grey background, and says a prop reference you type in the editor is “immediately translated into a property token”. The trip runs the other way too: when you copy property tokens out of the editor, your clipboard gets prop references. Frank says the same about pasting a formula anywhere outside the editor, and his formula reference keeps prop() in its examples for that reason: they paste straight into the new editor.

Open the editor from a database row and Notion's formulas help says you'll see a live preview of the result for that row. I'd use that preview as the first check on any reference: open the editor from a row whose values you know, and a wrong token shows up as a wrong result before you save.

Type the name exactly as the property has it

A typed reference has to match the property name character for character. Notion's guide to writing formulas says the editor is case sensitive and “will only recognize inputs that are exactly the same”, and its advice is to type the name exactly as it is in the database or select it from the menu. The guide's own tax example shows how literal that is:

prop("Net ") * prop("Tax (%)")

The space after Net and the percent sign in parentheses sit inside the quotes because they belong to those property names. A formula copied from a tutorial brings the author's property names with it, so when a pasted prop() formula shows an error as you click save, compare each quoted name with your own properties before you change anything else.

Dot notation works on references

Formulas 2.0 also lets you call a function on a property with a dot instead of passing the property in. Notion's writing guide shows both forms for one person property: length(prop("Upvoted by")) or prop("Upvoted by").length(). Borowski's example puts date(Date), with Date as a token, next to Date.date(), the same function called two ways, and he points out that the dot form makes it harder to forget a closing parenthesis. I'd write the dot form for anything longer than one step, because each function then reads left to right after the property instead of wrapping it in another pair of parentheses.

Three-step diagram: prop("Due Date") > now() typed as text, shown in the formula editor as a grey Due Date chip followed by > now(), then copied out again as prop("Due Date") > now(); both forms point at the same property
Typing prop() gives you a token; copying the token gives you prop() text again.

What prop() returns for each property type

The property's type decides what the formula receives, and that decides which function you can call next. Notion's syntax reference opens its property table by saying formulas support almost all property types and that “formula types are different from property types”. The table below follows that reference; where it says nothing, the row cites the property table on Frank's reference page.

What a formula receives from prop() for each property type, with a pattern that fits
Property typeWhat the formula getsPatternSource
Title, TextTextprop("Title").length()Notion's property table; Frank lists Title as a string
NumberNumberprop("Number") / 2Notion's property table
SelectOne option, compared with ==prop("Priority") == "High"Notion's property table
StatusText, even when Status is displayed as a checkboxprop("Status") != "Done"Frank's property table; Notion's formulas help compares Status with "Done"
Multi-selectA list of textprop("Tags").includes("Finance")Notion's property table: Text (list)
CheckboxBooleannot prop("Checkbox")Notion's property table
Date, Created time, Last edited timeDateprop("Due Date") > now(); for a range, dateEnd(prop("Date range"))Notion's property table and dateEnd entry
PersonA list of peopleprop("Person").at(0).name()Notion's property table: Person (list)
Created by, Edited byOne personprop("Created By").name()Notion's property table; Frank: “a single Person data type”
Email, URL, PhoneA value you can test or build a link from!empty(prop("Phone"))Notion's property table
Unique IDAn ID you split on the hyphen for its prefix or numberprop("Task ID").split("-").last()Notion's property table
RelationA list of pagesprop("Tasks").filter(current.prop("Status") !== "Done")Notion's property table: Page (list)
RollupA number, a date or a list, depending on the rollup's configurationprop("Purchases").length()Notion's property table
Files & mediaA list of file URLsprop("Files").length()Frank's property table

Five rows hand you a list: Multi-select, Person, Relation, Files & media, and any rollup configured to return one. A list needs a list function before it gives you a single value, which is where most prop() surprises start. When a value looks like a date but behaves like text or a list, the guide to date errors in Notion formulas traces the input type for that case.

prop() on a relation: patterns for Projects and Tasks

A formula can read properties on related pages without a rollup. Notion's guide to what changed in the new formulas says formulas “can reach into properties of related databases”, and its Projects and Tasks example works out the share of completed tasks from the relation without creating a rollup first. The catch is the type. Frank's map() reference explains that Relation properties “always contain a list of pages (even when there's only one element)”, so you either pick pages with first, last or at, or use map() to reach the properties on every page.

Inside map(), filter(), every() and the other list functions, current is the related page being processed at that moment. With a Projects database related to Tasks, these are the patterns I'd reach for, written from the project's side:

  1. Count related tasks. prop("Tasks").length() is the first example in Notion's Relation row.
  2. List every task's status. prop("Tasks").map(current.prop("Status")) returns one status per page; Frank's sample output for it reads [To Do, Done, To Do, Doing, Done].
  3. Keep the open tasks. prop("Tasks").filter(current.prop("Status") !== "Done") returns the pages whose Status isn't Done. Add .length() to count them.
  4. Filter on a date in the related page. Notion's writing guide uses prop("Meetings").filter(current.prop("Date") > now()) to keep the meetings still ahead.
  5. Add up a number. prop("Sessions").map(current.prop("Duration")).sum() is Frank's formula for the total minutes tracked on a task across many work sessions. If you don't log sessions yet, the Notion time tracking guide sets up a Time entries database with a minutes formula on each entry, the kind of number this pattern totals.
  6. Read one page. prop("Tasks").first().prop("Status") reads Status on the first related page. .at(0) does the same job, and both are the functions Notion's errors page names for getting the first element.
  7. Check all or any. prop("Tasks").every(current.prop("Status") == "Done") is true only when every related task is Done. .some() is true when at least one page matches, and .find() returns the first page that does.

The first-page pattern still applies to a relation limited to one page. Notion's relations and rollups help lets you limit a relation to 1 page, so people can select only one, while the syntax reference's property table lists one type for Relation, Page (list), with no exception for that setting. I'd keep .first() in the formula for that reason: the property holds one page, and the formula still receives it inside a list.

Notion writes the lookup on current two ways. The syntax reference uses current.prop("Status"), and an automation example in the formulas help uses the token form, Parent Task.Sub-item.every(current.Status == "Done"). Neither page describes a difference between them. In a formula you'll share, I'd write current.prop("Status"), because it matches the examples in the syntax reference.

Flow diagram: the Website refresh project's Tasks relation becomes a list of five task pages; map(current.prop("Status")) turns it into five statuses, and filter on Status not Done plus length() turns it into the number 3
A relation gives the formula pages; map() and filter() turn them into the values you want.

Percent complete in one formula

Notion's 2.0 guide builds a progress label from the Tasks relation, with the percentage stored in a let() variable. A shorter version returns the number on its own, with lets() naming the two counts so each is written once:

lets(
  total, prop("Tasks").length(),
  done, prop("Tasks").filter(current.prop("Status") == "Done").length(),
  if(total == 0, 0, round(done / total * 100))
)

The if() gives a project with no tasks a 0 instead of a division by zero. Naming rules and scope are covered in the guide to let() and lets(), and the if() and ifs() guide turns the same counts into a status label.

Formula or rollup for related data?

Use a rollup when one built-in calculation answers the question, and a formula when the answer needs a condition or arithmetic. Notion's relations help lists what a rollup can calculate: Show original and Show unique values; Count all, Count values, Count unique values, Count empty and Count not empty; Percent empty and Percent not empty; Sum, Average, Median, Min, Max and Range for numbers; Earliest date, Latest date and Date range for dates. A project's total estimate or its latest due date fits a rollup. Open tasks only, or minutes on unfinished work, is formula work, because the condition sits on a property of the related pages and none of the rollup menu's calculations takes a condition.

For a client tracker, I'd start with rollups for plain totals and switch a column to a formula only when a condition appears, because a rollup's calculation is picked from a menu, so there's no formula text to mistype. Three limits shape that choice:

The guide to Notion relations and rollups sets up the client and project relations these formulas sit on. When a rollup shows nothing, the empty-rollup checklist traces it from the source value; when its total looks wrong, reconciling a wrong rollup sum compares it with the linked records.

Multi-select, Person and Files values are lists too

A relation isn't the only property that gives a formula a list. Multi-select arrives as a list of text and Person as a list of people, which changes how you test and display both. For a tag check, use .includes():

prop("Tags").includes("Urgent")

Writing prop("Tags") == "Urgent" compares the whole list with one word. Notion's common formula errors page gives the fix for automations directly: when a condition runs on a multi-select, “use .includes instead of == in your if() statement”. I'd write .includes() in formula properties too, because the same list arrives there and one habit is easier to keep than two.

People follow the same rule. prop("Person").at(0).name() returns the first assigned person's name, and prop("Person").map(current.email()) returns every email. Notion's reference joins names into one line with prop("Pioneers").map(name(current)).join(", "), which gives "Grace Hopper, Ada Lovelace". Created by and Edited by are the exception: prop("Created By").name() reads the name without at(0), and Frank's table calls Created by “a single Person data type”. Files & media is a list as well, which Frank's table describes as “a list of string URLs for the assets”.

Formulas written before 2.0 can carry an extra conversion. Notion's 2.0 guide says that to preserve output compatibility, existing formulas referencing rollup, person, file and multi-select properties “were converted to text”, and shows a converted person reference as prop("Person").map(current.format()).join(", "). To get the rich type back, the guide says to remove the conversion language and use prop("Person") directly, which returns a list of people. The Formulas 2.0 overview walks through repairing old formulas in that state.

When a prop() formula errors or shows the wrong thing

Each row pairs a symptom with the cause its source documents and the fix that goes with it. Read the cause column first: three of the nine rows come back to a list the formula didn't narrow.

prop() symptoms, their documented causes and the fix
SymptomCauseFix
A pasted prop("Due Date") turned into a grey token, or a copied formula is full of prop()The editor's round trip between tokens and prop() text, described by Notion Mastery and FrankNothing to fix; paste the prop() text whenever you share a formula
The formula shows related task pages instead of their statusesA Relation returns a list of pages.map(current.prop("Status")) for every page, or .first().prop("Status") for one
An automation pauses when its formula reads a property on a related pageThe list of pages wasn't narrowed to one element.first() or .at(0), or loop with .map(), .filter(), .every(), .some() or .find()
A condition on a Multi-select doesn't match the tag you expect== compares the whole list with one valueprop("Tags").includes("Urgent")
An automation errors and pauses on some rows onlyAn unexpected empty value, most commonly a date or personCheck for a value first, as in Notion's example if(Date, Date.dateAdd(1, "day"), empty())
Notion shows an error message on a long chain of formulas and rollupsFormulas can be only 15 layers deep, and each formula or rollup referenced adds oneCombine unnecessary formulas and rollups into fewer properties
A formula in a button or automation can't compute reliablyYou don't have access to a database it referencesGet access to every page and database the formula references
An old person or tag formula returns plain textPre-2.0 formulas on rollup, person, file and multi-select properties were converted to textRemove the conversion and reference the property directly
An error message appears when you click saveA quoted property name doesn't match exactly, one of the writing guide's first checksCopy the name with its case and spaces, or select the property from the menu

Rows two through five come from Notion's errors page and property table, the depth and access rows from the errors page, the text row from the 2.0 guide and the save row from the writing guide. On depth, the errors page adds that the limit “applies even if the formula is in a different database”. A Project formula that reads a Task formula, which reads a Task rollup, which reads a formula in a third database, spends a layer at each step. By that definition a plain property on the related page adds no layer, so I'd point relation formulas at plain properties where the value already exists there.

Automations are stricter than formula properties. The errors page says formulas in automations expect a specific return type, and that an empty value makes the automation error and pause; its fix is to check the property with if or ifs before operating on it. Access matters there too: inside a button or database automation, you need access to every page and database the formula references. Deciding what the automation should do with a row that fails that check belongs in the plan, which is what defining an automation's trigger, action and exception covers, and choosing between a button and a database automation settles which one should run the formula.

Test results: one project's relation formulas, worked through

The patterns are easiest to check on a project small enough to count by hand. Here's one: a Website refresh project related to five tasks, each with a Status and a Minutes number property. Every output below follows from the function definitions in Notion's syntax reference, with the arithmetic shown.

The five tasks related to the Website refresh project, in relation order
TaskStatusMinutes
BriefDone45
WireframesDone120
Copy draftDoing90
Client reviewTo Do30
Launch checklistTo Do15
Each relation pattern on the Website refresh project and the result it returns
Formula on the projectResultHow it gets there
prop("Tasks").length()5Five related pages
prop("Tasks").map(current.prop("Status"))Done, Done, Doing, To Do, To DoOne status per page, in list order
prop("Tasks").filter(current.prop("Status") !== "Done").length()3Copy draft, Client review and Launch checklist pass the filter
prop("Tasks").map(current.prop("Minutes")).sum()30045 + 120 + 90 + 30 + 15
prop("Tasks").filter(current.prop("Status") !== "Done").map(current.prop("Minutes")).sum()13590 + 30 + 15, the minutes still open
prop("Tasks").first().prop("Status")DoneBrief is the first page in the relation's list
prop("Tasks").every(current.prop("Status") == "Done")falseThree tasks aren't Done
prop("Tasks").some(current.prop("Status") == "Doing")trueCopy draft is Doing
The lets() percent formula above402 / 5 * 100 = 40, and round() leaves 40

Now add a sixth task, Handoff notes, at To Do with 60 minutes. The count becomes 6, the open count 4, total minutes 360 and open minutes 195. The percent formula returns 33: 2 / 6 * 100 is 33.33, and round() takes it to the nearest integer. The every() row stays false, which is why I'd keep it as a finished-project check and leave progress to the percent formula.

Two rows need a decision only you can make. The filter treats Doing as open because the condition is !== "Done"; if your team wants Doing to count toward progress, that's one more condition in the filter, not a different formula. And the .first() row depends on the relation's order, so for a value that has to come from one specific task, a dedicated relation or property is clearer than whichever page happens to be first.

Verdict: tokens while you build, prop() when you share

Pick properties from the editor's list while you build, and paste the prop() version whenever a formula leaves the editor. Assume every Relation, Person, Multi-select and Files & media value is a list until you've taken one item from it or mapped over it; that one habit covers the page-names output, the == mismatch and the paused automation in the table above.

Start with the count. Add prop("Tasks").length() to one project, open the editor from that project's row so the live preview shows its number, and compare it with the tasks you can see in the relation. Once that matches, swap in map(), filter() or the percent formula. The formula reports what the related pages say; deciding which statuses mean finished stays with you. If you're building the databases from scratch, the client project tracker build sets up the Clients, Projects and Tasks relations these formulas read.

Use the property in the next formula