{
  "$schema": "https://www.schemafirst.org/schemas/utm-standard.schema.json",
  "$id": "https://www.schemafirst.org/standards/utm/0.1",
  "name": "SchemaFirst UTM Standard",
  "id": "utm",
  "version": "0.1.0",
  "status": "draft",
  "updated_at": "2026-10-04",
  "repository": "https://github.com/schema-first/utm-standard",
  "license": "Apache-2.0",
  "summary": "Deterministic rules for generating UTM parameters. Given the same campaign, source, and distribution context, two conforming agents generate the same UTM.",
  "principle": "Same context + same standard = same UTM.",
  "source_registry": {
    "index": "https://www.schemafirst.org/standards/utm/sources/index.json",
    "record_url_template": "https://www.schemafirst.org/standards/utm/sources/{source}.json",
    "versioned_independently": true
  },
  "inputs": {
    "required": ["campaign", "source", "distribution_context"],
    "optional": ["destination_url", "campaign_id", "source_platform", "content", "term", "creative_format", "marketing_tactic"],
    "definitions": {
      "campaign": "The approved campaign identity. An object with `slug` (preferred) and/or `name`.",
      "source": "A canonical source or alias from the Source Registry, e.g. `linkedin`.",
      "distribution_context": "How the asset is distributed for that source, e.g. `organic`, `paid`, `owned`, `display`. Must be a context supported by the source record.",
      "destination_url": "The absolute URL the tracking parameters are appended to."
    }
  },
  "parameters": [
    {
      "name": "utm_source",
      "requirement": "required",
      "value_source": "Source Registry",
      "derivation": "The `canonical_source` of the resolved source record.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "source_records_only",
      "google_meaning": "The referrer of the traffic, such as a search engine, newsletter, or other source."
    },
    {
      "name": "utm_medium",
      "requirement": "required",
      "value_source": "Source + distribution context",
      "derivation": "`contexts[distribution_context].utm_medium` of the resolved source record.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "source_records_only",
      "google_meaning": "The marketing medium, such as cpc, banner, or email newsletter."
    },
    {
      "name": "utm_campaign",
      "requirement": "required",
      "value_source": "Approved campaign identity",
      "derivation": "`campaign.slug` exactly if present; otherwise the campaign slug algorithm applied to `campaign.name`.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": false,
      "google_meaning": "The name of the campaign, product, promotion, or other identifier."
    },
    {
      "name": "utm_id",
      "requirement": "recommended",
      "value_source": "Campaign system",
      "derivation": "The campaign ID from the system of record, passed through unchanged.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "requirement",
      "google_meaning": "The campaign ID used to identify a specific campaign or promotion."
    },
    {
      "name": "utm_source_platform",
      "requirement": "recommended_where_applicable",
      "value_source": "Platform",
      "derivation": "The buying or management platform directing the traffic, when one exists. Normalized with the value format rule.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "requirement",
      "google_meaning": "The platform responsible for directing traffic to a given property, such as a buying platform that sets budgets and targeting criteria."
    },
    {
      "name": "utm_content",
      "requirement": "optional",
      "value_source": "Creative / asset",
      "derivation": "The creative or asset identifier, normalized with the value format rule.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "requirement",
      "google_meaning": "Used to differentiate ads or links that point to the same URL."
    },
    {
      "name": "utm_term",
      "requirement": "optional",
      "value_source": "Keyword / targeting",
      "derivation": "The paid keyword or targeting term, normalized with the value format rule.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "requirement",
      "google_meaning": "Identifies paid search keywords."
    },
    {
      "name": "utm_creative_format",
      "requirement": "optional",
      "value_source": "Creative format",
      "derivation": "The creative format, normalized with the value format rule.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "requirement",
      "google_meaning": "The type of creative, such as display, native, video, or search."
    },
    {
      "name": "utm_marketing_tactic",
      "requirement": "optional",
      "value_source": "Marketing tactic",
      "derivation": "The targeting criteria or tactic, normalized with the value format rule.",
      "semantics": "google_defined",
      "default_basis": "schemafirst_default",
      "overridable": "requirement",
      "google_meaning": "The targeting criteria applied to a campaign, such as remarketing or prospecting."
    }
  ],
  "minimum_conforming_set": ["utm_source", "utm_medium", "utm_campaign"],
  "campaign_naming": {
    "rule": "utm_campaign represents campaign identity. It is not a container for every available marketing dimension.",
    "precedence": [
      "If `campaign.slug` exists, use it exactly. Do not re-normalize it.",
      "Otherwise derive the slug from `campaign.name` with the slug algorithm.",
      "If neither exists, return `requires_context` with `campaign` in `missing`."
    ],
    "slug_algorithm": [
      "Apply Unicode NFKD normalization and remove combining marks.",
      "Convert to lowercase.",
      "Replace every run of characters outside a-z and 0-9 with a single underscore.",
      "Trim leading and trailing underscores."
    ],
    "constraints": {
      "pattern": "^[a-z0-9]+(_[a-z0-9]+)*$",
      "character_set": "a-z, 0-9 and _",
      "separator": "_",
      "stable_for_campaign_lifetime": true
    },
    "must_not_add_unless_part_of_identity": [
      "source",
      "medium",
      "platform",
      "placement",
      "creative",
      "audience",
      "ad_set",
      "keyword",
      "date",
      "year",
      "quarter"
    ],
    "examples": [
      { "input": { "name": "Spring Product Launch" }, "utm_campaign": "spring_product_launch" },
      { "input": { "slug": "spring_product_launch", "name": "Spring Product Launch 2026" }, "utm_campaign": "spring_product_launch" }
    ]
  },
  "value_format": {
    "applies_to": ["utm_source_platform", "utm_content", "utm_term", "utm_creative_format", "utm_marketing_tactic"],
    "rule": "Apply the campaign slug algorithm. Organization-owned identifiers in utm_id are passed through unchanged."
  },
  "url_assembly": {
    "parameter_order": [
      "utm_source",
      "utm_medium",
      "utm_campaign",
      "utm_id",
      "utm_source_platform",
      "utm_content",
      "utm_term",
      "utm_creative_format",
      "utm_marketing_tactic"
    ],
    "rules": [
      "Remove any existing utm_* parameters from the destination URL.",
      "Preserve all other existing query parameters in their original order.",
      "Append UTM parameters after existing parameters in `parameter_order`.",
      "Omit parameters that have no value. Never emit an empty parameter.",
      "Percent-encode values per RFC 3986. Conforming values require no encoding.",
      "Preserve the URL fragment after the query string."
    ]
  },
  "generation": {
    "steps": [
      "Resolve `source` to a registry record by exact match on `canonical_source` or `aliases`, case-insensitive.",
      "Resolve `distribution_context` against the record's `contexts`.",
      "Set utm_source and utm_medium from the record.",
      "Set utm_campaign from campaign naming precedence.",
      "Set recommended and optional parameters only from supplied inputs. Never invent values.",
      "Apply the organization profile, if any.",
      "Assemble the URL using `url_assembly`."
    ],
    "missing_context": {
      "behavior": "Do not guess. Return a requires_context response listing every missing required input.",
      "response": { "status": "requires_context", "missing": ["distribution_context"] }
    },
    "unknown_source": {
      "behavior": "Do not invent a source. Return an unknown_source response.",
      "response": { "status": "unknown_source", "source": "<input>" }
    },
    "unsupported_context": {
      "behavior": "Do not substitute a different context. Return an unsupported_context response listing supported contexts.",
      "response": { "status": "unsupported_context", "source": "<canonical_source>", "supported": ["<context>"] }
    },
    "success_response": {
      "status": "ok",
      "standard_version": "0.1.0",
      "parameters": {
        "utm_source": "linkedin",
        "utm_medium": "social",
        "utm_campaign": "spring_product_launch"
      },
      "url": "https://example.com/product?utm_source=linkedin&utm_medium=social&utm_campaign=spring_product_launch"
    }
  },
  "provenance_classes": {
    "google_defined": "Semantics, supported parameters, channel behavior, and documented recommendations from Google Analytics.",
    "schemafirst_default": "Canonical implementation decisions made by SchemaFirst for deterministic agent generation. Not authored or endorsed by Google.",
    "organization_override_permitted": "Defaults that may be replaced through an explicit organization profile."
  },
  "organization_profile": {
    "extends_value": "https://www.schemafirst.org/standards/utm/0.1",
    "precedence": "Organization profile > SchemaFirst default. Google-defined parameter semantics cannot be overridden.",
    "permitted": [
      "Require recommended or optional parameters.",
      "Define proprietary source records.",
      "Define additional distribution contexts.",
      "Override defaults marked as overridable.",
      "Define where organization-owned values come from."
    ],
    "not_permitted": [
      "Redefine the meaning of Google-defined UTM parameters.",
      "Remove a parameter from the minimum conforming set.",
      "Change the campaign slug algorithm."
    ],
    "example": {
      "extends": "https://www.schemafirst.org/standards/utm/0.1",
      "organization": "Example Company",
      "overrides": {
        "utm_id": { "required": true }
      }
    }
  },
  "versioning": {
    "scheme": "semver",
    "breaking_changes_require_major": [
      "parameter semantics",
      "required fields",
      "campaign naming",
      "generation behavior",
      "override precedence"
    ],
    "non_breaking": [
      "Adding a source record",
      "Adding a distribution context to a source record"
    ]
  },
  "references": [
    {
      "title": "URL builders: Collect campaign data with custom URLs",
      "url": "https://support.google.com/analytics/answer/10917952",
      "covers": "UTM parameter family, parameter meanings, UTM best practices"
    },
    {
      "title": "Default channel group",
      "url": "https://support.google.com/analytics/answer/9756891",
      "covers": "Channel classification rules for manually tagged traffic"
    },
    {
      "title": "Traffic-source dimensions, manual tagging, and auto-tagging",
      "url": "https://support.google.com/analytics/answer/11242870",
      "covers": "How UTM parameters map to dimensions; interaction with auto-tagging"
    },
    {
      "title": "About traffic-source dimensions",
      "url": "https://support.google.com/analytics/answer/15612152",
      "covers": "Definitions of source, medium, campaign, campaign ID, source platform"
    }
  ]
}
