{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://webtracking.org/standards/datalayer/v1/datalayer.schema.json",
  "title": "webtracking.org dataLayer Spec v1",
  "description": "A portable, vendor-neutral event taxonomy for the dataLayer. Validate live or pasted dataLayer pushes against this with the free dataLayer Validator. Models GA4 recommended e-commerce semantics while staying tool-agnostic.",
  "type": "object",
  "required": ["event"],
  "properties": {
    "event": {
      "type": "string",
      "description": "Snake_case event name. Use the canonical names below; custom events must still be snake_case and documented in your tracking plan.",
      "pattern": "^[a-z][a-z0-9_]{1,49}$"
    },
    "page": { "$ref": "#/$defs/page" },
    "user": { "$ref": "#/$defs/user" },
    "consent": { "$ref": "#/$defs/consent" },
    "ecommerce": { "$ref": "#/$defs/ecommerce" }
  },
  "allOf": [
    {
      "if": { "properties": { "event": { "const": "purchase" } } },
      "then": {
        "properties": {
          "ecommerce": {
            "required": ["transaction_id", "value", "currency", "items"]
          }
        },
        "required": ["ecommerce"]
      }
    },
    {
      "if": { "properties": { "event": { "enum": ["add_to_cart", "view_item", "begin_checkout"] } } },
      "then": { "required": ["ecommerce"] }
    }
  ],
  "$defs": {
    "page": {
      "type": "object",
      "description": "Page context — set on every push, ideally via a single dataLayer object on load.",
      "properties": {
        "path": { "type": "string", "pattern": "^/" },
        "title": { "type": "string" },
        "type": { "type": "string", "enum": ["home", "category", "product", "cart", "checkout", "confirmation", "article", "search", "account", "other"] },
        "language": { "type": "string", "description": "BCP-47, e.g. en-US" },
        "environment": { "type": "string", "enum": ["production", "staging", "development"] }
      },
      "required": ["path", "type"],
      "additionalProperties": true
    },
    "user": {
      "type": "object",
      "description": "User context. NEVER place raw PII (email, phone, name) in the dataLayer — hash client-side first (see the PII Hasher tool) or pass only opaque IDs.",
      "properties": {
        "id": { "type": "string", "description": "Stable, non-PII pseudonymous ID (e.g. hashed CRM key)." },
        "logged_in": { "type": "boolean" },
        "type": { "type": "string", "enum": ["new", "returning", "member", "guest"] }
      },
      "not": {
        "anyOf": [
          { "required": ["email"] },
          { "required": ["phone"] },
          { "required": ["phone_number"] },
          { "required": ["name"] }
        ]
      },
      "additionalProperties": true
    },
    "consent": {
      "type": "object",
      "description": "Mirror of the granted consent state at the moment of the push. Maps to Google Consent Mode v2 signals.",
      "properties": {
        "ad_storage": { "$ref": "#/$defs/consentValue" },
        "analytics_storage": { "$ref": "#/$defs/consentValue" },
        "ad_user_data": { "$ref": "#/$defs/consentValue" },
        "ad_personalization": { "$ref": "#/$defs/consentValue" }
      },
      "additionalProperties": false
    },
    "consentValue": { "type": "string", "enum": ["granted", "denied"] },
    "ecommerce": {
      "type": "object",
      "description": "GA4-aligned e-commerce object. Reset the ecommerce object (push { ecommerce: null }) before each new e-commerce event to avoid value bleed.",
      "properties": {
        "transaction_id": { "type": "string" },
        "value": { "type": "number", "minimum": 0 },
        "currency": { "type": "string", "pattern": "^[A-Z]{3}$", "description": "ISO 4217." },
        "coupon": { "type": "string" },
        "shipping": { "type": "number", "minimum": 0 },
        "tax": { "type": "number", "minimum": 0 },
        "items": {
          "type": "array",
          "minItems": 1,
          "items": { "$ref": "#/$defs/item" }
        }
      },
      "additionalProperties": true
    },
    "item": {
      "type": "object",
      "required": ["item_id", "item_name"],
      "properties": {
        "item_id": { "type": "string" },
        "item_name": { "type": "string" },
        "item_brand": { "type": "string" },
        "item_category": { "type": "string" },
        "item_variant": { "type": "string" },
        "price": { "type": "number", "minimum": 0 },
        "quantity": { "type": "integer", "minimum": 1 }
      },
      "additionalProperties": true
    }
  }
}
