Skip to content
notionsolutions

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

Formula guides

Notion Formula length(): Count Tasks, Tags, Words and Unique Values

There's no count() in Notion's formula reference, so length() does the counting. Copy formulas for open and overdue tasks, percent done, distinct tags and names, characters and words, see what each returns for six sample tasks, and know when a rollup Count is the simpler choice.

Published
Six frosted glass tiles lined up on a graphite rail leading into a steel gate, with two clear tiles held back in a white tray and four glowing blue tiles stacked on a steel plinth
length() counts whatever reaches it, so filter the list first when only some items should count.

To count in a Notion formula, use length(): it returns the number of characters in text or items in a list, so prop("Tasks").length() counts a project's related tasks. Notion's formula reference has no count() function, so to count only some items, filter the list and take its length. I'd use a rollup Count for a plain total, because it needs no code, and a formula once the count needs a condition, a percentage or a case rule.

prop("Tasks").filter(current.prop("Status") != "Done").length()

That one line counts the open tasks on a project. The sections below cover what length() returns for each property type, conditional counts, percentages, distinct values, words, the rollup alternative and the mistakes behind a wrong number. The worked example near the end takes six sample tasks and works out every formula's result by hand, so you can compare your own preview against it.

What length() counts for each property type

length() takes one value and returns a number. What that number means depends on the property you feed it, and Notion's formula syntax reference lists the type each property turns into inside a formula.

What .length() returns for each property type
Property typeWhat .length() countsExample
Title or TextCharacters, including spaces and punctuationprop("Title").length(); "Monkey D. Luffy" returns 15
Multi-selectOptions selected on that page (a text list)prop("Tags").length()
PersonPeople in the cellprop("Upvoted by").length()
RelationRelated pages (a page list)prop("Tasks").length()
RollupDepends on the rollup: a list counts its items, a number should be used as it isprop("Purchases").length()
NumberDigits, which is rarely what you want12345.length() returns 5

The Title, Multi-select, Relation and Rollup examples come from Notion's table, which says a rollup's type "depends on rollup configuration." The Person example is from Notion's guide to formulas that extend what databases do, where it adds up the people tagged in an Upvoted by column. Thomas Frank's length() page gives the 15 for "Monkey D. Luffy", and his Formulas 2.0 cheat sheet notes that length() also accepts a number, with 12345.length() returning 5. That's why a rollup that already returns a number, say 12, gives 2 once you wrap it in length().

Notion's intro to formulas uses the same function for a Tasks remaining property, Tasks.length(). As written, that counts every related task, finished or not; a filter is what turns it into a remaining count. Before formulas had lists, counting comma-separated items took a replaceAll() trick; Frank's cheat sheet keeps that old version and notes that lists make the job much simpler.

Count only the items that match a condition

Every conditional count has the same shape: list, filter(), length(). Inside filter(), current stands for each item in turn, and current.prop() reads a property of that related page; the prop() guide covers how those references work. The recipes below sit on a Projects database with a Tasks relation.

Open tasks

prop("Tasks").filter(current.prop("Status") != "Done").length()

Notion's property table writes this filter with !==; its function list documents != under unequal, which is the form used here. I'd count "not Done" rather than listing every open status, because a status you add later, such as Waiting on client, then counts as open without anyone editing the formula. The flip side is that a Cancelled status would count as open too, so add it to the condition if you use one.

Overdue tasks

prop("Tasks").filter(
  !empty(current.prop("Due")) and
  current.prop("Due") < today() and
  current.prop("Status") != "Done"
).length()

today() returns the current date without the time, so with date-only Due values the strict < keeps a task due today out of the overdue count. The !empty() check means a task with no due date never counts as overdue. If the property shows a type error instead of a number, the date error guide walks through the usual causes.

Billable, tagged and upcoming

prop("Tasks").filter(current.prop("Billable")).length()
prop("Tasks").filter(current.prop("Tags").includes("Design")).length()
prop("Meetings").filter(current.prop("Date") > now()).length()

A checkbox already returns true or false, so it can be the whole condition. A multi-select is a list, so test it with includes(), the way Notion's syntax table does with prop("Tags").includes("Finance"); a single Select compares with ==, as in prop("Priority") == "High". For the meetings line, now() includes the current time and today() drops it, so pick the one that matches how your dates are entered.

Turn two counts into a percentage

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

lets() names each count once, so the formula reads like the sentence it stands for. The if(total == 0, 0, …) part matters on a new project. Notion's Formulas 2.0 guide builds a similar percentComplete value by dividing completed tasks by all tasks, with no check for a project that has no tasks yet, and that project would be dividing by zero.

round() with one argument gives a whole number; Notion's reference shows a second argument for decimal places, so round(done / total * 100, 1) keeps one decimal. I'd keep the result a number rather than gluing a % sign onto it, because a number can feed another formula, such as an ifs() that labels the project, while text would need converting back first. The let() and lets() guide explains the variable syntax, and the if() and ifs() guide covers turning the percentage into labels.

Count distinct values

Distinct tags across a project's tasks

prop("Tasks").map(current.prop("Tags")).flat().unique().length()

Read it left to right. map() collects each task's Tags, which gives a list of lists, one per task. flat() merges them into one list; Frank's flat() page points out that Notion's version flattens only one level, and one level is all this needs. unique() drops the repeats and length() counts what's left. Leave out unique() and you get total tag uses instead. Frank also recommends the Show Details icon while you build list formulas, because it shows the data type and the brackets around list items, so you can see where each step stands.

Distinct names typed by hand

prop("Tasks").map(current.prop("Requested by").lower()).unique().length()

unique() is case-sensitive, so Dana and dana count as two people. Frank's unique() page handles that by mapping every item through lower() first, which turns "Zangief", "zangief" and "ZANGIEF" into one value. In this setup Requested by is a Text property. If stray spaces creep in, use current.prop("Requested by").trim().lower() inside the map, and see the text-cleaning formulas if you'd rather fix the stored names than only count them.

Counting through two relations

prop("Projects").map(current.prop("Tasks")).flat().length()

From a Clients database, this counts every task on every project for that client. Formulas can reach into properties of related databases, and the same map-then-flat pattern turns the per-project task lists into one list, so you can still add a filter() before length() to count only open work. The rollup shortcut isn't available here: Notion's relations and rollups help answers "Can I rollup a rollup?" with "Unfortunately not." If one task is linked to two projects, it sits in both lists and is counted twice. For the clients, projects and tasks structure itself, the client project tracker build sets up those relations.

Two Notion counting pipelines: a Tasks relation of 6 pages passes through filter(current.prop("Status") != "Done") to 4 pages and length() returns 4; the same 6 pages map to 6 tag lists, flat() gives 7 tags, unique() leaves Design, Copy, Dev and Admin, and length() returns 4
A conditional count filters first; a distinct count maps, flattens and removes repeats before length().

Count characters and words in text

Characters, and characters left

prop("Caption").length()
let(limit, 150, limit - prop("Caption").length())

The first returns the character count, spaces included. The second shows how many characters remain under a limit you choose; 150 is a placeholder for whatever the client's brief says, and a negative result means the text runs over.

Words

prop("Notes").match("\\S+").length()

match() returns every match of a regular expression as a list. The pattern \S+ matches each run of characters that isn't whitespace, and length() counts the runs. That's the word count I'd use, because double spaces, spaces at either end and line breaks don't change it. Notion's syntax reference writes regular expressions with a doubled backslash, as in match("Notion 123 Notion 456", "\\d+"), which returns ["123", "456"]; Frank's string reference lists \\ as the escape for one backslash, which is why the doubled form reaches the regex as \S+, and his match() page shows the single-backslash form returning the same matches. I copy Notion's doubled form. Open the formula editor from a row that has text, and its live preview shows the count for that row before you save.

prop("Notes").trim().split(" ").filter(current != "").length()

If you'd rather avoid regular expressions, this version trims the ends, splits on spaces and drops the empty pieces that double spaces leave behind. It splits only on spaces, so two words separated by a line break count as one.

An older approach counts spaces instead. Notionthings' 2022 word count formula removes every character that isn't a space, takes the length of what remains and adds 1, which it describes as counting "the spaces between words." That holds on tidy text, but each extra space adds a word: in the test table below it gives 5 for a four-word line with a double space.

The phrase Send the revised quote with a double space after the, its four words numbered, and three word counts: match("\\S+").length() returns 4, trim().split(" ").filter(current != "").length() returns 4, and counting spaces plus 1 returns 5, marked overcount
A double space adds a word only when the formula counts spaces.

For the body of a page, skip the formula. Notion's writing and editing basics says that when you select one or more blocks, the menu shows how many words and characters are in the selection, and the ••• menu at the top right of the page shows the count for the whole page. Use that for drafts and long notes, and a formula for text that lives in a property.

Rollup Count or formula: which one to use

A rollup is the no-code option. Notion's rollup setup asks for the relation, a property of the related pages, the calculation, the number formatting and the decimal places, with no step for a condition. Here's what each count calculation does according to that help page, and a formula that gets the same kind of number.

Rollup count calculations and their formula equivalents
Rollup calculationWhat Notion's help says it countsFormula for the same kind of number
Count allThe total number of values in the selected property for all related pagesprop("Tasks").length(), which matches when the property holds one value per page, such as Name
Count valuesNon-empty values in the selected propertyprop("Tasks").filter(!empty(current.prop("Due"))).length()
Count unique valuesUnique values in the selected propertyprop("Tasks").map(current.prop("Requested by")).unique().length()
Count emptyRelated pages with an empty value for the propertyprop("Tasks").filter(empty(current.prop("Due"))).length()
Count not emptyRelated pages with assigned values for the propertySame as Count values on a single-value property like Due
Percent empty, Percent not emptyThe percentage of related pages with no value, or with a valueThe lets() percentage above, with empty() or !empty() as the condition

Because none of these takes a condition, "tasks not Done" or "billable tasks" can't come from a rollup count alone; that's the point where I switch to a formula. For a plain total on a project page, I'd still pick the rollup, since a teammate can open it and see exactly what it counts without reading code. Rollups can only be sorted when they output a number, and every count calculation does. The wording also differs: Count values counts values, while Count not empty counts pages. On a single-value property like Due they match; on a multi-select like Tags they may not, so check either one against a project you can count by eye. If a rollup shows the wrong number, the rollup troubleshooting guide starts with empty and missing relations, and the relations and rollups walkthrough covers the setup.

Why a count comes out wrong

Wrong counts, their usual cause and the fix
What you seeUsual causeFix
The count includes finished taskslength() runs on the whole relationAdd filter() before length()
A rollup count wrapped in length() reads 1 or 2length() on a number counts its digitsUse the rollup's number directly
An older formula counts characters instead of itemsThe 2.0 update converted formulas that referenced rollup, person, file and multi-select properties to textRemove the conversion part, such as .map(current.format()).join(", "), so length() sees the list
A multi-select condition never matches== compares the whole list with one optionUse includes("Option")
Duplicate names survive unique()unique() is case-sensitiveMap through lower() first
The distinct-tag count looks like a task countflat() is missing, so the list still holds one tag list per taskAdd flat() after map()
The percentage breaks on a project with no tasksThe formula divides by a total of 0Guard it with if(total == 0, 0, …)
An error after chaining formulas and rollupsEach reference to another formula or rollup adds a layer, and the limit is 15Count from the relation in one formula
The word count is too highThe formula counts spaces, so extra spaces add wordsUse match("\\S+")

Notion's guide to common formula errors sets the depth rule: formulas "can only be 15 layers deep," every reference to another formula or rollup adds a layer even across databases, and the fix is to combine formulas and rollups into fewer properties. A single filter().length() on the relation is one property where a rollup feeding a formula feeding another formula is three. The same guide tells automation formulas to use .includes instead of == for a multi-select condition. For migrated formulas, Notion's Formulas 2.0 guide says you'll need to remove the conversion language to get the list back, and the Formulas 2.0 fix guide shows how to spot it in a copied template.

Test results: six sample tasks, every count worked by hand

Here's one sample project, Site launch, with six related tasks. Assume today() returns March 10 and every Due date falls in the same year. Status, Due, Billable (checkbox), Tags (multi-select) and Requested by (text) are properties of the Tasks database. Each result below follows from the function definitions in Notion's syntax reference, with the tasks behind it listed so you can recount.

The six tasks related to the Site launch project
TaskStatusDueBillableTagsRequested by
1. Draft sitemapDoneMar 3YesDesignDana
2. Write homepage copyDoneMar 6YesCopydana
3. Build contact formIn progressMar 9YesDev, DesignLee
4. Review mobile layoutNot startedMar 8NoDesignDana
5. Set up analyticsNot startedMar 12YesDevPriya
6. Collect client logosNot startedEmptyNoAdminLee
What each count returns for the Site launch project
CountResultHow it gets thereRollup that matches
All tasks6Six related pagesCount all on Name: 6
Open (Status != "Done")4Tasks 3, 4, 5 and 6None, it needs a condition
Overdue2Tasks 3 (Mar 9) and 4 (Mar 8); 1 and 2 are Done, 5 is due later, 6 has no dateNone
Billable4Tasks 1, 2, 3 and 5None
Tagged Design3Tasks 1, 3 and 4None
Percent done332 of 6 is 33.33, rounded to a whole numberNone; Percent not empty measures filled-in values, not status
Tag uses, without unique()71 + 1 + 2 + 1 + 1 + 1Check Count values on Tags against 7
Distinct tags4Design, Copy, Dev, AdminCheck Count unique values on Tags against 4
Requesters as typed4Dana, dana, Lee, PriyaCheck Count unique values on Requested by against 4
Requesters after lower()3dana, lee, priyaNone
Tasks with a Due date5Every task except 6Count values or Count not empty on Due: 5
Tasks without a Due date1Task 6Count empty on Due: 1
Due date coverage835 of 6 is 83.33, roundedPercent not empty on Due, shown with the decimals you set

The coverage row reuses the percentage formula with !empty(current.prop("Due")) as the condition. Three rollup cells say "check" because Notion's help defines Count unique values as the number of unique values without saying whether Dana and dana are one value or how a multi-select's tags are counted. That's the useful test: build the rollup next to the formula on one project like this and see whether the two agree.

Text formulas get the same treatment. Each row is a value in a Notes text property; spaces and line breaks are described in words, since a web page would hide them.

Character and word counts for six Notes values
Notes valueCharacters (length)Words (match)Words (trim, split, filter)Words (counting spaces)
Empty0000
Invoice7111
Send the revised quote, with two spaces after "the"23445
Send quote, with one space before and one after12224
Call Dana, then a line break, then Send quote20433
Logo v2 - approved!19444

In the 20, the line break counts as one character. The hyphen in the last row counts as a word under every method, because each one splits on spaces or whitespace. The space-counting formula returns 0 for the empty value only because Notionthings wraps it in an if() that checks the length first. Only match() gives the same count however the spaces and line breaks fall, which is why it's the one I'd put in a Notes database.

Verdict: rollup for plain totals, filter().length() for anything with a rule

For a plain total, use a rollup Count. For a count with a condition, use filter().length(); for a percentage, two counts in lets() with a zero guard; for distinct values, map(), flat(), unique() and lower(); for words, match(). Start with the open-task count on one project you can count by eye, and copy the pattern to other properties once the preview matches. The formula counts whatever passes the filter. Deciding what "open" or "billable" means for your client work is still your call, and that decision is the condition you write once.

Questions that come up next

How do I add up numbers instead of counting them?

Map the list to a number property and end with sum() instead of length(). Frank's map() page totals time-tracking sessions with prop("Sessions").map(current.prop("Duration")).sum(), and a Sum rollup does the same without a condition. If a total doesn't match the linked records, the rollup sum guide reconciles it.

How do I count the days between two dates?

That's dateBetween(), not length(). Notion's example, dateBetween(prop("Birthday"), now(), "days"), returns the difference in the unit you name, from years down to minutes. If the result is one day off, the dateBetween off-by-one guide explains why.

Can Notion AI write a counting formula for me?

On Business and Enterprise plans, Notion AI can create and edit formulas and show a preview of the output. I'd still check its count against a project small enough to count by hand, because the AI writes the filter but you're the one who knows which tasks should pass it.

Count the right items