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

The contains() function in Attio: one question your filters can't ask

·6 min read

Half the questions a revenue team asks are membership questions. Is this account tagged SaaS? Does the description mention a competitor? Is "Enterprise" one of the segments on this record? Attio's filters can check what an attribute *equals*; contains() checks what an attribute *includes* — inside text and inside multiselects alike — and that difference covers a surprising amount of ground.

This is part of our function-by-function series on Attio formula attributes — previously: if(), timeSpentIn(), dateDiff(), the ?? operator, count(), hasBeenIn(), and valueSetAt(). This one covers contains(): the syntax, the two modes, and the formulas worth copying.

Table of contents

What the contains() function does

contains() checks whether one value includes another:

contains(haystack, needle)

So contains("Hello World", "World") returns true. It answers with true or false, which makes Checkbox the natural output type — force it explicitly rather than leaving the type on Auto, and the result is a clean, filterable tick on every record.

The function has two distinct modes, and the second one is where most of the value hides.

Text mode: searching inside a field

Point contains() at any text attribute and it searches inside it:

contains({Description}, "Salesforce")

Every record whose description mentions Salesforce gets a tick. This is the pattern for competitor-mention flags: your notes, descriptions, and enrichment fields are full of signal that no structured attribute captures, and a contains() check per competitor name turns free text into a filterable column. A rep walking into a call knowing the account already mentioned a rival is a different conversation than one finding out live.

Array mode: the multiselect trick

contains() doubles as a membership check on arrays — which means it works on multiselect attributes:

contains({Categories}, "SaaS")

"Does Categories include SaaS?" — answered per record, no matter what else is selected alongside it. This is the mode that earns contains() its place in the series, because multiselects are everywhere in a real workspace — industries, segments, product interests, tags from enrichment — and "is *this specific option* among them?" is exactly the question views struggle to ask cleanly. One formula, one Checkbox output, and the question becomes a filter.

It's the same philosophy as count() on relationships: Attio already holds the data in a structure — the formula just extracts the one fact you actually filter by.

Turning matches into labels

A checkbox is a filter. Wrapped in if(), it becomes a label your views and automations can read:

if(contains({Categories}, "SaaS"), "ICP fit", "Out of ICP")

The classic ICP-fit flag: your enrichment already tagged the industry, the formula reads the tag, and every record self-classifies. Sorting a pipeline view by ICP fit — or routing "Out of ICP" inbound to a lighter-touch sequence — needs no manual triage once this column exists.

Stack conditions with and() or or() for richer definitions: ICP fit might mean the right industry *and* the right segment, and both checks are just contains() calls on different multiselects.

Blank-proofing your checks

Same trap as every formula in this series, so briefly: an empty attribute evaluates to 13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">null, and null flowing through an expression can blank the whole result. A record with no Categories set would silently drop out of *both* sides of your ICP filter — and the untagged records are often exactly the ones that need attention. Guard with [??:

contains({Categories}, "SaaS") ?? false

Now empty reads as an explicit false: the record shows up unticked instead of vanishing.

contains() vs. hasBeenIn()

The two sound similar and answer different questions:

  • contains() checks the current value — is this option among the selected ones *right now*, is this string in the text *as it stands today*.
  • hasBeenIn() checks the history — was this select or status *ever* set to the value, even if it isn't anymore.

"Is this account tagged Enterprise?" is 13px] bg-[color:var(--color-bg-muted)] border border-[color:var(--color-border)] px-1.5 py-0.5 rounded">contains(). "Was this deal ever in Closed lost?" is [hasBeenIn(). If the question has the word "ever" in it, you want the history family; if it's about the present, contains() is simpler and works on more attribute types.

CRM use cases that earn their keep

  • ICP-fit flag from a multiselectif(contains({Categories}, "SaaS"), "ICP fit", "Out of ICP") — important because enrichment tools fill multiselects nobody filters on, and this converts that dormant data into a routing signal.
  • Competitor-mention alertscontains({Description}, "CompetitorName") on notes or description fields — important because competitive deals need different handling, and the mention is usually buried in free text where no filter reaches.
  • Segment membership checkscontains({Segments}, "Enterprise") — important because "is Enterprise one of the segments?" is a different question from "does Segments equal Enterprise?", and only the first one survives records with multiple selections.
  • Missing-tag detectornot(contains({Categories}, "SaaS") ?? false) — important because the inverse view — records that *should* have a tag and don't — is where data-quality work actually starts.
  • Product-interest routingcontains({Products interested}, "Enterprise plan") feeding an automation filter — important because routing on a multiselect option is exactly the kind of condition automations want as a clean checkbox.
  • Compound ICP definitionsand(contains({Categories}, "SaaS"), contains({Segments}, "Mid-market")) — important because a real ICP is rarely one tag, and stacking membership checks keeps the whole definition in one auditable formula.

Copy-paste formulas

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

Multiselect membership check (Checkbox output):

contains({Categories}, "SaaS")

ICP-fit label (Text output):

if(contains({Categories}, "SaaS"), "ICP fit", "Out of ICP")

Competitor mention in free text (Checkbox output):

contains({Description}, "CompetitorName")

Blank-safe membership check (Checkbox output):

contains({Categories}, "SaaS") ?? false

Two-condition ICP flag (Checkbox output):

and(contains({Categories}, "SaaS") ?? false, contains({Segments}, "Mid-market") ?? false)

Final thoughts

contains() is the bridge between the data you have and the filters you want: multiselects and text fields hold most of a workspace's qualitative signal, and this one function makes any piece of it filterable. Start with the ICP-fit flag — it's one line on top of enrichment data you already pay for, and the first saved view built on it replaces a recurring manual triage session.

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

And if you'd rather have ICP scoring, competitor flags, and routing 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