Files
obsidian-vault/work/wiki/apple-browsers/pixel-definitions.md
T

14 KiB

source, confidence, namespace, last_synced, description, alwaysApply
source confidence namespace last_synced description alwaysApply
~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/pixel-definitions.mdc 0.9 work 2026-04-28 Rules for creating and maintaining pixel definition JSON5 files that document pixels fired by the iOS and macOS apps false

Pixel Registry Definitions

Overview

Pixel definitions are JSON5 files that document pixels and wide events fired by the iOS and macOS apps. They live in:

  • iOS: iOS/PixelDefinitions/pixels/definitions/*.json5
  • macOS: macOS/PixelDefinitions/pixels/definitions/*.json5
  • iOS wide events: iOS/PixelDefinitions/wide_events/definitions/*.json5

Each platform has its own params_dictionary.json5 and suffixes_dictionary.json5 for reusable definitions.

Note: Each definitions directory contains a TEMPLATE.json5 file. These are scaffolds for creating new definition files — they are not real pixel definitions. Ignore them when reviewing or auditing existing definitions (their placeholder expires dates are intentional examples).

Pixel Definition Structure

Each .json5 file is a JSON5 object where keys are pixel names and values describe the pixel:

{
    "pixel_name_here": {
        "description": "When and why this pixel fires",
        "owners": ["githubUsername"],
        "triggers": ["other"],
        "suffixes": ["first_daily_count", "platform", "form_factor"],
        "parameters": ["appVersion", "errorCode", "errorDomain"],
        // Only for temporary pixels — omit for permanent ones
        "expires": "2025-06-30"
    }
}

Required Fields

Field Type Description
description string When the pixel fires and its purpose
owners string[] GitHub usernames of responsible people
triggers string[] What causes the pixel to fire (see trigger values below)

Optional Fields

Field Type Description
suffixes array Dynamic parts appended to the pixel name
parameters array Query parameters sent with the pixel
expires string ISO date (YYYY-MM-DD) for temporary pixels

Trigger Values

Valid trigger values: "other", "scheduled", "startup", "page_load", "new_tab", "exception", "user_submitted", "search_ddg".

Most pixels use "other". Use "scheduled" for daily/periodic pixels, "startup" for app-launch pixels, and "page_load" for navigation-related pixels.

Determining Parameters from Swift Code

Pixel definitions must document all query parameters sent over the wire, including default ones. To determine the correct parameters:

Always-included Parameters

appVersion is added by default to every pixel by PixelKit. Include "appVersion" in every definition, unless the pixel call disables it.

pixelSource is automatically added only if the pixel's standardParameters property returns [.pixelSource]. Check the pixel event's standardParameters computed property in Swift — if it returns [.pixelSource], include "pixelSource" in the definition.

Error Parameters

If the pixel event carries an Error (via associated value or the error property), PixelKit automatically extracts and sends:

  • errorCode (key: "e") and errorDomain (key: "d")
  • underlyingErrorCode (key: "ue") and underlyingErrorDomain (key: "ud") if present

Include these dictionary references in the definition when the pixel carries error information.

Custom Parameters

Check the pixel event's parameters computed property in Swift for any additional parameters. Also inspect the call site where the pixel is fired — look for withAdditionalParameters: arguments and trace any helper functions that build those parameters. These are pixel-specific and must be included in the definition (either as dictionary references or inline objects).

Where to Look in Swift

  • iOS: iOS/Core/PixelEvent.swift defines pixel names. iOS/Core/Pixel.swift has PixelParameters constants. Check the parameters and standardParameters properties on the pixel event enum.
  • macOS: macOS/DuckDuckGo/Statistics/GeneralPixel.swift defines many pixel names, parameters, and standard parameters. However, pixel events can also be defined in dedicated files (e.g. UpdateFlowPixels.swift, CrashReportPixels.swift) — search for types conforming to PixelKitEvent.
  • Shared: SharedPackages/BrowserServicesKit/Sources/PixelKit/ contains PixelKit.swift (firing logic) and PixelKitEvent.swift (protocol).

Reusing Parameters from the Dictionary

params_dictionary.json5 defines common parameters. Reference them by key name as a string:

"parameters": [
    "appVersion",       // Reuses definition from params_dictionary.json5
    "errorCode",        // key: "e", type: integer
    "errorDomain",      // key: "d", type: string
    "underlyingErrorCode",
    "underlyingErrorDomain"
]

To define a custom inline parameter, use an object instead:

"parameters": [
    "appVersion",
    {
        "key": "customParam",
        "type": "string",
        "description": "What this parameter represents",
        "enum": ["value1", "value2"]
    }
]

Parameter Object Fields

  • key — the actual query parameter key sent in the pixel (use this for fixed keys)
  • keyPattern — regex pattern for dynamic keys (e.g. "^ue[0-9]?$" for ue, ue0, ue1, etc.)
  • type"string", "integer", "number", or "boolean"
  • description — what the parameter represents
  • enum — allowed values (optional)
  • pattern — regex validation pattern (optional)
  • examples — example values (optional)

Use key or keyPattern, not both.

Suffixes

A suffix is a string appended to the base pixel name to create distinct variants of the same pixel. For example, a pixel with the "daily" suffix set will produce a variant with _daily appended to the name (e.g. m_mac_default-browser_daily). When a pixel has multiple suffix sets, PixelKit generates all combinations — so ["first_daily_count", "platform", "form_factor"] produces variants like m_pixel_count_ios_phone, m_pixel_daily_ios_tablet, etc.

In some cases a suffix value is always present (rather than being a set of variants). For example, a pixel fired with the legacy daily frequency always gets _d appended to its name. When the suffix is fixed and always present, it may be baked directly into the pixel name in the definition (e.g. m_mac_daily_active_user_d) rather than declared in the suffixes field. See "Legacy Suffix Patterns" below.

Reusing Suffixes from the Dictionary

suffixes_dictionary.json5 defines common suffixes. Reference them by key name:

// Each string references a suffix set from the dictionary
"suffixes": ["first_daily_count", "platform", "form_factor"]

Common suffix keys (both platforms unless noted):

  • first_daily_count["first", "daily", "count"]
  • legacy_daily_count["d", "c"]
  • daily["daily"]
  • daily_standard["daily", ""]
  • count["count"]
  • daily_count["daily", "count"] (macOS only)
  • platform["ios"] (iOS only)
  • form_factor["phone", "tablet"] (iOS only)
  • time_bucket["0", "0.1", "0.5", "1", "5", "10", "20", "40", "more"] (iOS only)

For custom inline suffixes, use an object:

"suffixes": [
    "first_daily_count",
    {
        "description": "The result of the operation",
        "enum": ["success", "failure"]
    }
]

Inline suffix objects support description, enum, and optionally key, type, and pattern (same fields as parameter objects).

Mapping Swift Firing Methods to Suffixes

The suffix you use depends on how the pixel is fired in Swift:

iOS (uses DailyPixel / Pixel / UniquePixel):

Swift Method Suffix Dictionary Key
Pixel.fire(pixel:) No scheduling suffix (use only platform/form_factor)
DailyPixel.fire(pixel:) "daily"
DailyPixel.fireDailyAndCount(pixel:) "first_daily_count"
DailyPixel.fireDailyAndCount(pixel:, pixelNameSuffixes: .legacyDailyPixelSuffixes) "legacy_daily_count"
UniquePixel.fire(pixel:) No suffix (pixel name typically ends in _u or _unique)

On iOS, most pixels also get platform and form_factor suffixes appended automatically. Include ["platform", "form_factor"] for iOS pixels unless you confirm otherwise.

macOS (uses PixelKit.fire(event, frequency:)):

frequency: Value Suffix Dictionary Key
.standard (or omitted) No scheduling suffix
.daily "daily"
.dailyAndCount "daily_count" (macOS) or "first_daily_count" (iOS)
.dailyAndStandard "daily_standard"
.legacyDaily No suffix field — the _d is baked into the pixel name (see "Legacy Suffix Patterns")
.legacyDailyAndCount "legacy_daily_count"

Note: iOS DailyPixel.fireDailyAndCount uses "first_daily_count" (includes a _first pixel on first-ever fire). macOS .dailyAndCount uses "daily_count" (no _first). Check which platform you are writing for.

Compound Suffixes

Suffixes can be nested in an inner array to form compound suffixes (combined into a single suffix segment):

"suffixes": [["platform", "form_factor"]]

This produces suffixes like ios_phone, ios_tablet rather than separate independent suffix positions.

Legacy Suffix Patterns

Some older pixels use legacy firing frequencies (.legacyDaily, .legacyDailyAndCount) where the pixel library appends short suffixes like _d (daily) or _c (count) to the pixel name. In these cases, the suffix is baked directly into the pixel name in the definition rather than declared in the suffixes field.

For example, m_mac_daily_active_user_d is fired with frequency: .legacyDaily. The Swift code defines the base name as "m_mac_daily_active_user", and PixelKit appends _d automatically. The definition uses the full name m_mac_daily_active_user_d with no suffixes field — this is correct.

Compare this to the modern pattern where m_mac_default-browser uses frequency: .daily and declares "suffixes": ["daily"], producing m_mac_default-browser_daily.

When writing definitions for legacy pixels, use the full pixel name (including the baked-in suffix) and use the "legacy_daily_count" suffix dictionary entry only if the pixel uses .legacyDailyAndCount (which produces both _d and _c variants).

Wide Events (iOS)

Wide events use a different, richer schema in iOS/PixelDefinitions/wide_events/definitions/. They have a hierarchical structure with meta, feature, and feature.data sections:

{
    "event-name": {
        "description": "The purpose of the wide event",
        "owners": ["githubUsername"],
        "meta": {
            "type": "unique-event-name",
            "version": "0.0"
        },
        "feature": {
            "name": "feature-name",
            "status": ["SUCCESS", "FAILURE", "UNKNOWN"],
            "data": {
                "ext": {
                    "custom_field": {
                        "type": "string",
                        "description": "A custom extension field",
                        "enum": ["val1", "val2"]
                    },
                    // Can reference props_dictionary.json entries by string
                    "application_state": "foregroundBackgroundState"
                },
                "error": {
                    "domain": { "type": "string", "description": "Error domain" },
                    "code": { "type": "integer", "description": "Error code" }
                }
            }
        }
    }
}

Wide events sent as standard pixels (in pixels/definitions/) use wideEvent* parameters from the params dictionary (e.g. wideEventAppName, wideEventFeatureStatus, wideEventErrorDomain).

Naming Conventions

  • iOS pixels: typically prefixed with m_ (e.g. m_netp_ev_good_latency), though some lack this prefix (e.g. autofill_extension_*, attributed_metric_*)
  • macOS pixels: typically prefixed with m_mac_ (e.g. m_mac_daily_active_user_d)
  • iOS wide events (as pixels): prefixed with m_ios_wide_ (e.g. m_ios_wide_vpn_connection)
  • Use lowercase with underscores or hyphens
  • Always use the exact pixel name string from the Swift code (e.g. from PixelEvent.swift or GeneralPixel.swift)

File Organization

Group related pixels into a single definition file named after the feature area, for example:

  • navigation.json5 — Browser navigation pixels
  • onboarding.json5 — Onboarding pixels
  • vpn_latency.json5 — VPN latency measurement pixels

See existing files in the definitions/ directory for naming patterns.

Validation and Linting

Run from the iOS/ or macOS/ directory:

# Install dependencies (from repo root)
npm clean-install --include-workspace-root

# Full validation (schema + formatting)
npm run validate-pixel-defs

# Schema validation only
npm run validate-defs-without-formatting

# Check formatting
npm run pixel-lint

# Auto-fix formatting
npm run pixel-lint.fix

Always run npm run validate-pixel-defs from the relevant platform directory after making changes.

Quick Reference: Adding a New Pixel

  1. Determine the pixel name and parameters from the iOS/macOS codebase
  2. Find or create the appropriate .json5 file in {platform}/PixelDefinitions/pixels/definitions/
  3. Add your pixel entry with description, owners, triggers
  4. Add suffixes and parameters — reuse dictionary entries wherever possible
  5. Check whether the pixel should be temporary, and if so then add "expires": "YYYY-MM-DD"
  6. Run npm run validate-pixel-defs from the platform directory
  7. Run npm run pixel-lint.fix if formatting issues are reported