All posts
Business Use CasesEngineeringBest practices

Agentic commerce for Shopify: practical setup and new revenue paths

A practical guide to agentic commerce for Shopify: what’s real today, what agents support WebMCP, and copy‑paste tools to add cart, pick variants, apply codes and start checkout.

Agent LayerOctober 6, 202612 min read

You looked for agentic commerce for Shopify. Here’s the straight answer: you can make a Shopify storefront executable by AI agents today using WebMCP page tools. Some agents already call them, some don’t. By the end, you’ll have working code for product discovery, variant selection, add-to-cart, discounts, and guarded checkout, plus the trade-offs and where this works in production.

TL;DR

  • Agentic commerce for Shopify works via WebMCP: register small, JSON-Schema tools in your theme so agents call them instead of scraping the DOM.
  • Ship the basics: find_variant, add_to_cart, apply_discount, get_cart, start_checkout (with a confirmation gate). Keep results small and explicit.
  • Support both addresses: document.modelContext and navigator.modelContext; some agents still look at the old one.
  • Today’s support: ChatGPT desktop and ChatGPT Work call WebMCP tools. Others range from behind-flag to no support; see the ecosystem tracker.
  • Use Agent Layer if you need drift protection, guardrails, analytics, and test scenarios without wiring it all yourself. Otherwise, the raw WebMCP snippets below work fine.

What “agentic commerce” actually means on Shopify

Agentic commerce isn’t a new checkout provider. It’s your existing Shopify storefront, but with a machine interface in-page. Agents—running in the shopper’s browser session—call tools you expose: “find the small, blue t-shirt”, “add 2 to cart”, “apply SUMMER10”. No scraping, no new API keys, no backend to stand up.

WebMCP is the small browser standard for that interface. It’s different from Anthropic’s MCP (servers over stdio/HTTP). If you want the longer background, see WebMCP explained and how WebMCP differs from MCP.

On Shopify, the rough mapping is simple:

  • Product and variant info → your theme’s JS state or DOM data attributes.
  • Cart operations → Shopify’s cart endpoints or AJAX cart.
  • Checkout → Shopify’s hosted checkout URL, but only after an explicit confirmation in-page.

Where this works today (and where it doesn’t)

WebMCP support is early. Based on hands-on checks:

  • ChatGPT desktop app and ChatGPT Work: calls page tools today.
  • Chrome, Edge (as browsers): origin trials in motion.
  • Several frameworks work if they launch a Chrome with WebMCP enabled (e.g., Vercel agent-browser, Stagehand).
  • Others are behind flags or have no signal.

This evolves fast. Keep an eye on the public WebMCP support tracker. It lists browsers, agents and frameworks, with dates and verification notes.

If you’re aiming for near-term revenue, build the tools now and treat them like a new funnel that gradually turns on as client agents add support. In our experience, shoppers using ChatGPT desktop or Work can already complete agent-led flows on sites that expose tools.

Minimal viable toolset for Shopify

Ship a clear set of tools with unambiguous descriptions. Start with five:

  • get_product: return normalized info for the current product page.
  • find_variant: choose a variant by options (size, color), return its ID and availability.
  • add_to_cart: add one or more line items by variant ID and quantity.
  • apply_discount: set a discount code, return whether it applied.
  • get_cart: snapshot totals and lines, for agent reasoning.
  • start_checkout: guarded; only after explicit user confirmation in-page.

Where to place the code

Add a small script to theme.liquid that runs on product, cart, and any page where you want agents to act. Feature-detect both addresses: document.modelContext first, then navigator.modelContext for older implementations.

Runnable example: essential WebMCP tools

The snippet below assumes your theme exposes basic product data via data attributes or a global productJson variable on product pages, and uses Shopify’s AJAX cart endpoints on others. Adapt selectors and endpoints to your theme.

<!-- In layout/theme.liquid, near </body> -->
<script>
(() => {
  // Feature-detect WebMCP
  const mc = (document && document.modelContext) || (navigator && navigator.modelContext);
  if (!mc) return; // No WebMCP host; page remains normal

  // Helpers: tiny wrappers around Shopify AJAX endpoints
  async function ajaxCart(path, body) {
    const res = await fetch(path, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
      body: JSON.stringify(body)
    });
    if (!res.ok) throw new Error('Cart request failed');
    return res.json();
  }

  function getProductContext() {
    // Prefer structured data your theme exposes
    // Example: window.productJson from product template
    if (window.productJson) return window.productJson;
    // Fallback: minimal scrape from JSON-LD if present
    try {
      const node = document.querySelector('script[type="application/ld+json"]');
      if (node) return JSON.parse(node.textContent);
    } catch (e) {}
    return null;
  }

  // 1) get_product
  mc.registerTool({
    name: 'get_product',
    description: 'Use on product pages to read product title, options, and variants. Returns minimal info needed for selection. No side effects.',
    inputSchema: { type: 'object', properties: {}, additionalProperties: false },
    execute: async () => {
      const p = getProductContext();
      if (!p) return { ok: false, error: 'No product context' };
      const variants = (p.variants || []).map(v => ({
        id: v.id,
        title: v.title,
        available: Boolean(v.available),
        price: v.price || v.price_amount || null,
        options: v.options || []
      }));
      return {
        ok: true,
        product: {
          title: p.title || p.name || document.title,
          options: (p.options || []).map(o => ({ name: o.name || o, values: o.values || [] })),
          variants
        }
      };
    }
  });

  // 2) find_variant
  mc.registerTool({
    name: 'find_variant',
    description: 'Select a product variant by option values (e.g., size=Small, color=Blue). Use on product pages after get_product.',
    inputSchema: {
      type: 'object',
      properties: {
        options: {
          type: 'array',
          items: { type: 'object', properties: { name: { type: 'string' }, value: { type: 'string' } }, required: ['name', 'value'] },
          minItems: 1
        }
      },
      required: ['options'],
      additionalProperties: false
    },
    execute: async ({ options }) => {
      const p = getProductContext();
      if (!p) return { ok: false, error: 'No product context' };
      const match = (p.variants || []).find(v => {
        const vOpts = (v.options || []).map((val, i) => ({ name: (p.options?.[i]?.name) || String(i+1), value: String(val) }));
        return options.every(o => vOpts.some(vo => vo.name.toLowerCase() === o.name.toLowerCase() && vo.value.toLowerCase() === o.value.toLowerCase()));
      });
      if (!match) return { ok: false, error: 'No matching variant' };
      return { ok: true, variant: { id: match.id, available: Boolean(match.available), title: match.title } };
    }
  });

  // 3) add_to_cart
  mc.registerTool({
    name: 'add_to_cart',
    description: 'Add one or more variant IDs to the cart with quantities. Use only with available variants. Side effect: modifies cart.',
    inputSchema: {
      type: 'object',
      properties: {
        items: {
          type: 'array',
          items: {
            type: 'object',
            properties: { variant_id: { type: 'number' }, quantity: { type: 'number', minimum: 1 } },
            required: ['variant_id', 'quantity'],
            additionalProperties: false
          },
          minItems: 1
        }
      },
      required: ['items'],
      additionalProperties: false
    },
    execute: async ({ items }) => {
      const cart = await ajaxCart('/cart/add.js', { items: items.map(i => ({ id: i.variant_id, quantity: i.quantity })) });
      return { ok: true, added_count: items.length, cart_token: cart?.token || null };
    }
  });

  // 4) get_cart
  mc.registerTool({
    name: 'get_cart',
    description: 'Read current cart lines and totals. No side effects.',
    inputSchema: { type: 'object', properties: {}, additionalProperties: false },
    execute: async () => {
      const res = await fetch('/cart.js', { headers: { 'Accept': 'application/json' } });
      if (!res.ok) return { ok: false, error: 'Cart read failed' };
      const cart = await res.json();
      return {
        ok: true,
        item_count: cart.item_count,
        total_price: cart.total_price,
        currency: cart.currency,
        lines: (cart.items || []).map(it => ({ key: it.key, title: it.title, variant_id: it.variant_id, quantity: it.quantity, price: it.price }))
      };
    }
  });

  // 5) apply_discount
  mc.registerTool({
    name: 'apply_discount',
    description: 'Apply a discount code to the cart if supported. Side effect: modifies cart discount state.',
    inputSchema: { type: 'object', properties: { code: { type: 'string' } }, required: ['code'], additionalProperties: false },
    execute: async ({ code }) => {
      // Many themes accept apply discount by setting attribute or navigating. Try cart update first.
      try {
        const res = await fetch(`/discount/${encodeURIComponent(code)}`, { redirect: 'manual' });
        const ok = res.status === 0 || (res.status >= 300 && res.status < 400) || res.ok;
        return { ok, code };
      } catch (e) {
        return { ok: false, error: 'Discount application failed' };
      }
    }
  });

  // 6) start_checkout (guarded)
  // Sensitive: require explicit user confirmation in the page UI before proceeding.
  let checkoutConfirmed = false;
  function ensureCheckoutGate() {
    if (document.getElementById('checkout-confirm-gate')) return;
    const bar = document.createElement('div');
    bar.id = 'checkout-confirm-gate';
    bar.style.position = 'fixed';
    bar.style.bottom = '0';
    bar.style.left = '0';
    bar.style.right = '0';
    bar.style.background = '#111';
    bar.style.color = '#fff';
    bar.style.padding = '12px';
    bar.style.zIndex = '9999';
    bar.innerHTML = '<span>Agent requested checkout. Proceed?</span> <button id="confirm-co">Yes</button> <button id="cancel-co">No</button>';
    document.body.appendChild(bar);
    document.getElementById('confirm-co').onclick = () => { checkoutConfirmed = true; window.location.href = '/checkout'; };
    document.getElementById('cancel-co').onclick = () => { checkoutConfirmed = false; bar.remove(); };
  }

  mc.registerTool({
    name: 'start_checkout',
    description: 'Request starting Shopify checkout. Must only execute after explicit human confirmation in the page. Side effect: navigates to checkout.',
    inputSchema: { type: 'object', properties: {}, additionalProperties: false },
    execute: async () => {
      if (!checkoutConfirmed) {
        ensureCheckoutGate();
        return { ok: false, needs_confirmation: true, message: 'Showed confirmation gate' };
      }
      return { ok: true, started: true };
    }
  });
})();
</script>

This illustrates the pattern:

  • State your tool’s preconditions and side effects in the description. Agents use them to choose tools.
  • Keep inputs and outputs small and explicit. Use variant_id and quantity, not free text.
  • For sensitive actions (checkout, account changes, payment methods), require an in-page confirmation before side effects.

If you want a step-by-step Shopify walkthrough with Liquid details, see WebMCP for Shopify. For broader enablement across stacks, see How to enable WebMCP.

Flow: how an agent uses your tools

The sequence below shows a typical purchase path an agent takes when tools are present.

Agent reads tools and executes a variant to checkout flow

Agents are expected to react to needs_confirmation and wait for the shopper to click “Yes”. That’s by design.

Revenue paths you can actually expect

Short-term, agentic commerce is incremental. Think intent completion and reduced friction rather than a replacement funnel.

  • Guided conversion from chat: Shoppers using ChatGPT desktop or Work can ask “buy two of the small blue tee” and complete the flow. Fewer misclicks, clearer intents.
  • Recovery and upsell: An agent reading get_cart can suggest size/care info or related items, then add_to_cart on approval.
  • Discounts and promos: apply_discount becomes deterministic. Agents don’t hunt for a hidden field or modal.
  • Support deflection: Agents can answer “is the medium in stock?” with get_product + find_variant without punting to chat support.

As more agents adopt WebMCP (track it at the ecosystem page), these same tools power affiliate-like flows and automated shopping lists without a custom API.

Honest constraints and trade-offs

  • Ecosystem maturity: Not every agent sees tools yet. You’re preparing the runway. If you need guaranteed volume today, keep normal UX first and treat tools as additive.
  • Data availability: Your theme must surface variant IDs and availability to JS. Many do. If not, add minimal JSON to the page.
  • Checkout is gated: Don’t bypass Shopify’s hosted checkout or introduce hidden navigations. Require an explicit human click as shown.
  • Privacy and security: Tools run in the user’s tab with their session. Don’t ask for or expose secrets via tool params. Keep outputs to necessary fields.

If you don’t want to maintain this glue, Agent Layer packages it: versioned tool configs per template, drift detection when selectors change, analytics on which agents call tools, and simple guardrails. Details below.

Guardrails and testing without surprises

You can—and should—test your tools like you test APIs.

  • Unit-style: Call execute directly in console with sample params.
  • Agent-style: Use an agent that supports tools (e.g., ChatGPT desktop) and speak intents: “Add two medium blue tees, then apply SUMMER10.”
  • Cross-browser: Check both addresses document.modelContext and navigator.modelContext.

For a deeper approach, see WebMCP for testing, which shows ways to verify tool selection against real intents, and WebMCP in Chrome for current flags and versions.

Expanding beyond the basics

Once the essentials are stable, add small, composable tools:

  • search_catalog: return SKUs or URLs by query (on collection or search pages).
  • check_store_policy: return shipping or return policy highlights from a known selector.
  • schedule_pickup_or_note: capture a gift note or pickup preference, but store it client-side and only submit after user confirmation.

Design these to reflect UI actions you already support. Document preconditions (“use on search page”, “returns up to 10 items”). Keep them deterministic and reversible where possible.

What breaks in production

  • Missing feature detection: Only checking document.modelContext causes silent failures on agents that still read navigator.modelContext.
  • Overly vague tool descriptions: If two tools claim to “add items”, the agent may pick the wrong one. Specify when to use which.
  • Variant mismatch: Themes with custom option names (e.g., “Fit” instead of “Size”) break naive matching. Normalize or expose a mapping.
  • Currency and price assumptions: Don’t hardcode currency symbols. Return raw numbers; let agents format.
  • Checkout without confirmation: Some agents will try. Your tool must refuse and signal needs_confirmation until a human clicks.
  • Modal or SPA timing: If your add_to_cart opens a modal, ensure your AJAX endpoint still updates the cart. Avoid relying on click simulation.
  • Bot walls and CAPTCHAs: Agents can’t pass CAPTCHAs. If cart or checkout trigger them, agent flows stop. Consider reducing friction for known safe paths.
  • Drift from theme updates: A class or selector change can break your helper code. If you hand-roll it, add tests. Agent Layer’s health checks catch drift and suggest fixes.

Using Agent Layer when you want less maintenance

You can ship all of the above by hand. Teams that ship weekly often prefer not to:

  • Versioned configs per template (product, cart, search). Roll back when a tool misbehaves.
  • Drift detection: health checks flag when a UI change breaks a tool and propose a new selector.
  • Guardrails: enforce confirmation on sensitive tools without bespoke UI each time.
  • Analytics: see which agents called which tools, success/error, and duration.
  • Test scenarios: declare intents and the expected tool; validate before deploy.

Agent Layer adds one script tag and keeps your tools working as themes evolve. If you’re evaluating, start with the free audit.

Try it

Run the free agent-readiness audit at agentlayer.tech. It scores your store and proposes page-by-page WebMCP tools. To track which agents and browsers currently support WebMCP, check the public ecosystem tracker. When you’re ready to build, the Shopify-specific walkthrough is here: WebMCP for Shopify: a practical path to agentic checkout.

Get started

Don't Be Invisible to Tomorrow's Buyers.

Make your platform executable for every AI assistant your customers already use. No engineering, no lock-in, no breakage.