This is the full developer documentation for Quomerce # Quomerce for developers > Add Quomerce to your shop in a few minutes. See every visit, every step of the funnel, and the silent failures that cost you orders. ## How it works [Section titled “How it works”](#how-it-works) 1\. Connect your website Add the website in Quomerce and turn on shop analytics. You get a public key for the site. 2\. Add the script Paste a small snippet into every page. It loads in the background and never slows the shop. 3\. Report events Let Quomerce detect cart, checkout and order events, or send them yourself from your code. 4\. Check that it works The Install & setup page ticks each item as data arrives. ## Start here [Section titled “Start here”](#start-here) [Quick start](/start/quick-start/)The shortest path from zero to the first event. [Next.js](/install/nextjs/)Install in a Next.js App Router shop, with types. [Commerce: auto or manual](/events/commerce-modes/)Choose where the funnel's events come from. [Privacy](/privacy/masking/)What is masked, what is never recorded, and how to hide more. # Quick start > Connect a website, add the script and see the first event. This page is the short version. Each step links to the full guide. 1. **Add your website.** Sign in at [app.quomerce.com](https://app.quomerce.com), open **Websites** and click **Add website**. Type the shop’s address. [More](/connect/add-website/) 2. **Turn on shop analytics.** Open the website, then **Install & setup**, and click **Turn on shop analytics**. This creates the site’s public key, `pk_live_…`. 3. **Check the allowed domains.** In **Analytics settings**, make sure every host the shop runs on is listed, for example `example.com` and `www.example.com`. Events from other hosts are refused. [More](/connect/settings/) 4. **Paste the snippet** into the `` of every page. Copy it from **Install & setup**, so the key is already filled in: ```html ``` Using Next.js? Follow the [Next.js guide](/install/nextjs/) instead. 5. **Connect your consent banner.** By default nothing is stored on the visitor’s device until your banner allows it. Pass the visitor’s choice to Quomerce on every page. [More](/install/consent/) ```js qm("consent", { analytics: true, replay: true }); ``` 6. **Choose how commerce events are reported.** If your shop already sends GA4 e-commerce events, you are done: Quomerce reads them (this is `auto` mode, the default). If you want to send them from your own code, use `manual` mode. [More](/events/commerce-modes/) 7. **Check that it works.** Open your shop, look at a product, add it to the cart and start checkout. The checklist on **Install & setup** ticks itself within seconds. [More](/install/verify/) Tip Add `debug: true` to `init` while you test: `qm("init",{key:"pk_live_…",debug:true})`. The script then writes what it does to the browser console. Remove it before going live. # Install with a coding agent > Let Claude Code, Cursor, Codex or another coding agent add the script for you. A coding agent can add Quomerce to your shop’s code. Give it a ready prompt, and it reads these docs and does the rest. 1. Open the website in [app.quomerce.com](https://app.quomerce.com), then **Install & setup**. 2. In **Install**, open the **AI agent** tab and click the copy button. The prompt already has your site’s key, script address and allowed domains. 3. Paste it into your coding agent, in the shop’s code. Read the changes it makes before you ship them. 4. Open your shop and watch the checklist on **Install & setup**. See [Check that it works](/install/verify/). ## Docs for agents [Section titled “Docs for agents”](#docs-for-agents) These docs are also served as plain Markdown, following [llms.txt](https://llmstxt.org). Anyone can read them, no sign-in needed: | File | What is in it | | ------------------------------------------------------------- | ---------------------------------------------- | | [`/llms.txt`](https://docs.quomerce.com/llms.txt) | A short index | | [`/llms-full.txt`](https://docs.quomerce.com/llms-full.txt) | Every page in one file | | [`/llms-small.txt`](https://docs.quomerce.com/llms-small.txt) | Every page, shorter, for small context windows | # Consent > Pass the visitor's consent choice to Quomerce. Quomerce follows your consent banner. You choose the **Consent** mode in **Analytics settings**. ## The two modes [Section titled “The two modes”](#the-two-modes) **Required (wait for the banner)** is the default. Until the script is told the visitor agreed: * no cookie or storage key is written on the device; * ids live in memory for one page load only, so each page looks like a new visit; * events are still sent, marked as sent without consent; * no replay is recorded. **Not required** means your shop takes responsibility for consent in another way. Storage and replays start right away. Use it only if you are sure you may. ## Pass on the choice [Section titled “Pass on the choice”](#pass-on-the-choice) The script does not remember the visitor’s choice. Your consent tool does. Call `consent` **on every page load**, as soon as the choice is known, and again whenever it changes: ```js // The visitor agreed to statistics qm("consent", { analytics: true, replay: true }); // The visitor declined qm("consent", { analytics: false, replay: false }); ``` | Field | Allows | | ----------- | ------------------------------------------------------------ | | `analytics` | Storing the session and visitor ids on the device (cookies). | | `replay` | Recording the session replay. It also needs `analytics`. | Withdrawing consent (`analytics: false`) deletes the script’s `_qm_*` cookies. ## Examples [Section titled “Examples”](#examples) Cookiebot ```js window.addEventListener("CookiebotOnConsentReady", () => { const ok = Cookiebot.consent.statistics; qm("consent", { analytics: ok, replay: ok }); }); ``` Any banner with a callback ```js myBanner.onChange((choices) => { qm("consent", { analytics: choices.analytics, replay: choices.analytics }); }); ``` For Next.js, see [Pass on consent](/install/nextjs/#3-pass-on-consent). Note Which consent category Quomerce belongs to is your decision. Most shops put it under statistics or analytics. See [URLs, requests and cookies](/privacy/data/) for exactly what is stored. # Add the script to a Next.js shop > Install Quomerce in a Next.js App Router shop, with types for window.qm. This guide is for the **Next.js App Router**. It covers the browser side: the script, consent and commerce calls from client components. ## 1. Load the script in the root layout [Section titled “1. Load the script in the root layout”](#1-load-the-script-in-the-root-layout) Add two `next/script` tags to `app/layout.tsx`: app/layout.tsx ```tsx import Script from "next/script"; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( {children} ``` What it does: * It defines `window.qm` right away. Every call made before the script has loaded is queued, then run in order once it has. So you can call `qm(...)` anywhere, at any time. * It loads `https://ingest-prod.quomerce.com/sdk/v0/qm.js` asynchronously. It never blocks the page from rendering. * A second copy of the snippet on the same page does nothing. Still, put it in one place only, such as your theme’s header template. `v0` always serves the newest compatible version of the script. You never need to update the snippet when the script changes. ## `init` options [Section titled “init options”](#init-options) ```js qm("init", { key: "pk_live_…", // required: the site's public key commerce: "auto", // "auto" (default) or "manual", see "Commerce: auto or manual" cookieDomain: ".example.com", // share one session across subdomains debug: false, // true: log what the script does to the console }); ``` | Option | Default | Meaning | | -------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------- | | `key` | — | The site’s public key, `pk_live_…`. Required. | | `commerce` | `"auto"` | Where commerce events come from. See [Commerce: auto or manual](/events/commerce-modes/). | | `cookieDomain` | The current host | Set it to `.example.com` if visitors move between `example.com` and `shop.example.com`, so it stays one session. | | `debug` | `false` | Writes what the script did, and what it ignored, to `console.debug`. The script logs nothing otherwise. | | `endpoint` | The script’s own host | Where events are sent. You never need it in production. | Call `init` once per page. The snippet already does it. ## Content Security Policy [Section titled “Content Security Policy”](#content-security-policy) If your shop sends a `Content-Security-Policy` header, allow the Quomerce host: ```text script-src ... https://ingest-prod.quomerce.com; connect-src ... https://ingest-prod.quomerce.com; ``` The inline snippet also needs your CSP nonce on its ` ``` # URLs, requests and cookies > Which URLs, network requests and storage Quomerce keeps. ## URLs [Section titled “URLs”](#urls) Every URL is cleaned in the browser before it is sent: * Only the query parameters in **Analytics settings → Kept URL parameters** stay. The default list is `utm_*`, `gclid`, `fbclid` and `msclkid`. Wildcards like `utm_*` work. Every other parameter is removed. * Email- and phone-like strings in the path are masked. So `https://example.com/search?q=rtx&utm_source=google&email=jan@example.com` is stored as `https://example.com/search?utm_source=google`. Search terms are not lost: the search page’s term is read separately (and masked) when a [search](/events/auto/) is detected. If you use other campaign parameters, such as `ref` or `aff_id`, add them to the list. ## Network requests [Section titled “Network requests”](#network-requests) The script watches `fetch` and `XMLHttpRequest` calls: | Request | Recorded? | | -------------------------------------------------------------------- | ------------------------------------- | | Failed, or status 400 and above, on **any** host | Yes, as a network error | | Successful, to **your shop’s own host or its subdomains** | Yes, as a network request, for timing | | Successful, to **any other host** (analytics, ads, payment widgets…) | No | “Your shop’s own host” is the page’s host without `www.`, and every subdomain of it. On `www.example.com`, requests to `example.com`, `www.example.com` and `api.example.com` are kept; requests to `google-analytics.com` are not. For each request, only the method, host, path (with emails and phone numbers masked), status and duration are stored. Never the query string, bodies or headers. ## Cookies and storage [Section titled “Cookies and storage”](#cookies-and-storage) With consent (or consent set to **Not required**), the script stores: | Name | Kind | Contents | Lifetime | | --------- | ------------------ | -------------------------- | ------------------------------------- | | `_qm_sid` | First-party cookie | Session id (a random UUID) | 30 minutes, rolling, at most 24 hours | | `_qm_vid` | First-party cookie | Visitor id (a random UUID) | 13 months | Both cookies are `SameSite=Lax`. Set `cookieDomain` in `init` to share them across subdomains. `localStorage` and `sessionStorage` hold the cached site settings, the session’s traffic source and a replay counter. Without consent, nothing is written: the ids live in memory for one page load. Withdrawing consent deletes the `_qm_*` cookies. ## Where data goes [Section titled “Where data goes”](#where-data-goes) The script sends data only to `ingest-prod.quomerce.com`, the same host it loads from. It loads no third-party code. See the [Quomerce privacy policy](https://quomerce.com/privacy) for how Quomerce processes this data. # Masking and replays > What Quomerce never records, what it masks, and how to hide more. Quomerce is built to keep personal data out. Most of it happens with no work from you. ## Never recorded [Section titled “Never recorded”](#never-recorded) * **What visitors type.** Values of inputs, text areas and selects are never recorded, on any page, in events or in replays. * **Request and response bodies and headers.** Network events carry only the method, host, path, status and duration. * **IP addresses.** Quomerce turns the IP address into a country and throws it away. No IP is stored. ## Masked automatically [Section titled “Masked automatically”](#masked-automatically) * **All text on checkout, thank-you and account pages**, in replays. This depends on the [page type](/events/page-types/), so make sure those pages get the right one. It also covers client-side navigation into checkout. * **Email addresses and phone numbers** in URLs, error messages, click text, search terms and custom event props. Quomerce filters them again when the data arrives. * **Query parameters** other than the ones you keep. See [URLs, requests and cookies](/privacy/data/). ## Hide more [Section titled “Hide more”](#hide-more) Use two HTML attributes on any page: ```html Jan Kowalski
…
``` Use `data-qm-mask` where personal data shows outside checkout, such as a customer name in the header or an address in a “My orders” widget. Use `data-qm-block` for things that should not be seen at all, such as an embedded chat. Can’t change the HTML? Add CSS selectors in **Analytics settings → Session replay**: **Mask text in** works like `data-qm-mask`, **Leave out entirely** works like `data-qm-block`. ## When replays are recorded [Section titled “When replays are recorded”](#when-replays-are-recorded) A session is recorded only when all of these are true: * **Record replays** is on for the website; * the visitor allowed replays (or consent is set to **Not required**), see [Consent](/install/consent/); * the browser shows no sign of being automated (driven by a test tool, or headless); * the session is at least 3 seconds old. Shorter sessions upload nothing. A recording lasts up to 6 hours per session. Replays are kept for 30 days by default (**Keep replays** in **Analytics settings**). Note Replay recording is a separate file that loads only when a session qualifies. Visitors who never get recorded never download it. # JavaScript API > Every command of window.qm. The snippet defines `window.qm`. Every command works in two forms: * **The call form**, `qm("command", …args)`, works at any time, also before the script has loaded. Use it. * **The method form**, `qm.command(…)` or `qm.commerce.method(…)`, works only after the script has loaded. Types for all of these are in [`qm.d.ts`](/install/typescript/). ## Core [Section titled “Core”](#core) | Command | Arguments | What it does | | ---------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | | `init` | `{ key, commerce?, cookieDomain?, debug?, endpoint? }` | Starts the script. Once per page; the snippet does it. See [options](/install/snippet/#init-options). | | `consent` | `{ analytics?, replay? }` | Passes on the visitor’s choice. Every page load. See [Consent](/install/consent/). | | `page` | `{ type }` | Sets this page’s [type](/events/page-types/). | | `track` | `name, props?` | A [custom event](/events/custom/). Both modes. | | `identify` | `customerId` | Links the session to a customer, as a hash. See [Custom events and customers](/events/custom/#link-a-session-to-a-customer). | ```js qm("init", { key: "pk_live_…" }); qm("consent", { analytics: true, replay: true }); qm("page", { type: "thank_you" }); qm("track", "size_guide_opened", { category: "shoes" }); qm("identify", "customer-1042"); ``` ## Commerce [Section titled “Commerce”](#commerce) Sent only in [`manual` mode](/events/manual/). In `auto` mode they are ignored. | Command | Arguments | | --------------------------------- | ------------------------------------------------------- | | `commerce.productViewed` | `product` | | `commerce.cartAdded` | `product, quantity?` | | `commerce.cartRemoved` | `product, quantity?` | | `commerce.cartViewed` | `cart` | | `commerce.checkoutStarted` | `cart` | | `commerce.shippingMethodSelected` | `method` | | `commerce.paymentMethodSelected` | `method` | | `commerce.paymentRedirected` | `method, provider` | | `commerce.paymentReturned` | `method, provider, "success" \| "failure" \| "unknown"` | | `commerce.couponApplied` | `code, accepted` | | `commerce.purchase` | `order` | | `commerce.searchPerformed` | `term, resultCount?` | | `commerce.itemListViewed` | `list, itemCount?` | | `commerce.itemSelected` | `product, list?, index?` | | `commerce.wishlistAdded` | `product` | | `commerce.promotionViewed` | `promotion` | | `commerce.promotionSelected` | `promotion` | | `commerce.signedUp` | `method?` | | `commerce.loggedIn` | `method?` | The shapes of `product`, `cart`, `order`, `list` and `promotion` are in [Manual mode](/events/manual/#data-shapes). ## Other [Section titled “Other”](#other) * `qm.version` is the loaded script’s version, such as `"0.1.0"`. It exists only after the script has loaded. * An unknown command, or arguments of the wrong shape, are ignored. With `debug: true`, the script logs why. * No command ever throws. ## HTML attributes [Section titled “HTML attributes”](#html-attributes) | Attribute | Effect | | --------------- | ----------------------------------------------------------- | | `data-qm-mask` | Text inside is masked in replays. | | `data-qm-block` | The element is not recorded; the replay shows an empty box. | ## Hosts [Section titled “Hosts”](#hosts) | URL | What | | ------------------------------------------------- | -------------------------------------------------- | | `https://ingest-prod.quomerce.com/sdk/v0/qm.js` | The script. `v0` follows every compatible release. | | `https://ingest-prod.quomerce.com/sdk/v0/qm.d.ts` | The [TypeScript types](/install/typescript/). | | `https://ingest-prod.quomerce.com` | Where events and replays are sent. | | `https://app.quomerce.com` | The Quomerce app. | # Troubleshooting > What to check when data does not arrive. Start with `debug`. Add it to `init` and reload the page: ```js qm("init", { key: "pk_live_…", debug: true }); ``` The script then writes what it did, and what it ignored and why, to the browser console (`console.debug`, shown under “Verbose” in Chrome). Remove it when you are done. ## Nothing arrives [Section titled “Nothing arrives”](#nothing-arrives) * **Is the host allowed?** The page’s host must be in **Allowed domains** in **Analytics settings**. `www.example.com` and `example.com` are different hosts; `*.example.com` covers subdomains. * **Is tracking on?** **Tracking on** in **Analytics settings**. * **Is the request blocked?** Open the browser’s Network tab and look for requests to `ingest-prod.quomerce.com`. Ad blockers and privacy extensions can block them. Test in a clean browser profile. * **Is there a Content Security Policy?** Add `https://ingest-prod.quomerce.com` to `script-src` and `connect-src`. See [Content Security Policy](/install/snippet/#content-security-policy). * **Is the key right?** It starts with `pk_live_` and must match the website. * **Is it a test browser?** Playwright, Puppeteer, Selenium, Cypress and headless browsers are recognised as bots: by default their events are not stored at all, and they are never recorded. Test with an ordinary browser window. ## Events arrive, but no replays [Section titled “Events arrive, but no replays”](#events-arrive-but-no-replays) * Is **Record replays** on? * With consent **Required**, the visitor must allow both `analytics` and `replay`. See [Consent](/install/consent/). * Sessions under 3 seconds upload nothing. ## Commerce events are missing [Section titled “Commerce events are missing”](#commerce-events-are-missing) * **You call `qm.commerce` but nothing arrives?** You are in `auto` mode, which ignores these calls. Pass `commerce: "manual"` to `init`. See [Commerce: auto or manual](/events/commerce-modes/). * **In `manual` mode, some calls are dropped?** The arguments do not describe a product or cart. A product needs `id`, `name`, `price` and `currency`. Money is in major units. `debug: true` logs each dropped call. * **In `auto` mode, a step is empty?** Check **Install & setup**, which lists events per source. Payment redirects need a rule. Add rules for anything else in **Analytics settings → Commerce events**. See [Auto mode](/events/auto/). * **Calls made before the script loads are lost?** Use the call form `qm("commerce.cartAdded", …)`, not `qm.commerce.cartAdded(…)`. Only the call form is queued. ## Every page looks like a new visit [Section titled “Every page looks like a new visit”](#every-page-looks-like-a-new-visit) With consent **Required**, ids live in memory for one page load until `qm("consent", { analytics: true })` is called. Make sure your banner calls it on every page load, not only when the visitor clicks. ## Sessions split between subdomains [Section titled “Sessions split between subdomains”](#sessions-split-between-subdomains) Set `cookieDomain: ".example.com"` in `init`, and add every subdomain to **Allowed domains**. ## Orders are counted twice [Section titled “Orders are counted twice”](#orders-are-counted-twice) A reload of the confirmation page sends `purchase` again. Guard the call per order id. See [Manual mode](/events/manual/#example-an-order-confirmation).