9.5 KiB
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
- Add a new enum case to
Pixel.EventiniOS/Core/PixelEvent.swift. - Add a corresponding case to the
namecomputed property (also inPixelEvent.swift) that returns the pixel's string name. - Fire the pixel using
Pixel.fire,DailyPixel.fireDailyAndCount, orUniquePixel.fire. - Add the matching pixel definition in
iOS/PixelDefinitions/pixels/definitions/*.json5- see thePixel Validationsection 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_actionorm_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
- 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.
- Use structured parameters: Define parameter keys as constants to avoid typos and enable refactoring.
- Include error context: When firing error pixels, include the error object as part of its error parameters.
- 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. - 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 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 fromsuffixes_dictionary.json5or inline suffix definitions.parameters: Either shared parameter keys fromparams_dictionary.json5or 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 implementationiOS/Core/DailyPixel.swift- Daily pixel implementationiOS/Core/UniquePixel.swift- Unique pixel implementationiOS/Core/PixelEvent.swift- iOS pixel event definitionsSharedPackages/BrowserServicesKit/Sources/PixelKit/- Shared PixelKit implementation