Everything you need to connect the 3D configurator to a hand-built site (no Shopify/WooCommerce/PrestaShop plugin). Every attribute and field below matches the real widget code.
embed.js — a loader you drop on your page. It reads your data-* attributes and injects an iframe (you host nothing)./e/{key} iframe — the configurator itself, in your brand (tenant logo & colors).config_id — the id of the customization, returned on add-to-cart. It is the thread that links the customer's design to the production order on the PrintWearAI side. Always keep it.Pass data-product="slug" (or data-sku) → opens directly on that model.
Pass no product → the iframe shows your branded header, category tabs (per sport, shown when there are ≥2 categories) and a product grid. Clicking a card opens the configurator on that product. This is the “generic button” entry — a single Design yours button that opens the choice panels.
Tenant workspace → Integration (/app/embed.html) → Public key (Embed Key) = fk_…. It is public — safe to expose in HTML.
<div id="pwai-configurator"></div>
<script src="https://printwearai.com/embed.js"
data-key="fk_YOUR_KEY"
data-autodetect="false"></script>
data-autodetect="false" + no data-product → the widget mounts the choice screen. (With auto-detect on, it would look for a SKU in your page — useless on a custom site.)
On a custom site there is no automatic add-to-cart (that is built in only for Shopify/Woo/Presta). Without a handler the widget just logs a warning. Register your own:
PwaiEmbed.onAddToCart(function (m) {
myCart.add({
name: m.product.name,
quantity: m.quantity,
sizes: m.sizes, // { "M": 3, "L": 2, ... }
preview: m.customization.thumbnail, // finished jersey image URL
pwai_config_id: m.config_id // ⚠️ keep it → link to production
});
// handle your own payment / redirect
});
Most robust: a button that opens the /e/{key} iframe in a modal. With no ?product=, it opens on the catalog.
<button id="btn-create">🎨 Design yours</button>
<div id="pwai-modal" style="display:none;position:fixed;inset:0;z-index:9999;background:rgba(0,0,0,.6)">
<div style="max-width:1100px;height:90vh;margin:5vh auto;background:#fff;border-radius:16px;overflow:hidden;position:relative">
<button onclick="document.getElementById('pwai-modal').style.display='none'"
style="position:absolute;top:10px;right:14px;z-index:2;border:0;background:#0002;border-radius:50%;width:32px;height:32px;cursor:pointer">✕</button>
<iframe id="pwai-frame" style="width:100%;height:100%;border:0"></iframe>
</div>
</div>
<script>
document.getElementById('btn-create').addEventListener('click', function () {
// No ?product= → the iframe opens the catalog (sport tabs + grid)
document.getElementById('pwai-frame').src = 'https://printwearai.com/e/fk_YOUR_KEY';
document.getElementById('pwai-modal').style.display = 'block';
});
</script>
embed.js loaded, PwaiEmbed.mount('#container') with no options opens the same choice screen; PwaiEmbed.mount('#container', { product_slug: 'x' }) opens a specific product. Advantage: your onAddToCart handler receives the event directly (with a hand-built iframe you'd have to listen to window.message yourself).
Targeted buttons alongside, if you want (direct product):
https://printwearai.com/e/fk_YOUR_KEY?product=home-jersey
The customer designs → the config is saved → the order lands in your workspace → Orders, with the production-ready SVG pattern export. You only handled your own cart/checkout.
The onAddToCart message is ephemeral — for your backend, use the tokenised exports:
| Need | How |
|---|---|
| Full order data (items, sizes, per-item texts/numbers/colors, buyer, totals) | GET /api/orders/{id}/host-export?token={order_token} — the URL arrives in the order.created webhook as export_url. Add &include=patron for the production SVG inline. |
| Full saved design data (config, preview, summary) | GET /api/embed/saved-design/{share_token} — from the design.saved webhook or the onDesignSaved callback. |
| Printable proof (bon à tirer) to approve before production | bat_url in both payloads → /bat.html?design={token} or /bat.html?order={id}&token={order_token} — print-ready page with preview, texts, colors and a signature block. |
| Raw pushed feed (no pull needed) | Set your webhook to full payload mode (Integration → Webhooks): order.created and design.saved then carry the complete raw data — config JSON, production pattern SVG, previews. The add_to_cart message also ships m.bat.zones (named zones + Pantone) and the design's export_url/bat_url (auto guest-save). |
| Design saved without an order (lead) | PwaiEmbed.onDesignSaved(fn) client-side + design.saved webhook server-side (includes guest_email when provided). |
data-* attributes| Attribute | Default | Role |
|---|---|---|
data-key | — | Required. Public key fk_… |
data-product | — | Product slug → direct open. Absent → choice screen |
data-sku | — | External SKU (resolved against the product's SKU / reference field) |
data-autodetect | true | "false" to disable SKU auto-detection (recommended on custom sites) |
data-target | #pwai-configurator | CSS selector of the host container |
data-height | 700 | Iframe height in px (auto-grows afterwards) |
data-locale | auto | fr / en / es / de / it |
data-config | — | Reopen a saved configuration by its id |
data-no-consent | false | "true" if you already handle cookie consent |
data-back-url | — | Adds a Back button in the configurator top bar linking to this URL (overrides the back-office setting in App → Integration) |
data-back-label | ← Retour | Label of the back/close button |
data-customer-token | — | SSO for AI Design. Your backend signs a token identifying YOUR logged-in customer — the widget unlocks AI generation for them (paid credits) without a separate widget account. See below. |
The AI Design feature requires an authenticated customer; every generation uses paid credits.
If your site already has customer accounts, sign a token server-side with your secret API key
(App → Integration → API) and pass it via data-customer-token:
// PHP (server-side only — NEVER expose your secret key in HTML)
$payload = base64_encode(json_encode([
'external_id' => $customer->id,
'email' => $customer->email,
'name' => $customer->name,
'exp' => time() + 3600, // valid 1 h
]));
$payload = rtrim(strtr($payload, '+/', '-_'), '='); // base64url
$token = $payload . '.' . hash_hmac('sha256', $payload, $API_SECRET);
// then: <script src=".../embed.js" data-key="fk_…" data-customer-token="<?= $token ?>">
Guests (not logged in on your site) are invited to create an account in the widget instead.
Without data-back-url (and no URL set in the back office), the button acts as
Close: your page receives PwaiEmbed.onClose(fn) — hide the widget,
close your modal, etc.
onAddToCart(m) payload| Field | Content |
|---|---|
m.config_id | Saved configuration id (keep it) |
m.product.name | Product name |
m.product.flare_id | Internal PrintWearAI id |
m.quantity | Quantity |
m.sizes | Size breakdown { "M": 3, "L": 2 } |
m.customization.thumbnail | Preview image URL |
m.sku | SKU if set |
data-sku)data-sku="JERSEY-001" triggers a call to /api/resolve, which looks up a product whose SKU / reference field equals JERSEY-001. The SKU must therefore be identical on both sides. Set it in Tenant workspace → Products → the product → SKU / reference, either manually or automatically via Integrations. Shortcut: use data-product="slug" instead — no SKU to sync.
For a server-side coupling (read a config back, create/track orders from your backend) use your secret key sk_… (≠ public key): endpoints /resolve, /configs, /orders. See Custom site (API).
data-key missing in console → you wrote data-embed-key; the widget only reads data-key.PwaiEmbed.onAddToCart(fn) (no automatic add outside Shopify/Woo/Presta).data-autodetect="false".m.config_id on your cart/order line.