New to Attio?Get 10% off when you sign up through Craftt.Try Attio free →
All articles

The count() function in Attio: turn your relationships into live numbers

·6 min read

Your CRM already knows how many contacts each account has, how many deals each company is running, how many people from your team are attached to every workspace. It just never showed you the number. count() is the function that surfaces it — a live tally on every record, computed from the relationships your data model already contains.

This is part of our function-by-function series on Attio formula attributes — previously: if(), timeSpentIn(), dateDiff(), and the ?? operator. This one covers count(): the syntax, the relationship trick that makes it special, and the formulas worth copying.

Table of contents

What the count() function does

count() takes an array and returns how many items are in it:

count(values)

So count([10, 25, 3]) returns 3. On its own, that's unremarkable — you'll rarely hand-type an array into a formula. The function earns its place in this series because of what Attio lets you point it at.

The relationship trick

Attio's whole data model is built on relationships between objects, and formulas inherit that graph: a formula on a Company can reach into its related records. Type { in the editor and relationship attributes appear alongside everything else. Which makes this the shortest genuinely useful formula in Attio:

count({Team})

That's it — a live headcount of the People linked to every Company record, sortable and filterable in any view. No workflow, no rollup configuration, no export. The same one-liner works for any relationship your data model connects: deals per company, contacts per account, whatever your objects link to.

This is the difference between count() and the same function in a spreadsheet. In a spreadsheet you count cells you can see. In Attio you count *edges in your data graph* — and most of the questions a revenue team asks are secretly edge counts. "How many stakeholders do we know there?" "How many open deals does this account have?" Those were always answerable; count() makes them columns.

Turning counts into signals

A raw number is a metric. Wrapped in if(), it becomes a judgment:

if((count({Team}) ?? 0) < 2, "Single-threaded", "Multi-threaded")

This is the classic. An account where you know fewer than two people is single-threaded — one champion holds the entire relationship, and if they leave or go quiet, the deal has no backup. Every experienced sales leader knows this risk; almost no CRM shows it. One formula turns it into a filterable label, and a saved view of "Single-threaded" accounts becomes a standing agenda item instead of a post-mortem finding.

The same pattern scales to tiers:

if((count({Team}) ?? 0) >= 4, "Committee", if((count({Team}) ?? 0) >= 2, "Multi-threaded", "Single-threaded"))

Enterprise deals are won by committees. Knowing whether you're talking to one person or four is a better stage-health check than the stage field itself.

Blank-proofing your counts

The ?? deep dive covered why this matters, so briefly: an empty input evaluates to null in Attio formulas, and null flowing through a comparison can blank the whole result. The records with *nothing linked* are exactly the ones your coverage formulas exist to catch — so guard the count before comparing:

(count({Team}) ?? 0) < 2

Now an account with zero contacts reads as zero, lands squarely in the single-threaded bucket, and can't silently drop out of both sides of your filter. Set the output type to Number explicitly rather than leaving it on Auto — forced types keep downstream comparisons honest.

The rest of the array family

13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">count() has siblings, and they all pair with relationship and multi-value attributes the same way: sum() for total pipeline per company across its linked deals, min() and max() for earliest renewal and largest deal, avg() and median() for typical deal size, unique() for distinct values. Same trick, different aggregation — the full rundown lives in the [pillar guide's math section. count() is the one to learn first because it needs no numeric attribute on the other side: it counts the records themselves.

CRM use cases that earn their keep

  • Single-threaded deal detectorif((count({Team}) ?? 0) < 2, "Single-threaded", "Multi-threaded") — important because one-contact deals are your biggest silent risk, and this makes the risk a column you can sort by.
  • Contacts per accountcount({Team}) — a live relationship-depth score. Important because account coverage is the leading indicator of expansion, and reps consistently overestimate theirs.
  • Deals per company — count the deal relationship on the Company — instantly separates one-off buyers from repeat customers. Important because your best expansion targets are hiding in plain sight as "2+".
  • Buying-committee size — the tiered version on active opportunities. Important because committee breadth predicts enterprise close rates better than most stage fields do.
  • Zero-contact flag(count({Team}) ?? 0) == 0 feeding a "No contacts" label — companies in your CRM with no people attached. Important because those records look like accounts but are really just enriched domain names, and they pollute every downstream metric.
  • Multi-value talliescount() on any multi-select or multi-value attribute — products owned, regions covered, tags applied. Important because "how many" questions about multi-selects are otherwise unanswerable in a view.

Copy-paste formulas

Swap in your attribute names and these work as-is:

Linked contacts per account (Number output):

count({Team})

Single-threaded deal flag (Text output):

if((count({Team}) ?? 0) < 2, "Single-threaded", "Multi-threaded")

Buying-committee tiers (Text output):

if((count({Team}) ?? 0) >= 4, "Committee", if((count({Team}) ?? 0) >= 2, "Multi-threaded", "Single-threaded"))

Empty-shell account flag (Text output):

if((count({Team}) ?? 0) == 0, "No contacts", "Has contacts")

Blank-safe count for downstream math (Number output):

count({Team}) ?? 0

Final thoughts

count() is one line long and needs no arguments beyond a relationship attribute, which makes it the easiest win in Attio's formula library: pick the relationship that matters most to your motion — contacts on accounts for sales, deals on companies for CS — ship count({Team}), and build one saved view on top of it. The single-threaded filter alone tends to pay for the five minutes of setup in the first pipeline review.

For the rest of the library — every math, logic, date, text, and history function with CRM use cases — see the complete guide to Attio formula attributes.

And if you'd rather have coverage metrics, deal-risk flags, and rollup formulas designed and shipped for you, that's literally what we do. Get a free workspace audit or see the AI-native Attio sprint.

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 call