Tracking, goals, targeting, preview, and harness
Generate tracking manifests, manage goals, test targeting, open QA previews, and publish AB Test Harness artifacts.
These command groups support measurement setup, live verification, targeting diagnostics, and external harness preview workflows.
Tracking manifests
apex tracking inspect \
https://example-store.test/products/example \
https://checkout.example-store.test/thank-you \
--shop-id 00000000-0000-4000-8000-000000000020 \
--out ./.apex/tracking-manifest.json
apex tracking plan ./.apex/tracking-manifest.json
apex tracking qa ./.apex/tracking-manifest.json| Command | Arguments | Flags | Behavior |
|---|---|---|---|
apex tracking inspect | <urls...> | --shop-id <id>, --endpoint <url>, --out <path> | Writes a manifest with normalized URLs, domains, recommended cookie domain, snippet, handoff settings, placeholder steps/goals, and QA checks. |
apex tracking plan | [manifest] | None beyond global flags | Prints the implementation plan from a manifest. |
apex tracking qa | [manifest] | None beyond global flags | Prints the QA checklist from a manifest. |
apex tracking verify | None | --minutes <n>, --limit <n>, --experiment <id>, --event-type <type> | Reads recent live tracking events for the current shop. |
apex events export | None | --from <iso>, --to <iso>, --experiment <id>, --event-type <types>, --qa <scope>, --limit <n>, --out <path>, --json-summary | Exports every raw event in a window of at most 31 days whose end is not in the future. |
apex tracking verify accepts comma-separated event types:
apex tracking verify --minutes 120 --limit 20 --event-type pageview,purchase --jsonHuman output includes a compact properties preview. Verification is capped and intended for spot
checks; use apex events export for complete counts and properties up to 32,768 characters per event (up to 20 event types per export):
apex events export \
--from 2026-08-01T00:00:00Z \
--to 2026-08-02T00:00:00Z \
--event-type pageview,purchase \
--out events.ndjsonThe export follows opaque keyset cursors until the server reports the final page, then compares the
number written with the first page's total. Use --qa exclude for non-QA traffic or --qa only for
QA traffic; the default include matches raw storage. In JSON mode (--json or piped stdout),
--out is required: rows go to the file and stdout carries one summary envelope. In an
interactive terminal rows print to stdout unless --out is set; --json-summary asks for
the summary envelope there too. Invalid timestamps and windows return invalid_window with
invalid_timestamp, to_not_after_from, window_too_long, or to_in_future in the details.
A failed run removes the partial --out file so it can be retried; an existing file is refused
and is never overwritten.
Purchase-capture verification runbook
Run the read-only battery after publishing purchase tracking:
apex shops doctor --check purchase_capture
apex tracking verify --event-type goal,conversion,purchase,checkout_completed,order_created --minutes 10080 --json
apex install check --shop-id "$APEX_SHOP_ID"The doctor verdict is deliberately three-state. OK requires a served purchase plane, a mature
publication-anchored window, known historical order cadence, and captured revenue events. DARK
means a stage is absent or orders are expected while capture is zero. NO_DATA means the window is
too young or cadence is unknown; it is never treated as healthy. The duplicate-order warning starts
above 2%. Magnitude mismatch uses |revenue - sum(price * quantity)| > max(0.01, 10% of revenue) as
an advisory signal because shipping and tax may legitimately differ. Reconciliation excludes
pre-publication and consent-suppressed orders.
Control-goal discriminator
When purchase is dark, add temporary diagnostic, read-only observations for sibling GA4 events
already emitted by the page (for example begin_checkout and add_payment_info) and compare them
with purchase over the same publication-anchored window. Sibling events present while purchase is
absent isolates the checkout-to-confirmation boundary; all siblings absent points earlier, at the
data-layer or consent boundary. Do not turn diagnostic siblings into business conversion goals, and
remove the diagnostic trigger after the boundary is identified.
Generic rescue patterns
- Session-storage purchase rescue: immediately before a checkout redirect, store the minimum
purchase context (stable order ID, revenue, currency, and line items) in
sessionStorage. On the confirmed return page, validate and emit it once, then delete it. Namespace the key, set a short expiry, and deduplicate by order ID so reloads cannot double-count. - PSP redirect-return rescue: when a payment service skips the normal thank-you page, recognize only its verified success-return route, reconstruct the purchase from trusted return-page context, and re-emit the same canonical purchase payload once. Require a stable transaction ID, expire the rescue record, and never infer success from an unverified query parameter alone.
Both recipes preserve the existing write plane; the CLI commands above only observe and reconcile what that plane produced.
Goals
- Measurement choice and value-source fields render only for custom goals; existing non-custom value goals keep their stored configuration and remain editable (grandfathered).
apex goals list --limit 50
apex goals create \
--name "Add to cart click" \
--type click \
--selector "[data-add-to-cart]" \
--page product-detail \
--count-once
apex goals update 00000000-0000-4000-8000-000000000030 \
--event-name purchase_completed \
--capture '[{"field":"order_id","source":"property","key":"order_id"}]'| Command | Flags |
|---|---|
apex goals list | --limit <n> |
apex goals create | --name <name>, --type <type>, --page-id <id>, --page <id-or-key>, --selector <selector>, --text-match <text>, --url-pattern <pattern>, --event-name <name>, --count-once, --capture <json-or-path> |
apex goals update | <id>, plus the same editable fields as create, with --no-count-once available; pass --page-id null or --page null to clear page scope. |
Goal type values are click, pageview, custom, and revenue. Capture can be inline JSON or @path/to/capture.json.
Click goals require a page. Run apex pages list to find its ID or key. Pass --page-id <id> for a UUID with no lookup or pages:read permission, or --page <id-or-key> for an ID or a key resolved by lookup (keys need pages:read). Use only one of these options.
Pages from the CLI. apex pages create --help and apex pages update --help list every body key; apex schema --json includes their body schemas. Set match_type to match the pattern: exact (the create default), contains, wildcard, or regex. The CLI refuses wildcard or regex shapes under a contradictory match type with match_type_mismatch; pass --literal to store the patterns as written. An update without a match type leaves it unchanged and skips checks that depend on its stored value.
apex pages create '{"name":"Product pages","url_pattern":"/products/*","match_type":"wildcard","include_rules":["/collections/*/products/*"],"trigger":{"mode":"immediate"}}'Targeting tests
Targeting schema
An experiment with no targeting reads targeting as null in v1 responses.
Any non-null targeting carries both url and authorUrl arrays. apex experiments pull
also returns both arrays in targeting.json, using canonical rule objects: { type: "simple" | "regex", pattern: string, include: boolean, matchType?: "exact" | "contains" | "wildcard" | "regex" }. v1 writes accept only this canonical shape; older rows
stored in legacy shapes (bare strings, urls, rules) are normalized on read. No migration rewrites stored rows; the compatibility projection runs
only on read.
The SDK enforces deviceType (edge falls back to the SDK for this rule); apex targeting test evaluates URL rules only and reports a device restriction as a runtime caveat rather than checking it.
apex targeting test https://example-store.test/products/example --status all
apex targeting test https://example-store.test/products/example --status draft,running --jsonapex targeting test evaluates URL targeting for the supplied page and returns matching count, experiment rows, and caveats from the API.
Note: tests that target multiple pages or audiences can now declare a match mode of ANY or ALL (the SDK, edge engine, and web evaluators honor this). When multiple selections are present the setup UI exposes an ANY/ALL choice to require either any matching selection or all selected ones. Existing tests retain the prior any-match behavior.
QA preview URL
apex preview homepage-hero-trust-copy
apex preview homepage-hero-trust-copy variant-a --store-url https://example-store.test/products/example --no-openapex preview resolves the experiment by ID or slug, defaults to the first non-control variation, and builds a forced QA URL with drip_force, drip_experiment_id, drip_variation_id, drip_qa=1, and drip_debug=1. It opens the browser unless --no-open is present.
Use drip_force=EXP:~ to hold one experiment at Original: it receives no assignment,
exposure, stickiness, or page changes. Other tests run normally. Combine it with a pin,
for example drip_force=EXP:~,OTHER:VAR, to hold EXP while previewing OTHER. A hold does
not survive a cross-domain redirect handover.
Forced preview isolation and coexistence
The QA panel’s Coexist with live tests toggle sets the same drip_force_coexist=1 choice for the session. Pre-launch QA and ready-to-launch experiments appear in the panel only when the server verifies the QA session’s authorization for this shop. Three authorizations count: a signed QA link opened from the dashboard (a client link without debug does not list them), an active harness session for the shop, or a preview-read capability scoped to the shop. These experiments render only when pinned. drip_qa=1 alone lists running tests.
A variation pin in drip_force (or legacy experiment/variation pair) isolates the SDK
preview to the forced experiments and any experiment handles listed in drip_exp.
Other experiments, personalizations, and flags are suppressed and produce no
exposure or tracking. This also applies to cookie-persisted and QA assistant forces.
The assistant's activation diagnostics show suppressed_by_forced_preview.
Add drip_force_coexist=1 to restore coexistence for the QA session. The SDK stores
this choice in the drip_force_coexist cookie alongside drip_qa_force, renewing both
for 30 minutes on QA force persistence. Navigation preserves the choice;
drip_force_coexist=0 disables it, and exiting QA clears it. The runtime snapshot
exposes forcedPreviewCoexist.
Without a force, running experiments targeting the page can be assigned unless an
exclusion group separates them. Assignments apply in served config order. When
multiple experiments claim the same mutation selector in QA mode, the QA bundle’s debug output warns
Shared container <selector>: <experiment>/<variation> and <experiment>/<variation>; later wins:
the later assignment wins on overlapping mutations.
This diagnostic includes selected URL-block mutations and does not change rendering.
The QA assistant runtime snapshot exposes the same selector and experiment/variation
pairs in conflicts. The conflict warning and snapshot conflicts list are available
only in the QA bundle, not in the live bundle.
AB Test Harness
apex harness init ./ab-test-harness/experiments/homepage-hero
apex harness dev ./ab-test-harness/experiments/homepage-hero --no-open
apex harness publish ./ab-test-harness/experiments/homepage-hero| Command | Arguments | Flags | Behavior |
|---|---|---|---|
apex harness init | <folder> | --force | Writes apex.harness.json from existing harness metadata, unless it already exists. |
apex harness dev | <folder> | --no-open | Runs ab-harness build, uploads the artifact to an Apex harness session, opens or prints the preview URL, and watches for rebuilds. |
apex harness publish | <folder> | None beyond global flags | Runs ab-harness verify, builds, uploads, and publishes the latest harness artifact to the Apex draft variation. |
Set AB_HARNESS_BIN when the harness executable is not named ab-harness on PATH.
Harness dev uses experimentName and targetUrl only when it creates the experiment. Selectors and the cleanup reset script are sent only when the harness config sets them.
Related pages
- Workspace, Operator, and roadmap commands
- Experiment filesystem and QA commands
- Push and GitOps with
.drip.config.ts
apex preview --proxy --json returns the existing session_id, preview_url, and
expires_at plus qa_run: { id, created, refreshed, automated_evidence_count }.
The receipt names the QA run created or refreshed by recording this preview.
automated_evidence_count counts the normalized evidenceRecords across all
evidence kinds, matching the list shown by apex experiments qa status. Human output prints QA run <id> created or
QA run <id> refreshed (N automated evidence records).
Decision latency before launch
apex tracking latency MOM-027 --days 7
apex tracking latency MOM-027 --days 7 --qa-session <session-id> --jsonCompare forced-preview and natural loads before launching an above-the-fold
change. --days accepts 1–30 (default 7). The table shows samples, decision
p50/p95/p99, config-fetch p50/p95 in milliseconds, and inline/cache/network/none
counts. --json includes the API body in the standard CLI envelope, including
assignment-ready percentiles and position: "runtime_inline_no_change".
All buckets include only page-load events (perf_event_kind = 'init' and exclude
route-change events and bot traffic. Forced rows contain the experiment's resolved ID in forced_experiment_ids.
Natural rows are shop-wide loads with no forced IDs and qa_mode = 0.
The optional QA-session bucket covers that session; it can overlap forced rows.
Fewer than five measured samples produce null percentiles (- in the table).
An unavailable analytics read returns an error, never a zero-sample success.
For page-readable timestamps during forced previews, see
SDK decision timing.
The runtime remains unchanged. Natural runtime decides within tens of milliseconds
of SDK start when config ships inline with the snippet (measured p50 12 ms /
p95 54 ms on a large storefront). QA previews, screenshot-mode previews and
cookie-forced sessions take the QA config lane and fetch fresh config over the
network (measured p50 1.3 s / p95 7 s); they are not representative of launch
behaviour. URL-forced sessions without a qa parameter keep the inline or cached
config, so these measurements do not describe them. No config preload or
anti-flicker hold is planned.
Tracking verification limits
apex tracking verify reads at most 200 of the newest events from a window of at most 7 days. When the limit is reached, truncated is true and totals describe that sample only. The response includes the exact from, to, minutes, and limit window.