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

9.5 KiB
Executable File
Raw Blame History

source, confidence, namespace, last_synced, description, alwaysApply
source confidence namespace last_synced description alwaysApply
~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/pixels.mdc 0.9 work 2026-04-28 How to define, name, and fire pixels on iOS and macOS. false

Pixels

Pixels are one-off telemetry events sent via HTTP GET with a name and optional parameters. They are used for:

  • Basic feature usage events (e.g., button clicks, screen impressions)
  • Errors (e.g., network failures, parsing errors)
  • Conversion and retention (e.g, subscription purchase and activation)

Pixels have the following requirements:

  • Use clear & transparent naming, so it's obvious what the pixel and parameters are for. Pixel names should be self-documenting - avoid cryptic abbreviations or shorthand.
  • Only include information that is essential for the pixel
  • Do not use values that are overly precise, e.g. if using an integer value in a parameter, bucket it into ranges rather than including the value verbatim
  • Never include PII, URLs, or other forms of user-identifiable information in pixel names or parameters

Types of Pixels

Standard Pixels

Sent every time the event occurs.

Pixel.fire(pixel: .subscriptionRestoreAfterPurchaseAttempt)

Pixel.fire(pixel: .autofillLoginsSavePromptDisplayed, withAdditionalParameters: [
    PixelParameters.autofillPromptTrigger: "manual"
])

Daily Pixels

Sent once per day per event. Used to determine the number of users affected by a particular error

DailyPixel.fireDailyAndCount(pixel: .subscriptionPurchaseAttempt, pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes)

Unique Pixels

Sent once per install for the lifetime of the install.

UniquePixel.fire(pixel: .subscriptionActivated)

Pixel Definition Patterns

iOS Pixels

iOS pixels are defined as cases on Pixel.Event in iOS/Core/PixelEvent.swift. Each enum case maps to an HTTP pixel name string via a computed name property.

Adding a New iOS Pixel

  1. Add a new enum case to Pixel.Event in iOS/Core/PixelEvent.swift.
  2. Add a corresponding case to the name computed property (also in PixelEvent.swift) that returns the pixel's string name.
  3. Fire the pixel using Pixel.fire, DailyPixel.fireDailyAndCount, or UniquePixel.fire.
  4. Add the matching pixel definition in iOS/PixelDefinitions/pixels/definitions/*.json5 - see the Pixel Validation section below for more.

Enum Case Definition

extension Pixel {
    public enum Event {
        case appInstall
        case appLaunch
        case subscriptionPurchaseAttempt
        case subscriptionPurchaseSuccess
        case subscriptionActivated
        // ...
    }
}

Enum-to-String Mapping

The Pixel.Event enum has a computed name property that maps each case to its HTTP pixel name string:

extension Pixel.Event {
    public var name: String {
        switch self {
        case .appInstall: return "m_install"
        case .appLaunch: return "ml"
        case .subscriptionPurchaseAttempt: return "m_subscribe"
        // ...
        }
    }
}

Naming Convention

iOS pixel names follow these conventions:

  • Prefix: Always start with m_ (for "mobile").
  • Separators: Use underscores (_) or hyphens (-) between words.
  • Format: Use m_feature_action or m_feature-sub-feature_action.
  • Clarity: Names should be clear and self-documenting. Anyone reading the pixel name should understand what it represents without needing additional context.

Avoid legacy shorthand naming. The codebase contains legacy pixels with cryptic names like ml, mp, mf, m_r. These are difficult to understand and should not be used as a template for new pixels. New pixels should use descriptive names.

Examples:

Style Enum Case String Name Notes
Good .pullToRefresh "m_pull-to-reload" Clear and descriptive
Good .autofillLoginsSavePromptDisplayed "m_autofill_logins_save_prompt_displayed" Self-documenting
Legacy .appLaunch "ml" Avoid this style for new pixels
Legacy .privacyDashboardOpened "mp" Avoid this style for new pixels

Parameterized Pixel Cases

Enum cases can have associated values that are interpolated into the pixel name:

// Enum definition with associated value
case networkProtectionLatency(quality: String)
case syncLocalTimestampResolutionTriggered(Feature)

// In the name property
case .networkProtectionLatency(let quality):
    return "m_netp_ev_\(quality)_latency"
case .syncLocalTimestampResolutionTriggered(let feature):
    return "m_sync_\(feature.name)_local_timestamp_resolution_triggered"

macOS Pixels (PixelKit)

macOS uses PixelKitEvent protocol, typically in feature-specific files. The implementation of the protocol looks like this:

// SharedPackages/BrowserServicesKit/Sources/PixelKit/PixelKitEvent.swift
public protocol PixelKitEvent {
    var name: String { get }
    var standardParameters: [PixelKitStandardParameter]? { get }
    var parameters: [String: String]? { get }
    var error: NSError? { get }
}

An example implementation of this protocol is:

// macOS/DuckDuckGo/Statistics/SubscriptionPixel.swift
enum SubscriptionPixel: PixelKitEvent {
    case subscriptionActive(AuthVersion)
    case subscriptionPurchaseAttempt
    case subscriptionPurchaseSuccess
    // ...

    var name: String {
        switch self {
        case .subscriptionActive: return "m_mac_privacy-pro_app_subscription_active"
        case .subscriptionPurchaseAttempt: return "m_mac_privacy-pro_terms-conditions_subscribe_click"
        // ...
        }
    }

    var parameters: [String: String]? {
        switch self {
        case .subscriptionActive(let authVersion):
            return [AuthVersion.key: authVersion.rawValue]
        default:
            return nil
        }
    }
}

Pixel Parameters

Use structured parameter keys when reusing them across multiple pixels:

extension PixelParameters {
    static let source = "source"
}

Pixel.fire(pixel: .featureUsed, withAdditionalParameters: [
    PixelParameters.source: "keyboard_shortcut"
])

PixelFiring Protocol

For dependency injection and testing, use the PixelFiring protocol:

// iOS/Core/PixelFiring.swift
public protocol PixelFiring {
    static func fire(_ pixel: Pixel.Event,
                     withAdditionalParameters params: [String: String],
                     includedParameters: [Pixel.QueryParameters],
                     onComplete: @escaping (Error?) -> Void)

    static func fire(_ pixel: Pixel.Event,
                     withAdditionalParameters params: [String: String])
}

Best Practices

  1. Choose the right pixel type: When using a standard pixel, consider using a daily pixel as well in order to determine how many users may be impacted by an issue.
  2. Use structured parameters: Define parameter keys as constants to avoid typos and enable refactoring.
  3. Include error context: When firing error pixels, include the error object as part of its error parameters.
  4. Consider using EventMapping: When sending pixels from inside a shared Swift package, define pixels as new enum and emit them using EventMapping, then implement the event mapper on the client side.
  5. Consider using instrumentation facades: For features with multiple related pixels, consider defining an instrumentation protocol to centralize pixel firing logic. See instrumentation-facade.mdc.

Pixel Validation

Pixel definitions are validated from the PixelDefinitions folders in each platform:

  • iOS/PixelDefinitions/
  • macOS/PixelDefinitions/

Validation runs via npm run validate-pixel-defs from the platform folder. JSON5 formatting is enforced with Prettier.

Directory Layout

  • pixels/definitions/*.json5: The actual pixel definitions. Each file contains multiple pixel entries.
  • pixels/params_dictionary.json5: Shared parameter definitions referenced by key.
  • pixels/suffixes_dictionary.json5: Shared suffix definitions referenced by key.
  • product.json: Product metadata.

Defining a Pixel in JSON5

Each JSON5 file defines a map of pixel_name to metadata. Use TEMPLATE.json5 from each platform folder as a starting point. Example:

{
    "m_feature_action": {
        "description": "Describe when the pixel fires and its purpose",
        "owners": ["github-username"],
        "triggers": ["other"],
        "suffixes": ["first_daily_count", "platform"],
        "parameters": ["appVersion"],
        "expires": "2025-01-30"
    }
}

Key fields:

  • description: Clear explanation of the pixels purpose and timing.
  • owners: GitHub usernames responsible for the pixel.
  • triggers: One or more trigger categories used by validation.
  • suffixes: Either shared suffix keys from suffixes_dictionary.json5 or inline suffix definitions.
  • parameters: Either shared parameter keys from params_dictionary.json5 or inline parameter definitions.
  • expires (optional): Date for temporary pixels; omit for permanent pixels.

Prefer referencing shared suffix/parameter keys where possible to keep definitions consistent and validatable.

Pixels have some default values, please check the Pixel implementation in the respective platform to determine what those are.

  • iOS/Core/Pixel.swift - iOS pixel firing implementation
  • iOS/Core/DailyPixel.swift - Daily pixel implementation
  • iOS/Core/UniquePixel.swift - Unique pixel implementation
  • iOS/Core/PixelEvent.swift - iOS pixel event definitions
  • SharedPackages/BrowserServicesKit/Sources/PixelKit/ - Shared PixelKit implementation