Skip to main content

Saleor Pulse Commerce Context

Saleor version required: 3.23

Commerce Context is an open note on the Saleor order: sectioned metadata under commerce.context.*. Storefronts, POS, imports, and agents write facts. Pulse reads the order and shows Origins on Financial β€” surface, campaign, or source, ranked by net after refunds.

The note is the join. Saleor stays the commerce record. Pulse is a reader. Write the note before checkoutComplete.

What Pulse shows​

Origins sits on Financial, on the same period, currency, and channel as the rest of that view. The subtitle is coverage: tagged revenue orders over all revenue orders. Unconfirmed orders count. Cancelled ones do not.

Pulse ranks tagged orders only. Marketing on the note is first-touch. Net after refunds is a books figure, not a lift study. Click IDs for Meta or Google stay on your ads path.

A document is useful as soon as origin.surface parses. Campaign and source stay empty until you write marketing. Untagged surfaces do not rank as worse β€” they are absent until they write the note.

Design rules​

  1. Metadata on the checkout β€” Saleor copies it onto the order at complete. See Metadata.
  2. Sectioned keys β€” each section is its own metadata key (origin, marketing, session), not one combined JSON blob. Saleor replaces a whole key. Do not read-merge-write someone else's section.
  3. The reader composes β€” Pulse (or any consumer) joins the keys. Writers attach facts.
  4. No people in the note β€” the order already links the customer. No emails, names, or tokens.
  5. Never block checkout β€” if the metadata write fails, complete the order anyway.

Keys​

KeyOwnerContents
commerce.context.originCheckout surface (storefront / POS / import / draft)surface (required), system, capturedAt, consent
commerce.context.marketingStorefront / marketing tag layerFirst-touch UTM-like fields + landingPath
commerce.context.sessionStorefrontsessionId when storage is allowed
commerce.context.actorsAffiliate / sales / agent / support appsWrite only if you are that system
commerce.context.experimentExperiment / flag toolid, variant
commerce.context.ext.<vendor>That vendorYour own JSON bag

There is no shared commerce.context.properties key in v1. It invites silent overwrites. Pulse folds each ext.<vendor> object into composed properties.<vendor>.

Channel stays on the Saleor order, not in this note.

origin​

{
"surface": "storefront",
"system": "paper",
"capturedAt": "2026-09-13T10:00:00.000Z",
"consent": "unknown"
}
FieldRequiredDescription
surfaceyesHow the order was created
systemrecommendedWriter identity β€” paper, your app id, a register build
capturedAtnoISO-8601 timestamp when this origin object was attached
consentrecommended for storefrontsShopper consent for storage-derived sections
devicenomobile | tablet | desktop | other. From the user agent; no storage, so no consent needed

origin is never gated on cookie consent. It describes the order, not the person.

surface​

Pulse recognizes storefront, pos, draft, import, marketplace, api, agent, and support. That list will grow. Pick the closest match. Put the writer in system. Do not invent a surface for a device or a brand.

A native shopping app that creates checkouts is still storefront. A till is pos. A Dashboard staff order is draft.

granted | denied | not_required | unknown

Whether storage-derived sections (marketing, session) are allowed. Origin itself is never consent-gated. not_required covers an implied storefront regime or a writer with no shopper cookie (till, import, agent). unknown is a missing decision β€” Paper's default required mode with no banner β€” not a till.

ValueMeaningSibling sections
grantedConsent given for analytics storagemarketing / session present when data existed
deniedShopper said nomarketing / session absent
not_requiredNo shopper opt-in needed β€” implied storefront regime, or no shopper cookie (till, import, agent)marketing / session present when data existed
unknownConsent primitive exists but no decision yet (Paper required with no banner), or not recordedany; Paper skips marketing

Write denied when the shopper said no β€” do not omit the field. Without it, a declined visit looks like direct traffic.

marketing​

First-touch UTM fields plus a redacted landingPath. Write once, fill-missing. Pulse ranks campaign and source on this first landing. Last-touch is a different policy β€” own the whole key if you need it; do not merge fields across writes.

Strip tracking query params from landingPath before you write. Pulse redacts common secrets again on ingest.

session​

A per-visit sessionId when storage is allowed. Do not write anonymousId in v1.

Two writes​

Paper is the reference storefront. Copy the keys, not the React tree. The builders live in saleor/storefront under src/lib/commerce-context/.

Saleor 3.21+ accepts metadata on checkoutCreate. Pulse and Paper target 3.23+.

On create β€” origin​

Put origin (and your ext.<vendor> bag) in checkoutCreate. Zero extra round trips. Present even if the shopper abandons before complete.

mutation CheckoutCreate(
$channel: String!
$lines: [CheckoutLineInput!]!
$metadata: [MetadataInput!]
) {
checkoutCreate(
input: { channel: $channel, lines: $lines, metadata: $metadata }
) {
checkout {
id
}
errors {
field
code
}
}
}
[
{
"key": "commerce.context.origin",
"value": "{\"surface\":\"storefront\",\"system\":\"paper\",\"capturedAt\":\"2026-09-13T10:00:00.000Z\",\"consent\":\"unknown\"}"
},
{
"key": "commerce.context.ext.paper",
"value": "{\"locale\":\"en-US\",\"paperVersion\":\"223382c0\"}"
}
]

Before complete β€” marketing and session​

Write these with updateMetadata before checkoutComplete, and only when origin.consent is granted or not_required. Saleor copies checkout metadata onto the order at complete. Pulse reads the order.

mutation EnrichCheckoutContext($id: ID!, $input: [MetadataInput!]!) {
updateMetadata(id: $id, input: $input) {
item {
... on Checkout {
id
}
}
errors {
field
code
message
}
}
}
[
{
"key": "commerce.context.marketing",
"value": "{\"source\":\"google\",\"medium\":\"cpc\",\"campaign\":\"summer_sale\",\"landingPath\":\"/collections/summer\"}"
},
{
"key": "commerce.context.session",
"value": "{\"sessionId\":\"sess_01J8…\"}"
}
]

If consent changed between create and complete, re-send the whole origin key. Keep the original capturedAt when you have it.

Paper's default consent mode is required, and core Paper ships no banner. Create-time consent is then unknown, this second write is skipped, and Pulse still treats the order as valid from origin alone. That is a complete origin note. You get surface on Origins. Campaign and source stay empty until you allow first-touch cookies.

If the metadata write fails, complete anyway. Pulse will show surface without a campaign β€” the same empty campaign as consent unknown. A thin note beats a failed payment.

Other checkouts​

Change surface and system. Keep the key names.

{
"commerce.context.origin": {
"surface": "pos",
"system": "register-ios",
"capturedAt": "2026-09-13T10:00:00.000Z",
"consent": "not_required"
}
}

An agent checkout is the same idea β€” no shopper cookie, so not_required β€” with surface: "agent" and actors if you own that identity. Do not reuse a storefront origin builder or copy the shopper's first-touch cookie.

{
"commerce.context.origin": {
"surface": "agent",
"system": "paper",
"capturedAt": "2026-09-13T10:00:00.000Z",
"consent": "not_required"
},
"commerce.context.actors": {
"agent": { "type": "mcp", "id": "…" }
}
}
Writersurfacesystem (example)Marketing
Paper / web storefrontstorefrontpaperFirst-touch, consent-gated
iOS or Android shopping appstorefrontyour app idSame rules if you keep a landing snapshot
POS / registerposregister buildUsually none
Dashboard draftdraftdashboardUsually none
CSV / orderBulkCreateimportimporter nameUsually none
Support or agent placing ordersupport / agentthat toolOptional own payload; consent: not_required; actors if you own that identity

What not to write​

  • Emails, names, tokens, click IDs you are not prepared to treat as durable commerce data
  • Tracking query params on landingPath
  • anonymousId (reserved in v1)
  • Someone else's section
  • A new surface for β€œmobile” or a brand name

Next steps​