Bolder — JS API

Integration guide and core library reference · Components & modules · Liquid

Bolder Headless Storefront — Integration Guide

A guide to embedding Bolder shopping functionality into any website without a Bolder-hosted theme.

Three-doc structure:

Table of Contents

  1. Overview
  2. Quick Start — Script Tag
  3. Quick Start — npm Package
  4. createStorefront(options)
  5. Emitter
  6. Config Reference
  7. Pre-load Queue Pattern
  8. Cart

- Cross-origin cart identity

  1. Search
  2. Forms
  3. Checkout

- Embedded mode - Hosted redirect mode - Return detection - Shopping beacon integration

  1. Bundled UI components

Overview

Bolder Headless lets you embed cart and other storefront functionality into any page — a custom HTML site, a framework-based SPA, a landing page — without building a full Liquid theme.

There are two integration paths:

PathUse when…
Script tag (storefront.umd.cjs)No build tooling; drop a <script> into any HTML page
npm package (@bolder/storefront)Using Vite, Webpack, or another bundler; want tree-shaking

Both paths expose the same JavaScript API. The script-tag path auto-initialises window.Bolder; the npm path exports ES modules you compose yourself.


Quick Start — Script Tag

Copy this loader snippet into your site's <head> or before </body>. It loads storefront.umd.cjs asynchronously and exposes Bolder.ready() so your code can queue up before the script has finished loading — no separate data-* tag or queue-shim tag needed, it's one block:

Pass your shop subdomain and (optionally) a 6th argument to override apiBase — omit it in production, where the SDK derives the API URL from shop itself:

<script>
(function(b,o,l,d,e,r){
b.Bolder=b.Bolder||{};b.Bolder.ready=b.Bolder.ready||function(n){
(b.Bolder.q=b.Bolder.q||[]).push(n)};var s=o.createElement(l);
s.src=e;s.async=1;s.dataset.shop=d;r&&(s.dataset.apiBase=r);
o.head.appendChild(s)})(window,document,'script','acme',
'https://assets.bolder.run/storefront.umd.cjs?17');

Bolder.ready(function(storefront) {
  storefront.cart.on('updated', function() {
    document.querySelector('#cart-count').textContent = storefront.cart.units
  })
})
</script>

The loader's positional arguments (after window/document/'script') are shop, src (the SDK URL), and an optional 6th apiBase override. They end up as data-shop/data-api-base on the dynamically-created script tag, which is what createStorefront() reads internally to auto-init. You never write those attributes by hand.

If you need to initialise manually with options the loader doesn't expose, skip the loader and call createStorefront() yourself once the script has loaded — see createStorefront(options).


Quick Start — npm Package

npm install @bolder/storefront
import { createStorefront } from '@bolder/storefront'

const storefront = createStorefront({ shop: 'acme' })

storefront.cart.on('updated', function() {
  document.querySelector('#cart-count').textContent = storefront.cart.units
})

createStorefront(options)

Factory function that wires up the cart and the shared context object used by components.

import { createStorefront } from '@bolder/storefront'

const storefront = createStorefront({
  shop:        'acme',          // shop subdomain (required unless apiBase is set)
  apiBase:     'https://...',   // override API base URL (optional)
  locale:      'es-CL',         // locale hint (default: 'es-CL')
  currency:    'CLP',           // currency hint (optional)
  onBeforeAdd: function(proceed) { /* gate for add-to-cart */ proceed() },
  onError:     function(msg)    { /* custom error handler */ },
})

Return value

PropertyTypeDescription
cartCartCart instance — see the Cart section in the Core API reference.
configObjectResolved configuration object.
sharedObjectContext object threaded into component factories.
mount(Factory, target, data?, opts?)FunctionMount a component factory onto a DOM element.

Config priority

Options are merged in this order (highest priority first):

  1. Explicit options passed to createStorefront()
  2. window.__BOLDER_CONFIG__ global object
  3. data-* attributes on the <script> tag that loaded storefront.umd.cjs
  4. Built-in defaults (locale: 'es-CL')

mount(Factory, target, data?, opts?)

storefront.mount(MyComponent, '#container', { someOption: true })
ArgumentTypeDescription
FactoryFunctionA component factory
target`string \Element`CSS selector or DOM element to mount into
dataObjectInitial data / settings passed to the component
optsObjectMount options (e.g. { shadow: false } to skip Shadow DOM)

Returns { update(data), unmount() }.


Emitter

A lightweight event emitter mixed into Cart and available as a standalone export for building custom event buses.

import { Emitter } from '@bolder/storefront'

const bus = new Emitter()
bus.on('change', handler)
bus.trigger('change', payload)
bus.off('change', handler)
bus.once('change', handler)  // fires once then auto-removes

// Mix into an existing object
const obj = {}
Emitter(obj)
obj.on('ping', handler)
MethodDescription
on(events, fn)Register a listener. events may be space-separated.
once(event, fn)Register a one-time listener.
off(events?, fn?)Remove a specific listener, all listeners for an event, or all listeners with '*'.
trigger(event, ...args)Fire all listeners for an event.

Config Reference

All options accepted by createStorefront():

OptionTypeDefaultDescription
shopstringShop subdomain (e.g. "acme"). Used to build apiBase and as the default search feed's shopSubdomain. Required unless apiBase is set.
apiBasestringhttps://<shop>.bootic.netFull base URL for API requests.
searchUrlstringBolder's hosted search serviceOverride the WebSocket URL storefront.search connects to. Rarely needed.
localestring"es-CL"Locale hint for components.
currencystringCurrency code hint (e.g. "CLP", "USD").
widgetsObject{}Widget configuration forwarded to components via shared.
stringsObject{}Localisation strings forwarded to components via shared.
helpersObject{}Helper functions forwarded to components via shared.
onBeforeAddfunction(proceed)Gate function called before every quickAdd. Must call proceed() to continue.
onErrorfunction(message)Error handler for cart mutation failures. Defaults to console.warn.

Pre-load Queue Pattern

The loader snippet in Quick Start — Script Tag already implements this: Bolder.ready(fn) pushes fn onto window.Bolder.q if the SDK hasn't finished loading yet, and calls fn immediately once it has. You can call Bolder.ready() as many times as you like, from as many separate <script> blocks as you like (e.g. splitting embed setup from page-specific logic) — every callback queues or fires the same way, in call order.

<script>Bolder.ready(function(storefront) { /* runs once ready, queued or not */ })</script>

There's no manual queue array to manage — don't push onto window.Bolder.q directly, always go through Bolder.ready().


Cart

storefront.cart is an event emitter (see Emitter) wrapping the shop's cart session.

storefront.cart.on('updated', function() {
  document.querySelector('#cart-count').textContent = storefront.cart.units
})

// Add by numeric product/variant id — see addItem below for the full signature
storefront.cart.addItem(productId, variantId, 1)

// Add by a product's URL slug instead — no need to look up its numeric id yourself,
// the server resolves it (product must have exactly one variant, or you'll need
// to resolve the variant id yourself and use addItem)
storefront.cart.quickAdd('aurora-runner', function(item) {
  if (item) console.log('added', item)
})

numeric-looking string/number is sent as product_id; anything else is sent as product_slug and resolved server-side. Respects onBeforeAdd (see Config Reference) the same way addItem does.

quantity of a line item. Pass variantId: -1 to let the server pick the product's default/only variant. formData.product_slug resolves a slug the same way quickAdd does internally.

Cross-origin cart identity

The cart session is identified server-side by an id (_btc_oid) that hosted themes carry in a same-site cookie automatically. A headless page served from a different origin than apiBase — true for almost any headless integration that isn't proxied under the shop's own domain — can't rely on that cookie the normal way: SameSite/Secure cross-site cookie rules would require the API to be served over HTTPS just for the cookie to be honored.

To sidestep that entirely, the SDK's cart client doesn't depend on the cookie:

localStorage and sends back as an X-Cart-Token header on subsequent cart requests — a plain custom header, not a cookie, so none of the SameSite/HTTPS requirements apply. Server-side, cart_cookie (the single method every cart-resolving code path in app.rb goes through) accepts either the cookie or this header, in that order.

full-page navigation (via cart-table-v3), which can't carry a custom header. It carries the same id as a hidden cart_token field instead, which cart_cookie also accepts as a final fallback.

headless integrations get it for free) — the token/field are purely an additional path, not a replacement.

This means cart identity survives even with cookies fully blocked (verified by wiping all cookies mid-session and continuing to add items / checking out — same cart throughout).


storefront.search gives you live, typeahead-style product search over a WebSocket connection to Bolder's hosted search service — the same backend that powers hosted themes' search boxes.

bindTo(input, opts?) — the common case

Wires an <input> element up end to end: connects on focus (so the connection is warm before the first keystroke), debounces input, and emits results. Returns an Emitter — call .destroy() to unbind.

const handle = storefront.search.bindTo(document.querySelector('#search-input'), {
  minChars: 3,   // default: 3
  debounce: 150, // ms, default: 150
})

handle.on('results', (evt, results, query) => {
  renderResults(results.products, results.groups)
})

handle.on('cleared', () => clearResults())
handle.on('error', (evt, err) => console.error('Search failed', err))

// later, e.g. on component unmount:
handle.destroy()

You don't need to manage the connection yourself — bindTo reuses one socket per storefront instance and lets it disconnect on inactivity automatically.

search(query, opts?, fn) — build your own UI

Lower-level: runs a single query without binding to an input. Auto-connects if needed.

storefront.search.search('running shoes', { minChars: 3 }, (err, results) => {
  if (err) return console.error(err)
  console.log(results.products, results.groups)
})

connect(fn?) — pre-warm the connection

Rarely needed directly — both search() and bindTo() connect automatically. Useful if you want to open the connection earlier than the first query (e.g. as soon as a search UI becomes visible, before the user has typed anything) to shave connection latency off the first result.

storefront.search.connect((err, socket) => {
  if (err) return console.error('Could not connect to search', err)
})

Results shape

{
  products: [
    { id, name, slug, url, type, vendor, price, image, vars, skus }
  ],
  groups: [
    { type, name, slug } // Collection, ProductType, Vendor, ProductSet, or Tag matches
  ]
}

Which fields are present depends on config passed to createStorefront()image and price require withImages/withPrices (both on by default for headless).


Forms

storefront.forms lets a headless shop submit to a Bootic contact form (the same forms you build in the shop admin) without any Bootic-hosted markup. Useful for a contact page or feedback form on a statically-generated site, where the form's HTML needs to be part of the static page for SEO but the submission still needs to reach the shop's contacts.

get(slug, fn)

Fetches a form's field definitions so you can render your own inputs.

storefront.forms.get('contact', (err, form) => {
  if (err) return console.error(err)

  console.log(form.title, form.message) // form.message is the form's body/intro text
  form.contactFields.forEach(field => {
    // field: { key, name, field_type, required, value, options? }
  })
  form.customFields.forEach(field => { /* same shape */ })
})

submit(form, data, fn)

Submits data collected from your own form UI. Pass the form object returned by get()submit uses it to sort your flat data object into the contact_fields/ custom_fields split the backend expects, keyed by each field's key.

storefront.forms.get('contact', (err, form) => {
  myForm.addEventListener('submit', e => {
    e.preventDefault()

    storefront.forms.submit(form, {
      name: myForm.name.value,
      email: myForm.email.value,
      message: myForm.message.value, // a custom field, e.g.
    }, (err, result) => {
      if (err) return showErrors(err.errors)
      showSuccess(form.sentMessageText)
    })
  })
})

On validation failure err is { success: false, errors: [{ field, messages }] }.


Checkout

Bolder's checkout can be integrated in two modes. Choose based on the UX you want and the payment methods your shop uses.

ModeUXGateway redirectsSetup
EmbeddedCustomer stays on your page; checkout opens in a slide-out iframeHandled transparently — SDK navigates the top-level window when a gateway (PayPal, Webpay, Khipu) redirectsImport @bolder/checkout or include checkout.umd.cjs
Hosted redirectCustomer navigates to Bolder's checkout page, then returns to your pageNative — no iframes involvedPass return_url in the checkout link

Both modes share the same return-detection API so your page always knows when a customer comes back after payment.

Embedded mode

The checkout opens in a closeable slide-out panel on top of your page. When an inline payment succeeds the SDK fires a success event directly. When a gateway redirect is required (PayPal, Webpay, Khipu), the SDK breaks out of the iframe and navigates the top-level window — on return, handleReturn() detects the result.

Install:

npm install @bolder/checkout
# or use the script tag: https://assets.bolder.run/checkout.umd.cjs

Usage with @bolder/storefront cart:

import { createStorefront, openCheckout } from '@bolder/storefront'
// or: import { openCheckout } from '@bolder/checkout'

const storefront = createStorefront({ shop: 'acme' })

// Cart's checkout() returns a URL — open it in the embedded slider.
document.querySelector('#checkout-btn').addEventListener('click', function() {
  storefront.cart.checkout(function(err, url) {
    if (err) return
    openCheckout({ checkoutUrl: url })
  })
})

The ShoppingBeacon component wires this automatically — you don't need to handle it manually if you're using the beacon.

Usage with your own order API (no storefront cart):

import { openCheckout } from '@bolder/checkout'

// Your backend creates the order and returns a checkout_url.
const { checkout_url } = await fetch('/api/orders', { method: 'POST', ... }).then(r => r.json())

const session = openCheckout({ checkoutUrl: checkout_url })
session.on('success', () => showConfirmation())
session.on('closed',  () => console.log('checkout closed'))

Hosted redirect mode

The customer navigates to Bolder's checkout UI (same as for a regular hosted shop). When they complete (or cancel) their order, Bolder redirects them back to the URL you specified as return_url.

This is the simpler mode — no iframes, no postMessage, no SDK required on the checkout page itself.

Navigating to the checkout:

Use redirectToCheckout() — it accepts the same URL options as openCheckout() and appends return_url automatically:

import { redirectToCheckout } from '@bolder/checkout'
// or: import { redirectToCheckout } from '@bolder/storefront'

redirectToCheckout({ checkoutUrl: cartData.checkout_url })

// Optional: override the return page (defaults to the current page)
redirectToCheckout({ checkoutUrl: cartData.checkout_url, returnUrl: 'https://mysite.com/thanks' })

Script-tag: Bolder.checkout.redirect({ checkoutUrl: cartData.checkout_url })

Bolder stores return_url on the checkout session when the checkout page first loads and uses it to redirect back after any gateway round-trip. The return_url must be a URL whose hostname matches the shop's configured domain or an allowed embed origin — Bolder rejects unknown hosts to prevent open redirects.

What the customer receives on return:

Bolder appends two parameters before redirecting back:

https://your-site.com/products/hat?checkout_session=ABC123&checkout_status=success
ParameterValues
checkout_sessionThe order permalink — identifies which order was placed
checkout_statussuccess or cancelled

Return detection

Whether you use embedded or hosted redirect mode, load @bolder/checkout on the page the customer returns to. It runs handleReturn() automatically at module load — before any framework router can consume the URL — cleans the params, and fires a bolder:checkout:return event.

Listening for the event (registers before module load completes):

import { handleReturn } from '@bolder/checkout'
// handleReturn() already ran by the time the next line executes.
// Use document.addEventListener *before* the import for early listeners,
// or use getLastReturn() for components that mount later.

document.addEventListener('bolder:checkout:return', function(e) {
  const { sessionCode, status } = e.detail  // status: 'success' | 'cancelled'
  if (status === 'success') showConfirmationBanner(sessionCode)
})

Checking in a late-mounting component (misses the event):

import { getLastReturn } from '@bolder/checkout'
// or: import { getLastCheckoutReturn } from '@bolder/storefront'

// Call this inside your component's mount/init callback:
const ret = getLastReturn()
if (ret?.status === 'success') showConfirmationBanner(ret.sessionCode)
if (ret?.status === 'cancelled') showRetryPrompt()

handleReturn() return value{ sessionCode, status } if checkout params were present, undefined otherwise. Also available via Bolder.checkout.handleReturn() in script-tag usage.

URL cleanuphandleReturn() removes checkout_session and checkout_status from the URL via history.replaceState so a page refresh doesn't re-trigger the handler.

Shopping beacon integration

The ShoppingBeacon component integrates both modes automatically:

Bolder.checkout.open({ checkoutUrl }). The beacon passes return_url (the current page URL) and origin to the checkout iframe automatically.

status === 'success', the beacon displays a success toast notification. In hosted themes the toast uses the styled Toasts module; in headless mode a self-contained fallback renders instead.

No additional wiring is needed — mount the beacon and both flows work out of the box.

import { createStorefront, ShoppingBeacon } from '@bolder/storefront'

const storefront = createStorefront({ shop: 'acme' })
storefront.mount(ShoppingBeacon, '#beacon-container')

To handle the return yourself and suppress the beacon's built-in toast, listen to the event before the beacon mounts and call e.stopImmediatePropagation(), or simply don't use the beacon and wire your own UI around getLastReturn().


Bundled UI components

@bolder/storefront also re-exports a couple of prebuilt component factories, meant to be mounted with storefront.mount() like any other component. Their full options are documented in components-reference.md (they're the same base components used by hosted themes via Bootic.components.render(...)); this section only covers the headless import path.

import { createStorefront, CartTable, FeedbackButton } from '@bolder/storefront'

const storefront = createStorefront({ shop: 'acme' })

storefront.mount(CartTable, '#cart-table', { show_cart_gauge: true, cart_gauge_limit: 50000 })
storefront.mount(FeedbackButton, '#feedback-button')

hints, optional cart gauge). Purely a renderer: it doesn't wire clicks itself, so headless callers (like ShoppingBeacon) mount it and wire interactions on top.

when available, so it works without the hosted-theme Forms AMD module.

ShoppingBeacon (see above) already mounts its own CartTable internally — you only need to mount CartTable directly if you're building a custom cart UI outside of the beacon.


Core API

Core modules available in hosted themes via Bootic.call() and as services on storefront in headless integrations.

Auth

Customer authentication — log in, register, OTP verification, and password reset. Access via: Bootic.call('Auth', 'methodName', args, callback)

Methods
login(email, password, cb)Log in a customer with email and password.
createAccount(body, cb)Create a new customer account. body: { email, password, name, ... }.
validateOTPCode(email, otp, otpMode, activationToken, password, customPhone, cb)Validate a one-time password code.
resendOTPCode(email, otpMode, customPhone, cb)Resend an OTP code to the customer.
forgotPassword(email, cb)Send a password reset email.
checkExists(email, cb)Check whether a customer account exists for the given email.
oauthLogin(provider)Initiate an OAuth login flow. provider: 'google' or 'github'.

Base

Base utilities — path/environment helpers, cookies, storage, and general-purpose helpers used across core modules. Access via: Bootic.call('Base', 'methodName', args, callback)

Methods
isCustomizing()Returns true if the storefront is being previewed in the theme editor or in local development.
inEditor()Returns true if the current page is loaded inside the theme customizer.
inDevelopment()Returns true if the current page is loaded via the local preview/dev path.
getPath(path)Rewrites a path to the local preview/dev path when in development mode.
setCookie(name, value, days)Sets a cookie.
getCookie(name)Reads a cookie value.
delCookie(name)Removes a cookie.
storelocalStorage when available (and not cross-origin framed), otherwise falls back to cookies.
formatPrice(n, decPlaces, thouSeparator, decSeparator)Formats a number as a currency string.
formatCurrency(value)Adds thousands separators to a numeric string.
parseQuery(str, nested)Parses a querystring into a key/value object.
generateRandomId()Returns a random id string, used for session/pixel ids.

Browser

Browser/OS/device sniffing, used to enrich landing/session data (see user.js). Access via: Bootic.call('Browser', 'methodName', args, callback)

Methods
detect(nav)Returns `{ device_type, browser_name, browser_version, os_name, distro? }` for the given (or current) navigator.

Cart

Cart management — add, remove and update line items; listen to cart events. Access via: Bootic.cart (hosted theme) or storefront.cart (headless).

Methods
quickAdd(productIdOrSlug, fn?)Add a product by numeric ID or by URL slug, using its default variant. Slugs are resolved server-side.
addItem(productId, variantId, qty, formData?, fn?)Add or increase quantity of a line item. If the same variant already exists, its quantity is increased.
addVariant(variantId, qty, fn?)Add by variant ID without specifying a product ID.
addBundle(productId, formData, qty, fn?)Add a bundle product with its selected variants.
addMany(items, fn?)Add multiple items at once. Items not found in the response are reported as errors.
addGiftCard(productId, qty, formData, fn?)Add a gift-card product.
updateUnits(itemId, units, fn?)Update the quantity of an existing line item. No-op if quantity is unchanged.
remove(itemId, fn?)Remove a line item by its cart item ID.
load(fn?)Load cart data from the server. Calls fn(cart) on completion.
isLoaded()Returns true if the cart has been loaded at least once.
isEmpty()Returns true if the cart has no items.
findByItemId(id)Find a line item by its cart item ID.
findByVariantId(id)Find a line item by variant ID.
findByProductId(id)Find a line item by product ID.
on(event, handler)Register an event listener.
off(event, handler?)Remove an event listener.
once(event, handler)Register a one-time event listener.
Events
loadingCart load started.
loadedCart loaded successfully.
addingAdd request started (args: item, srcElement).
addedItem successfully added (args: item, srcElement).
adding_failedItem not found in response after add.
updatingUpdate request started (args: { id, quantity }, srcElement).
updatedCart data refreshed; fires after every mutation.
updated_itemSpecific item quantity confirmed by server (args: item, srcElement).
update_failedServer quantity did not match requested value.
removingRemove request started (args: item, srcElement).
removedItem successfully removed (args: item, srcElement).
clearedAll items removed.
errorA cart operation failed (args: message).

CartDecorator

Computes the per-item/cart display flags the cart-table templates (v1 and v3) check directly — can_change_quantity, has_description, etc. Neither the raw cart JSON nor createCart()'s own _decorateProducts() (cart.js) compute these; they used to live only inside the hosted-theme legacy DynamicCartTable Loader module's renderCart(), so headless-rendered cart tables (ShoppingBeacon, the SDK's CartTable) silently rendered without quantity controls, descriptions, or the promotions panel. One implementation, two callers — dynamic_cart_table.js and cart-table-v3's beforeUpdate() — mirroring how SearchSocket is shared between the headless search API and the legacy SearchIndex adapter.

CartRules

Validates a cart against shop-configured order requirements (min/max order total, units per product/product-type/variant, quantity multiples, product group presence/absence, customer-group/branch restrictions), returning one ready-to-show message (may contain inline HTML like <strong>, but never block-level wrapping like <li>/<ul> — that's the caller's rendering layout to decide) per unmet requirement. Shared by the hosted theme's legacy CartRules Loader module (modules/cart/cart_rules.js, which injects the messages directly as DOM HTML) and cart-table-v3's headless beforeUpdate() (which hands them to the qiq template to wrap in <li> and loop over) — same shape as cart-decorator.js. `requirements` defaults to `cart.rules`, which the server already includes on the cart JSON response in both hosted and headless mode (see core/cart.js's update()), so no extra config wiring is needed to use this headlessly.

Delivery

Shipping and delivery — fetch countries, regions, costs, and branch information. Access via: Bootic.call('Delivery', 'methodName', args, callback)

Methods
getShippingCountries(cb)Fetch all countries the store ships to.
getShippingRegions(params, cb)Fetch regions for a country. params: { country_code }.
getShippingOptions(address, weight, withWindows?, branchId?, cb)Calculate shipping costs for an address and package weight.
getShippingBranches(params, cb)Fetch all configured shipping branches.
getPickupBranches(cb)Fetch branches available for customer pickup.
getFulfillmentBranches(cb)Fetch branches available for order fulfillment.
getFulfillmentOptions(address, weight, withWindows?, cb)Calculate fulfillment options for an address.
getShippingPolygons(params, cb)Fetch shipping zone polygons for map display.

Dom

DOM utilities — element selection, creation, and traversal including Shadow DOM support. Access via: Bootic.DOM (hosted theme) or via Util (util.$, util.find, etc).

Methods
ready(fn)Run fn when the DOM is ready (or immediately if already loaded).
find(selector, context?)Return an array of elements matching the CSS selector.
get(selector, context?, ensure?)Return the first matching element (or throw if ensure is true).
deepFind(selector, root?)Like find() but traverses into Shadow DOM subtrees.
deepGet(selector, context?, ensure?)Like get() but traverses into Shadow DOM subtrees.
each(selector, context?, fn)Iterate over all matching elements calling fn(el).
el(tag, html?, opts?)Create and return a new DOM element with optional inner HTML and properties.
is(el, selector)Return true if el matches the given CSS selector.

Events

DOM event helpers and a tiny pub/sub `Emitter` used across cart, delivery, and other modules. Access via: Bootic.call('Events', 'methodName', args, callback), or `new Emitter()` directly.

Methods
on(el, type, selector, callback)Listen for an event, optionally delegated to a child selector.
off(el, type, selector, callback)Remove an event listener registered with `on`.
once(el, type, selector, callback)Listen for an event once, then automatically remove the listener.
trigger(el, eventName, data, opts)Dispatch a DOM event (native `click` or a `CustomEvent`) on an element.
Emitter(obj)Mixes `on`/`once`/`off`/`trigger` pub/sub methods into an object, or returns a new emitter.

Forms

Contact forms — fetch field definitions and submit form data for headless storefronts. Useful for building a contact/feedback form outside of a Bootic theme (e.g. a statically-generated site) while still delivering submissions to the shop's contacts. Access via: storefront.forms (headless).

Methods
get(slug, fn)Fetch a form's field definitions. Calls fn(err, form) with form: { id, slug, title, message, sentMessageText, contactFields, customFields }. Each field: { key, name, field_type, required, value, options? }.
submit(form, data, fn)Submit form data. form: the object returned by get(). data: { fieldKey: value, ... }, keyed by each field's `key`. Calls fn(err, result) with result: { success: true } on success, or { success: false, errors } on validation failure.

Loader

AMD-style module loader — resolves modules by name (not path). Backs `Bootic.Loader` and `Bootic.call`/`Bootic.require`. Access via: Bootic.Loader.methodName(args)

Methods
define(name, deps, fn)Registers a module under a name, resolving its dependencies first.
require(name1, name2, ..., cb)Requires one or more registered modules and calls back with their instances.
requireMany(queue, finished)Requires several groups of modules in sequence.
load(name, opts, cb)Loads a module (calling its `load` method) with the given options.
loadMany(list, finished)Loads several modules in sequence.
unload(name, obj, cb)Unloads a previously loaded module instance, or all instances.
isLoaded(name)Returns true if a module has an active loaded instance.
call(moduleName, method, ...args)Requires a module and calls a method on it, e.g. `Bootic.call('Cart', 'open')`.
addAlias(existingModule, newName)Registers an existing module under an additional name.

Products

Product catalog — list/filter products for headless storefronts. Access via: storefront.products (headless).

Methods
get(params?, fn)List products, optionally filtered. params: { q, collections, product_types, vendors, tags, price_gte, price_lte, page, facets }. Calls fn(err, data) with data: { products, page, per_page, total_items, total_pages, facets }.

Request

Simple XHR wrapper used for all AJAX calls (hosted theme) plus a factory for scoped, cross-origin-aware request functions used by the headless `@bolder/storefront` client. Access via: Bootic.call('Request', 'methodName', args, callback), or the default export directly.

Methods
ajax(method, url, data, onsuccess, onerror, headers)Make an XHR request. JSON-encodes data for `.json`/`.js` URLs, form-serializes otherwise.
createRequest(baseUrl, opts)Returns a request function scoped to a base URL; pass `opts.withCredentials` for cross-origin requests needing session cookies, `opts.getHeaders()` for per-request extra headers.

SearchSocket

Low-level WebSocket client for the product search service. Shared by the headless `search.js` API and the hosted-theme legacy `SearchIndex` adapter (registered in register-amd.js) — one implementation, two callers. No Bootic/DOM dependency beyond WebSocket/localStorage/crypto.

Templates

Client-side template compilation and rendering, backed by the `qiq2` engine. Access via: Bootic.call('Templates', 'methodName', args, callback)

Methods
compile(templateName, body)Compile and cache a template string under a name.
render(templateNameOrBody, data)Render a cached template by name, or compile-and-render a raw template string.
renderRaw(body, data)Compile and render a template string without caching it.
renderCached(templateName, data)Render an already-compiled template by name. Throws if not found.
loadAndCompile(elementId, templateId)Compile a template from a `<script>` element's contents.
loadAndRender(elementId, content)Load-and-compile (if needed) then render a template from a `<script>` element.
isCompiled(templateName)Returns true if a template has already been compiled.
setEngine(obj, opts)Swap the underlying template engine and its options.

User

User session — read session data, cookies, and navigation history. Access via: Bootic.call('User', 'methodName', args, callback)

Methods
getJSONCookie(cookieName)Parse and return a JSON-encoded cookie value.
getCurrentData(cb)Fetch the current customer's session data from the server.
getUrlHistory()Returns the last 10 visited page paths as an array.
getMembership(cb)Returns `{ id, plan_id, plan_name }` if the customer has an active membership, or null.

Util

General-purpose utilities — event helpers, DOM querying, AJAX, scroll/intersection observers, product fetching, and more. Access via: Bootic.util (hosted theme) or window.Bootic.util.

Methods
debounce(fn, wait, immediate?)Return a debounced version of fn that delays execution until wait ms after the last call.
$(selector, context?)Select DOM element(s); returns a DOMNode wrapper with helper methods.
on(el, event, fn)Attach an event listener to one or more elements.
off(el, event, fn)Remove an event listener from one or more elements.
once(el, event, fn)Attach an event listener that fires only once.
trigger(el, event, data?)Dispatch a custom DOM event, optionally with extra data.
ajax(url, opts?)Make an HTTP request. Returns a promise-like object. opts: method, data, headers, type.
serialize(form)Serialize a form element into a query string.
sendForm(form, opts?)Submit a form via AJAX, returning the JSON response.
streamGet(url, opts?)GET a URL and stream the response line by line via opts.onData(line).
onScroll(el, fn)Call fn(entry) each time el enters or leaves the viewport during scroll.
onIntersect(el, fn, opts?)Call fn(isIntersecting) when el intersects the viewport (IntersectionObserver wrapper).
isTouchDevice()Return true if the current device supports touch input.
getProduct(shopId, productId, opts?)Fetch a single product from the Bootic API. Returns a promise.
getProductAt(shopId, urlPath, opts?)Fetch the product at a given URL path.
getProductForCart(shopId, productId, opts?)Fetch a product formatted for cart use.
getMenu(shopId, menuHandle, opts?)Fetch a navigation menu by handle.
isDark(color)Return true if the given hex/rgb color is perceptually dark.
isLight(color)Return true if the given hex/rgb color is perceptually light.
loadImage(src)Load an image and return a promise that resolves when loaded.
loadImages(els)Load all img elements in the given list.
sha256(message)Return a SHA-256 hex digest of message (returns a Promise).
tabList(el, opts?)Enhance a list of tabs with keyboard navigation and active-state management.
selectize(el, opts?)Enhance a <select> element with a searchable custom dropdown.