Sign Customiser

Navigation

Dark mode

Install Sign Customiser on a headless Shopify storefront

Copy page

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.

  1. 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.

  2. On the Shopify card, select View Details.

  3. Select Edit Integration.

  4. Set Publication to the publication used by your headless storefront.

  5. Select Update.

The Integrations and Webhooks page in the Universal admin, with the Shopify banner and the Custom API, WooCommerce, Wix and Etsy cards.

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.

HTML
<iframe
id="sign-customiser"
title="Sign customiser"
style="border: 0; display: block; min-height: 800px; width: 100%"
></iframe>
CSS
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.

CSS
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.

CSS
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.

CSS
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.

CSS
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_limited when the response has an error whose extensions.code is THROTTLED. The Storefront API sends this error in the body of an HTTP 200 response.

  • Use platform_variant_missing when Shopify did not publish the product variant to the storefront.

  • Use platform_rejected for any other Shopify response with HTTP status 400 or above, and for an HTTP 200 response whose errors or userErrors list is not empty and does not contain THROTTLED.

  • Use platform_unavailable for any other Shopify cart failure.

  • Use cart_unavailable when your storefront cannot reach its cart.

  • Use unknown_failure for 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:

  1. Open the customiser in each supported market and currency.

  2. Confirm that the displayed price uses the correct presentment currency.

  3. Create a design and select the add-to-cart button (Add to Bag by default).

  4. Confirm that the generated variant is available to the headless storefront publication.

  5. Confirm that the cart contains one generated variant and all line attributes.

  6. Complete checkout and open the order in Shopify admin.

  7. 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.

Type a question and press Enter. Use the arrow keys to move between the results.