Noodle Seed
Guides

Noodle Seed for Shopify

Deploy one reusable Shopify search, focused conversational-commerce, store knowledge, and checkout solution for any Shopify store.

This guide deploys one reusable Noodle Seed solution for Shopify businesses. Buyers can clarify what they want, review up to three live matches, inspect one selected product, ask about published policies, pages, and articles in a public embedded assistant, and continue to Shopify's hosted checkout. Products and published store records come directly from the Storefront GraphQL API; natural-language policy and FAQ answers use Shopify's standard Storefront MCP server through a frozen Noodle connector, with published Storefront content as a deterministic fallback when that FAQ source has no answer. There is no product database, vector index, synchronization job, transparent MCP proxy, or tenant source fork.

The interface is deliberately chat-first. It never renders an entire storefront inside the conversation. An ambiguous request gets one short natural-language question with no widget; a search gets at most three recommendation cards; details replace those cards for the selected product; and a one-item checkout summary appears only after an explicit choice. The final Continue to Shopify action calls one app-only cartCreate mutation. Shopify returns the final checkoutUrl, and the host opens that exact allowlisted URL.

What ships out of the box

Shopify-native natural-language relevance with partial-prefix matching, one bounded zero-result rewrite, availability controls, price sorting, natural conversational clarification, three-result recommendations, selected-product detail, live policy/page/article knowledge, one composed ask_store path with a Noodle-owned answer widget, a public shopping assistant, variants and cursor pagination, one-item review, one confirmed cartCreate, Shopify-authoritative totals and availability, and exact-origin checkout handoff.

Architecture

Loading diagram…

This boundary matters. The widget never receives the Storefront access token, never calls Shopify directly, and never receives Shopify's cart ID. Noodle resolves the token server-side through the credential broker.

Before you start

You need:

  • A Shopify store where you can install sales channels and manage Storefront API permissions.
  • At least one active product with a purchasable variant published to the Headless sales channel.
  • Node.js 24 LTS or newer.
  • The Noodle Seed CLI and a Noodle account for hosted deployment. Local development does not require an account.
  • The store's canonical https://<shop>.myshopify.com origin. Do not use the admin URL.

The finished implementation is the Shopify Storefront example. Use it as the source of truth while following this guide.

1. Create a Shopify Headless storefront

In Shopify Admin:

  1. Open Apps and sales channels and install the Headless sales channel.
  2. Open Headless, then select Add storefront or Create storefront.
  3. Give the storefront a name that identifies this MCP App and environment, such as MCP checkout - dev.
  4. Open Storefront API permissions and grant only unauthenticated_read_product_listings, unauthenticated_read_content, unauthenticated_read_checkouts, and unauthenticated_write_checkouts for this example.
  5. Publish the products you want buyers to see to the Headless sales channel.

Shopify creates public and private Storefront access tokens for the storefront. Copy the private Storefront access token for this server-side integration.

Keep the private token server-side

The example sends Shopify-Storefront-Private-Token, Shopify's server-side authentication header. Never put this token in browser JavaScript, widget props, tool results, logs, or source control. Enter it only through Noodle's managed-secret prompt.

For buyer-initiated server traffic, Shopify also recommends forwarding Shopify-Storefront-Buyer-IP so its bot protection and throttling can distinguish buyers. This reusable baseline does not forward network identifiers into merchant connectors. Load-test against expected traffic before opening a high-volume store; add buyer-IP forwarding only through a dedicated, privacy-reviewed trusted-proxy boundary.

Official Shopify setup references:

2. Start the Noodle project

Create a widget project and install its dependencies:

noodle init shopify-checkout --template widget
cd shopify-checkout
npm install

Copy the files from the complete example into the matching project paths:

src/
  helpers.ts
  server.ts
  shopify-assistant.ts
  shopify-config.ts
  shopify-content-responses.ts
  shopify-mcp-responses.ts
  shopify-queries.ts
  shopify-recommendation-responses.ts
  shopify-responses.ts
  views/
    product-detail.tsx
    product-recommendations.tsx
    store-answer.tsx
    shopify-mini.css
    shopify-widget-model.ts
test/
  server.test.ts
  shopify-mini-widgets.test.tsx

The example uses idiomatic TypeScript authoring. Do not copy or hand-edit compiled manifest or connector artifact files.

3. Bind one merchant environment

The reusable source names an operator-managed variable:

src/shopify-config.ts
import { variable } from '@noodleseed/one';

export const SHOPIFY_STORE_ORIGIN = variable('SHOPIFY_STORE_ORIGIN');
export const SHOPIFY_STOREFRONT_MCP_ENDPOINT = variable('SHOPIFY_STOREFRONT_MCP_ENDPOINT');
export const SHOPIFY_STOREFRONT_API_VERSION = '2026-07';

Do not edit that file per store. Link a merchant environment and bind its exact origin:

noodle link --org <org> --app shopify --env dev
noodle variables set SHOPIFY_STORE_ORIGIN --scope env \
  --value https://your-shop.myshopify.com
noodle variables set SHOPIFY_STOREFRONT_MCP_ENDPOINT --scope env \
  --value https://your-shop.myshopify.com/api/mcp

Use one canonical bare HTTPS origin with no path, trailing slash, credentials, or wildcard. The same managed value drives:

  • The connector baseUrl.
  • The connector SSRF allowedOrigins list.
  • The separately bound Storefront MCP endpoint must resolve inside that exact origin.
  • The app's checkout handoff.allowedDomains list.
  • The embedded assistant's browser-origin allowlist.
  • The widget's final checkout URL validation.

Noodle binds that value across every runtime authority and widget projection at deployment. Missing or malformed values fail closed. This is what lets one application serve many Shopify businesses without weakening the SSRF, embed, or external-link boundary.

The example pins Shopify Storefront API 2026-07 so a deploy does not change behavior unexpectedly. Shopify releases stable API versions quarterly. Review the Storefront changelog and update the pinned version and tests deliberately rather than switching to an unversioned URL.

If your product images come from another origin, add only that exact origin to the widget's csp.resourceDomains. The default example allows https://cdn.shopify.com. Its csp.connectDomains remains empty because the widget makes no direct network requests.

4. Understand the Shopify connector

The server declares one HTTP connector with brokered API-key authentication:

src/server.ts
const shopifyStorefront = connector('shopify_storefront')
  .version('1.0.0')
  .http({
    baseUrl: SHOPIFY_STORE_ORIGIN,
    allowedOrigins: [SHOPIFY_STORE_ORIGIN],
    auth: {
      kind: 'apiKey',
      header: 'Shopify-Storefront-Private-Token',
      secret: secret('SHOPIFY_STOREFRONT_PRIVATE_TOKEN'),
    },
    operations: {
      search_products: { /* POST Shopify storefront search */ },
      get_recommendations: { /* POST nodes(ids:) for the final one to three products */ },
      get_product: { /* POST one product(handle:) detail query */ },
      get_shop_information: { /* POST live shop and policy fields */ },
      search_store_content: { /* POST live page and article search */ },
      create_cart: { /* POST one cartCreate mutation */ },
    },
  });

All operations POST to /api/2026-07/graphql.json. They return the raw GraphQL envelope to a sandboxed compute connector, which emits only the typed fields the tools need.

The same source also declares a headless MCP connector for Shopify's standard Storefront server:

src/server.ts
const shopifyStorefrontMcp = connector('shopify_storefront_mcp')
  .version('1.0.0')
  .mcp({
    endpoint: SHOPIFY_STOREFRONT_MCP_ENDPOINT,
    allowedOrigins: [SHOPIFY_STORE_ORIGIN],
    operations: {
      store_answer: {
        type: 'read',
        tool: 'search_shop_policies_and_faqs',
        result: 'text',
        input: z.object({ query: z.string(), context: z.string().optional() }),
      },
    },
  });

Noodle never runs tools/list during a shopper request and never forwards Shopify tool metadata, _meta, resources, prompts, or UI. The published ask_store tool calls the frozen operation, bounds and normalizes its text, and attaches a Noodle-owned React view. If the normalized result is not_found, the recorded server flow searches published pages and articles and returns at most three sources through the same tool and view. An explicit guide request selects that content path directly. Text-only MCP clients receive the same bounded result without the view; source fields keep FAQ answers and published content distinct.

Product search, detail, and policy knowledge

search_products sends Shopify's bounded Storefront search query with types: [PRODUCT] and prefix: LAST. It exposes Shopify's native relevance or price ordering, ascending/descending direction, and unavailable-item policy. Shopify returns totalCount, merchant-configured productFilters, at most 20 products, and at most 20 variants per result. Each product also reports whether its returned variants are complete, so the assistant cannot mistake a high-variant product preview for the full option set. Each variant includes the merchandise ID required by cartCreate, availability, selected options, current price, and compare-at price.

get_product fetches up to 100 variants plus completeness metadata for one returned handle. For a named comparison, the assistant fetches each product and answers in concise prose organized by the requested criteria, with links. It does not render recommendation cards: those cards help select an item, but do not communicate differences well enough to constitute a comparison. get_recommendations re-fetches only the final one to three Shopify product IDs after all headless ranking work finishes, so intermediate pages never create transcript widgets and stale model-provided display data never enters the view. For a top-N request that Shopify's selected order proves, the assistant requests exactly N products and stops after that page. It does not call get_product for a routine recommendation list because get_recommendations already re-fetches the finalists. Full detail and pagination are reserved for named comparisons, selected products, or client-side constraints that unseen matches could change. get_store_information remains a lower-level direct-MCP read for the live shop profile, shipping countries, contact information, and published privacy, refund, shipping, and terms policies. The embedded assistant receives one knowledge tool: ask_store. Its store_information source reads the shop profile; its policy source selects exactly one canonical policy kind (contact, privacy, refund, shipping, or terms); its answer source checks the curated Storefront MCP FAQ operation first and conditionally calls the internal search_store_content operation only after not_found; its published_guides source searches published Shopify pages and blog articles directly for explicit guide, sizing, care, brand, and other written-material requests. The separate outward search_published_guides tool remains available to direct MCP clients that need the lower-level paginated result. Both content normalizers strip HTML and bound text before model exposure.

The cursor belongs inside the GraphQL variables object:

variables: {
  query: '${args.query}',
  first: '${args.first}',
  after: '${args.after}',
  sortKey: '${args.sortKey}',
  reverse: '${args.reverse}',
  unavailableProducts: '${args.unavailableProducts}',
}

Do not enable automatic HTTP query-string pagination for this operation. Shopify's endCursor must be passed back as the GraphQL after variable in the next request body.

Checkout mutation

create_cart receives only the explicitly reviewed merchandise line and quantity (the connector contract also permits an optional bounded note for extensions):

mutation CreateCheckout($input: CartInput!) {
  cartCreate(input: $input) {
    cart {
      checkoutUrl
      cost {
        subtotalAmount { amount currencyCode }
        totalAmount { amount currencyCode }
      }
    }
    userErrors { code field message }
    warnings { code message target }
  }
}

Notice that the selection does not request cart.id. A Shopify cart ID contains a key that must be treated as a secret. This app does not need it because it creates the cart once and immediately hands the buyer to Shopify checkout.

Shopify's cartCreate reference defines the input, checkoutUrl, userErrors, and warnings used here.

5. Understand the tool surface

The raw MCP server exposes seven reads and one confirmed widget action. The embedded assistant deliberately receives five reads: every merchant-knowledge question uses the composed ask_store path instead of choosing between adjacent tools. Three reads have focused views; the knowledge view preserves canonical-policy, FAQ, and published-content source boundaries while lower-level content search remains headless:

ToolVisibilityResponsibility
search_productsModel and appSearch and paginate headlessly with Shopify-native ordering.
show_product_recommendationsModel and appRe-fetch one to three final product IDs and render exactly one recommendation view.
get_productModel and appVerify one product's bounded detail headlessly.
show_productModel and appRe-fetch and render one named or shopper-selected product.
ask_storeMCP model and embedded assistantRead the shop profile, select one canonical policy, check FAQ then conditionally fall back to published content, or directly search guides; render the bounded source-specific result.
get_store_informationDirect MCP modelRead the shop profile and published policies as a lower-level primitive; omitted from the embedded assistant.
search_published_guidesDirect MCP modelSearch and paginate published pages and articles as a lower-level primitive; omitted from the embedded assistant.
create_checkoutApp onlyAfter explicit confirmation, create one Shopify cart and return the checkout URL.

create_checkout uses visibility: ['app'] so the selected-product view owns its one-line checkout request. It still crosses the same runtime policy and connector boundary and declares exact-action confirmation before external mutation. The five assistant reads form its closed capability allowlist; adding another server tool does not expose it automatically.

Every list is bounded, every tool has a human-readable title and annotations, and tool output is normalized instead of forwarding raw Shopify payloads. See Designing tools for agents for the rules enforced by noodle check.

6. Understand progressive checkout

The product-detail view stores only the selected variant and quantity with useViewState:

const [variantId, setVariantId] = useViewState('shopify_selected_variant', initialVariantId);
const [quantity, setQuantity] = useViewState('shopify_quantity', 1);

This state is a buyer-interface convenience, not a cart session. Choose this item first reveals a compact one-item summary. Only Continue to Shopify calls create_checkout; the host then presents the tool's exact-action confirmation. Do not put access tokens, checkout URLs, Shopify cart IDs, customer credentials, or payment data in view state. The displayed multiplication is an estimate. Shopify revalidates merchandise, quantity, stock, contextual price, discounts, tax, shipping, and the final total.

7. Configure Shopify and the embedded assistant

For local development, put the merchant bindings in a project-root .env excluded from version control:

.env
SHOPIFY_STORE_ORIGIN=https://your-shop.myshopify.com
SHOPIFY_STOREFRONT_MCP_ENDPOINT=https://your-shop.myshopify.com/api/mcp
SHOPIFY_STOREFRONT_PRIVATE_TOKEN=replace-with-your-private-storefront-token

Confirm .env and .env.noodle are ignored before saving the value. Never put the token in server.ts, a test fixture, a widget property, a tool result, or a command-line argument.

For the hosted target, link the project and enter the token through the managed-secret prompt:

noodle login
noodle link --org <org> --app shopify --env prod
noodle variables set SHOPIFY_STORE_ORIGIN --scope env \
  --value https://your-shop.myshopify.com
noodle variables set SHOPIFY_STOREFRONT_MCP_ENDPOINT --scope env \
  --value https://your-shop.myshopify.com/api/mcp
noodle secrets set SHOPIFY_STOREFRONT_PRIVATE_TOKEN --scope env

Noodle resolves these names for the linked organization, app, and environment. The reusable source and compiled artifact contain references, never credential values. The source uses noodleManaged(), so the merchant does not configure a provider endpoint, model identifier, or model secret. Hosted inference fails closed until a Noodle operator enrolls the exact org/app/environment; enrollment is operator state and does not fork or modify the reusable source.

8. Test locally

Run the deterministic tests and static checks first:

pnpm test
noodle validate
noodle check --min-severity warn
noodle check --target chatgpt

Then start the local runtime and Devtools:

noodle dev

Open the Devtools URL printed by the command. Call search_products with a Shopify search string:

{
  "query": "boots",
  "first": 12,
  "unavailableProducts": "HIDE"
}

Verify all of the following before deploying:

  • “What are the best items you can find me?” asks one natural-language question, calls no tool, and shows no widget.
  • “Find the best in-stock options under my budget” asks for the product type and numeric maximum budget in one question; “my budget” is never treated as an amount.
  • A short “well?” follow-up restates only the missing information and never starts a generic priority loop.
  • The recommendation view shows at most three products and contains no search form, sort/filter controls, catalog navigation, cart, or checkout.
  • Any number of headless search/pagination calls produces exactly one final recommendation widget.
  • A Shopify-sorted top-three request fetches three results, does not paginate, and does not make redundant finalist detail calls.
  • A named comparison fetches each product and explains the requested differences in prose; it never substitutes ordinary recommendation cards for the comparison.
  • Clarification stays in prose. Recommendation and single-product views have only their documented one-sentence action cue, never a summary, tool narration, or repeated view content.
  • Products and variants match the live Shopify catalog.
  • Shopify-native result counts, natural-language relevance, partial-prefix matching, low/high price sorting, and in-stock filtering match the storefront.
  • Product and content discovery make at most two search calls: the original query and, only after no relevant result, one materially different rewrite. They never repeat a query or relax a hard constraint.
  • An exact product name is never broadened; no result is preferable to an irrelevant substitute or a recommendation widget.
  • A cheapest/most-expensive answer remains correct when more products exist than one page; the assistant follows the cursor whenever ordering does not already prove the answer.
  • Details replaces the recommendation list with the one live selected product rather than appending a storefront.
  • A high-variant product is never described as complete when the returned variant page is partial.
  • Published shipping, refund, privacy, and terms answers call ask_store once with source: "policy" plus the exact policy kind, match Shopify, and report missing policies as missing without consulting FAQ or content search.
  • Shopify FAQ sentinels such as [], {}, null, or whitespace normalize to not_found, trigger the one conditional published-content fallback, and never appear as a successful answer or raw sentinel.
  • Natural-language FAQs and explicit guide, page, and article searches both use ask_store in the embedded assistant. The former uses source: "answer"; the latter uses source: "published_guides". Missing evidence is reported as missing.
  • A harmless general educational question uses no tool and clearly distinguishes general knowledge from facts about this merchant.
  • The embedded assistant is accepted only from the exact configured storefront origin.
  • An unpublished product does not appear.
  • The assistant follows the next GraphQL cursor internally when ranking completeness requires it; pagination controls never clutter the view.
  • Unavailable variants cannot be chosen.
  • Product search, detail, selection, and quantity changes do not make a Shopify cart request.
  • Choose this item reveals only the one-item review; Continue to Shopify makes exactly one create_checkout tool call.
  • The entire widget background follows the host in light and dark mode with no light iframe band around a dark card.
  • The inline view remains readable and operable at 280px without nested vertical scrolling.
  • Shopify errors prevent handoff and remain understandable.
  • A successful call opens an HTTPS URL on the exact configured Shopify origin.
  • Tool results, logs, view state, and widget payloads contain no Shopify cart ID or token.

Answer-quality golden prompts

Run these against the embedded assistant, then verify every product, amount, option, availability statement, and policy/content claim against the live Shopify source:

ShapePromptPass condition
Ambiguous discovery“What are the best items you can find me?”Asks one concise natural-language question, calls no tool, shows no widget or checkout UI, and waits.
Missing budget“Find the best in-stock options under my budget.”Asks once for what the shopper is buying and the numeric maximum budget; after the answer, searches without another clarification.
Recovery“well?” after the missing-budget questionRestates the one missing detail naturally; never emits a priority menu, internal directive, or repeated widget.
Direct ranking“What are the three cheapest products in stock?”Uses ascending Shopify price order, excludes unavailable items, and states the ranking basis.
Fuzzy constraints“I need a blue gift under $75.”Applies hard constraints first, labels near misses, and does not infer color from a title or tag.
Semantic discovery“I am new to snowboarding and want something easy to learn on.”Starts with one concise concept-preserving query; after no relevant match it may try one materially different rewrite, never more than two searches total, and renders at most three relevant finalists once.
Comparison“Compare A and B, including every available option.”Fetches both details and discloses any partial variant list.
Exact-name negative“Do you carry the exact Orbital Noodle Seed Quantum Teapot?”Searches the exact name once, says no when it is absent, never broadens the name, and renders no substitute or widget.
Policy edge“Can I return a used item after 60 days?”Quotes or paraphrases only a published policy and says when the store has not published an answer.
Canonical policy“What is your published shipping policy?”Calls ask_store once with the shipping policy source and does not call FAQ or content search when the canonical field answers it.
Missing FAQ“Do you offer repairs?”Calls ask_store once; an empty FAQ sentinel triggers the published-content fallback, and an empty fallback becomes a neutral unpublished-answer state rather than ok.
Store knowledge“What does your size guide recommend?”Calls ask_store once with the guide source, names returned pages/articles, and distinguishes them from a formal policy.
General education“In general, what does water-resistant mean?”Answers without tools and labels the answer as general, not a claim about this merchant or its products.
Follow-up“Only show the available ones, cheapest first.”Preserves the subject, switches to HIDE plus ascending PRICE, and does not restart with an unrelated query.

Also score visual restraint: no answer may render more than three recommendation cards; no recommendation view may contain global search, sort, filter, cart, or checkout controls; product detail must replace the list; and checkout review must appear only after an explicit selection. Test both host themes. A factually correct answer still fails if its view becomes a miniature storefront, if intermediate search pages create extra widgets, or if the prose repeats information already visible in the widget.

Score each answer on factual grounding, constraint satisfaction, ranking completeness, uncertainty discipline, useful comparison, source discipline, and a clear next step. A fluent answer with one unsupported product fact fails. A correct answer that hides a hard-constraint miss also fails.

After the manual matrix, run the repeatable live subset. It creates and revokes a temporary assistant client using the current CLI login and emits only a bounded JSON summary:

pnpm smoke:shopify:semantic -- \
  --service <deployed-noodle-service-url> \
  --origin https://your-shop.myshopify.com \
  --org <org> --app shopify --env dev

Pass --expectations <ignored-private-json> to layer exact live product-title expectations onto the committed generic cases without committing merchant data to the repository. The expectations file itself contains live merchant product titles, so keep it private. Pass --client-credentials-file <0600-json> when an operator has already provisioned a target-bound assistant client and the smoke must not create one.

shopify-live-expectations.json (keep outside the repository)
{
  "version": 1,
  "cases": [
    {
      "id": "semantic_product_discovery",
      "expect": { "expectedProductTitles": ["Your known live product title"] }
    }
  ]
}

9. Deploy safely

Start with owner-only access while you run the live-store smoke test:

noodle deploy --access owner-only
noodle open

The deploy output prints a public assistant embed ID. Install its one-line loader in the active Shopify theme so it appears on every storefront page:

<script src="https://cloud.noodleseed.dev/v1/assistant/embed.js"
        data-embed-id="pub_replace_with_deploy_output" async></script>

In Shopify Admin, open Online Store → Themes, open the active theme's action menu, choose Edit code, open layout/theme.liquid, and paste the exact deploy-generated snippet immediately before </body>. Save the file, reload the storefront, and verify the launcher opens Noodle Seed for Shopify. The embed ID is a public surface identifier, not a credential; the private Storefront token remains only in Noodle managed secrets. Repeat this small installation step whenever the merchant publishes a different theme.

Repeat the local verification against the deployed endpoint. Confirm the hosted target has the correct environment-scoped secret and that Shopify attributes the checkout to the intended Headless channel.

Before changing the deployment to public consumer access, review expected traffic, Storefront API rate behavior, abuse controls, product visibility, fulfillment rules, returns, privacy disclosures, and the host's app-directory requirements. Checkout still happens on Shopify, so the MCP App must never collect card or payment details.

Error handling you must keep

Shopify GraphQL can return HTTP 200 with a top-level errors array. A successful HTTP status is not enough. The example normalizer also checks:

  • Missing or malformed product data.
  • cartCreate.userErrors such as an invalid or unavailable merchandise line.
  • cartCreate.warnings such as insufficient stock or a reduced quantity.
  • A missing or invalid checkoutUrl.
  • A checkout URL whose origin differs from the configured store.

Keep these checks if you customize the output. Do not render identifier-dependent actions from a pending, errored, or malformed initial tool result.

Troubleshooting

SymptomCheck
Shopify returns 401, 423, or an authentication errorConfirm you copied the private token from the Headless storefront and use Shopify-Storefront-Private-Token. Re-enter the managed secret instead of logging it. Development stores remain password-protected in the browser, but private server authentication is separate from the storefront password.
Product list is emptyPublish the products and variants to the Headless sales channel, confirm they are active, and review Shopify Search & Discovery configuration.
GraphQL says access is deniedReview the storefront's Storefront API permissions and make sure the query has not been expanded beyond them.
Search works but images are blockedAdd the image CDN's exact HTTPS origin to csp.resourceDomains; do not add a wildcard.
Hosted assistant works but no launcher appears in ShopifyConfirm the exact deploy-generated embed.js snippet is saved immediately before </body> in the active theme's layout/theme.liquid. Reinstall it after publishing a different theme.
Checkout returns a user errorShow the normalized code/message, keep the buyer in the one-item review, and let them change the selected variant or quantity.
Checkout URL is rejectedConfirm SHOPIFY_STORE_ORIGIN is the canonical myshopify.com origin returned by checkout.
Totals differ from the widget estimateUse Shopify's mutation/checkout total. The one-item multiplication is only a display estimate.
A shopper wants several productsKeep the chat view focused; hand off the selected item to Shopify and let Shopify own multi-item cart management.

Migrating from a synchronized storefront app

When replacing an older app that synchronized products into its own database, remove the synchronization assumptions instead of rebuilding them in Noodle:

Older patternThis guide
Product records copied into an app databaseLive Storefront search and product(handle:) queries on demand.
Database product IDShopify product GID for display identity.
Database variant/SKU recordShopify variant GID as merchandiseId.
Search index/filter stateShopify GraphQL endCursor, totalCount, and merchant productFilters.
Copied FAQ/policy indexLive published Shopify policies, pages, and articles, normalized to bounded plain text.
Server-side session cart created while browsingNo cart while browsing; only selected variant and quantity live in widget state.
Incremental cart mutationsOne cartCreate when checkout begins.
Stored cart ID used to resumeNo cart ID requested, returned, or persisted.

Reconcile old filters and search behavior with Shopify Search & Discovery and Storefront search syntax. Add semantic indexing only after merchant evidence shows Shopify's native catalog search is insufficient; keep Shopify identity, publication, price, and availability authoritative either way.

When you need cart resume later

Do not move Shopify's cart ID through the model, widget, view state, ordinary Noodle state handles, or tool results. The ID includes a secret key. A resumable design needs an encrypted server-side cart vault keyed by an opaque, non-secret handle, plus tenant/user ownership checks, expiry, redaction, and lifecycle cleanup.

Until that dedicated boundary exists, keep this app on the selected-item plus one-cartCreate design.

Next steps

On this page