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") anderrorDomain(key:"d")underlyingErrorCode(key:"ue") andunderlyingErrorDomain(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.swiftdefines pixel names.iOS/Core/Pixel.swifthasPixelParametersconstants. Check theparametersandstandardParametersproperties on the pixel event enum. - macOS:
macOS/DuckDuckGo/Statistics/GeneralPixel.swiftdefines 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 toPixelKitEvent. - Shared:
SharedPackages/BrowserServicesKit/Sources/PixelKit/containsPixelKit.swift(firing logic) andPixelKitEvent.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]?$"forue,ue0,ue1, etc.)type—"string","integer","number", or"boolean"description— what the parameter representsenum— 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.swiftorGeneralPixel.swift)
File Organization
Group related pixels into a single definition file named after the feature area, for example:
navigation.json5— Browser navigation pixelsonboarding.json5— Onboarding pixelsvpn_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
- Determine the pixel name and parameters from the iOS/macOS codebase
- Find or create the appropriate
.json5file in{platform}/PixelDefinitions/pixels/definitions/ - Add your pixel entry with
description,owners,triggers - Add
suffixesandparameters— reuse dictionary entries wherever possible - Check whether the pixel should be temporary, and if so then add
"expires": "YYYY-MM-DD" - Run
npm run validate-pixel-defsfrom the platform directory - Run
npm run pixel-lint.fixif formatting issues are reported