In a Notion formula, let(name, value, expression) stores a value under a name you choose, and the expression after it can use that name as often as it needs. lets() does the same for several names: pairs of name and value, then one final expression. A name exists only inside the parentheses of the call that created it. Notion's formula syntax reference shows let(person, "Alan", "Hello, " + person + "!") returning "Hello, Alan!".
For a formula that repeats one value, such as a subtotal or a days-left number, write let(). For two or more names, I'd write lets(), because it's the form Notion's reference documents for several variables. The patterns below are built for a client or task database, with property names you swap for your own. For the rest of the 2.0 language, from tokens to lists, the Formulas 2.0 overview covers what changed.
The let() and lets() syntax, argument by argument
Notion's guide to the Formulas 2.0 changes describes the arguments of let() in order: the first is the variable's name, the second is its value, and the third is where you write the rest of the formula using that name. Whatever that third argument works out to is the formula's result.
let(name, value, expression)
lets(name1, value1, name2, value2, ..., expression)
lets() repeats the name and value pair as many times as you need, and the last argument is always the expression. That gives you a quick check when a formula won't save: a one-name let() has three arguments, and a lets() always has an odd number, because every name needs a value and one expression closes the call.
A later pair can use a name from an earlier one. In the duration example on Thomas Frank's lets() reference page, hoursCount is defined first and then used to build the variables after it. I'd order the pairs the way you'd explain the calculation out loud: the raw value, then anything built from it, then the result, since that's also the order someone checking the formula will read it in.
Function form or method form
Both functions also have a method form, with the name in front of a dot. Ben Smith's let() reference lists variable.let(value, expression), and Frank's lets() page lists variable.lets(value, variable, value, ..., expression). That matches the general dot-notation rule in Ben Borowski's Notion Mastery walkthrough of Formulas 2.0: the first argument moves in front of the dot and the function stays the same. Every let() and lets() example in Notion's syntax reference uses the function form, so that's the form used here. Pick one style per formula; switching between them makes the pairs harder to count.

Should you write let() or lets()?
Write lets() whenever a formula has two or more names. The sources disagree only on whether let() can also take several, and lets() is right under every version of the documentation.
- Notion's syntax reference says
let()"Assigns a value to a variable" andlets()"Assigns values to multiple variables", and each of itslet()examples uses a single name. - Thomas Frank's let() reference page says that since April 2025
let()accepts several variables, "making it identical in functionality to lets", and credits Ben Borowski with pushing for the change. The same page callsletsan alias oflet(). - Frank's lets() page and his page on formula variables still describe the older split:
letfor one variable,letsfor several.
A multi-name let() in a copied template is valid by Frank's account and outside what Notion's reference describes. Renaming it to lets() settles that either way. Frank's lets() page points at the same fix: if you're defining several variables and hit an error, check that you're using lets() rather than let(), a mistake he calls common when a formula starts with one variable and grows. It's a one-word change, so I'd make it before looking anywhere else.
Where a variable exists: scope and nesting
A name can be used only between the parentheses of the let() or lets() that created it. Frank states this on both of his reference pages, and it decides where your closing parenthesis goes: everything that uses the name has to sit before it.
You can also put one call inside another. An outer name is visible inside the inner call; an inner name isn't visible outside it. Frank's let() page shows the first half:
let(firstName, "Monkey", let(lastName, "Luffy", firstName + " D. " + lastName))
This returns "Monkey D. Luffy", because the inner call reads firstName from the outer one. When the inner call reuses an outer name, the inner value wins inside that call:
let(lastName, "Luffy", "Monkey D. " + let(lastName, "Garp", lastName))
This returns "Monkey D. Garp". Outside the inner parentheses, lastName is still "Luffy". Nothing fails here, so in a long client formula the reuse can go unnoticed while the result quietly shows the inner value. Give every name in a nested formula its own spelling. An inner lets() can also supply the value of an outer name, which the last row of the results table below works through step by step.

Keep the outer current inside a nested list function
List functions such as map() and filter() give you current for the item being processed, and a list function nested inside another brings its own current. Frank's lets() page uses a variable to keep the outer one: inside filter(), it stores current as currentList, so the inner map() can still reach the list it came from.
[[1, 3, 5, 7], [2, 4, 6, 8], [1, 2, 3, 6]].filter(lets(
currentList, current,
remainders, current.map(currentList.sum() % current),
remainders.every(current == 0)
))
A list stays only if every number in it divides the list's sum evenly. Frank doesn't print the result, but the arithmetic settles it: 16 leaves a remainder when divided by 3, 20 leaves one when divided by 6, and 12 divides evenly by 1, 2, 3 and 6, so only [1, 2, 3, 6] survives the filter. The same move shows up in client work once a formula walks related pages. Notion's syntax reference lists a relation as a list of pages, so when one list function runs inside another over a client and project relation, a variable is how the inner call refers to the outer page.
let() patterns for a client or task database
Each pattern names the value a formula would otherwise repeat. Swap the property names for yours, and open the editor from a row you can check by hand.
From sales tax to budget left
Notion's own example is a sales tax line. The Formulas 2.0 guide suggests a variable when a total is built from properties you'd rather not add up again each time you type it:
let(SalesTax, (prop("Subtotal")*0.099), prop("Subtotal") + SalesTax)
The result is the Subtotal plus 9.9% tax, and the tax calculation is written in one place. 0.099 is the rate in Notion's example; use the rate that applies to your invoices.
The same idea gives a project record a budget line. The gap between Budget and Spent appears in the comparison and in the text, so it gets a name:
let(
remaining, prop("Budget") - prop("Spent"),
if(remaining < 0, "Over by " + format(abs(remaining)), format(remaining) + " left")
)
With a Budget of 5000 and Spent of 3200, the result is "1800 left". With Spent at 5600, remaining is -600 and the result is "Over by 600", because abs() drops the minus sign before format() turns the number into text. The formula reports the gap. Whether an overrun calls for a talk with the client is your decision.
Days until due, with one status label
Without a variable, a days-left label repeats the same dateBetween() in every check and again in the text. With lets() it appears once:
lets(
/* Days from today to Due: positive ahead, 0 on the day, negative after */
daysLeft, dateBetween(prop("Due"), today(), "days"),
/* "day" or "days" for the label */
unit, if(daysLeft == 1, " day", " days"),
ifs(
prop("Status") == "Done", "Done",
daysLeft < 0, "Overdue",
daysLeft == 0, "Due today",
format(daysLeft) + unit + " left"
)
)
The sign comes from the argument order. Thomas Frank's dateBetween() reference says the result is positive when the first date is later than the second, so with Due first, a task due next week gets a positive number and an overdue task, whose Due is earlier than today, a number below zero. today() returns the date without the time, so for a Due date without a time, a task due tomorrow shows "1 day left" and one due today shows "Due today". The unit pair borrows the plurality trick from Frank's let() page, which adds "s" only when the count isn't 1. ifs() returns the value for the first true condition, which is why the Status check comes first: a finished task reads "Done" even after its date has passed.
Two cases sit outside this formula. If Due includes a time, or you'd count both the first and the last day, the dateBetween off-by-one guide sets the counting rule. A blank Due needs its own first condition, and the right label depends on where the result goes; the guide to Notion formula date errors covers empty and text inputs. Which status counts as finished is your call, and the formula checks only the one you type in.
A cleaned contact name used twice
A greeting line for a client email needs the contact's name twice: once to check it's there and once in the text. For a Text property called Contact:
let(
contact, prop("Contact").trim(),
if(empty(contact), "Add a contact name", "Hi " + contact + ",")
)
trim() removes white space from the beginning and end of text, so " Dana Reyes " gives "Hi Dana Reyes,". A Contact holding only spaces trims to "", which empty() counts as empty, so the row shows the prompt instead of a greeting with a gap. If the names also arrive in the wrong case, the first-letter capitalization formulas deal with stray spaces and capitals together, and their result belongs in the contact value so the check and the greeting read the same cleaned name.
Lay out a long lets() so it still reads next month
The formula editor takes several lines, tabs and comments, which Notion's Formulas 2.0 guide lists among the changes. Notion Mastery adds that comments are ignored when the formula calculates and can go almost anywhere in it. That makes one comment per pair practical. I'd write any lets() longer than a line with one pair per line, a comment above each saying what the value means, and the expression last, so whoever opens it next meets each name before the formula uses it:
lets(
/* Amount before tax, from the invoice row */
amount, prop("Subtotal"),
/* Rate from Notion's example; use your own */
tax, amount * 0.099,
/* Result: total with tax */
amount + tax
)
This returns the same total as Notion's SalesTax formula above, with the subtotal and the tax each named.
When a let() formula won't save or returns the wrong value
Notion's intro to formulas says the field at the top of the editor tells you what's missing or what the formula expects. The table covers the causes that come from let() and lets() themselves.
| Pattern in the formula | What's wrong | Fix |
|---|---|---|
| A name is used after the closing parenthesis of the call that created it | The name exists only inside those parentheses. | Move that parenthesis to the end, so every use of the name sits inside it. |
lets(amount, prop("Subtotal"), tax, amount * 0.099) | The call ends on a value; there's no expression after the last pair, and the argument count is even. | Add the expression: lets(amount, prop("Subtotal"), tax, amount * 0.099, amount + tax). |
Several name and value pairs inside let() | Notion's reference documents let() for one variable; only Frank's let() page describes the multi-variable form. | Rename let to lets. |
| A nested call reuses an outer name | The inner value wins inside the inner call, so the result shows it instead of the outer one. | Give the inner variable its own name. |
A number variable joined to text with + | Notion's guide to writing formulas says numbers are combined with text by first converting them, and lists mixed data types as a cause of a "Type mismatch" error. | Wrap the number in format() before joining, as the recipes above do. |
A date variable needs one more check: its value has to be a Date. A date-looking value can arrive as Text, or as a List from a rollup, which Notion's syntax reference says can hold a number, a date or a list. The date errors guide linked above traces the input type before anything else.
let() variables vs. the Define variables action
Buttons, database buttons and database automations have an action called Define variables, and it does a different job. Notion's database automations help page says it creates a custom variable from mentions and formulas that you can use in other automation actions. You add it with New action or Add action → Define variables, click Variable 1 to name it, and fill the value with @ for a mention or ∑ for a formula. The help pages for buttons and database buttons describe the same action as a button step. Notion's automation example defines Variable 1 as Trigger page.Sub-item, then points an edit action at Variable 1, so marking a parent task complete also marks its sub-items complete.
The difference is where the name lives. A let() name exists inside one formula's parentheses, and nothing outside that formula can read it. A Define variables value is a step in a button or automation, and the actions you add after it can use it. For a label shown in a property, I'd keep the name in the formula with let(). When a later action has to find or edit pages, as in Notion's sub-item example, Define variables is the tool. The automation runs the edit, but which pages it may touch is a rule you set and check; planning the trigger, action and exception covers that before you build. If you're still choosing what should start the run, the buttons vs. automations comparison starts there.
Plans differ too. Notion's help says database automations are available on paid plans, with Free plan users limited to creating Slack notification automations, while buttons work on all plans and reserve certain actions for paid ones.
Results for each let() and lets() example
These are the published let() and lets() results from Notion's syntax reference, Ben Borowski's Notion Mastery walkthrough and Thomas Frank's reference pages: eight formulas from three sources. Seven results are printed next to their formulas; the last one isn't, and its working is under the table. Paste any of them into a formula property opened from a row and compare the live preview with the result column.
| Formula | Result | What it shows | Source |
|---|---|---|---|
let(person, "Alan", "Hello, " + person + "!") | "Hello, Alan!" | One name, used inside the expression. | Notion syntax reference |
let(radius, 4, round(pi() * radius ^ 2)) | 50 | The name works inside nested functions. | Notion syntax reference |
let(var, 2 + 2, var * var) | 16 | The variable holds the result of 2 + 2, so the expression squares 4. | Notion Mastery |
lets(a, "Hello", b, "world", a + " " + b) | "Hello world" | Two pairs, then one expression. | Notion syntax reference |
lets(base, 3, height, 8, base * height / 2) | 12 | The same shape with numbers. | Notion syntax reference |
let(firstName, "Monkey", let(lastName, "Luffy", firstName + " D. " + lastName)) | Monkey D. Luffy | An outer name is visible inside an inner call. | Thomas Frank, let() |
let(lastName, "Luffy", "Monkey D. " + let(lastName, "Garp", lastName)) | Monkey D. Garp | An inner name overrides the outer one inside its call. | Thomas Frank, let() |
lets(var1, 2 + 2, var2, 3 + 3, var3, lets(var4, var1 * var2, var5, var4 / var1, var5), var3 - var1) | 2 | An inner lets() supplies the value of an outer name. | Thomas Frank, lets(); result worked out below |
The last row step by step: var1 is 4 and var2 is 6. Inside the inner call, var4 is 4 × 6 = 24 and var5 is 24 / 4 = 6, and the inner lets() returns var5, so var3 is 6. The final expression is 6 - 4, which is 2. Notion's SalesTax pattern isn't in the table because its result depends on each row's Subtotal.
Start with the value you repeat most
- Open a formula you rely on from a row whose result you already know, so the editor shows a live preview for that row.
- Find the piece that appears more than once: a subtraction, a
dateBetween(), a trimmed property. - Wrap the formula in
let()with that piece as the value, and replace each copy with the name. - Compare the preview with the result you knew. If they match, the rewrite changed how the formula reads, not what it returns.
- When a second value needs a name, change
lettoletsand add the pair before the expression.
let() earns its place the second time a formula repeats itself. For a freelancer with one budget or deadline formula, I'd wrap the repeated piece in let() and stop there, because one well-chosen name already makes the formula readable. For a small team keeping several formulas across client databases, I'd move every formula with two or more names to lets(), one pair per line with a comment above each, because that's the layout a teammate can check without asking you what it does.
More questions about let and lets
Can a variable hold a list, a date or a page?
Yes. Ben Smith's let() reference lists all data types for both what let() accepts and what it returns. The examples above store a number, text and a list (currentList), and Frank's let() page stores a date in a birthday calculation. A relation's pages arrive as a list, so they can go into a variable the same way.
Can another formula property use my variable?
No. A name exists only inside the parentheses of the let() or lets() that created it, so a second formula property can't see it. The second formula can still read the first one's final result with prop(), like any other property, and Notion's page on common formula errors counts each reference to another formula or rollup as a layer, up to a limit of 15. For a value only one formula needs, give that formula its own let().
A copied formula uses a name that isn't a property. What is it?
Look for it as the first argument of a pair in a let() or lets() earlier in the formula. If it's current or index, it's one of the built-in variables that list functions provide; Frank's variables page notes that index counts from 0, so the first item in a list is 0.