Why go headless for personalization
Three reasons personalization stores go headless: (1) the theme layer's JavaScript sandboxing limits the frame-rate + memory-budget for live product preview canvases — a headless React or Hydrogen storefront can push 60fps WebGL previews that theme-layer JS cannot, (2) native mobile apps need a data-source that isn't a themed HTML page, and (3) international multi-brand deployments (one Shopify backend, five brand storefronts on different domains) require headless architecture.
The four headless stacks in 2026
| Stack | Best for | Hosting | Complexity |
|---|---|---|---|
| Hydrogen + Oxygen | Shopify-native, best DX | Shopify Oxygen (free with Plus) | Medium |
| Next.js + Vercel | Existing Next apps, ecosystem | Vercel, Netlify, self-hosted | Medium |
| Nuxt / SvelteKit | Non-React teams | Any | Medium |
| Native mobile (Swift/Kotlin) | iOS/Android apps | App Store / Play Store | High |
Storefront API for personalization variants + options
All storefront reads (products, variants, options, metafields) go through the Storefront GraphQL API. Auth via public access token (browser-safe) or private access token (server-side, better rate limits).
query ProductWithOptions($handle: String!) {
product(handle: $handle) {
id
title
options { name values }
variants(first: 100) {
nodes {
id
title
price { amount currencyCode }
selectedOptions { name value }
metafield(namespace: "personalization", key: "max_chars") {
value
}
}
}
}
}
Line-item properties for personalization data
Personalization data (custom text, uploaded photo URL, chosen font) attaches to cart lines as line-item properties. These persist through checkout to the order:
mutation AddToCart($cartId: ID!, $line: CartLineInput!) {
cartLinesAdd(cartId: $cartId, lines: [$line]) {
cart { id lines(first:50) { nodes { id merchandise{...on ProductVariant{title}} attributes{key value} } } }
}
}
Variables:
{
"cartId": "gid://shopify/Cart/...",
"line": {
"merchandiseId": "gid://shopify/ProductVariant/40123",
"quantity": 1,
"attributes": [
{ "key": "Custom text", "value": "Happy Birthday Sarah" },
{ "key": "Font", "value": "Playfair Display" },
{ "key": "_photo_url", "value": "https://cdn.your-app.com/uploads/abc123.jpg" },
{ "key": "_dpi", "value": "300" }
]
}
}
Keys starting with underscore (_photo_url) are hidden from the storefront cart display but persist on the order — used for POD partner fulfillment metadata.
Live preview architecture (headless React)
The killer feature of headless personalization is a native React canvas that updates in real time as the customer types. Instead of theme-layer inline scripts, you use a proper React component with useEffect + Canvas API:
function LivePreview({ text, font, color, uploadedImage }) {
const canvasRef = useRef(null);
useEffect(() => {
const ctx = canvasRef.current.getContext('2d');
ctx.clearRect(0, 0, 800, 800);
if (uploadedImage) {
const img = new Image();
img.onload = () => ctx.drawImage(img, 100, 100, 600, 400);
img.src = uploadedImage;
}
ctx.font = `48px "${font}"`;
ctx.fillStyle = color;
ctx.fillText(text, 400, 700);
}, [text, font, color, uploadedImage]);
return <canvas ref={canvasRef} width={800} height={800} />;
}
Cart Transform in a headless world
Cart Transform Functions still run server-side in Shopify's Functions runtime — you write them the same way whether your storefront is themed or headless. The difference: your storefront's cart mutations trigger the function at checkout. See Cart Transform Functions guide for the full pattern.
Checkout in headless
Two options: (1) redirect to Shopify-hosted checkout (default, easiest, less UX control), or (2) use Checkout Kit (Plus feature) for embedded checkout in your headless storefront. Most personalization stores use option 1 because the personalization UX happens on the product page — checkout can be Shopify-hosted with full metadata preserved.
File uploads for personalization photos
Photo-upload personalization needs a file storage layer. Shopify's Files API supports uploads via Storefront API since 2024-10, or you can upload directly to Cloudinary / S3 and pass the URL as a line-item property. Cloudinary is preferred for on-the-fly image transformations (background removal, DPI upscale, aspect crop) that you'd otherwise implement yourself.
Frequently asked questions
What is headless Shopify for personalization?
A Shopify architecture where you build the storefront in React/Vue/Svelte (or native mobile) instead of using Shopify's theme layer. Shopify handles commerce (checkout, orders, inventory) via Storefront API; you handle all UI. Best for personalization stores that need high-fidelity live-preview canvases or native mobile apps.
Should I use Hydrogen or Next.js for headless Shopify?
Hydrogen if you're starting fresh — best Shopify DX, free hosting on Oxygen (with Plus), Shopify-native. Next.js if you have an existing Next codebase or need Vercel-specific features (edge functions, ISR).
How do I add personalization data to a headless Shopify cart?
Use line-item properties via the cartLinesAdd mutation. Attach personalization data as attributes: [{key, value}]. Underscore-prefixed keys are hidden from cart display but persist on the order.
Can I use Cart Transform Functions in headless Shopify?
Yes — Cart Transform runs server-side in Shopify's Functions runtime, independent of your storefront architecture. Your headless cart mutations trigger it at checkout the same way theme-based storefronts do.
How does live product preview work in headless?
Build a React canvas component that watches personalization state (text, color, uploaded image) and re-renders on change. WebGL for 3D, Canvas API for 2D. Much higher performance than theme-layer inline JS.
Where should I store photo uploads in headless personalization?
Shopify Files API (native, integrated) or Cloudinary (better for on-the-fly transformations). Pass the URL as a line-item property; POD partner fetches at fulfillment.
Does Shopify Storefront API support personalization option metafields?
Yes — query metafields on variants with metafield(namespace: "personalization", key: "max_chars"). Metafield definitions with the STOREFRONT_ACCESS setting expose them to the Storefront API.
Should I use Shopify-hosted checkout or Checkout Kit for headless?
Shopify-hosted checkout for most cases — highest conversion, PCI compliance handled, personalization data preserved via line-item properties. Checkout Kit (Plus) only when you need embedded checkout inside your app.
Related reading
- Storefront API for variants — GraphQL reference
- Admin GraphQL for variants — server-side mutations
- Cart Transform Functions guide — pricing logic
- Custom app for options — app architecture
Try it free on Shopify
Print It My Way's permanent Free plan integrates with headless Hydrogen and Next.js storefronts — same personalization API for themed and headless.
Install Print It My Way