Attio formula attributes: the complete guide to calculated fields
Attio has finally shipped one of its most requested features: formula attributes. Your CRM can now do the math for you — weighted pipeline, days in stage, lead scores, renewal countdowns — all computed live, right inside your records, with no workflows, no spreadsheet exports, and no Zapier duct tape.
We've been testing formula attributes across client workspaces at Craftt, and this is our full breakdown: what they are, how they work, every function in the library with a real CRM use case, formulas you can copy today, and the limitations you should know about before you build.
Table of contents
- What are formula attributes?
- Availability and permissions
- Creating your first formula
- Output types
- Let AI write the formula for you
- Referencing attributes and relationships
- The full function library — operators, logic, math, date and time, text, attribute history
- How recalculation works
- Handling empty values
- Real-world formulas you can steal
- Limitations to know about
- Formula attributes vs. workflows vs. AI attributes
- Final thoughts
What are formula attributes?
A formula attribute is a new attribute type you can add to any object or list in Attio. Instead of storing a value someone typed in, it derives its value from other data on the record — and recalculates automatically whenever that data changes.
Think of it as a spreadsheet formula that lives inside your CRM, with three properties Attio has been deliberate about:
- Deterministic. The same inputs always produce the same output. No AI guesswork in the result — which means you can forecast, report, and audit on top of formulas with confidence.
- Composable. Formulas can reference other attributes *and other formulas*. Set your stage probabilities once, then reuse them in weighted pipeline, reports, and scoring models.
- Reactive. When an underlying value changes, the formula recalculates within seconds. No refresh button, no nightly sync.
Because the computed value is a first-class attribute, it flows everywhere attributes do: you can filter and sort views by it, build reports on it, trigger workflows from it, and personalize sequences with it.
Availability and permissions
- Formula attributes are available on Pro and Enterprise plans.
- Only Admins and Members with Full access can create and manage them.
- AI-generated formulas don't consume workspace AI credits — describe formulas in plain language as much as you like.
Creating your first formula
The flow is the same as any other attribute:
- Open the object or list settings and click to add a new attribute.
- Choose Formula as the attribute type.
- The formula editor opens — a proper editor, not a one-line input.
Inside the editor you get:
- Autocomplete as you type — a searchable list of every function and attribute, with inline syntax guidance and examples.
- Attribute references — type
{to pull up every attribute on the object. - A record picker to preview the result against real records before you save. This is the underrated part: you see exactly what the formula returns for actual data, not a theoretical example.
- Error highlighting with AI-assisted fixes, so a broken formula never silently ships.
- Undo/redo, so you can experiment freely.
Output types
By default, the output type is set to Auto — Attio infers it from whatever your formula returns. But you can override it via the Output dropdown and force the result into a specific type: Text, Number, Checkbox, Date, Timestamp, Rating, Currency, or Phone number.
This matters more than it looks. Forcing a number output to Currency gets you proper formatting in views and reports. Forcing a boolean to Checkbox gives you a clean filterable tick. And a 1–5 score rendered as a Rating attribute reads instantly in a list view. For numeric outputs you can also control decimal places and number grouping.
Let AI write the formula for you
You don't have to learn the syntax at all. Below the editor sits a prompt box — *"Describe the formula you want Attio to create..."* — and it does exactly what it says.
Type something like *"Calculate the number of days this deal has been in its current stage"* and Attio writes the formula for you. If the result isn't quite right, there's a Refine your formula input for iterating: "actually, round it to whole days" or "return 'Stale' if it's more than 14 days." You converse your way to the formula.
Two things we appreciate about how this is built:
- It doesn't burn workspace credits, so there's no penalty for iterating.
- The AI produces a *formula* — a deterministic, inspectable expression — not an AI-generated value. You get the convenience of natural language with none of the unpredictability.
For most teams, this is the way to start: describe what you want, read what the AI wrote, and use it as a syntax tutorial.
Referencing attributes and relationships
Type { in the editor and every attribute on your object appears — from Record ID, Domains, Name and Description to enriched data like Twitter follower count, Estimated ARR, Funding raised, Foundation date and Employee range, plus interaction data like First interaction and Last interaction.
And here's the powerful part: if your object connects to other objects, relationship attributes are available too. A formula on a Company can reach into its related records — count the Team members linked to it, work with the deals attached to it, and so on. Since Attio's whole data model is built on relationships between objects, formulas inherit that graph.
A tiny example that's impossible in most CRMs without a report:
count({Team})That's it — a live headcount of linked People on every Company record, sortable and filterable in any view.
The full function library
Attio ships a full library of operators and functions across six categories. Here's every one of them — what it does, and a concrete CRM use for it.
Operators
| Operator | Syntax | What it does | Example |
|---|---|---|---|
+ | a + b | Addition | 100 + 50 → 150 |
- | a - b | Subtraction | 100 - 50 → 50 |
* | a * b | Multiplication | 100 * 2 → 200 |
/ | a / b | Division | 100 / 4 → 25 |
== | a == b | Equal to | 100 == 100 → true |
!= | a != b | Not equal to | 100 != 50 → true |
> | a > b | Greater than | 100 > 50 → true |
>= | a >= b | Greater than or equal to | 100 >= 100 → true |
< | a < b | Less than | 50 < 100 → true |
<= | a <= b | Less than or equal to | 50 <= 100 → true |
! | !value | Negates a boolean | !true → false |
?? | left ?? right | Null coalescing — fallback when the left side is empty | {ARR} ?? 0 |
The 13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">?? operator deserves a special mention — it's how you keep formulas from breaking on incomplete data. More on that in [Handling empty values, and in our full deep dive: the ?? operator in Attio.
CRM use cases:
+—{Base fee} + {Onboarding fee}— total contract components on the record itself, so reps never re-add line items in a spreadsheet.-—{Contract value} - {Discount amount}— net revenue after concessions, the number finance actually wants to see.*—{Seats} * {Price per seat}— quote math that can't drift out of sync with the inputs./—{Deal value} / {Seats}— effective price per seat, which instantly exposes over-discounted deals.==—{Plan} == "Enterprise"— the building block for tiered logic: route enterprise accounts to a different playbook.!=—{Billing country} != "US"— flag international deals that need tax or legal review before signature.>—{Estimated ARR} > 100000— segment true enterprise accounts using enriched data, not gut feel.>=—{Employee count} >= 200— encode your ICP threshold once instead of every rep rebuilding the filter.<—{Days to renewal} < 60— the renewal risk window that feeds your CS team's queue.<=—{Lead score} <= 20— the cutoff below which leads go to nurture instead of a rep.!—!{Is customer}— target prospects only, and keep existing customers out of cold sequences.??—{MRR} ?? 0— one blank field can't blank out a whole rollup or forecast column.
Logic functions
| Function | Syntax | What it does | Example |
|---|---|---|---|
if() | if(condition, trueValue, falseValue) | Returns trueValue when the condition is true, otherwise falseValue | if({Deal value} > 10000, "Enterprise", "Standard") |
and() | and(left, right) or left and right | True only when both arguments are true | and({Deal value} > 10000, {Deal stage} == "Negotiation") |
or() | or(left, right) or left or right | True when at least one argument is true | or({Deal stage} == "Closed won", {Deal stage} == "Closed lost") |
not() | not(value) or !value | Returns the opposite boolean | not(hasBeenIn({Deal stage}, "Closed lost")) |
13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">if() is the workhorse — nest it to build tiered logic like lead scores and pricing bands. We wrote a full deep dive on it: [the if() function in Attio formulas. Both and/or support keyword syntax (a and b) as well as function syntax, so formulas read naturally.
CRM use cases:
if()—if({Deal value} > 50000, "High touch", "Standard")— turns raw numbers into labels reps can filter, sort, and act on without interpreting data themselves.and()—and({Employee range} >= 200, {Purchase timeline} == "This quarter")— a single "Sales qualified" flag instead of a multi-condition saved filter every rep rebuilds slightly differently.or()—or({Stage} == "Closed won", {Stage} == "Closed lost")— one "Closed" bucket for clean win/loss reporting regardless of outcome.not()—not(hasBeenIn({Deal stage}, "Closed lost"))— keep previously lost deals out of automated re-engagement sequences, where a tone-deaf email does real damage.
Math functions
| Function | Syntax | What it does | Example |
|---|---|---|---|
abs() | abs(value) | Absolute value | abs(-42.5) → 42.5 |
round() | round(value, num_digits) | Rounds to the given decimal places | round(1234.5678, 2) → 1234.57 |
ceil() | ceil(value) | Rounds up to the nearest integer | ceil(7.2) → 8 |
floor() | floor(value) | Rounds down to the nearest integer | floor(9.8) → 9 |
sum() | sum(values) | Sum of an array | sum([10, 25, 3]) → 38 |
count() | count(values) | Number of items in an array | count([10, 25, 3]) → 3 |
min() | min(values) | Smallest value in an array (numbers, currency, or dates) | min([10, 25, 3]) → 3 |
max() | max(values) | Largest value in an array (numbers, currency, or dates) | max([10, 25, 3]) → 25 |
avg() | avg(values) | Average of an array | avg([1.5, 2.3, 4.7]) → 2.8333 |
mean() | mean(values) | Arithmetic mean (numbers, currency, or dates) | mean([10, 20, 90]) → 40 |
median() | median(values) | Middle value (numbers, currency, or dates) | median([10, 20, 90]) → 20 |
unique() | unique(values) | Removes duplicates from an array | unique([1, 2, 2, 3, 3]) → [1, 2, 3] |
power() | power(base, exponent) | Base raised to an exponent | power(2, 8) → 256 |
mod() | mod(dividend, divisor) | Remainder after division | mod(10, 3) → 1 |
exp() | exp(value) | e raised to the given power | exp(2.5) → 12.1825 |
log() | log(value) | Natural logarithm | log(1000) → 6.9078 |
random() | random() | Random number between 0 and 1 | round(random() * 100, 0) |
Note that the array functions (sum, count, avg, min, max, median, unique) pair naturally with multi-value and relationship attributes — that's how count({Team}) works.
CRM use cases:
abs()—abs({Forecast} - {Actual})— forecast variance regardless of direction, for spotting reps who consistently miss either way.round()—round({Deal value} * 0.4, 0)— clean weighted-pipeline figures that don't show $19,847.3333 in a board deck.ceil()—ceil({Users} / 10)— seat bundles needed when you sell in packs of 10; you never sell 4.2 bundles.floor()—floor(dateDiff({Signup date}, today(), "months"))— full months as a customer, for tenure-based segmentation.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">sum()— total pipeline per company across its linked deals, live on the record instead of buried in a report. Full deep dive: [the sum() function in Attio.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">count()—count({Team})— contacts per account: fewer than 2 means the deal is single-threaded, your biggest silent risk. Full deep dive: [the count() function in Attio.min()— earliest renewal date among an account's linked contracts — the real "next action" date for CS.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">max()— the largest deal in an account's history — anchors expansion conversations and upsell targets. Full deep dive on both: [the min() and max() functions in Attio.avg()/mean()— average deal size across an account's closed deals — flags accounts buying below their potential.median()— median deal size for a segment — one whale deal can't distort what "typical" looks like.unique()— distinct products per account across its deals, the cleanest cross-sell whitespace metric.power()—{ARR} * power(1.2, 3)— three-year value projection at 20% expansion, useful for account prioritization.mod()—mod({Record ID}, 4)— deterministic bucketing into 4 groups for territory splits or experiment cohorts.exp()/log()—log({Twitter follower count} + 1)— tame heavy-tailed enrichment metrics so one celebrity account doesn't dominate a lead score.random()—round(random() * 100, 0)— sample a random slice of records for data-quality audits or call reviews.
Date and time functions
| Function | Syntax | What it does | Example |
|---|---|---|---|
now() | now() | Current date and time as a timestamp; adds a daily recalculation around midnight UTC | dateAdd(now(), 7, "days") |
today() | today() | Current date, without time; adds a daily recalculation around midnight UTC | today() |
date() | date(raw_date) | Parses text/number (ISO 8601 or numeric timestamp) into a date | date("2024-01-15") |
timestamp() | timestamp(value) | Parses text/number (ISO 8601 or Unix timestamp) into a timestamp | timestamp("2024-01-15T09:30:00Z") |
dateAdd() | dateAdd(date, units, unit_type) | Adds time units ("days", "months", "years"…) to a date | dateAdd(date("2024-06-01"), 3, "months") → 2024-09-01 |
dateDiff() | dateDiff(start, end, unit_type) | Absolute difference between two dates; units from "seconds" up to "years" | dateDiff(date("2024-01-01"), date("2024-01-31"), "days") → 30 |
formatDate() | formatDate(date, format) | Formats a date as text; tokens include YYYY, MM, DD, MMMM, Q[Q] YYYY (quarter) | formatDate({Close date}, "MMMM YYYY") → March 2026 |
day() | day(date) | Day of month (1–31) | day(date("2024-03-15")) → 15 |
hour() | hour(date) | Hour of day (0–23) | hour(timestamp("2024-03-15T14:30:00Z")) → 14 |
eomonth() | eomonth(date) | Last day of the month | eomonth(date("2024-02-10")) → 2024-02-29 |
setTimezone() | setTimezone(date, timezone) | Converts to a specific timezone | setTimezone(now(), "Europe/London") |
Formulas using now() or today() recalculate daily, so "days since last interaction" style fields stay current without anyone touching the record.
CRM use cases:
today()—dateDiff({Last interaction}, today(), "days")— days of silence on every account; the single best "who's going cold" signal.now()—dateDiff({Created at}, now(), "hours")— lead age in hours, for enforcing speed-to-lead SLAs where minutes decide conversion.date()—date({Imported close date})— turn text dates from a migration or CSV import into real, sortable, filterable dates.timestamp()— parse datetime strings pushed in by integrations, so webhook data behaves like native Attio timestamps.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">dateAdd()—dateAdd({Close date}, 12, "months")— auto-computed renewal date the moment a deal closes; nobody ever forgets to set it. Full deep dive: [the dateAdd() function in Attio.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">dateDiff()—dateDiff({Created at}, {Close date}, "days")— sales cycle length per deal, the raw material for velocity reporting. Full deep dive: [the dateDiff() function in Attio.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">formatDate()—formatDate({Close date}, "Q[Q] YYYY")— quarter labels like "Q1 2026" you can group and pivot on in views and reports. Full deep dive: [the formatDate() function in Attio.day()—day({Contract start})— billing day of month, for aligning invoicing and dunning workflows.hour()—hour({Created at})— what hour inbound leads arrive; the case for (or against) after-hours routing coverage.eomonth()—eomonth({Contract start})— align renewals and invoices to month-end without manual date arithmetic.setTimezone()—setTimezone({Next meeting}, "America/New_York")— meeting times in the prospect's timezone, killing the "wait, whose 3pm?" problem.
Text functions
| Function | Syntax | What it does | Example |
|---|---|---|---|
contains() | contains(haystack, needle) | Checks whether text contains a value (or an array contains an element) | contains("Hello World", "World") → true |
replace() | replace(subject, search, replacement) | Replaces the first occurrence | replace("Hello World", "World", "There") → "Hello There" |
replaceAll() | replaceAll(subject, search, replacement) | Replaces every occurrence | replaceAll("Hello World", "l", "r") → "Herro Worrd" |
length() | length(value) | Length of a text value | length("Hello World") → 11 |
contains() doubles as a membership check on arrays, which makes it handy for multiselect attributes — "does Categories include SaaS?"
CRM use cases:
13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">contains()—contains({Categories}, "SaaS")— a filterable ICP-fit checkbox from a multiselect; also great for flagging competitor mentions in a notes field. Full deep dive: [the contains() function in Attio.replace()—replace({Domains}, "www.", "")— normalize domains for clean matching and dedupe across enrichment sources.replaceAll()—replaceAll({Phone}, " ", "")— strip formatting noise so phone numbers match what your dialer expects.length()—if(length({Description}) < 50, "Needs research", "OK")— a data-quality flag showing which records are too thin to hand to a rep.
Attribute history functions
This is the category no spreadsheet can replicate, because it queries the history of an attribute, not just its current value. Attio has been tracking every change to your select and status attributes all along — these functions finally let you compute on that history.
| Function | Syntax | What it does | Example |
|---|---|---|---|
hasBeenIn() | hasBeenIn(attribute, values) | True if the attribute is currently, or has ever been, set to the value. Accepts a single value or an array; optional match argument ("any" or "all") and a dateRange to limit the history checked | hasBeenIn({Deal stage}, ["Negotiation", "Signing"]) → true |
timeSpentIn() | timeSpentIn(attribute, value, unit) | Total time the attribute spent at a value across its history. Pass the attribute itself as the value to target its current value. Units: "hours", "days", "weeks", "months" (months ≈ 30 days) | timeSpentIn({Deal stage}, {Deal stage}, "days") → 14 |
valueSetAt() | valueSetAt(attribute, value) | Timestamp when the attribute was first set to a value. Pass the attribute itself as the value for when the current value was set; optional position argument ("first" or "last") | valueSetAt({Deal stage}, {Deal stage}) → 2024-04-15 |
valueAt() | valueAt(attribute, timestamp) | The historical value at a specific point in time | valueAt({Deal stage}, date("2024-01-15")) → Proposal |
previousValue() | previousValue(attribute) | The value immediately before the current one; empty when there's no prior history. Actor reference and interaction attributes aren't supported | previousValue({Deal stage}) → Proposal |
Note: hasBeenIn(), valueSetAt() and timeSpentIn() only work with select and status attributes — they're not supported on text, number, date, or other attribute types.
Think about what this unlocks with zero engineering:
13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">timeSpentIn()—timeSpentIn({Deal stage}, "Negotiation", "days")— deal velocity: how long deals actually sit in each stage, and which ones are stuck right now. The metric every pipeline review needs and no CRM showed out of the box. Full deep dive: [the timeSpentIn() function in Attio.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">hasBeenIn()—hasBeenIn({Deal stage}, "Closed lost")— boomerang detection: which "active" deals were previously lost? They close differently and deserve different handling — and conversely, "ever reached Proposal" gives you true stage-conversion cohorts. Full deep dive: [the hasBeenIn() function in Attio.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">previousValue()—previousValue({Deal stage})— regression alerts: a deal that just moved *backwards* is your loudest early-warning signal, and it's invisible in a normal stage column. Full deep dive: [the previousValue() function in Attio.13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">valueSetAt()—valueSetAt({Status}, "MQL")— SLA measurement: the exact timestamp a lead became an MQL, so you can compute (and enforce) time-to-first-touch against it. Full deep dive: [the valueSetAt() function in Attio.valueAt()—valueAt({Deal stage}, date("2026-07-01"))— point-in-time snapshots: what stage was every deal in at quarter start? Pipeline-movement reporting without exporting a single row.
Historically this was the stuff of BI tools and change-data-capture pipelines. Now it's one attribute.
How recalculation works
A formula recalculates:
- when it's first created,
- when the formula itself is edited,
- within seconds whenever any referenced value changes,
- once daily (around midnight UTC) for formulas using
now()ortoday().
There's nothing to schedule and no sync to babysit. If the inputs are right, the output is right.
Handling empty values
CRM data is never complete, and formulas need to survive that. Attio's rules:
- An empty attribute evaluates to
null. - Use the
??fallback operator to substitute a default:{MRR} ?? 0means "MRR, or zero if it's empty." - Explicitly check for emptiness with
{attribute} == null.
Rule of thumb: wrap every numeric input in a ?? 0 fallback (or handle null in an if()) before multiplying or summing, or a single blank field will blank out the whole result.
Real-world formulas you can steal
Here are the patterns we're deploying in client workspaces, based on Attio's own examples and our builds.
1. Weighted pipeline value. Multiply deal value by the close probability of its stage:
{Deal value} * if({Stage} == "Negotiation", 0.4,
if({Stage} == "Proposal", 0.2,
if({Stage} == "Contract sent", 0.8, 0.1)))A $50,000 deal in a 40% stage shows a $20,000 weighted value. Set output to Currency, sum the column in a pipeline view, and you have a live forecast.
2. Count linked records.
count({Team})Live team size on every Company via the relationship attribute. Works for any relationship — deals per company, contacts per account, whatever your data model connects.
3. Stale deal alert.
if(timeSpentIn({Stage}, {Stage}, "days") > 14, "Stale", "Active")Passing {Stage} again as the value targets whatever stage the deal is in right now. Filter a view to Stale and you have a standing triage list.
4. Days to renewal.
dateDiff(today(), {Renewal date}, "days")A live countdown on every account — recalculated daily thanks to today(). Filter for < 60 and your CS team has a renewal queue.
5. MRR to ARR.
({MRR} ?? 0) * 12Trivial — and exactly the kind of trivial calculation that used to require a spreadsheet export.
6. Lead scoring.
if({Channel} == "Referral", 30, if({Channel} == "Inbound", 20, 10))
+ if({Employee range} == "200+", 25, 10)
+ if({Purchase timeline} == "This quarter", 30, 0)Points per signal, summed into a single priority number. Set the output to Rating for an at-a-glance score, sort your lead view by it, and route from there.
7. Seat-based contract value.
{Seats} * {Price per seat}Quote math that can never drift out of sync with the inputs.
Limitations to know about
Formula attributes are impressively complete for a v1, but plan around these:
- Formula nesting is capped at three levels deep. A formula can reference other formula attributes, but the chain can only go so far — you can't drop in a formula attribute that's itself built on too many layers of other formulas. If formula A references B, which references C, that's your limit; a fourth layer won't be accepted. Design accordingly: keep base formulas (stage probability, normalized revenue) flat, and compose only one or two levels on top of them.
- Pro and Enterprise only. Free and Plus workspaces don't get the attribute type.
- Creation is limited to Admins and Full-access members — reasonable governance, but worth knowing before you promise your whole team they can build these.
now()/today()formulas refresh daily (around midnight UTC), not continuously. Fine for "days since" fields; don't expect minute-level countdown timers.- History functions only work on select and status attributes.
hasBeenIn(),valueSetAt()andtimeSpentIn()aren't supported on text, number, or date attributes, andpreviousValue()skips actor reference and interaction attributes. Structure your pipeline stages as statuses (which you should be doing anyway) to get full value.
Formula attributes vs. workflows vs. AI attributes
Attio now has three ways to populate a field automatically. Pick the right tool:
| Formula attributes | Workflows | AI attributes | |
|---|---|---|---|
| Best for | Deterministic calculations from record data | Multi-step automation with side effects (Slack, email, record creation) | Unstructured judgment — summarizing notes, classifying, enriching |
| Recalculates | Automatically, seconds after inputs change | On trigger | On configured runs |
| Output | Always the same for the same inputs | Whatever the workflow writes | Model-generated, may vary |
| Cost | No credits (including AI composer) | Workflow runs | AI credits |
The old pattern of using workflow Calculation blocks to write computed values into fields is now effectively legacy for pure math — formulas are simpler, always current, and don't consume workflow runs. Save workflows for automation that *does* something, and AI attributes for questions with no formula-shaped answer.
Final thoughts
Formula attributes close one of the last real gaps between Attio and the "export it to a spreadsheet" workflow. The design choices are the right ones: deterministic results you can audit, an AI composer so nobody needs to memorize syntax, output types that keep data clean, and — the sleeper feature — history functions that turn your CRM's change log into queryable data.
If you build only one formula this week, make it timeSpentIn({Stage}, {Stage}, "days") on your deals. Stage velocity is the metric almost every team wants and almost no CRM shows out of the box. Now yours does.
Want formulas like these wired into your workspace — weighted pipeline, lead scoring, renewal countdowns, the lot? That's exactly the kind of build we do at Craftt. Start with a free workspace audit or see how the 7-day AI-native Attio sprint works.
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.