Craft Commerce 5
Commerce, minus the head
Craft Commerce exposes products over GraphQL but has never shipped a single cart mutation. A headless store ends up posting form-encoded requests with a session cookie, a CSRF token and credentials: 'include', then gets stuck at payment, because Craft only accepts a return URL it hashed into a Twig form itself. Headdy is the storefront API that was missing.
One request, one cart
The first add-to-cart creates the cart and hands back a token. Send that token as a header from then on. No cookie, no CSRF round trip, and every amount is an object, never a float.
const res = await fetch(`${API}/carts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Headdy-Key': KEY },
body: JSON.stringify({ items: [{ purchasableId: 123, qty: 2 }] }),
});
const { cart } = await res.json();
cart.token; // "hdc_…" — send back as X-Headdy-Cart
cart.totals.total; // { amount: "49.98", minorUnits: 4998,
// currency: "USD", formatted: "$49.98" }
Features
Everything a storefront calls, from the first add-to-cart to a paid order, with one service behind all of it.
Carts over REST
Create, patch and empty carts; add, update and remove line items; set addresses, email, coupon and shipping, in one PATCH or six small calls, with the same result either way.
- Changes apply in a fixed order, so a coupon never misses its minimum
- A failed change still returns the cart, so the UI can re-render
Checkout that tells you what's missing
One GET returns what checkout still needs, the shipping methods and gateways that can supply it, and the store's own rules, so the front end never finds them out by trial and error.
Payment that works headlessly
Any Commerce gateway, including off-site ones. Their redirect comes back as JSON, with a method and form data, instead of a 302 your fetch can't follow.
- Return URLs checked against an allow-list, not a Twig hash
- The list starts empty, so nothing redirects until you allow it
Bearer tokens, stored as hashes
Cart and customer tokens are stored only as SHA-256 hashes, so a database dump yields nothing usable. Keys carry scopes, origins and rate limits of their own.
GraphQL cart mutations
Eleven mutations and two queries on Craft's own GraphQL endpoint, only on schemas you grant them to. They resolve through the same service as REST, so the two can never drift.
Customer accounts
Sign-in, registration, order history, an address book and saved cards. A customer token never logs anyone into Craft, and control panel accounts are refused at the door.
- Wrong passwords count toward Craft's own lockout
- Registration honours Craft's email verification
Webhooks and a request log
Signed, queued webhooks for cart, order and payment events, and a log of every API request, so you can see exactly what a front end sent.
A setup check that gates a deploy
The Overview screen and a console command flag the misconfiguration before your front-end team does: no key, no gateway, an empty redirect list, an open CORS policy.
Headdy in the control panel
Real screens from a Craft 5 install running Commerce: the setup check, the request log, an API key and a webhook endpoint.
Screenshots from a live install, not mockups.
Or the same cart over GraphQL
Grant a schema the Headdy storefront component and the cart mutations appear on Craft's own GraphQL endpoint. Pass the cart token as an argument. Payment stays on REST, because a gateway redirect needs a real HTTP response.
mutation {
headdyCartAddItem(cartToken: "hdc_…", purchasableId: 456, qty: 2) {
totalQty
lineItems { description qty total { formatted } }
totals { total { formatted } }
}
}
query {
headdyCheckout(cartToken: "hdc_…") {
ready
missing # ["shippingMethod"]
amountDue { formatted }
}
}
Frequently Asked Questions
The questions worth answering before you install it.
For reading products, yes. It has never had cart or order mutations. That gap is craftcms/commerce discussion #2350, open for years. Headdy fills it, over REST and GraphQL both.
Yes. CSRF works because the browser attaches a cookie on its own. Headdy uses no cookie. The cart token travels in a header your code sets on purpose, which is the same protection a CSRF token gives, without the extra round trip.
Any Commerce gateway. Gateways that charge in place, like Stripe, work out of the box. Off-site gateways like PayPal or Mollie work once you add your storefront's origin to the redirect allow-list. The redirect comes back as JSON for your front end to follow.
All of them. It's JSON over HTTPS with headers. There's no SDK to install and nothing framework-specific.
Not inside v1. Response shapes and error codes are a contract. A breaking change means a /v2 alongside it. Clients branch on a stable error.code, never on a message.
Lite is a complete guest checkout: carts, shipping, coupons, checkout and payment, multi-store included. Pro adds customer accounts, GraphQL mutations, webhooks and the request log. Upgrading keeps every key and setting.
Two editions, both of them complete
Lite is $99 with a $79/year renewal and runs a complete guest checkout on its own: carts, shipping, coupons, checkout and payment through any gateway. Pro is $199 with a $159/year renewal, and adds customer accounts, GraphQL cart mutations, webhooks and the request log.