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

The valueAt() function in Attio: what stage was every deal in at quarter start?

Written by

Published 7 min read

Tested in live Attio workspaces.

Every pipeline review starts with the same question and the same workaround: *what did the pipeline look like at the start of the quarter, and what's moved since?* The workaround is an export on day one, filed somewhere, compared by hand on day ninety. valueAt() deletes the workaround. It reads an attribute's history at any moment you name, so the quarter-start snapshot is a column, not a file.

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(), and min()/max(). This one completes the history family.

Table of contents

What the valueAt() function does

Two arguments: the attribute, and the moment to read it at.

valueAt(attribute, timestamp)

valueAt({Deal stage}, date("2024-01-15")) returns Proposal if that's where the deal sat on 15 January 2024 — regardless of how many stages it has passed through since. Attio keeps the full change history behind every attribute; valueAt() is the function that opens it at a given page.

The second argument should be a real date, not a string, so wrap literals in date(): date("2026-07-01"). Force the output type to match the attribute you're reading — Text for a status like Deal stage — rather than leaving it on Auto.

The snapshot column

The headline use: one formula on Deals, one date in it.

valueAt({Deal stage}, date("2026-07-01"))

That's "Stage at Q3 start" — for every deal, live, as a sortable and groupable column. Group a view by it and you're looking at the pipeline exactly as it stood on 1 July, sitting next to the pipeline as it stands today. Board the quarter's starting position against its current one and the movement story reads off the screen.

The date is a literal, so when the quarter rolls over you edit one formula — or keep "Stage at Q3 start" and add "Stage at Q4 start" alongside it. Two or three snapshot columns cost nothing and give you a rolling history of the pipeline without ever exporting a row.

Movement: snapshot vs. now

The snapshot gets more useful when you compare it to the present. The current value is just the attribute itself, so via if() and the == operator:

if(valueAt({Deal stage}, date("2026-07-01")) == {Deal stage}, "Unchanged", "Moved")

Filter to "Unchanged" and you have the deals that haven't progressed one stage in an entire quarter — the stall list, which is a different and more damning thing than "old deals". A deal can be old and moving; these are the ones that aren't.

Push it one step further and label the direction, which is the same pattern the previousValue() article uses for regression alerts, just anchored to a date instead of to the last change: a deal that was in Negotiation on 1 July and is in Proposal today has slipped this quarter, whatever happened in between.

Blank means "didn't exist yet"

A record created after the snapshot date has no history on that date, so 13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">valueAt() returns blank. This is one of the cases where blank carries real information — it's the "new this quarter" cohort — so name it with [?? rather than hiding it:

valueAt({Deal stage}, date("2026-07-01")) ?? "New this quarter"

Now the snapshot column has a complete story: every deal is either at the stage it was on 1 July, or it's new since then. Group by the column and the quarter's *creation* is visible in the same view as its *movement*. Apply the same guard inside the movement flag, otherwise new deals compare blank against a real stage and land in "Moved" for the wrong reason.

Where valueAt() sits in the history family

Five functions read the change history; each answers a different question about time:

QuestionFunction
"Has it ever been X?"hasBeenIn({Deal stage}, "Closed lost")
"How long has it been at X?"timeSpentIn({Deal stage}, {Deal stage}, "days")
"When did it become X?"valueSetAt({Deal stage}, "MQL")
"What was it just before?"previousValue({Deal stage})
"What was it on *this date*?"valueAt({Deal stage}, date("2026-07-01"))

The first four are anchored to *values* or to the *last change*. valueAt() is the only one anchored to a *date you choose* — which is exactly what period-over-period reporting needs. previousValue() tells you where a deal just came from; valueAt() tells you where it was when the quarter opened, however many moves ago that was.

CRM use cases that earn their keep

  • Quarter-start pipeline snapshotvalueAt({Deal stage}, date("2026-07-01")) — important because "what did the pipeline look like on day one" is the baseline every forecast review is measured against, and it shouldn't live in a spreadsheet.
  • Stall listif(valueAt({Deal stage}, date("2026-07-01")) == {Deal stage}, "Unchanged", "Moved") — important because a deal that hasn't moved a single stage all quarter is a different problem from a deal that's merely old.
  • New-this-quarter cohortvalueAt({Deal stage}, date("2026-07-01")) ?? "New this quarter" — important because pipeline creation and pipeline movement are separate questions, and the blank tells them apart for free.
  • Slipped-this-quarter flag — compare snapshot to current stage for backward moves — important because a Q-start-to-now regression is the forecast risk that a single "previous stage" check can miss when the deal bounced twice.
  • Account status at renewalvalueAt({Health}, date("2026-06-30")) on Companies — important because "what was their health score when the contract renewed" is the question churn post-mortems actually ask.
  • Lead status at campaign datevalueAt({Status}, date("2026-05-12")) on People — important because attributing a campaign means knowing where each lead was *when it ran*, not where they are now.

Copy-paste formulas

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

Stage at quarter start (Text output):

valueAt({Deal stage}, date("2026-07-01"))

Stage at quarter start, with new-deal cohort (Text output):

valueAt({Deal stage}, date("2026-07-01")) ?? "New this quarter"

Moved-this-quarter flag, blank-safe (Text output):

if((valueAt({Deal stage}, date("2026-07-01")) ?? "New") == {Deal stage}, "Unchanged", "Moved")

Health score at last renewal (Text output):

valueAt({Health}, date("2026-06-30"))

Ever-been-lost, but only counting the lost deals we've since revived — pair with the rest of the family (Text output):

if(hasBeenIn({Deal stage}, "Closed lost") and valueAt({Deal stage}, date("2026-07-01")) != "Closed lost", "Revived", "")

Gotchas

  • Wrap the date. valueAt({Deal stage}, "2026-07-01") hands the function text; date("2026-07-01") hands it a date. Use date() (or timestamp() for an exact time) every time.
  • Stick to select and status attributes. The rest of the history family is documented as select/status-only, and stages and statuses are where the value is anyway — structure your pipeline as statuses and every history function pays off.
  • Blank is a cohort, not an error. Records created after the snapshot date return nothing on it. Label it with ?? instead of treating it as a bug.
  • The date is a literal. The snapshot doesn't roll forward by itself; you re-point it (or add a new column) each period. That's a feature — a snapshot that silently moved would be worthless.
  • Nesting limit still applies. Three formula attributes deep, so build the snapshot as its own attribute and reference it from the movement flag rather than stacking further.

Final thoughts

valueAt() is the history function that turns reporting from an export into a column. Ship the snapshot first — valueAt({Deal stage}, date("2026-07-01")) on Deals — then the ?? cohort label, then the == movement flag. In three formulas the quarter's pipeline baseline, its new business, and its stalled deals all live in one view, and nobody files a spreadsheet on day one again.

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 pipeline snapshots, movement flags, and forecast views 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 call