Skip to content

Add commerce-llms-txt template: agent-readable product catalogs powered by Workers AI#968

Open
mconroy-cf wants to merge 7 commits intocloudflare:mainfrom
mconroy-cf:add-commerce-llms-txt-template
Open

Add commerce-llms-txt template: agent-readable product catalogs powered by Workers AI#968
mconroy-cf wants to merge 7 commits intocloudflare:mainfrom
mconroy-cf:add-commerce-llms-txt-template

Conversation

@mconroy-cf
Copy link
Copy Markdown

Summary

Adds a new Workers template that makes merchant product catalogs visible to AI shopping agents by serving a dynamic /llms.txt endpoint.

  • Workers AI enriches raw product specs (e.g., "DIN 0.75-3.0, polycarbonate cap") into natural language descriptions agents can reason with (e.g., "lightweight skis for toddlers, bindings release easily for safety")
  • KV-backed caching persists enriched products across cold starts with configurable TTL
  • Shopify integration via public /products.json API, with a sample catalog fallback for zero-config demo
  • Configurable merchant vertical drives how AI describes products (outdoor gear, electronics, fashion, etc.)

What's included

File Purpose
src/llms-txt-worker/index.ts Main Worker: routes, llms.txt generation, KV caching
src/enrichment/index.ts Workers AI enrichment with configurable vertical + fallback
src/lib/shopify.ts Shopify product fetcher + mapper
src/lib/catalog.ts Sample product catalog for demo
src/lib/types.ts TypeScript interfaces
test/index.test.ts 9 vitest tests (all passing)
playwright-tests/commerce-llms-txt-template.spec.ts E2E test

Endpoints

Route Description
GET /llms.txt Agent-optimized catalog (concise)
GET /llms-full.txt Detailed version with specs + highlights
GET /api/products Full enriched catalog as JSON
GET /api/products/:slug Single product detail
GET /api API docs

Bindings

  • AI — Workers AI (Llama 3.1 8B Instruct)
  • KVENRICHMENT_CACHE for persistent enrichment cache
  • VarsMERCHANT_NAME, MERCHANT_DESCRIPTION, STORE_CURRENCY, MERCHANT_VERTICAL, SHOPIFY_STORE_DOMAIN, etc.

Checklist

  • package.json with cloudflare metadata (label, products, categories, bindings)
  • wrangler.json (not .toml)
  • README.md with deploy button + dash-content-start/end markers
  • .gitignore (standard template)
  • 9 vitest tests (all passing)
  • Playwright E2E test
  • TypeScript strict mode, zero errors
  • preview_image_url / preview_icon_url — need from Growth team
  • publish: true — waiting for team approval

@mconroy-cf
Copy link
Copy Markdown
Author

Preview Assets

Here's the preview screenshot for the template gallery card (16:9, styled rendering of the /llms.txt output):

Preview image source: I've included an HTML file below that renders the template output as it would appear in a browser. Happy to provide a PNG screenshot at whatever resolution you need, or you can render it directly from this HTML. The template serves JSON/markdown endpoints (no visual UI), so this styled rendering of the /llms.txt output is the most representative view.

Preview screenshot HTML (expand to view source)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Commerce llms.txt — Preview</title>
  <style>
    * { box-sizing: border-box; margin: 0; padding: 0; }
    body {
      font-family: ui-monospace, "SF Mono", "Fira Code", monospace;
      background: #1e1e2e;
      color: #cdd6f4;
      padding: 2rem 3rem;
      font-size: 14px;
      line-height: 1.7;
    }
    .url-bar {
      background: #313244;
      border-radius: 8px;
      padding: 0.5rem 1rem;
      margin-bottom: 1.5rem;
      color: #a6adc8;
      font-size: 13px;
      display: flex;
      align-items: center;
      gap: 0.75rem;
    }
    .url-bar .dots { display: flex; gap: 6px; }
    .url-bar .dot { width: 12px; height: 12px; border-radius: 50%; }
    .dot.red { background: #f38ba8; }
    .dot.yellow { background: #f9e2af; }
    .dot.green { background: #a6e3a1; }
    .url-bar .url { color: #89b4fa; }
    h1 { color: #cba6f7; font-size: 1.5rem; margin-bottom: 0.75rem; }
    blockquote {
      border-left: 3px solid #45475a;
      padding-left: 1rem;
      color: #a6adc8;
      margin-bottom: 1rem;
    }
    .meta { color: #6c7086; margin-bottom: 1.5rem; }
    .meta strong { color: #89b4fa; }
    h2 { color: #89dceb; font-size: 1.1rem; margin: 1.5rem 0 0.75rem 0; }
    h3 { color: #f5c2e7; font-size: 1rem; margin: 1rem 0 0.3rem 0; }
    ul { list-style: none; padding: 0; }
    li { padding: 0.1rem 0; }
    li strong { color: #89b4fa; }
    .price { color: #a6e3a1; }
    .summary { color: #cdd6f4; }
    .bestfor { color: #f9e2af; }
    .usecases { color: #a6adc8; }
    a { color: #89b4fa; text-decoration: none; }
    .badge {
      display: inline-block;
      background: #313244;
      border: 1px solid #45475a;
      padding: 0.15rem 0.5rem;
      border-radius: 4px;
      font-size: 12px;
      margin-right: 0.5rem;
    }
    .badge.ai { border-color: #cba6f7; color: #cba6f7; }
    .badge.kv { border-color: #89dceb; color: #89dceb; }
    .badge.shopify { border-color: #a6e3a1; color: #a6e3a1; }
    .badges { margin-bottom: 1.5rem; }
  </style>
</head>
<body>
  <div class="url-bar">
    <div class="dots">
      <div class="dot red"></div>
      <div class="dot yellow"></div>
      <div class="dot green"></div>
    </div>
    <span class="url">https://yourstore.com/llms.txt</span>
  </div>
  <div class="badges">
    <span class="badge ai">Workers AI</span>
    <span class="badge kv">KV Cache</span>
    <span class="badge shopify">Shopify</span>
  </div>
  <h1># Summit Sprouts</h1>
  <blockquote>A family-focused ski and snow shop specializing in children's gear. This catalog is dynamically generated with real-time inventory and AI-enriched product descriptions optimized for agent consumption.</blockquote>
  <div class="meta">
    <li><strong>Generated</strong>: 2026-04-15T13:00:51.445Z</li>
    <li><strong>Total products</strong>: 6 (5 in stock, 1 out of stock)</li>
    <li><strong>Categories</strong>: toddler-skis, helmets, boots, clothing, accessories</li>
    <li><strong>Pricing</strong>: All prices in USD, tax not included</li>
  </div>
  <h2>## Products In Stock</h2>
  <h3>### Little Ripper 70 Ski Package</h3>
  <ul>
    <li><strong>Price</strong>: <span class="price">$89.99</span></li>
    <li><strong>Availability</strong>: In stock (18 available)</li>
    <li><strong>Summary</strong>: <span class="summary">Lightweight plastic skis designed for toddlers skiing for the very first time. Short enough for a 3-year-old to control on a bunny hill, with step-in bindings simple enough for parents to operate while wearing gloves.</span></li>
    <li><strong>Best for</strong>: <span class="bestfor">First-time skiers ages 2-4 who need lightweight, forgiving gear to build confidence on gentle slopes.</span></li>
    <li><strong>Use cases</strong>: <span class="usecases">first ski lesson, resort bunny hill, backyard snow play</span></li>
  </ul>
  <h3>### Snow Sprout Toddler Helmet</h3>
  <ul>
    <li><strong>Price</strong>: <span class="price">$49.99</span></li>
    <li><strong>Availability</strong>: In stock (31 available)</li>
    <li><strong>Summary</strong>: <span class="summary">A properly fitted toddler ski helmet that actually fits heads as small as 48cm — most kid helmets start at 52cm. Dial-adjust sizing means it grows with the child for 2-3 seasons.</span></li>
    <li><strong>Best for</strong>: <span class="bestfor">Toddlers and preschoolers (ages 2-5) who need a helmet that actually fits.</span></li>
  </ul>
  <h3>### Tiny Tracks Ski Boots</h3>
  <ul>
    <li><strong>Price</strong>: <span class="price">$59.99</span></li>
    <li><strong>Availability</strong>: In stock (14 available)</li>
    <li><strong>Summary</strong>: <span class="summary">Toddler ski boots with a single buckle and wide rear opening — designed so parents aren't wrestling a squirming 3-year-old's foot into a stiff boot in a parking lot.</span></li>
  </ul>
</body>
</html>

For the icon SVG: Happy for the Growth team to generate one, or I can provide a custom SVG if preferred. A shopping cart + document or text-file-with-sparkle motif would fit the template's purpose.

Let me know the preferred format/resolution for the preview image and I'll provide a final PNG, or feel free to render directly from the HTML above at 1920x1080.

- Remove PLACEHOLDER KV namespace so one-click deploy works out of the box
- Restructure README: deploy-first flow, Shopify/KV as optional next steps
- Add configurable AI_MODEL env var (no more hardcoded model ID)
- Fix stock count: report actual Shopify variant availability, not fabricated numbers
- Add Shopify pagination (up to 5,000 products across pages)
- Use waitUntil for non-blocking KV writes instead of awaiting
- Document Content-Signal header, caching behaviour, and known limitations
- Add missing bindings to package.json cloudflare metadata
- Remove broken image URL placeholders from sample catalog
@deloreyj deloreyj added the allow preview allows for preview links on forks label Apr 21, 2026
@mconroy-cf
Copy link
Copy Markdown
Author

Hey James — grabbed that visual we talked about. Shows the raw merchant data vs. what Workers AI generates side-by-side, which felt like the clearest way to land the point. Hope that helps!
Commerce LLMS

Address review feedback: simplify caching to a single KV-backed path and
swap the default enrichment model to Google Gemma 4.

- Remove in-memory fallback cache and its module-scoped globals from
  llms-txt-worker. KV is now the only cache path, which matches
  production behaviour and removes branching a template reader has to
  reason about.
- Make ENRICHMENT_CACHE a required binding on the Env interface; collapse
  the `if (env.ENRICHMENT_CACHE) else` branches in getEnrichedCatalog.
- Declare `kv_namespaces` for ENRICHMENT_CACHE in wrangler.json with a
  clearly-marked placeholder id. One-click "Deploy to Cloudflare"
  provisions the namespace automatically; manual deploys paste the id
  from `wrangler kv namespace create` into the placeholder.
- Swap DEFAULT_AI_MODEL from @cf/meta/llama-3.1-8b-instruct to
  @cf/google/gemma-4-26b-a4b-it in the enrichment module and wrangler
  defaults.
- Update README Setup section to reflect that KV is part of the deploy
  flow (not a later-optional upgrade), update the AI_MODEL config-table
  default, and note Miniflare provisions a local KV namespace for
  `npm run dev` and `npm run test`.
- Update package.json cloudflare.bindings description for AI_MODEL.
@deloreyj
Copy link
Copy Markdown
Contributor

Hey James — grabbed that visual we talked about. Shows the raw merchant data vs. what Workers AI generates side-by-side, which felt like the clearest way to land the point. Hope that helps! Commerce LLMS

Love this. Would it be possible to throw something like this on top of the API endpoint so there's a visual component to the app itself? If you look at the vite react template you can see how we can do both the api and little static react spa in the same worker: https://github.com/cloudflare/templates/tree/main/vite-react-template

matthewconroy added 3 commits April 22, 2026 12:53
Per @deloreyj's review feedback, graft the vite-react-template pattern
onto the Worker so visiting the root URL renders a visual landing page
instead of returning JSON. The SPA fetches the Worker's own endpoints
(/api/raw-catalog and /api/products) and shows the raw merchant data
next to the Workers AI-enriched output per product, making the
template self-demonstrating in the gallery.

Worker behavior is unchanged for existing API routes (/llms.txt,
/llms-full.txt, /api/products, /api/products/:slug, /api). The old
GET / JSON handler is removed (assets now own that route); its fields
are merged into /api. A new /api/raw-catalog endpoint exposes the
pre-enrichment catalog so the SPA can render the 'before' side.

- wrangler.json: add assets.directory + SPA fallback
- vite.config.ts, index.html, src/react-app/*: React 19 SPA
- tsconfig split into project references (app/worker/node)
- Tests updated: former GET / test retargeted to /api; new
  /api/raw-catalog test added. Playwright spec now checks for the
  SPA heading on / and API JSON on /api.
@mconroy-cf
Copy link
Copy Markdown
Author

I think this should cover it, right? Thanks a ton for helping me get this over the line. I really appreciate it

@mconroy-cf
Copy link
Copy Markdown
Author

Let me know if this addresses your ask. I'll follow this advice on all future template additions too! thanks!

Copy link
Copy Markdown
Contributor

@deloreyj deloreyj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

Reviewed at HEAD 9a54f14. Strong template — approving.

Checks (all green locally)

  • pnpm run check:templates — pass
  • pnpm run check:lockfiles — pass
  • pnpm run check:prettier — pass
  • tsc && vite build && wrangler deploy --dry-run — pass
  • pnpm test — 10/10 pass (ran 3x to confirm reliability)

What's good

  • README is best-in-class: deploy button at top, dash-content markers, "Who is this for", "After deploy", endpoint table, configuration table, caching explanation, Shopify integration notes, known limitations
  • 10 vitest tests + 3 Playwright E2E tests — well above the 5 minimum
  • Clean architecture: worker / enrichment / shopify / catalog / types are properly separated
  • Defensive fallbacks throughout: no AI binding → rule-based fallback, Shopify failure → sample catalog, KV write uses ctx.waitUntil
  • Hand-written FALLBACK_ENRICHMENTS make local dev demo-quality without needing AI
  • Content-Signal response header is a thoughtful agent-friendly touch
  • The bundled React + Vite SPA gives the deploy-button experience real visual punch — raw vs enriched side-by-side is a great way to communicate what Workers AI actually adds
  • @cf/google/gemma-4-26b-a4b-it is a current Workers AI model (released April 2026) — appropriate default

Minor non-blocking observations

  1. Tests take ~30s because each test triggers a real Workers AI call (KV is per-test in the miniflare pool, so the cache doesn't persist). Not blocking, but consider seeding the KV namespace in a beforeAll hook in a future revision to make the suite run in a few seconds.
  2. <REPLACE_WITH_YOUR_KV_NAMESPACE_ID> literal in wrangler.json — the Deploy to Cloudflare flow auto-provisions KV (per docs) and the README documents the manual override clearly, so this is fine. Other KV-using templates in this repo (to-do-list-kv-template, openauth-template) ship with real-looking hex IDs as their default, which is also a valid pattern. Either works.

Pending Cloudflare team

Per CONTRIBUTING.md these are not contributor-blockable:

  • cloudflare.preview_image_url — Growth team uploads
  • cloudflare.preview_icon_url — Growth team uploads
  • cloudflare.publish: true — set when ready to publish

Approving — looks ready to merge once preview assets and publish: true are in place.

@slavos1
Copy link
Copy Markdown

slavos1 commented May 3, 2026

Preview link not generated: you must be on a branch, not on a fork.
Collaborators may enable previews for this pull request by attaching the allow preview label.
If you are already a collaborator, please create a branch rather than forking.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

allow preview allows for preview links on forks

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants