docs
/
Client Integration

Browser runtime

window.appmint — the JavaScript API that HTML pages and no-code markup use to reach the platform, and how it bridges to the host application.

The runtime is a single browser bundle that mounts window.appmint. It is what an HTML page, an AI-authored block or a data-action attribute uses to add to a cart, price it, look up a product or open a drawer — without writing any framework code.

The runtime currently ships inside base-app and is built from its source tree. It is expected to become a standalone package; the surface described here is the contract that will move with it, so write against window.appmint rather than internals.

How it is built and loaded

src/runtime/index.ts ──esbuild IIFE──▶ public/runtime/appmint.js ──▶ window.appmint

The bundle is produced by esbuild during the host's own compile — once per next dev start, once per next build — and loaded by the page:

<script src="/runtime/appmint.js"></script>

Watch the build output. The runtime build is a side-effect of the host's compile, so a resolution failure prints an error and the compile carries on. The symptom is a bundle that silently stops changing while the source keeps moving. If runtime edits are not reaching the browser, check for Could not resolve in the dev server output and compare the bundle's timestamp against your last edit.

The bridge

The runtime is a separate bundle, so an import inside it resolves to a different module instance than the host application's. State mutated there would land in a phantom store nothing renders.

The host therefore publishes its real singletons on window, and the runtime prefers them:

const cartState = () => {
  const bridged = (window as any).__appmintCartStore;
  if (bridged?.getState) return bridged.getState();
  return useCartStore.getState();   // fallback: the runtime's own copy
};
SlotWhat it shares
__appmintCartStoreThe host's cart store
__appmintUIStoreDrawers, quick-view, notifications
__appmintUserStoreCurrent customer / session
__appmintAuthLogin, logout, register, password reset
__appmintClientThe host's configured API client — auth, orgid, 401 refresh

The practical consequence: behaviour changes in the host's stores are picked up by the runtime immediately, because it is calling the same objects. Changes to the runtime's own API surface need the bundle rebuilt.

Namespaces

window.appmint is grouped by domain — cart, product, order, storefront, customer, rental, ticket, reservation, events, page, site, repository, files, activity, api, contentPlayer, media, ui, state, form. (contentPlayer plays Content Studio posts — see Content Player; api reaches any endpoint that has no named method yet.)

await appmint.cart.add({ sku: 'ST-SOFA', quantity: 1 });
await appmint.cart.recalc();
const { total, subtotal, tax, shipping, itemCount } = await appmint.cart.current();

const products = await appmint.product.list({ ps: 20 });
appmint.ui.toast('Added to your basket');

Most namespaces are thin wrappers over an endpoint:

paymentGateways: () => t.get('/storefront/payment-gateways'),
checkout:        (input) => t.post('/storefront/checkout-cart', input),

A few are not, and that is the point of them: cart owns local state, optimistic updates and a re-price after every mutation; ui, state and form have no backend at all.

Reaching an endpoint with no wrapper

A named wrapper has to be written by hand, so a newly added AppEngine endpoint is not automatically available. The api namespace closes that gap — same transport, same auth, orgid and error mapping, with the path given at the call site:

await appmint.api.get('storefront/shipping/options');
await appmint.api.post('shipping/options', { items, toAddress });
await appmint.api.put('crm/contact/123', { name: 'Ada' });
await appmint.api.patch('crm/contact/123', { phone: '+1…' });
await appmint.api.del('crm/contact/123');

Prefer a named method where one exists — it is discoverable in the manifest and survives a path change. Reach for api.* for anything not yet wrapped.

Declarative markup

The dispatcher resolves namespace.method from HTML attributes, so a page needs no script:

<button data-action="cart.add" data-payload='{"sku":"ST-SOFA","quantity":1}'>
  Add to basket
</button>

<button data-action="cart.toggle">Basket</button>

Cart totals

cart.current() returns the server's figures as rendered fields. Nothing here is computed in the browser:

const { subtotal, total, tax, shipping, discount, itemCount } = await appmint.cart.current();

recalc() posts the cart — with the coupon and shipping address — to storefront/pricing/calculate-cart and stores the whole summary. It runs after every mutation.

If pricing has not resolved, totals are absent rather than guessed. Render a placeholder while the cart is unpriced; do not fall back to summing line items, or this channel will disagree with every other one.