Sign Customiser

Navigation

Dark mode

Use cart, form, AI sign designer, lightbox, and ShareLink events from inline storefront JavaScript.

Storefront JavaScript events let theme code respond to the customer customiser on the same page. They cover cart actions, forms, the AI sign designer, lightbox sizing, and ShareLinks.

These browser integrations use CustomEvent on document. They are not REST endpoints.

Browser scope

Listen on the storefront document. With an inline customer customiser, all events in this guide reach this document.

With an iframe embed, the Sign Customiser iframe wrappers (the Shopify embed script, the WooCommerce plugin, the Wix embed, and the generic embed element) dispatch these events on the storefront document:

  • The cart events, when the platform sends them. See Add-to-cart tracking and Checkout tracking.
  • signCustomiserFormSubmitted and signCustomiserQuoteSubmitted.
  • The ShareLink events. The wrapper relays signCustomiserShareLinkRequested to the customer customiser iframe. See Request a ShareLink.

The AI sign designer events and signCustomiserLightboxSizeMatched stay inside the iframe. They do not reach the storefront document.

Add listeners before the action that sends an event. Remove temporary listeners when your integration finishes.

Event list

EventDirectionWhen it occurs
triggerUpdateCartCustomer customiser to themeA product enters the Shopify cart and the Store uses a custom cart action.
signCustomiserProductAddedToCartCustomer customiser to themeA product is added to the Shopify cart, or a generic embed with the custom driver hands off the product. WooCommerce and Wix do not send it.
signCustomiserCheckoutStartedCustomer customiser to themeA shopper is about to go to checkout, with the Buy it now button or the Straight to Checkout cart action.
signCustomiserFormSubmittedCustomer customiser to themeA custom design form submission succeeds.
signCustomiserQuoteSubmittedCustomer customiser to themeA quote form submission succeeds.
signCustomiserAiLogoCompletedCustomer customiser to themeThe AI sign designer completes a design.
signCustomiserAiLogoSuccessCustomer customiser to themeThe AI sign designer completes a design. This event fires with the completed event.
signCustomiserAiLogoFailedCustomer customiser to themeThe AI sign designer cannot complete a design and uses its fallback.
signCustomiserAiLogoInsufficientCreditsCustomer customiser to themeThe Store has no AI sign designer credits.
signCustomiserLightboxSizeMatchedCustomer customiser to themeA lightbox face image reaches automatic size matching.
signCustomiserShareLinkRequestedTheme to customer customiserTheme code requests a ShareLink for the current design.
signCustomiserShareLinkCreatedCustomer customiser to themeShareLink creation succeeds.
signCustomiserShareLinkFailedCustomer customiser to themeShareLink creation fails.

Listen for an event

Add a listener to document:

js
function handleProductAdded(event) {
useCartResult(event.detail.cart);
}
document.addEventListener(
"signCustomiserProductAddedToCart",
handleProductAdded,
);

Replace useCartResult with your integration code.

Cart events

Custom cart hand-off

triggerUpdateCart fires after an inline customer customiser adds a product to Shopify. It fires only when the Store uses the custom cart action.

The event detail is the Shopify cart response:

ts
type TriggerUpdateCartDetail = Record<string, unknown>;

Use this event to update a cart drawer or start another custom cart step.

Add-to-cart tracking

signCustomiserProductAddedToCart fires after a successful Shopify cart request. This includes the inline embed, the Shopify iframe embed, and the Sign Customiser Modal block. It also fires for a generic embed with the custom driver, when the customer customiser hands off the product. The generic embed does not confirm a cart add, and cart can be undefined. It fires before a cart redirect or custom cart hand-off. WooCommerce and Wix embeds do not send this event.

ts
type SignCustomiserProductAddedToCartDetail = {
customiserId: string | number | null | undefined;
domain: string | number | null | undefined;
cart: Record<string, unknown> | null | undefined;
intent: "cart" | "checkout";
adClickIds: Record<string, string> | null;
utmParams: Record<string, string> | null;
};

intent is "checkout" when the shopper used the Buy it now button. For all other adds it is "cart".

For the inline Shopify embed, this event is not available to Starter, Standard, or Pro plan families. This restriction includes yearly variants. Embeds that use an iframe send the event for every plan.

Checkout tracking

signCustomiserCheckoutStarted fires immediately before the page goes to checkout. It fires for the Buy it now button and, on Shopify, for the Straight to Checkout cart action. On Shopify it fires after signCustomiserProductAddedToCart. On WooCommerce there is no add-to-cart event, so this event is the only one. It does not fire when the shopper stays on the page or goes to the cart page.

On WooCommerce, the Buy it now button goes to the checkout page of the store. The customiser finds this page from the checkout-url attribute of the embed element, then checkout_url from the Sign Customiser WordPress plugin (version 1.6.3 and later), then the WooCommerce checkout settings of the page. If none of these exists, the customiser checks the checkout page next to the WooCommerce cart page. If the page cannot be confirmed, the shopper gets the normal cart behaviour of the store and this event does not fire.

The event detail has the same shape as signCustomiserProductAddedToCart. The cart value is the cart response of the platform.

ts
type SignCustomiserCheckoutStartedDetail = SignCustomiserProductAddedToCartDetail;

The Buy it now button does not fire triggerUpdateCart. For the inline Shopify embed, this event is not available to Starter, Standard, or Pro plan families. Embeds that use an iframe send the event for every plan.

Form submission events

signCustomiserFormSubmitted fires after a custom design form submission succeeds. signCustomiserQuoteSubmitted fires after a quote form submission succeeds.

Both events use this detail:

ts
type SignCustomiserFormSubmittedDetail = {
quoteId: number | null;
customiserId: string | number | null | undefined;
formId: string | number;
domain: string | null;
adClickIds: Record<string, string> | null;
utmParams: Record<string, string> | null;
data: Array<{
fieldId: string;
label: string | undefined;
value: string | File | Array<string | File>;
urls?: string[];
}>;
};

quoteId is the ID of the saved submission. It is the same value as id in the quotes API. Use it to read the submission with Retrieve a quote. The value is null when the response has no saved submission ID.

Use formId and fieldId to map the submission to your system. Do not use label text as a stable field identifier.

These events are not available to Starter, Standard, or Pro plan families. This restriction includes yearly variants.

AI sign designer events

signCustomiserAiLogoCompleted and signCustomiserAiLogoSuccess fire together after the AI sign designer completes a design.

Both events use this detail:

ts
type SignCustomiserAiLogoCompletedDetail = {
svg: string | null;
svgUrl: string | null;
rawUrl: string | null;
};

signCustomiserAiLogoFailed fires when the AI sign designer uses its failure fallback. signCustomiserAiLogoInsufficientCredits fires when the Store has no credits. These events have no detail fields.

The URLs and SVG can contain customer design data. Send this data only to systems that need the design.

signCustomiserLightboxSizeMatched fires when a lightbox face image reaches automatic size matching.

ts
type SignCustomiserLightboxSizeMatchedDetail = {
outcome:
| "matched"
| "skipped-not-offered"
| "skipped-no-image"
| "skipped-empty-envelope";
};

matched means that the customer customiser applied an image-matched custom size. A skipped-* value identifies why the customer customiser kept the current size.

Use signCustomiserShareLinkRequested when theme code needs a ShareLink for the current design.

Do not click the customer Share button from theme JavaScript. Do not read, close, or remove the Share modal. The customer customiser owns this interface.

The request does not open a modal, move focus, or change the current screen.

DirectionEvent name
Theme to customer customisersignCustomiserShareLinkRequested
Customer customiser to theme, successsignCustomiserShareLinkCreated
Customer customiser to theme, failuresignCustomiserShareLinkFailed

While the runtime remains mounted, each valid request receives one success or failure event.

Inline and iframe embeds

Dispatch the request on the storefront document in both embed modes:

  • An inline customer customiser answers the request directly.

  • A Sign Customiser iframe wrapper forwards the request to its customer customiser iframe and dispatches the reply on the storefront document. A wrapper with no mounted iframe, for example a closed modal, answers with signCustomiserShareLinkFailed and the code not_ready. The wrapper also answers not_ready when the modal closes during a request, or when the customer customiser does not reply within 15 seconds.

Use one customer customiser per page for this integration. Each mounted customer customiser answers every request.

An iframe that you add to a page yourself, without a Sign Customiser wrapper, does not receive the request.

Request detail

Dispatch signCustomiserShareLinkRequested with this detail:

ts
type SignCustomiserShareLinkRequestedDetail = {
requestId: string;
};

requestId must match this pattern:

text
[A-Za-z0-9._:-]{1,128}

Create a new value for each pending request. Use it only to match the result to the request. It is not a server idempotency key.

The request does not accept callbacks, DOM nodes, cache data, customiser IDs, host URLs, or endpoint URLs. The mounted customer customiser supplies these values.

The customer customiser ignores invalid detail. Invalid detail does not create a ShareLink or change the design.

Success detail

Listen for signCustomiserShareLinkCreated:

ts
type SignCustomiserShareLinkCreatedDetail = {
requestId: string;
customiserId: string;
domain: string | null;
url: string;
};

The customer customiser returns requestId without changes. Use it to match a result to a pending request.

url is the complete ShareLink. Its security value is in the URL fragment. Store the complete returned URL. Do not build a URL from token parts.

Failure detail

Listen for signCustomiserShareLinkFailed:

ts
type SignCustomiserShareLinkFailedDetail = {
requestId: string;
customiserId: string | null;
domain: string | null;
code: "not_ready" | "sharing_unavailable" | "request_failed";
};

Use code to select the recovery action:

CodeMeaning
not_readyThe customer customiser is not ready to create a ShareLink.
sharing_unavailableThe Store is not eligible to create ShareLinks.
request_failedA request or server problem stopped ShareLink creation.

not_ready can occur before the runtime knows the customiser or domain. Therefore, customiserId and domain can be null.

Failure events do not contain response bodies, stack traces, cache data, URLs, or token fields.

Eligibility

The Store must be able to go live. Its plan must include sharing.

Settings such as shareLocation control the visible customer Share control. They do not block an eligible request after the runtime is ready.

Privacy and expiry

A ShareLink is a bearer link to a saved design. Anyone with the complete URL can open the design until it expires or you revoke it.

Each ShareLink expires 365 days after creation. Store the complete URL only in the workflow that needs the design.

To stop access before expiry, open Shares in the merchant admin, open the saved share, and select Revoke ShareLink. Revocation cannot be undone.

Do not log the ShareLink URL. Do not send it to analytics. Do not extract, build, or log token parts.

This example requests a ShareLink before a cart flow. It adds both terminal listeners before the request. It removes them after success, failure, or timeout.

js
function requestSignCustomiserShareLink(options = {}) {
const timeoutMs = options.timeoutMs ?? 10000;
const requestId =
options.requestId ??
`cart-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
return new Promise((resolve, reject) => {
let settled = false;
function cleanup() {
document.removeEventListener(
"signCustomiserShareLinkCreated",
handleCreated,
);
document.removeEventListener(
"signCustomiserShareLinkFailed",
handleFailed,
);
window.clearTimeout(timeout);
}
function settle(action, value) {
if (settled) {
return;
}
settled = true;
cleanup();
action(value);
}
function handleCreated(event) {
if (event.detail?.requestId !== requestId) {
return;
}
settle(resolve, event.detail.url);
}
function handleFailed(event) {
if (event.detail?.requestId !== requestId) {
return;
}
settle(
reject,
new Error(`ShareLink request failed: ${event.detail.code}`),
);
}
const timeout = window.setTimeout(() => {
settle(reject, new Error("ShareLink request timed out."));
}, timeoutMs);
document.addEventListener(
"signCustomiserShareLinkCreated",
handleCreated,
);
document.addEventListener(
"signCustomiserShareLinkFailed",
handleFailed,
);
document.dispatchEvent(
new CustomEvent("signCustomiserShareLinkRequested", {
detail: { requestId },
}),
);
});
}
async function continueCartFlow() {
try {
const shareLinkUrl = await requestSignCustomiserShareLink();
await saveShareLinkForCart(shareLinkUrl);
} catch {
// Continue without a ShareLink.
}
await continueToCart();
}

Replace saveShareLinkForCart and continueToCart with your existing cart code.

Private implementation

Do not call private ShareLink routes from theme code. Storefront JavaScript events are the supported merchant integration.

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