The length() function in Attio: finding the fields that only look filled in
Every CRM has a filter for "description is empty." None of them catch the record whose description is a hyphen. We once inherited a workspace where 148 of 436 people had - as their name — imported that way, technically populated, invisible to every emptiness check in the system. length() is the function that finds those, and the reason it matters is simple: *not empty* and *filled in* are not the same claim.
This is part of our function-by-function series on Attio formula attributes — previously: if(), timeSpentIn(), dateDiff(), the ?? operator, count(), hasBeenIn(), valueSetAt(), contains(), sum(), previousValue(), dateAdd(), formatDate(), min()/max(), valueAt(), replace()/replaceAll(), avg()/median(), round()/ceil()/floor(), and and()/or()/not().
Table of contents
- What length() does
- Not empty is not filled in
- Data-quality flags that actually fire
- Validating shape, not just presence
- Character limits before they bite
- A completeness score
- Composing with the rest of the library
- CRM use cases that earn their keep
- Copy-paste formulas
- Gotchas
- Final thoughts
What length() does
One argument, one number:
length(value)length("Hello World") returns 11 — every character, spaces included. That's the entire function, and it's deceptively boring until you realise it's the only tool in the library that can tell you how much of an answer a field actually contains.
Force the output type to Number. On its own the count is diagnostic; wrapped in if() it becomes a flag you can filter and automate on, which is where it earns its place.
Not empty is not filled in
Emptiness filters test for the absence of a value. Data rot rarely looks like absence. It looks like:
- A dash, a period, or a single space, left behind by an import that needed *something* in a required column.
- "n/a", "tbd", "unknown" — a human answering a mandatory field they couldn't answer.
- A truncated paste: half a phone number, a first name in the full-name field.
- A one-word description where the process expected a paragraph.
Every one of these passes "is not empty." Every one of them fails the moment a rep opens the record. length() is the check that agrees with the rep instead of with the database.
Data-quality flags that actually fire
The base pattern, straight from the pillar:
if(length({Description} ?? "") < 50, "Needs research", "OK")Fifty characters is roughly one sentence — below that, nobody has actually written anything about this account. Pick the threshold from your own data rather than from instinct: sort by length({Description}) descending, look at where the real entries stop and the junk starts, and set the number just above it.
Placeholder detection is the same idea with a tighter threshold:
if(length({Name} ?? "") < 3, "Placeholder", "Real")Two characters or fewer is not a name, a company, or a job title. This one formula would have surfaced all 148 of those dashes in a single filtered view, on day one instead of month three.
Validating shape, not just presence
Stripped of formatting, a length check becomes a cheap format check. Combine it with replaceAll():
length(replaceAll(replaceAll({Phone}, " ", ""), "-", ""))That's the digit count. Under ten and it's truncated, missing a country code, or someone typed an extension into the wrong field. It isn't full validation — it won't tell you the number is *right* — but it catches the majority of unusable numbers before a rep dials one, which is the practical goal.
The same shape works for anything with a known size: registration numbers, VAT IDs, ticket references, internal codes. If your account IDs are always eight characters, length({Account ID} ?? "") != 8 is a one-line integrity check across the whole object.
Character limits before they bite
Anything you push out of Attio has a limit somewhere — a subject line, an SMS, a LinkedIn connection note, a field in the billing system that silently truncates at 40 characters. Checking on the way in beats discovering it on the way out:
if(length({Company name} ?? "") > 40, "Too long for invoicing", "OK")The value here isn't the flag; it's that the truncation stops being a mystery someone debugs in the downstream system three weeks later.
A completeness score
length() is at its best summed. Each field contributes a point when it clears its own bar:
if(length({Description} ?? "") > 20, 1, 0) + if(length({Phone} ?? "") > 9, 1, 0) + if(length({Title} ?? "") > 2, 1, 0)Force the output to Number — or to Rating, which renders the score as stars and makes a list view scannable at a glance. Sort ascending and you have a cleanup queue ordered by how broken each record is, which is a far more useful piece of work than "go fix the data."
Weight the fields if some matter more: give the phone number two points on an object where calls are the motion, one on an object where they aren't.
Composing with the rest of the library
13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">length() is a measurement, so it almost always lives inside something else. With [if() it becomes a flag; with and() it becomes a gate on a record being ready to work:
and(length({Description} ?? "") > 50, length({Phone} ?? "") > 9)One "Ready for outreach" checkbox that means the rep will find both a story and a way to make the call. The ?? guard is doing real work in every example on this page — it turns a genuinely blank field into an empty string, so the length is 0 and the comparison behaves instead of the whole formula going quiet.
And when the thing you want to count is records rather than characters, that's count(), not this — different question, different function.
CRM use cases that earn their keep
- Thin-description flag —
if(length({Description} ?? "") < 50, "Needs research", "OK")— important because a rep opening a blank account before a call is the most expensive kind of surprise. - Placeholder-name detection —
if(length({Name} ?? "") < 3, "Placeholder", "Real")— important because dashes and initials pass every emptiness filter and quietly corrupt every report keyed on names. - Phone digit check —
length(replaceAll(replaceAll({Phone}, " ", ""), "-", ""))— important because a truncated number costs a rep a call slot and the account a first impression. - ID integrity check —
length({Account ID} ?? "") != 8— important because fixed-length identifiers that aren't fixed-length break every downstream join. - Downstream character limit —
if(length({Company name} ?? "") > 40, "Too long for invoicing", "OK")— important because silent truncation in a billing system surfaces as a customer complaint, not an error. - Completeness score — summed
if(length(...) > n, 1, 0)checks — important because it turns "our data is bad" into a sorted queue someone can work through today.
Copy-paste formulas
Swap in your attribute names and these work as-is:
Thin-description flag (Text output):
if(length({Description} ?? "") < 50, "Needs research", "OK")Placeholder detection (Text output):
if(length({Name} ?? "") < 3, "Placeholder", "Real")Phone digit count (Number output):
length(replaceAll(replaceAll({Phone}, " ", ""), "-", ""))Ready-for-outreach gate (Checkbox output):
and(length({Description} ?? "") > 50, length({Phone} ?? "") > 9)Three-field completeness score (Rating or Number output):
if(length({Description} ?? "") > 20, 1, 0) + if(length({Phone} ?? "") > 9, 1, 0) + if(length({Title} ?? "") > 2, 1, 0)Fixed-length ID integrity check (Checkbox output):
length({Account ID} ?? "") != 8Gotchas
- color:var(--color-text-heading)]">Spaces count.
length()counts every character including whitespace, so a field holding three spaces has a length of 3, not 0. Strip with [replaceAll() before comparing when whitespace is noise. - Guard the blanks. A genuinely empty field isn't the same as an empty string. Use
length({Field} ?? "")so blanks land on0rather than making the formula go quiet. - color:var(--color-text-heading)]">It's a text function.
length()is documented for text values. To count linked records or list entries use [count(); to test whether a multiselect includes an option use contains(). - Thresholds are workspace-specific. 50 characters is a starting point, not a standard. Sort by the raw length once and set the cutoff where your real data actually stops.
- Length is not validity. Ten digits doesn't mean the phone number is right, and 60 characters doesn't mean the description is useful. This catches the obviously broken, not the subtly wrong — which is still most of the problem.
- Nesting limit. Three formula attributes deep. Build a completeness score in one formula rather than stacking per-field flag attributes and summing them in a fourth.
Final thoughts
length() is the least glamorous function in the library and the one most likely to change what your team does on Monday. Every workspace has a layer of records that look complete to the system and are worthless to a human; nothing else in Attio can see that layer. Measure it once, flag it, sort by it — and the argument about data quality turns into a list of records with names on them.
For the rest of the library — every history, logic, math, date, and text function with CRM use cases — see the complete guide to Attio formula attributes.
And if you'd rather have your data-quality flags, completeness scores, and cleanup queues designed and shipped for you, that's literally what we do. Get a free workspace audit or see the AI-native Attio sprint.
Official sources
Attio documentation used to verify this guide:
Need help with your Attio setup?
We migrate teams, build data models, wire automations, and train Claude agents inside your workspace. Discovery call is free.
Book a free discovery callReady when you are.
Two ways in. Pick the friction that fits.