A
?
← Handbook

Campaign naming

Existing campaigns are not being renamed. Everything here applies to campaigns created from 25 Aug 2026 onward. The portal reads the old names too — it just has to guess more.

Why the portal cares

Meta records no intent. Interior Box's client acquisition campaign and its hiring campaign both carry objective OUTCOME_LEADS, so nothing but the name distinguishes a $98.62 client lead from an $18.64 recruitment lead. Averaged together they read $58.63 — a figure that describes neither, and which hid the fact that acquisition cost had doubled.

The name is the only signal. That is the whole reason a convention exists.

The convention

<CLIENT>_<PURPOSE>_<YEAR>_<DETAIL>

TODZ_LEADGEN_2026_RENO
IBOX_RECRUITMENT_2026_DESIGNERS
ABONE_SALES_2026_KNITWEAR
CLARTE_RETARGETING_2026_CART

Only the second field is load-bearing. The rest is for humans.

Purpose — the one word that must be right

Write For Counted in client results
LEADGENclient enquiries — the core serviceyes
SALESecommerce purchasesyes
RETARGETINGwarm audiences, catalogue, DPAyes
RECRUITMENThiring for the client — designers, sales staffno
AWARENESSreach and brand, no conversion expectedno

RECRUITMENT and AWARENESS are excluded from every cost-per-result figure a client sees, and shown separately underneath. Everything else counts.

Anything unrecognised is treated as client work, because that is what an ad account is mostly full of and because the campaign that matters is often named after the client rather than after its job — ATLAS_CAMPAIGN is Interior Box's acquisition campaign, and excluding it would drop the number that matters most.

The consequence is one-directional and worth knowing: a mislabelled recruitment campaign quietly flatters the client's cost per lead. Getting RECRUITMENT right matters more than any other word in the name.

What does not need naming

Whether a client sells or collects enquiries is never configured. The account answers it: if purchases fire it sells, if only leads fire it collects. Atlas & Bone is the only ecommerce account in the portfolio and is described by purchases and ROAS without anyone setting a flag — lib/creative/outcome.ts. A field somebody has to remember to set is null on most rows forever, and the first ecommerce client added after today would be described by its lead count again.

Where the rule lives

  • src/lib/meta/campaign-purpose.ts — classification, and the exclusion rule
  • supabase/migrations/0079_ad_results_purpose.sql — the same rule in SQL, for the backfill
  • src/lib/services/purpose-rule.test.ts — asserts no client-facing surface reads unfiltered results

Matching runs on a normalised name, so IBOX_RECRUITMENT_2026 and IBOX recruitment 2026 classify identically. Underscores are word characters, so a \b-based pattern would never have matched inside RE_2026_LEADS — that bug is why the normaliser exists.

Adding a purpose

Three places, in this order: the pattern list, the SQL in a new migration backfilling existing rows, then the exclusion rule if it should be held out of client figures. The test will fail until the first two agree.