Skip to content

Structural / providers

  • Purpose: root provider. Boots @storesynk/core (creates the Storefront client, installs the window.Shopify.actions standard-actions runtime + cart store, fetches the shop’s moneyFormat/currency), and provides storeContext to the whole subtree.
  • Attributes:
    • token : string = "" - Storefront API public access token.
    • domain : string = "" - store domain (e.g. your-store.myshopify.com).
    • country : string = "" - optional ISO country for @inContext pricing. The initial market; a persisted buyer choice (see persist-locale) wins over it at bootstrap, and the Markets pickers switch it at runtime.
    • language : string = "" - optional language for @inContext. Same persisted-choice precedence as country.
    • persist-locale : "cookie" | "localStorage" | "none" = "cookie" - (Markets) how the buyer’s country/language choice persists across visits. cookie (default) writes storesynk_locale=<COUNTRY>/<LANGUAGE> - server-readable, so SSR routes can render the buyer’s market (the buyer-market SSR recipe depends on it); localStorage uses the key _storesynk-locale (client-only opt-out); none disables persistence. A persisted choice overrides the country/language attributes at boot. (The default was localStorage before this version.)
    • track-events - (optional pixels module) boolean; presence enables the analytics module (a separate pixels.js chunk, loaded on demand). Fires standard storefront events to any onsite pixels it detects (Meta, GA, TikTok, Pinterest, Reddit, Klaviyo).
    • track-client : string - (pixels) comma-separated allow-list pinning which client pixels run (meta, ga, tiktok, pinterest, reddit, klaviyo), e.g. track-client="meta,ga". Absent = all auto-detected pixels run. Use it when you already fire events through some pixels yourself (e.g. via GTM) and want to avoid double-counting. (The old track-exclude deny-list was removed - replace it with a track-client allow-list naming the providers you do want; without one, every detected provider runs.)
    • require-consent - (pixels) boolean; gate tracking on the visitor’s consent.
    • track-server : string - (pixels) opt into server-side forwarding to the Storesynk relay (the paid pipeline, which authors the real provider calls from the shop’s stored config). The value is a comma-separated provider list declaring which providers are handled server-side (track-server="meta,ga"); presence of the attribute (even bare) enables the relay beacon. Meta dedups client+server via the shared event id, so it may run both ways; GA cannot dedup, so ga in track-server always drops its client pixel (server wins - GA never runs both ways). Requires track-events. NOT a URL - the endpoint override moved to relay-endpoint.
    • relay-endpoint : string - (pixels) override the default relay endpoint with a self-hosted URL (e.g. relay-endpoint="https://…/collect"); only meaningful alongside track-server.
    • customer-client-id : string - (customer-accounts module) the Shopify Customer Account API OAuth client id (shp_…). Presence enables a separate customer.js chunk (loaded on demand): login, the show-customer-* displayers, buyer-identified carts, and <storesynk-customer-pricing>. Absent = no customer module at all.
    • customer-redirect-uri : string - (customer) the OAuth callback URL (must be allow-listed in the app’s Customer Account API config). Defaults to the current page’s origin + pathname.
    • customer-account-pages : "shopify" | "custom" = "shopify" - (customer) where <account-link> sends the customer: shopify (default) = the hosted customer-account portal (derived from the shop’s OAuth discovery documents); custom = the site’s own pages at customer-account-url.
    • customer-account-url : string - (customer) the account destination URL. Required for customer-account-pages="custom"; ignored in shopify mode.
  • Required ancestor / context: none - the root. Provides storeContext. One per page/tree; never nest (rule SSK-104).
  • Children: wrap your entire storefront markup inside it.
  • Events: consumes (catches + stops) sf:country-change / sf:language-change from the Markets pickers and owns the switch; emits sf:locale-change (bubbles, composed; detail = { country, language }) after a completed switch. With the customer module enabled, also emits sf:customer-change (bubbles, composed; detail = { customer }, null on logout) whenever the login state settles.
  • State it sets: [ss-loading] (+ aria-busy="true") on itself during a locale switch; [ss-logged-in] while a customer session is logged in (the CSS hook for auth-gated UI - client and SSR); otherwise none. (domain/token may be injected by a script before registration - see SSK-206.)
  • Purpose: product provider + state owner. Fetches a product by handle or product-id, or is fed already-fetched data by a list/search container via provideProduct() (in which case it carries no handle/product-id - correct). Owns runtime selection state (selectedVariant, selectedOptions, activeMediaIndex, quantity) and catches child sf:* events to update it.
  • Attributes:
    • handle : string = "" - product handle to fetch. Wins if both are set.
    • product-id : string = "" - numeric id or gid://shopify/Product/… to fetch.
    • revalidate - boolean; only meaningful after SSR payload adoption. After hydrating from an embedded payload, background-refetch the product and re-provide context only if it changed, preserving the user’s current selection. Keeps static builds fresh without a rebuild; a no-op on a normal client-fetched product. (See Server-side rendering & adoption.)
    • sync-url - boolean; mirror the selected option(s) into the page URL. On every sf:option-change the element writes the option name/value into URL query params via history.replaceState (no navigation, no history spam), and it adopts option state back from the URL on load and on popstate (browser back/forward) - with sync-url set, the URL wins over the default-variant selection. Off by default (nothing touches the URL). Use it so a chosen variant is shareable/bookmarkable and survives back/forward.
  • Required ancestor / context: storesynk-store (consumes storeContext). Provides productContext. Never nest inside another storesynk-product (SSK-103).
  • Children: any product displayers/controls. As a list/search item template it must hold exactly one product subtree.
  • Events: consumes (catches + stops) sf:option-change, sf:media-select, sf:quantity-change; emits sf:variant-change (bubbles, composed) on selection change.
  • State it sets: [ss-out-of-stock] / [ss-unavailable] (mutually exclusive) on itself, tracking the selected variant’s availability - the passive sold-out hook for cards/grids with no buy button (storesynk-product[ss-out-of-stock] img { filter: grayscale(1) }). Set by the SSR transform too, so built HTML carries it pre-JS. Never add a hidden add-to-cart to a card just to read its state - this hook replaces that.
  • Purpose: cart drawer shell. Registers the openCart standard-action override so the global open action opens this drawer. Owns open/close and reflects cart emptiness. While open it is a modal dialog (dialog archetype): the engine sets role="dialog" + aria-modal + a fallback aria-label (“Shopping cart” - author your own to override), moves focus in, traps Tab inside, closes on Escape, and returns focus to the opener on close. All of that applies only between open and close - the same tag used as an always-visible cart page (never opened as a drawer) keeps plain semantics. Don’t hand-roll Escape/focus handling. Closed-state CSS must include visibility: hidden if it only transforms the drawer off-screen (off-screen buttons are still tabbable).
  • Attributes: open-on-add : boolean - opens the drawer after a successful add-to-cart (listens for the post-success sf:add-to-cart event; a failed add never pops it). This is the DEFAULT behavior - a bare <storesynk-cart> already opens on add, so you rarely need the attribute. The site-wide opt-out is {"cart":{"openOnAdd":false}} in the data-storesynk-config block; open-on-add on the tag only exists to force the drawer back on over that opt-out. Omit both on a <storesynk-cart> used as an always-visible cart page only if a config opt-out is in play - otherwise it opens on add regardless. close-on-outside : boolean - a pointerdown outside the open drawer closes it (style your own scrim; clicks on it count as outside, no handler needed).
  • Required ancestor / context: storesynk-store (consumes storeContext).
  • Children: your drawer markup; typically a cart-line-list, totals, and checkout-link.
  • Events: none (open/close via the global action / close-cart / Escape).
  • State it sets: [ss-open] and [ss-empty] (no lines, or no cart created yet) on itself - [ss-empty] is the styling hook for a “your cart is empty” message (storesynk-cart[ss-empty] .empty-message { display:block }); never use an action tag as that message container.
  • Purpose: renders the cart’s lines. Captures its first cart-line child as a template, removes it, then clones it per line (driven by core’s richCart observable) and feeds each clone via provideLine().
  • Attributes: none.
  • Required ancestor / context: storesynk-store (consumes storeContext); typically placed inside storesynk-cart.
  • Children / template rule: TEMPLATE-CLONE - the first cart-line child is the template; missing → renders nothing (SSK-107).
  • Events: none.
  • State it sets: none.
  • Purpose: one cart line; provides cartLineContext to the show-line-* displayers and exposes a .line property the line controls read.
  • Attributes: none.
  • Required ancestor / context: cart-line-list (SSK-106). Provides cartLineContext.
  • Children: show-line-* displayers and increase-line-quantity / decrease-line-quantity / remove-cart-line controls.
  • Events: none.
  • State it sets: none.

Imported from the Storesynk skill v0.1.0 (references/component-reference.md). To change this page, change it there.