--- source: ~/DuckDuckGo/apple-browsers.git/main/.cursor/rules/pixels.mdc confidence: 0.9 namespace: work last_synced: 2026-04-28 description: How to define, name, and fire pixels on iOS and macOS. alwaysApply: 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. ```swift 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 ```swift DailyPixel.fireDailyAndCount(pixel: .subscriptionPurchaseAttempt, pixelNameSuffixes: DailyPixel.Constant.legacyDailyPixelSuffixes) ``` ### Unique Pixels Sent once per install for the lifetime of the install. ```swift 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 ```swift 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: ```swift 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: ```swift // 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: ```swift // 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: ```swift // 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: ```swift 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: ```swift // 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: ```json5 { "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 pixel’s 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. ## Related Files - `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