Next Commerce

Campaigns OS agency enablement guide

v0.12.0Updated Oct 1, 2026

Build a NEXT campaign from approved HTML, Figma, a hybrid source, or a maintained starter template. Campaigns OS keeps commerce, source, build, and QA evidence tied to the same campaign.

Begin with Campaigns OS Start: configure products and offers in the NEXT dashboard, map and save the campaign in Workspace, then copy the Handoff for a developer or agent to build, preview, and run QA.

Overview

Campaigns OS is the supported workflow for custom performance campaigns on NEXT. It prepares the build contract, checks inputs, guides each stage, and records proof. It does not decide pricing, rewrite the approved design, or replace Campaigns App configuration.

The workflow has four sources of truth:

1. Campaign data

Campaigns App owns packages, Offers, shipping methods, payment support, API keys, and allowed domains.

2. Campaign intent

Campaign Map Builder exports the CampaignSpec and Map ID for routes, page roles, packages, Offers, and QA identity.

3. Design source

Approved HTML, Figma, or template contributions own composition, imagery, copy, and visual hierarchy.

4. Runtime proof

The Build Packet, sidecars, preview, browser QA, test orders, and Run Record show what was built and tested.

Current supported surface

This guide is written against the Campaigns OS supported surface, not against one pinned release. For the version in force today, read the toolkit CHANGELOG and the surface_version field in contracts/supported-surface.json. Start every build from the Campaigns OS Start page: it provides the instructions an operator or agent follows to pin the toolkit in the campaign folder and install the agent skills. Run npx --no-install campaigns-os tooling status before work. If installed help or npx --no-install campaigns-os next --packet differs from this guide, follow the installed toolkit.

Let the runtime choose the next stage

Do not choose setup, build, polish, deploy, or QA from memory. Run npx --no-install campaigns-os next --packet campaign-runtime.build.json --json. The command reads the packet and its sidecars, reports blocked gates, and returns exact next_actions.

Run npx --no-install campaigns-os tooling status before starting. If the bundled agent Skills are stale, refresh them from the pinned toolkit. This matters because Skills and CLI contracts can change independently of a campaign repo.

  • Claude Code: npx --no-install campaigns-os tooling setup --target . --platform claude installs the skills, the project context, and the QA browser in one step, and keeps existing pages and project instructions. When it reports restart_required, restart Claude Code in the campaign folder.
  • Codex, Cursor, and other agents: run install-skills --platform codex (or --platform agents), install-agent-context --target ., and qa install-browser, then point the project's AGENTS.md or Cursor rules at the matching file in .campaign-runtime/agent-context/. install-skills lists each SKILL.md it wrote under Read now. Read those files in the current session: a running agent does not load skills installed after it started. Restart only if the agent cannot read them.

The toolkit ships five lifecycle Skills for Claude and Codex, covering orchestration, setup, build, polish, and QA, plus agent context for Copilot and Cursor. Install them from the pinned toolkit, as the Start page does, rather than pasting workflow steps into a prompt. An agent joining a campaign cold should read the toolkit's AGENTS.md orientation contract first: it names what to read, in what order, and which paths are safe to depend on. It also sets the discipline for the read itself. Resolve one commit OID and read every contract at that OID, so nothing shifts underneath you mid-orientation. Orientation is a read of contracts and schemas, not a toolkit command run to vouch for the toolkit.

Commerce rules for current builds

NeedCurrent pattern
Product or variant identityOne package for each sellable product or variant.
Checkout quantity pricingCart quantity plus an automatic Campaign Offer.
Upsell, downsell, or exit-pop discountA code Offer applied through the supported SDK voucher path.
Displayed savings and totalsCampaigns and SDK state. Do not calculate or hardcode discount math in the page.

Package Retail Price and Quantity fields exist for older campaigns. Do not introduce them for a current build unless the target campaign already depends on that compatibility model. Scope Offers to the intended packages and test both qualifying and non-qualifying carts.

Boundaries

  • Campaigns OS does not make an incomplete campaign launch ready. Production domains, payment methods, shipping markets, support links, analytics, and legal copy still need owner review.
  • A custom design does not authorize custom checkout or payment behavior. Preserve SDK-owned cart, address, payment, submit, upsell, and receipt contracts.
  • Manual notes can explain a decision. They do not replace generated JSON evidence, and stage completion is recorded with campaigns-os record, not by editing sidecars.
  • A committed QA sidecar records a disposition. It is not automatically a passing result.

Before you start

  • A saved CampaignSpec or Map ID with the public route slug.
  • Campaigns App commerce data, including package and Offer references, shipping methods, payment support, and the Campaign API key source.
  • The approved design source and a clear assembly mode: HTML, full Figma, hybrid, or template led.
  • A target Page Kit repo and the public route the preview will serve.
  • A Campaign Build Brief for decisions that do not belong in CampaignSpec.
  • A deploy and QA plan, including who owns non-local domain authorization and how much test-order coverage is required.

Stop when a missing input would force you to guess a price, package, Offer code, route, payment method, shipping promise, legal URL, or analytics requirement.

Sources of truth and evidence

Campaigns App and CampaignSpec

Campaigns App is live commerce truth. CampaignSpec is approved funnel intent. Fix conflicts before assembly.

Brief and design source

The Campaign Build Brief records presentation decisions. The Design Source Package records source files, page coverage, visual references, gaps, waivers, and freshness.

Runtime envelope

The root Build Packet and the Build Context, Assembly Report, and Doctor Output sidecars bind the campaign to one target and source set.

QA and closeout

The QA verdict projection, bundle check, and Run Record preserve the final result for another operator or CI consumer.

Standard workflow

The toolkit is pinned in the campaign folder as an exact dev dependency and every command is npx --no-install campaigns-os … run from there (see the Start page). --no-install makes sure the pinned copy is the one that runs. Contributors working on a toolkit checkout use npm run campaigns-os -- instead.

Check tooling and start

Check installed tooling, then create the packet from the Map ID or CampaignSpec, prepared source directory, target repo, Build Brief, and the commerce contract family used by runtime pages.

Clear doctor

start prepares the artifacts and runs doctor. Resolve every collect-inputs or setup gate before editing campaign code.

Follow the runtime cursor

Run next --packet and execute its returned actions. Repeat after each stage instead of carrying a hand-written command sequence forward.

Assemble and build

Keep the approved source composition, replace demo commerce data with CampaignSpec or API values, preserve the SDK contract, then run the target repo build and SDK checks. Record setup once the campaign output directory exists, and record the build after every rebuild (see Record each stage).

Polish and capture

Run the dedicated polish pass. Serve the current build and attach package-owned desktop and mobile page-load evidence with polish capture, then record the result with record polish --packet campaign-runtime.build.json --evidence <file>.

Prove locally, then deploy

Serve the development build on localhost and prove it there first (see Prove on localhost first). Then deploy a preview, record its URL in the runtime artifacts, and run qa resolve. Fix a route-root mismatch or unreachable preview before full QA.

Run QA and test orders

Run browser QA and the selected typed-card path against the preview as the second check. Review the verdict rather than counting assertions.

Check the evidence bundle

After QA, run bundle check --require-qa. Commit the canonical JSON sidecars required by the repo's workflow.

npx --no-install campaigns-os tooling status

npx --no-install campaigns-os start \
  --map-id <map-id> \
  --source <prepared-source-dir> \
  --target <page-kit-repo> \
  --template-family <commerce-contract-family> \
  --brief <campaign-build-brief.yaml>

npx --no-install campaigns-os next \
  --packet campaign-runtime.build.json \
  --json

Record each stage

Record a stage's completion with campaigns-os record. Do not hand-edit .campaign-runtime/build-context.json or .campaign-runtime/assembly-report.json to mark a stage done.

# Setup: once the campaign output directory exists
npx --no-install campaigns-os record setup --packet campaign-runtime.build.json

# Build: after every page-kit build, so the build fingerprint matches _site/
npx --no-install campaigns-os record build --packet campaign-runtime.build.json

# Polish: after polish capture, from a polish evidence file
npx --no-install campaigns-os record polish \
  --packet campaign-runtime.build.json \
  --evidence <polish-evidence.json>

# Theme: once the brand layer is linked and the build is recorded
npx --no-install campaigns-os record theme --packet campaign-runtime.build.json

# Deploy (local preview): GETs every built page under the served route root
npx --no-install campaigns-os record deploy \
  --packet campaign-runtime.build.json \
  --base-url http://localhost:4173/

# A changed brief or CampaignSpec after stages are recorded
npx --no-install campaigns-os record brief --packet campaign-runtime.build.json --brief <campaign-build-brief.yaml>
npx --no-install campaigns-os record spec --packet campaign-runtime.build.json
  • record build stamps the build fingerprint doctor computes from _site/<slug>/ and makes polish required again. Rerun it after every rebuild.
  • record deploy is bound to the build it probed. A later record build of different output makes the deploy required again, so rebuild, record, polish and deploy again before QA. It is refused unless the URL is a loopback route root that answers 2xx for every built page.
  • record brief and record spec bind a changed brief or CampaignSpec without starting over. An unchanged save keeps every stage; a material change moves the superseded build, Polish and QA records to stage history, and next names the stage that is owed again. Re-running start or prepare-build over recorded stages is refused without --force.
  • The polish evidence file carries the status (completed, completed_with_warnings, blocked with blockers, or skipped with skip_reason), the seven evidence fields, and an optional repair_loop_defect. The captured page_load stays package-owned. A completed status is refused unless the polish gate would pass. The field rules are in the toolkit's docs/polish-evidence.md.
  • Each command validates what it would write and refuses a stage next has not reached yet, listing each problem by field. On any failure it writes nothing. Add --dry-run to run every check without writing.

--template-family selects the protected commerce structure for runtime pages. It does not make that family the visual source when approved HTML or Figma governs the design.

A packet created on another machine needs no catalog path fix: doctor resolves the toolkit's commerce catalog itself. A stale machine-local path is reported as a ready line, never a blocker. To clear it, re-run npx --no-install campaigns-os prepare-build with the same --map-id, --source, --target, and --template-family as start; it refuses to overwrite an assembly report that carries stage evidence unless --force is passed, which sets the recorded stages back to pending (so they are owed again) while keeping the completed build, Polish and QA records in stage history.

Assembly rules

  • Approved HTML or Figma owns composition, imagery, copy, and visual hierarchy.
  • CampaignSpec and Campaigns App own routes, packages, Offers, shipping, payment support, tracking intent, footer links, and SEO values.
  • The selected commerce contract owns SDK attributes and protected checkout, cart, payment, upsell, and receipt behavior.
  • Pre-checkout pages need the declared SDK bootstrap when they use SDK conditions, attribution transfer, or analytics.
  • Remove template features that the campaign does not support. This includes unused payment methods, Offers, footer links, tracking, and SEO demo values.
  • Load a generated brand theme after next-core.css. A theme may change variables and presentation, but it must not rewrite SDK selectors or runtime structure.

A page whose CampaignSpec design_source is Figma, but whose approved source is hand-written HTML, has no Figma export provenance, so doctor blocks it under source_html.producer_provenance. Write the source-html manifest by hand, set wrapper_policy: preserve_document_wrappers for full-document HTML, and waive the gate for that page only. The waiver needs a named human, a reason, and an expiry (--expires-at) or a review condition; --dry-run checks it without writing.

npx --no-install campaigns-os checkpoint waive \
  --packet campaign-runtime.build.json \
  --gate source_html.producer_provenance \
  --page <page_id> \
  --reason "<why the approved source is hand-written HTML>" \
  --waived-by "<named human>" \
  --review-condition "<what triggers a review>"

For hybrid work, record which source owns each page or component. If two sources could plausibly govern the same surface, stop and ask. Do not let template inference displace stronger approved design evidence.

Doctor and QA checks to expect

  • Host-prefixed routes. Some saved Maps store a page_url such as shop.example.com/route/upsell/. Intake from --map-id strips the host, keeps the rooted path, and records each change as routing_meta.host_stripped. A local --spec file or a --cached-spec copy is never rewritten: doctor blocks with routing_meta.host_prefixed until you edit the file, or rerun without --cached-spec so the Map is fetched again.
  • Store Profile fields left blank on purpose. An explicit "" in one of the eight optional Store Profile fields (campaign.store_name, store_terms, store_privacy, store_contact, store_returns, store_shipping, store_phone, store_phone_tel) means the merchant has no such value. page-kit sync blanks the starter's demo URL or phone number in that field, and doctor reads the field as intentionally empty. An absent or null field still means "not provided". campaign.store_url stays required.
  • Decline links outside the offer. Doctor blocks under built_output.sdk_markup.orphaned_upsell_action when an element with data-next-upsell-action has no data-next-upsell ancestor. The SDK binds upsell actions only inside that container, so a "No thanks" link beside it goes nowhere. Move the element inside its container; the check cannot be waived.
  • Live campaign refs. When the built _site/<route>/ exists and a public Campaigns API key resolves, doctor and qa run make one read-only request for the live campaign. A built page that names a shipping method or package the live campaign does not serve blocks (built_output.shipping_ref_live_missing, built_output.package_ref_live_missing). Differences between the CampaignSpec and the live campaign are the separate warning spec.campaign_drift. A read that fails is reported as not run, never as a pass. --no-live-refs skips the read.
  • Checkout bumps tagged as upsells. A checkout order bump carries data-next-is-upsell="true". That is intended: it tags the bump's line as an upsell in order reports, so reports show upsell items apart from core items. It is a reporting tag, not a billing change. Leave the attribute in place. To report a bump as a core item instead, pass is_upsell: false to the bump include. Doctor reports nothing either way.
  • Subscription claims. QA commercial parity warns commercial_parity.recurring_claim_absent, naming the package, when a page renders a subscription package with no readable recurring price claim, and reports parity as incomplete.

Polish, deploy, and QA

Install the toolkit's Playwright browser once per pin (older pins: npx playwright install chromium). Prove the campaign on localhost first, commit, then use the PR preview as the second check.

npx --no-install campaigns-os qa install-browser

Prove on localhost first

Set the deploy target

Set deploy.target to local-serve and record the localhost URL you will serve on.

Build the development environment

Run the development build command campaigns-os next prints at the build stage (CPK_ENV=development npx campaign-build --json > .campaign-runtime/page-kit-build-summary.json). campaign-build is the page-kit's own binary, installed with next-campaign-page-kit in the campaign repo. The render goes into _site/.

Serve and prove

Serve _site/ on localhost with any static server. Run polish capture, qa run --browser, and the typed-card orders against it.

Check production parity

Run page-kit parity. It must print PASS; a failure names the first non-gated difference and exits 2. Fix the source, rebuild, and rerun.

Commit, then open the PR

Commit the source and the sidecars. The PR preview is the second check.

npx --no-install campaigns-os qa policy set \
  --packet campaign-runtime.build.json \
  --deploy-target local-serve \
  --preview-url http://localhost:4173/

# page-kit's own binary (next-campaign-page-kit), not a campaigns-os subcommand
CPK_ENV=development npx campaign-build --json \
  > .campaign-runtime/page-kit-build-summary.json

npx --no-install campaigns-os record build --packet campaign-runtime.build.json

# serve _site/ on http://localhost:4173/ with any static server, then:
npx --no-install campaigns-os polish capture \
  --packet campaign-runtime.build.json \
  --base-url http://localhost:4173/

# write the polish evidence file (see Record each stage), then:
npx --no-install campaigns-os record polish \
  --packet campaign-runtime.build.json \
  --evidence <polish-evidence.json>

npx --no-install campaigns-os record deploy \
  --packet campaign-runtime.build.json \
  --base-url http://localhost:4173/

npx --no-install campaigns-os qa run \
  --packet campaign-runtime.build.json \
  --base-url http://localhost:4173/ \
  --browser \
  --test-order common

npx --no-install campaigns-os page-kit parity \
  --packet campaign-runtime.build.json
  • .campaign-runtime/page-kit-build-summary.json is a build-local artifact: doctor reads it (built_output.build_summary); it is not part of the canonical bundle and bundle check does not validate it.
  • A QA run against a localhost base URL keeps its verdict local by default. Pass --post-verdict, or run qa publish afterwards, to put it on the Map's QA tab.
  • Localhost on any port is a Development origin: the SDK loads and analytics are suppressed. Non-local previews and production origins still need domain authorization in Campaigns App.
  • Never edit a generated include (analytics-head.html, analytics-body.html, or any _includes/ file marked GENERATED) to get a capture to pass. A capture that fails on a cross-origin http: loader means a production build was served locally: rebuild in development and recapture.

Second check: the PR preview

Deploy the branch, resolve the deployed routes, and run QA against the preview.

npx --no-install campaigns-os qa resolve \
  --packet campaign-runtime.build.json \
  --base-url https://preview.example.com/campaign/

npx --no-install campaigns-os qa run \
  --packet campaign-runtime.build.json \
  --base-url https://preview.example.com/campaign/ \
  --browser \
  --test-order common
  • qa resolve derives and probes entry routes. A derived route is not proof that the preview serves it.
  • --test-order common is the default depth, and what it runs depends on the order cap (--max-test-orders, 6 by default). When the checkout topology has no more terminal paths than the cap, it runs all of them. When it has more, it runs checkout, the first offer's accept and decline, and the shortest receipt path, then adds a decline path for each upsell or downsell page not yet declined, up to the cap, and names any page left out. Use targeted, tier, or full coverage only when the handoff calls for it.
  • Every test-order run with offer pages records a browser-test-order:upsell-action-coverage row. It warns, naming the page, when no order clicked an upsell or downsell decline, and the verdict becomes ready_with_exceptions. A run that never proves a decline, such as --test-order accept, no longer reads as ready.
  • For a browser-only run, declare it first with npx --no-install campaigns-os qa policy set --packet campaign-runtime.build.json --order-path-depth off, then run --test-order off. --test-order off alone leaves purchase proof unknown and next cannot reach done.
  • Global test cards bypass the gateway and create no transaction. The order count cap prevents accidental floods; it is not an approval gate.
  • Browser QA checks declared checkout Offer surfaces and compares displayed packages and totals with the persisted order.

Bundle closeout and CI

Campaigns OS defines one canonical migration and readback bundle:

  • campaign-runtime.build.json
  • .campaign-runtime/build-context.json
  • .campaign-runtime/assembly-report.json
  • .campaign-runtime/doctor-output.json
  • .campaign-runtime/qa-verdict.json after QA
# Check the base bundle during the build
npx --no-install campaigns-os bundle check \
  --packet campaign-runtime.build.json \
  --json

# Require the QA projection at closeout or in post-QA CI
npx --no-install campaigns-os bundle check \
  --packet campaign-runtime.build.json \
  --require-qa \
  --json

The checker validates canonical paths, schemas, timestamps, freshness, and identity across the files. It also emits exact file digests and a stable material digest. ok alone is conformance: the sidecars agree with each other and with the contract. A blocked doctor or a blocked QA verdict is reported as a warning, and as an error under --require-qa (exit 2); the text output carries a Readiness: line under Status:. CI must still inspect stage_blocked and the warnings, not the exit code alone.

Do not substitute markdown reports for missing JSON. Do not select a packet or historical verdict by file modification time. If CI carries forward a historical verdict, name that file explicitly with qa promote before running the strict bundle check.

Run Record

Close the run, then emit its record. After run end, a bare run-record re-emits the closed run's record under the same run ID; --new-run starts a new run under a fresh ID; --list prints every record on disk for the packet and writes nothing.

npx --no-install campaigns-os run end --packet campaign-runtime.build.json

# Re-emit the closed run's record (same run ID)
npx --no-install campaigns-os run-record --packet campaign-runtime.build.json

# Inspect every record for this packet
npx --no-install campaigns-os run-record --packet campaign-runtime.build.json --list

# Start a new run under a fresh ID
npx --no-install campaigns-os run-record --packet campaign-runtime.build.json --new-run

Complex cases

Partial builds

Map only the pages being built and give every omitted page an explicit skip reason. A presell-only or landing-only preview can pass route and visual checks, but it is not checkout-launch-ready.

Existing or non-Page-Kit campaigns

Run campaigns-os standardize --target <campaign-repo> --json before choosing a repair or migration path. Treat runtime_proof_required as missing browser evidence, not a confirmed defect.

Multiple markets

Confirm the store and Campaigns App can serve every intended currency and country before build. Check package and shipping prices, payment methods, and market-specific claims. QA should cover at least one non-default market when the campaign has one.

Mixed source contributions

Record the assembly mode and source owner for each page or protected component. Use a certified template family only for the surfaces it contributes. Remove unsupported optional surfaces rather than leaving their demo configuration in the build.

Agency handoff

Give the builder one current package of inputs:

  • CampaignSpec or Map ID, public route slug, and target repo.
  • Campaign Build Brief, approved source files, and page mapping.
  • Campaigns App commerce references and domain owner.
  • Assembly mode and the contract family for protected runtime surfaces.
  • Deploy target, preview URL discovery method, and QA depth.

The handoff should name missing inputs and manual decisions. It should not require the builder to reconstruct them from chat history.

Suggested agent prompt

Use the current Campaigns OS runtime to build or evaluate this NEXT campaign from the supplied CampaignSpec or Map ID, Build Brief, approved design source, target repo, and commerce contract.

Read the toolkit's AGENTS.md orientation contract, run campaigns-os tooling status, install or refresh the lifecycle Skills, then start or reopen the Build Packet. Follow campaigns-os next --packet and its exact actions, and record each stage with campaigns-os record setup|build|polish|theme|deploy instead of editing sidecars, and save a changed brief or CampaignSpec with record brief or record spec rather than re-running start. Preserve SDK-owned commerce behavior. Replace demo commerce, tracking, footer, and SEO values only from approved sources.

Prove on localhost first: set the deploy target to local-serve, build the development environment, serve it, run the dedicated polish pass, package-owned polish capture, and browser QA with the requested typed-card depth, and require page-kit parity to pass before commit. Then use preview deployment, qa resolve, and browser QA on the preview as the second check. Finish with bundle check --require-qa. Stop when a missing input would require a guess or an unapproved commerce change.

Definition of done

  • The CampaignSpec, Map ID, public route, target directory, and assembly mode agree.
  • Campaign commerce and design sources are current, and no demo values remain.
  • Doctor, development build, SDK checks, polish capture, production parity, deploy, and route resolution completed.
  • Browser QA and the required typed-card paths produced a reviewed verdict.
  • The committed QA projection matches that verdict.
  • bundle check --require-qa passes and CI also accepts the recorded disposition.
  • The Run Record is closed and remaining launch decisions have named owners.

Resources


Use the runtime cursor for the next action and the strict bundle check for closeout.

On this page