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_idandutm_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 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.mdand 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. Querycampaign,ad_group,asset_groupandad_group_ad; queries in 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_creativesandads_get_ad_preview. Ask the user to authorise withads_mcp_managementandads_read, notads_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_adspermission. - Keep every ID as text. Excel keeps 15 significant digits and zeroes the rest.
- Lint the export with a
spendcolumn (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):
- Initiative level (Google Ads campaign, Meta campaign, LinkedIn campaign group):
market_objective_funnel_audience_offer_launch, for exampleus_leads_bofu_nonbrand_demo_202610. - Targeting level: Google Ads ad group and asset group
targeting_theme; Meta ad settargeting_theme_placement; LinkedIn campaigntargeting_theme_format. - Ad level:
format_concept_variantfor Meta ads and named Google Ads ads,concept_variantfor 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.
launchis 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, audienceall, placementauto, targetingbroad. No vocabulary hasotherormisc. - Free text only in
themeandconcept: 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, lowercasea-zand0-9only, 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}}, LinkedInCAMPAIGN_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_campaignwhere 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_idand theutm_campaignits links send, and feeds that skill's campaign map. - Per-platform strings and resolved-URL checks: references/utm-mapping.md.
5. Draft the rules, vocabulary and registry
- Draft from assets/naming-rules.yaml and 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 innaming-rules.md. - Start the registry from 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
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 optionalaccount_id,object_id,parent_id,status,start_date,spend. 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
failblocks the build. Awarn(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-7890becomes1234567890, Metaact_1234567890becomes1234567890, 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. - 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_idfor Google Ads,session_traffic_source_last_click.manual_campaign.campaign_id(utm_id) for Meta and LinkedIn.collected_traffic_sourceis event-scoped; use it only for URL checks.
8. Handle renames
- Rename only to fix a wrong name (
change_typecorrection, reported as is). If what runs changes market, objective, funnel or audience, launch a new object withpredecessor_id; when that is not possible, record anattribute_change(reported as was). - Every rename goes on 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.
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 innaming-rules.md. - Report SQL, approvals, renames and cleanup: 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_idwarnings; 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_campaignoff a connector route. Detect: GA4 campaign values equal to platform names; onemanual_campaign_idwith several names. - Literal macros reach GA4. Detect: campaign values containing
{{,%7B%7Bor{_. - Vocabulary drift (
lead,leads,leadgen). Detect:vocabandretired_valueresults; 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 setlal_customers-1pct_auto, advid_founder-story_v2. URL parameters typeutm_campaign=2026_10_pipeline_webinarwithutm_id={{campaign.id}}andutm_content={{ad.id}}. - LinkedIn: campaign group
uk_leads_mofu_ent_pipeline-webinar_202610, campaignjob_revops-leaders_docwith staticutm_campaign=2026_10_pipeline_webinarand dynamicCAMPAIGN_GROUP_IDandCREATIVE_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
smbwas wrong. The registry row is closed and replaced with audiencemmandchange_typecorrection; the platform rename follows from the generated name. Spend stays on the same ID, reports restate undermm, the name history keeps both periods, and GA4 is untouched becauseutm_campaignnever 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 accountact_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
failrows outsideexempt_ids, or a dated cleanup plan for each. - Registry: registry-mode lint. Pass: zero
registry_mismatchandregistry_tokensrows. - 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_idequal to the initiative-level ID andutm_campaignequal to the registry row; Google Ads keepsgclidafter redirects, and manual values resolve when used. - GA4: a BigQuery query of
manual_campaign_idfor 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 indecisions.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_*orads_delete_*tool,ads_activate_entityorads_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_readonlytool. 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.