Install Sign Customiser on a headless Shopify storefront
Connect Sign Customiser to a Storefront API or Hydrogen cart outside a Shopify Online Store theme.
Last updated:
This guide is for developers who manage a headless Shopify storefront.
It explains how to show Sign Customiser and add the generated product to the existing Shopify cart.
Do not use this guide for a custom Liquid template. Use Choose how to install Sign Customiser on Shopify instead.
Before you start
You need:
-
Sign Customiser connected to the same Shopify store.
-
The Sign Customiser ID.
-
A Storefront API or Hydrogen cart integration.
-
The presentment currency and its conversion rate.
-
The active Shopify market when you use Shopify Markets.
Never put a private Storefront API token in browser code.
1. Select the Shopify publication
Sign Customiser creates a new Shopify product when the customer selects the add-to-cart button (Add to Bag by default).
The product must be available to the sales channel that serves your headless storefront.
-
Open the integrations page.
-
In the Sign Customiser app in Shopify admin, go to Tools & Settings > Integrations and Webhooks.
-
In the Universal admin at web.signcustomiser.com, go to Integrate > Integrations.
-
-
On the Shopify card, select View Details.
-
Select Edit Integration.
-
Set Publication to the publication used by your headless storefront.
-
Select Update.

The Integrations and Webhooks page in the Universal admin (Integrate > Integrations). In this screenshot the Store has no active integration, so the page shows the Selling on Shopify? banner with Install on Shopify. When Sign Customiser is connected to your Shopify store, the Shopify card shows View Details.
If you do not select a publication, Sign Customiser uses the publication named Online Store when your store has one. If your store has no publication with this name, Sign Customiser uses the first publication that Shopify returns. Select a publication yourself if your headless storefront uses a different publication.
2. Add the iframe
Build the iframe URL with URLSearchParams.
<iframe id="sign-customiser" title="Sign customiser" style="border: 0; display: block; min-height: 800px; width: 100%"></iframe>const signCustomiserOrigin = "https://web.signcustomiser.com";const iframe = document.querySelector("#sign-customiser");const params = new URLSearchParams({ client: "headless-shopify", currency_code: storefront.currencyCode, currency_rate: String(storefront.currencyRate),});if (storefront.currencyFormat) { params.set("currency_format", storefront.currencyFormat);}if (storefront.marketId && storefront.marketHandle) { params.set("shopify_market_id", String(storefront.marketId)); params.set("shopify_market_handle", storefront.marketHandle);}if (storefront.unitSystem) { params.set("storefront_unit_system", storefront.unitSystem);}iframe.src = `${signCustomiserOrigin}/embed/${customiserId}?${params}`;Use a positive currency_rate. Use 1 when the presentment currency is the store base currency.
The rate must convert a price from the store base currency to the presentment currency.
If you use Shopify Markets, send both shopify_market_id and shopify_market_handle.
Use storefront_unit_system if your storefront shows measurements in one unit system. This parameter is optional. The value is metric or imperial. If the customiser offers both centimetres and inches, imperial makes the custom size input start in inches.
3. Send the storefront context
Listen for the spcReady message. Then send the current page and Shopify context to the iframe.
function sendStorefrontContext() { iframe.contentWindow?.postMessage( { type: "setHostUrl", hostUrl: window.location.href, shopifyContext: { marketHandle: storefront.marketHandle, marketId: storefront.marketId, unitSystem: storefront.unitSystem, }, }, signCustomiserOrigin, );}window.addEventListener("message", async (event) => { if ( event.origin !== signCustomiserOrigin || event.source !== iframe.contentWindow ) { return; } if (event.data?.type === "spcReady") { sendStorefrontContext(); }});iframe.addEventListener("load", sendStorefrontContext);The origin of hostUrl must match the origin of the page that sends this message.
The unitSystem value is optional. Use the same value as storefront_unit_system.
4. Connect the Shopify cart
Sign Customiser sends sc:product:created after it creates the Shopify product.
Use product.external_data.variant_gid as the Storefront API merchandiseId.
Copy every productFormatted entry to the cart-line attributes. Include the keys that start with an underscore. Sign Customiser uses them for design details, such as the links to uploaded artwork.
async function addSignCustomiserProduct(message) { const variantGid = message.product?.external_data?.variant_gid; if (!variantGid) { throw new Error("The generated product has no Shopify variant GID."); } const attributes = Object.entries(message.productFormatted ?? {}).map( ([key, value]) => ({ key, value: String(value ?? "") }), ); await storefrontCart.addLines([ { merchandiseId: variantGid, quantity: 1, attributes, }, ]);}storefrontCart.addLines represents your existing cart function. It must create a cart when no cart exists.
For Hydrogen 2025-07 through 2026-04, connect this function to the Hydrogen cart handler. For another Hydrogen version, follow the Hydrogen cart documentation for that version. For another framework, use cartCreate or cartLinesAdd.
Read Shopify’s documentation for the Hydrogen 2026-04 cart handler, About the Hydrogen cart, and cartLinesAdd.
Check the returned cart and its userErrors. Reject the operation when Shopify does not add the line.
5. Report the cart result
Report the result before you open the cart or change the page.
Wait for the matching sc:cart:result:ack message. Use a short timeout so that reporting does not block the customer.
function reportCartResult(checkoutAttemptId, result) { return new Promise((resolve) => { const timeout = window.setTimeout(finish, 400); function finish() { window.clearTimeout(timeout); window.removeEventListener("message", receiveAck); resolve(); } function receiveAck(event) { if ( event.origin === signCustomiserOrigin && event.source === iframe.contentWindow && event.data?.type === "sc:cart:result:ack" && event.data.checkoutAttemptId === checkoutAttemptId ) { finish(); } } window.addEventListener("message", receiveAck); iframe.contentWindow?.postMessage( { type: "sc:cart:result", checkoutAttemptId, ...result, }, signCustomiserOrigin, ); });}6. Handle the product-created event
Add this case to the validated message listener from step 3.
if (event.data?.type === "sc:product:created") { const { checkoutAttemptId } = event.data; try { await addSignCustomiserProduct(event.data); await reportCartResult(checkoutAttemptId, { outcome: "succeeded" }); storefrontCart.open(); } catch (error) { await reportCartResult(checkoutAttemptId, { outcome: "failed", classification: error?.classification ?? "unknown_failure", }); iframe.contentWindow?.postMessage( { type: "addToCartFailed", errorMessage: "We could not add this product to the cart. Try again.", }, signCustomiserOrigin, ); }}Keep the origin and iframe-window checks from step 3 around this code.
When the cart operation fails, set classification to one of these values:
-
platform_rejected -
platform_rate_limited -
platform_variant_missing -
platform_unavailable -
cart_unavailable -
unknown_failure
The sample reads classification from the error that your cart function throws. Set this property in your cart function with these rules:
-
Use
platform_rate_limitedwhen the response has an error whoseextensions.codeisTHROTTLED. The Storefront API sends this error in the body of an HTTP 200 response. -
Use
platform_variant_missingwhen Shopify did not publish the product variant to the storefront. -
Use
platform_rejectedfor any other Shopify response with HTTP status 400 or above, and for an HTTP 200 response whoseerrorsoruserErrorslist is not empty and does not containTHROTTLED. -
Use
platform_unavailablefor any other Shopify cart failure. -
Use
cart_unavailablewhen your storefront cannot reach its cart. -
Use
unknown_failurefor an error that is not a Shopify cart failure.
Sign Customiser records any other text value of 64 characters or fewer as unknown_failure.
Do not send exception messages or Shopify response bodies in the result message.
7. Test the installation
Complete these checks before you publish the storefront:
-
Open the customiser in each supported market and currency.
-
Confirm that the displayed price uses the correct presentment currency.
-
Create a design and select the add-to-cart button (Add to Bag by default).
-
Confirm that the generated variant is available to the headless storefront publication.
-
Confirm that the cart contains one generated variant and all line attributes.
-
Complete checkout and open the order in Shopify admin.
-
Confirm that the order line has the design details. If the design includes uploaded artwork or face text, confirm that the order line also has the links to the design files.
If the generated variant is unavailable, check the Publication setting and the active Shopify market first.