Naming conventions
Lower-case everything, separator is the hyphen -. Every name is parseable by grep on a single line. These four patterns are the only ones that should appear in dashboards, asset filenames, and UTM params.
Ad-set name pattern
Pattern
{audience}-{funnel_stage}-{geo}-{platform_suffix}Audience tells you who, funnel_stage tells you where in the funnel they entered, geo tells you where to fly it, platform_suffix disambiguates Meta from Google when the same audience runs on both. Each piece is grep-able.
valid
prospecting-cold-lal1-us-meta
Matches all four slots.
prospecting-cold + lal1 (lookalike) + us + meta platform. Grep for prospecting-cold finds every campaign that targets the cold-prospect audience.invalid
Prospecting LA Lookalike US — Meta
Mixed case, embedded spaces, no separators, natural language phrases. Cannot be grep'd by audience without false positives. No
funnel_stage slot.valid
retargeting-tyvisit-uk-google
Targets UK visitors to the thank-you page on Google. Audience + stage + geo + platform all present.
invalid
retargeting_UK_v3
Underlines instead of hyphens. No funnel_stage slot.
v3 is a version, not an audience — versioning belongs in the dashboard, not the name.Creative filename pattern
Pattern
{concept_id}-{variant}-{format}.{ext}First two segments become
utm_content + utm_term. Filename stays parseable after the platform's auto-tagging mangling.valid
hook-v1-wagleak.mp4
hook is the concept id, v1-wagleak is the variant. Drops cleanly into utm_term=hook-v1-wagleak.invalid
Hook V1 - Wage Leak (Final)!.MP4
Spaces break every shell tool. Upper-case. Punctuation the platform will mangle.
(Final) and ! end up as _- or stripped when the URL is built.valid
explain-30s-payrollrun.png
Static image creative, named for the concept. Extension matched to the actual file format.
invalid
FINAL_Image_Copy_03_v2.jpeg
Naming reveals the iteration ladder, not the concept.
FINAL_Image is meaningless to anyone but the author. New variant == new concept id; iterate the variant field, not the workflow.Folder pattern per vertical
Pattern
/campaigns/{key}/assets/{concept_id}/{format}/{file}Concept ids are stable across variants, formats (image, video, doc) are flat siblings inside the concept, files sit at the leaf. One per creative variant, never bundled.
valid
/campaigns/pl/assets/hook/image/hook-v1-wagleak.png
Concept
hook, format image, file inside. Drift between campaigns is visible at the campaign level; drift between variants is visible at the concept level.invalid
/campaigns/pl/assets/creatives/2026/aug/v1/png/hook.png
Time-stamped folders (
2026/aug/v1) document when an asset was made, not what it is. New campaigns can't reuse old assets without renaming. Also: campaign pl is buried under generic labels.invalid
/campaigns/pl/assets/hook-v1.png
All assets at the campaign root. The next variant (
hook-v2) ends up a sibling, then the directory fills with hook-v1.png, hook-v1-wagleak.png, hook-v2-final.png and you can't tell which is current without opening them.Product ID role suffixes
Product IDs in registry/products.json carry a role suffix so the same vertical can run multiple offers per role without ID collisions across environments. The suffix is appended to a stable vertical prefix:
Pattern
{VERTICAL_PREFIX}_{ROLE}[_{ENV}]Vertical prefix + role is the same in every environment. The
_ENV tail only changes for staging/test IDs — production IDs never carry a suffix.| Role | Suffix | Stable example |
|---|---|---|
| core offer | _MAIN | PROD_PAYROLL_MAIN |
| order bump | _BUMP | PROD_PAYROLL_BUMP |
| OTO1 (assist) | _OTO1 | PROD_PAYROLL_OTO1 |
| OTO2 (case study) | _OTO2 | PROD_PAYROLL_OTO2 |
| test-pay (any role) | _TEST | PROD_PAYROLL_MAIN_TEST |
Scenario link naming
The media planner at /plan/ writes its scenario into the URL hash. That URL is the share-link, and the slug in the hash should be human-readable so it survives copy-paste into chat / docs:
Pattern (URL hash)
#c={key}&p={preset}&{param}={value}&…The hash is the canonical share format.
c = campaign key, p = preset (conservative/base/aggressive), the rest are documented at the bottom of the planner page. Keys are stable across versions.valid
/plan/#c=pl&p=base&s=8000&d=45&cv=6
Campaign + preset + spend bump + duration bump + a critical CVR change. Identifiable in chat: "the $8000/45d at 6% scenario."
invalid
/plan/#c=pl&s=8000&cv=6&d=45
No preset anchor. Receiver of the link can't tell whether
cv=6 is a Conservative or Aggressive run — they have to click and inspect.invalid
/plan/?scenario=pl-base-v2-s8000
Query-string form. Not used by the planner. The hash is the canonical contract.