---
name: campaign-naming-convention
description: Defines and enforces names for paid campaigns, ad groups, asset groups, ad sets and ads in Google Ads, Performance Max, Meta (Facebook and Instagram) and LinkedIn. One token order and delimiter, fixed vocabularies with a retire-and-alias lifecycle, a YYYYMM launch month and length limits checked per platform. Builds a registry that generates each name from its token columns, a dependency-free lint script, a warehouse parse model that joins on normalised platform IDs, a rename procedure and a weekly exception report ranked by spend. Use when asked for a campaign, ad set or ad naming convention, a naming template or campaign taxonomy, a Facebook ads or LinkedIn ads naming convention, Performance Max asset group names, cleaning up messy or inconsistent campaign names, keeping names consistent across Google Ads, Meta, LinkedIn and agencies, parsing campaign names in BigQuery, Snowflake or dbt, building cross-channel spend reports, renaming live campaigns, or checking names before launch.
license: CC-BY-4.0
metadata:
  tier: free
  category: tracking-plans-and-naming
  version: "0.2.0"
  last_reviewed: "2026-10-17"
  status: reviewed
---

# Campaign naming convention

Gives every paid campaign, ad group or ad set, and ad a name that people can scan and a parser can split. The output is a rules file, a vocabulary file, a registry, a lint report per launch, a warehouse parse model and a weekly exception report, all proposed for approval.

The rule everything else depends on: the platform object ID is the join key, and the name is a changeable attribute. Never join spend, sessions or CRM records on a name.

## When to use

Use this skill when:

- A Google Ads, Meta or LinkedIn account is being set up and names need a standard before the first launch.
- Names are inconsistent and a cross-channel spend report cannot group by market, objective, funnel stage or offer.
- An analyst needs names parsed into reporting columns in BigQuery, Snowflake or dbt.
- Live campaigns need renaming, or someone asks how to keep naming consistent across people and agencies.
- A launch wave needs its names checked before build.

Use something else when:

- Source, medium, non-paid UTM values, the UTM registry and the platform campaign map: utm-convention-and-registry. By its contract, this skill owns only the paid templates for `utm_campaign`, `utm_id` and `utm_content` (step 4).
- GA4 shows Unassigned, `(not set)` or literal macro text: ga4-unassigned-not-set-diagnosis.
- Event names: event-naming-convention.
- Applying an approved rename or any write through an API or MCP tool: agent-write-safety first.
- Deciding account structure: b2b-search-campaign-structure if installed; otherwise take the structure from the account owner. This skill names a structure; it does not choose it.
- Google Ads Editor import files for approved changes: google-ads-editor-import-builder if installed; otherwise hand over the rename change list for manual entry.
- Rollout and training across teams, or a bulk audit of tagged URLs: utm-governance-and-adoption or utm-link-audit if installed; otherwise the governance loop in [references/governance.md](references/governance.md) and utm-convention-and-registry's link tests.

## Inputs

Read these first if they exist (marketing-context-kit creates the folder):

- `martech-context/naming-rules.md` and the rules and vocabulary files it points to.
- `martech-context/stack.md`: platforms, account IDs, spend connectors, warehouse, owners.
- `martech-context/metrics.md`: which reports group spend, and by what.
- `martech-context/data-dictionary.md`: existing campaign dimension columns.
- `martech-context/decisions.md`: earlier naming or UTM decisions and cutover dates.
- The UTM registry from utm-convention-and-registry, for initiative names and IDs.

Then gather:

- Platforms, accounts and object types in use. Performance Max, Demand Gen and LinkedIn Conversation Ads change what can be named or tagged.
- The cuts people use in spend reports, each tied to a decision someone makes.
- A read-only export of current names with IDs, parent IDs, status, start date and 90 days of spend in one currency.
- How spend reaches the warehouse (BigQuery Data Transfer, a connector), the ID format each source uses, and since when daily snapshots exist.

Never ask for, print or store tokens, passwords or customer data. Exports need IDs, names, status, dates and spend only.

## Procedure

### 1. Inventory current names, read-only

- Google Ads: Google's read-only MCP server, tools `list_accessible_customers`, `search` (GAQL), `get_resource_metadata`, namespaced (`search_search`) over OAuth. Query `campaign`, `ad_group`, `asset_group` and `ad_group_ad`; queries in [references/platform-reference.md](references/platform-reference.md).
- Meta: Meta's Ads MCP server. Call only `ads_get_ad_accounts`, `ads_get_ad_entities`, `ads_get_field_context`, `ads_get_creatives` and `ads_get_ad_preview`. Ask the user to authorise with `ads_mcp_management` and `ads_read`, not `ads_management`; Meta does not document whether that blocks write tools, so the allow-list still applies.
- LinkedIn: no official MCP server was found on 2026-10-17. Use a Campaign Manager export or the Marketing API with the read-only `r_ads` permission.
- Keep every ID as text. Excel keeps 15 significant digits and zeroes the rest.
- Lint the export with a `spend` column (step 6). The linter prints the share of spend on failing names per platform. That sizes the cleanup.

### 2. Choose the tokens

A token earns its place only when both hold:

- a recurring report or decision groups spend by it, and
- the platform does not store it as a flat field the spend connector exports. Campaign type, bid strategy, budget, status, geo targets, devices, owner and end dates are fields. Keep them out.

Exception: a field-backed token may stay when the field is nested or missing from the team's spend connector, or when buyers need it in the platform's list view. That is why the defaults keep `targeting`, Meta `placement` and LinkedIn `format`.

Default grammar (definitions and values in [references/grammar-and-vocabulary.md](references/grammar-and-vocabulary.md)):

- Initiative level (Google Ads campaign, Meta campaign, LinkedIn campaign group): `market_objective_funnel_audience_offer_launch`, for example `us_leads_bofu_nonbrand_demo_202610`.
- Targeting level: Google Ads ad group and asset group `targeting_theme`; Meta ad set `targeting_theme_placement`; LinkedIn campaign `targeting_theme_format`.
- Ad level: `format_concept_variant` for Meta ads and named Google Ads ads, `concept_variant` for LinkedIn creatives. Responsive search ads have no name field, so their concept and variant live in the registry against the ad ID.

Decision rules:

- Broadest grouping first, launch month last. Never reorder after go-live: parsers read by position.
- Children never repeat parent tokens. The warehouse joins child to parent by ID.
- `launch` is the YYYYMM of first serving and never changes. A relaunch as a new object (a duplicate, or an experiment converted to a new campaign) gets a new launch month; budget and bid changes do not.
- "Spans several" values only where needed: market `global`, audience `all`, placement `auto`, targeting `broad`. No vocabulary has `other` or `misc`.
- Free text only in `theme` and `concept`: a hyphenated slug of at most 30 characters.
- A term with two meanings gets two values, and on Search two ad groups and two landing pages.

### 3. Fix the delimiter, characters and length

- `_` between tokens, `-` inside a token, lowercase `a-z` and `0-9` only, so one SQL function splits them.
- Reject pipes, spaces, capitals and emoji even where a platform accepts them. Meta names support emoji.
- House maximum: 100 characters at initiative level, 60 below. Platform limits checked on 2026-10-17: Google Ads campaign 256 characters, ad group under 255, asset group 128; Meta ad set 400; LinkedIn campaign group 200 bytes UTF-8. Meta campaign and ad names and LinkedIn campaign and creative names have no published limit.

### 4. Set the paid UTM templates

utm-convention-and-registry's contract gives this skill the paid templates for `utm_campaign`, `utm_id` and `utm_content`; that skill supplies source and medium and checks the templates.

- `utm_id`: the initiative-level ID by macro: `{campaignid}`, `{{campaign.id}}`, LinkedIn `CAMPAIGN_GROUP_ID`. `utm_content`: the ad ID by macro.
- `utm_campaign`, default: the registry initiative name (`2026_10_pipeline_webinar`), typed, identical across platforms and owned channels of one initiative. A platform name would split one initiative into a row per platform.
- `utm_campaign` where cost reaches GA4 through an automated connector (Meta): the platform name by macro, as that skill's cost step requires. Name before first publish; never rename while the connector runs.
- Each initiative-level registry row holds the platform ID, the generated name, `initiative_id` and the `utm_campaign` its links send, and feeds that skill's campaign map.
- Per-platform strings and resolved-URL checks: [references/utm-mapping.md](references/utm-mapping.md).

### 5. Draft the rules, vocabulary and registry

- Draft from [assets/naming-rules.yaml](assets/naming-rules.yaml) and [assets/naming-vocab.csv](assets/naming-vocab.csv), the single source of approved and retired values (also the warehouse seed). Show the diff and write to `martech-context/` only after the user approves; record owners in `naming-rules.md`.
- Start the registry from [assets/campaign-registry-template.csv](assets/campaign-registry-template.csv): one row per initiative-level object and per responsive search ad.
- Generate each name from its token columns, never type it: `=LOWER(TEXTJOIN("_",TRUE,I2:T2))` in the template's column order, or take the linter's suggestion. The generated name is the only approved name.

### 6. Lint before launch and after build

```bash
python3 scripts/lint_names.py --rules martech-context/naming-rules.yaml --names launch-names.csv --out lint-report.csv
python3 scripts/lint_names.py --rules martech-context/naming-rules.yaml --registry martech-context/campaign-registry.csv
```

- Names columns: `platform`, `level`, `name`, plus optional `account_id`, `object_id`, `parent_id`, `status`, `start_date`, `spend`. [assets/example-names.csv](assets/example-names.csv) shows every result code.
- Registry mode fails a current row whose name differs from its token columns, and lints the rest like names.
- Any `fail` blocks the build. A `warn` (duplicate where the platform allows it, launch month off, legacy retired value, ID format) needs an owner note in the registry.
- Run it on the planned names, then on the platform export after build: names get retyped and duplicated objects keep old launch months.

### 7. Parse names in the warehouse

- Normalise IDs in staging and in the registry load: text, digits only. Google Ads `123-456-7890` becomes `1234567890`, Meta `act_1234567890` becomes `1234567890`, LinkedIn URNs keep the trailing number. Test that every ad-platform registry ID matches `^[0-9]+$`.
- Build the directory, name history, vocabulary seed, registry, parse model and `dim_paid_campaign`: SQL in [references/warehouse-parsing.md](references/warehouse-parsing.md).
- Attribute precedence: the registry row for the ID, then a name that parses cleanly, then `unknown`. Unknown spend stays visible in every report.
- Spend joins on platform, account ID and object ID. Test that the join leaves row count and total cost unchanged, and report the registry match rate apart from the parse rate.
- Sessions: `session_traffic_source_last_click.google_ads_campaign.campaign_id` for Google Ads, `session_traffic_source_last_click.manual_campaign.campaign_id` (`utm_id`) for Meta and LinkedIn. `collected_traffic_source` is event-scoped; use it only for URL checks.

### 8. Handle renames

- Rename only to fix a wrong name (`change_type` `correction`, reported as is). If what runs changes market, objective, funnel or audience, launch a new object with `predecessor_id`; when that is not possible, record an `attribute_change` (reported as was).
- Every rename goes on [assets/rename-change-list-template.csv](assets/rename-change-list-template.csv), a human applies it, and the registry row is updated the same day. Reports restate only after that.
- Before renaming, list everything that selects the object by name: automated rules, scripts, report filters, bid tools.
- Google Ads ad names are immutable: correct the registry. In Google Ads Editor's Replace text, leave Make changes in duplicate items unchecked; copies get new IDs.
- Name history covers only days with snapshots. Other rename evidence: weekly section 5 in [references/governance.md](references/governance.md).

### 9. Run the governance loop

- Generated name, registry row and a clean lint before build; object IDs added within one business day of launch.
- A weekly exception report over the last seven complete days, ranked by spend. Escalate when unparsed or unregistered spend is above 5% of a platform's weekly spend in one currency.
- Vocabulary changes go through `naming-vocab.csv`: add, alias, retire; never edit in place. Never add a value to make a name pass: list it as a candidate with the names and spend that use it, for the approvers named in `naming-rules.md`.
- Report SQL, approvals, renames and cleanup: [references/governance.md](references/governance.md).

## Common failure modes

- Name used as a join key. Detect: spend drops or doubles after a rename; cost differs before and after the attribute join.
- IDs in different formats (UI dashes, `act_`, URNs, scientific notation). Detect: `bad_id` warnings; registry match rate far below parse rate.
- Registry columns disagree with the name. Detect: `registry_mismatch`; weekly section 4.
- Duplicated campaign keeps the old launch month. Detect: `launch_mismatch`; launch month earlier than first spend.
- Platform name macro in `utm_campaign` off a connector route. Detect: GA4 campaign values equal to platform names; one `manual_campaign_id` with several names.
- Literal macros reach GA4. Detect: campaign values containing `{{`, `%7B%7B` or `{_`.
- Vocabulary drift (`lead`, `leads`, `leadgen`). Detect: `vocab` and `retired_value` results; new values in the weekly report.
- Tokens parsed by position after a reorder. Detect: market values in the objective column.
- Duplicate rows in the vocabulary seed. Detect: spend multiplied after the vocabulary join; uniqueness test.
- A renamed campaign drops out of an automated rule or a dashboard filter. Detect: rules and filters that match on name text.
- Objects created outside the registry: boosted posts (including `ads_boost_ig_post`), experiments converted to new campaigns, platform default or copy-suffixed names, buying tools' templates. Detect: spend IDs with no registry row; a new ID whose first spend date equals a paused sibling's last spend date; default or copy name patterns seen in the export.

## Worked example

Acme, a fictional B2B software company, runs a pipeline webinar in October 2026. Its UTM registry initiative is `2026_10_pipeline_webinar`. Cost is joined in the warehouse and imported to GA4 by file from the campaign map, so every platform takes the default route.

- Meta: campaign `uk_leads_tofu_smb_pipeline-webinar_202610`, ad set `lal_customers-1pct_auto`, ad `vid_founder-story_v2`. URL parameters type `utm_campaign=2026_10_pipeline_webinar` with `utm_id={{campaign.id}}` and `utm_content={{ad.id}}`.
- LinkedIn: campaign group `uk_leads_mofu_ent_pipeline-webinar_202610`, campaign `job_revops-leaders_doc` with static `utm_campaign=2026_10_pipeline_webinar` and dynamic `CAMPAIGN_GROUP_ID` and `CREATIVE_ID`.
- Newsletter: `utm_campaign=2026_10_pipeline_webinar`, `utm_id=cmp_2026_104`.
- GA4 shows one campaign value for all three. The registry links the Meta campaign, the LinkedIn group and the newsletter to `ini_2026_014`, so the warehouse reports the webinar's spend and sessions by offer and launch month.
- In week two the team sees the Meta campaign targets mid-market, so `smb` was wrong. The registry row is closed and replaced with audience `mm` and `change_type` `correction`; the platform rename follows from the generated name. Spend stays on the same ID, reports restate under `mm`, the name history keeps both periods, and GA4 is untouched because `utm_campaign` never carried the platform name.
- The lint of the build export catches a duplicated ad group name, a campaign still named `202609`, and an ad exported with account `act_1234567890`.

## Acceptance checks

- Rules and vocabulary: every level used in the accounts has tokens, vocabularies and a house maximum below the platform limit. Pass: the linter loads them without a rules error.
- Lint: run on the full export. Pass: zero `fail` rows outside `exempt_ids`, or a dated cleanup plan for each.
- Registry: registry-mode lint. Pass: zero `registry_mismatch` and `registry_tokens` rows.
- IDs: the ID format test and the registry match test in the warehouse. Pass: no rows from either; the registry match rate is reported per platform.
- Spend coverage: share of 90-day spend whose attributes come from the registry or a clean parse. Pass: at least 95% per platform, the rest visible as `unknown`.
- Join integrity: spend row count and total cost before and after the attribute join. Pass: identical.
- UTMs: one resolved landing URL per platform. Pass: Meta and LinkedIn show no literal macros, `utm_id` equal to the initiative-level ID and `utm_campaign` equal to the registry row; Google Ads keeps `gclid` after redirects, and manual values resolve when used.
- GA4: a BigQuery query of `manual_campaign_id` for paid social. Pass: every ID matches a directory row, with one campaign name each.
- Renames: name history for the last 30 days against the change list. Pass: every rename has a change-list row and an updated registry row.
- Owners: rules owner, vocabulary approvers and report owner are named in `naming-rules.md`. Pass: a dated entry in `decisions.md`.
- Approval: the user approved the rules, vocabulary, registry and any rename list before they were written or applied. Pass: nothing in `martech-context/` or a platform changed without it.

## Boundaries

- Read-only. The skill proposes rules, vocabulary, registry rows, lint reports, SQL and rename lists. It never renames, creates or edits platform objects; a human applies changes in the platform UI, Google Ads Editor or an approved API call, after agent-write-safety's checks when an agent is involved.
- Never call Meta Ads MCP write tools: any `ads_create_*`, `ads_update_*` or `ads_delete_*` tool, `ads_activate_entity` or `ads_boost_ig_post`.
- It does not set source, medium or non-paid UTM values, account structure, bidding or budgets.
- Hand-offs:
  - Google Ads: Google's read-only Google Ads MCP server to read names and IDs; google-ads-editor-import-builder, if installed, to prepare approved changes for Editor.
  - Meta: Meta's Ads MCP server, the read tools in step 1 only.
  - LinkedIn: Campaign Manager exports or the Marketing API with `r_ads`. The Microsoft Learn MCP server searches LinkedIn's API docs.
  - UTMs: utm-convention-and-registry for source, medium, the UTM registry, the campaign map and its channel and cost-join checks.
  - Warehouse: run SQL through a read-only role, for example the BigQuery MCP server's `execute_sql_readonly` tool. Model changes go through the team's code review.

## Live docs

Platform settings, limits and menu paths change. Before stating any parameter name, limit, quota or UI path:

- Fetch the current page from the vendor docs MCP server or llms.txt listed in `sources.yaml`, or open the canonical URL.
- If the live page disagrees with this skill, follow the live page and tell the user what changed.
- Cite the URL you used.

The entries that matter most here: the Google Ads API system limits page and resource definitions, the ValueTrack and custom parameter pages, the BigQuery Google Ads transfer page, Meta's URL tags guide and Ads MCP tools page, LinkedIn's campaign group and dynamic UTM tracking pages, and GA4's default channel group and BigQuery export schema pages. Every limit in this skill and its references was checked on 2026-10-17.
