---
title: "How to Let Customers Design Products and Get a Print-Ready File"
description: "Build an embeddable product customizer for React and Vue, with CMYK and bleed on export."
url: "https://img.ly/blog/product-customizer-print-ready-pdf/"
type: "blog"
date: "2026-11-12"
author: "Graeme"
tags: ["CE.SDK","How-To","Print","Web-to-print"]
---

> This is the markdown version of [How to Let Customers Design Products and Get a Print-Ready File](https://img.ly/blog/product-customizer-print-ready-pdf/). For all pages in one file, see [llms-full.txt](https://img.ly/llms-full.txt). For an index of all available pages, see [llms.txt](https://img.ly/llms.txt).

---

## What You Will Build: A Product Customizer With Print-Ready PDF Output

By the end of this guide, you'll have a product page where shoppers design a t-shirt, an 11 oz mug or an A6 postcard in the CE.SDK editor, all inside your own front end. While they're busy designing, print checks warn them about soft photos and text that is too close to the cut. Add to Cart saves the design with the cart item, and when the order is paid, a headless worker renders one PDF per print area in CMYK with bleed. The code then hands the files over to your fulfillment provider.

The complete code is in the [product customizer example repository on GitHub](https://github.com/imgly/blog-product-customizer).

This guide is aimed at developers and product leads at print-on-demand, merch, photo-product and web-to-print companies who want to add a design step to the product page and get a file that their printer can accept.

<div class="image-column">

![The Larkhollow Print t-shirt editor with a Good Vibes sunset design inside the dashed print area of a petrol shirt, a panel of seven shirt colors and an Add to basket button](https://img.ly/_astro/larkhollow-tshirt-editor.F3v_xwN2.webp)

</div>

_A shopper designing a t-shirt in [Larkhollow Print](https://kq0qod49.imgly.dev/), IMG.LY's sample print shop._

## Why Most Product Designer Tools Stop at the Preview

In reality, the shopper usually wants to see their photo on the mug before they pay for anything. The printer wants an accurate file at the exact finished size, artwork that runs past the cut line, color defined for ink, and every font and image inside the file.

Many designer tools serve the shopper first and leave parts of the print file to somebody else, such as CMYK color or the bleed, so prepress has to fix the order by hand.

Done looks like this: every order arrives with one PDF per print area at the spec's size, with bleed, in CMYK, checked before the print shop opens it.

Swiss Post's customers create more than a million personalized postcards a year on CE.SDK. [PostBuddy](https://img.ly/case-studies/postbuddy.md) launched its personalized direct mail platform on CE.SDK, and its A/B tests found a 4× gap between a plain and a polished postcard design.

## Product Customizer Options Compared: Storefront Apps, Iframe Designers, Embedded SDK

| Property                        | Storefront customizer app                                                                                     | Iframe designer                                                                                                   | Embedded SDK in your own front end                                                                                                                |
| ------------------------------- | ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| Print file output               | An RGB print file per order (PNG or JPG, some also PDF or SVG) at a resolution the merchant sets              | A PDF per order, rendered on the vendor's servers                                                                 | One PDF per print area, rendered by your own worker                                                                                               |
| CMYK output                     | Mostly RGB; a few apps offer CMYK for PDF or vector exports                                                   | CMYK PDF; some also offer ICC profiles and PDF/X-4                                                                | CMYK through IMG.LY's PDF/X exporter, with the profile set per product                                                                            |
| Bleed and safe area             | Bleed and a design-area guide in some apps; others leave it to how the merchant sizes the canvas              | A safe-zone guide in the editor; bleed in the PDF only where the template is set up for it                        | In the product spec: bleed written into the PDF, safe zone drawn in the editor                                                                    |
| Template locking                | Option-based: the shopper fills fields or moves elements only inside zones the merchant sets                  | Per element, set in the vendor's template tool                                                                    | Per block: what the shopper may replace, crop or retype, and nothing else                                                                         |
| Restyling to your brand         | Colors and fonts in the app's settings; custom CSS in some apps, white-label as a paid extra                  | Themes or custom CSS on the vendor's interface; full white-label on higher plans                                  | Full: your own interface around a white-label editor                                                                                              |
| Works in your framework         | Only inside its storefront platform                                                                           | Any page that loads the vendor's script or iframe, plus store plugins                                             | React, Vue, Angular, Svelte, Next.js and plain JavaScript                                                                                         |
| Mobile                          | Described as mobile-friendly, with no published browser or device list                                        | A mobile layout from the vendor (device detection, a reduced phone layout or a mobile-friendly theme)             | Mobile web for graphics editing (Chrome 114+ on Android and iOS, Safari 15.6+ on iOS 15+); native SDKs for iOS, Android, React Native and Flutter |
| Where the design data is stored | On the app vendor's hosting; the order holds links, and the vendor decides how long files are kept            | In the vendor's cloud; editable designs reopen only in the vendor's editor                                        | With you: the design is saved with your cart item                                                                                                 |
| Upload resolution checks        | A low-resolution warning or a minimum pixel size per upload, set by the merchant                              | A DPI warning in some designers; none documents blocking                                                          | A live DPI warning in the editor, against the spec                                                                                                |
| Where rendering runs            | The app vendor's service                                                                                      | The vendor's servers                                                                                              | Previews in the browser, print files in your worker                                                                                               |
| Maintenance you own             | Low to install; then options or a template for every product, and re-checks after theme changes               | Low with a store plugin, higher with a custom integration; a template per product in the vendor's tool either way | Higher: the component, the specs and the worker                                                                                                   |
| Pricing shape                   | Monthly subscription billed through Shopify: tiers capped by order volume, or a base fee plus a fee per order | Monthly subscription with included orders, then a fee per extra order; enterprise tiers by quote                  | SDK license, see [pricing](https://img.ly/pricing.md)                                                                                                              |

Storefront apps are the quickest to set up, but their files mostly come out in RGB. Iframe designers add CMYK PDFs; the embedded SDK also keeps the interface, the design data and the rendering in your own stack.

## Step 1: Define the Product Spec (Print Area, Safe Zone, Bleed, DPI)

Each product has one JSON file in `packages/core/specs/`. The editor, the print checks and the order worker all read the same file, so the page that the shopper designs on is the page that gets printed.

```json
// packages/core/specs/mug-11oz.json
{
  "id": "mug-11oz",
  "label": "11 oz mug",
  "printMethod": "Sublimation",
  "unit": "Millimeter",
  "dpi": 300,
  "outputProfile": "fogra39",
  "outputProfileNote": "Demo value. Sublimation printers often want RGB files: srgb keeps the boxes but is not PDF/X compliant.",
  "areas": [
    {
      "id": "wrap",
      "label": "Wrap",
      "trim": { "width": 200, "height": 85 },
      "bleed": 2,
      "safeZone": 5
    }
  ]
}
```

`trim` is the finished size of each print area, `bleed` is how far the artwork runs past it, and `safeZone` is how far inside it text stays. `dpi` is the minimum image resolution and the print file's resolution, `outputProfile` is the color profile that it's converted to, and `outputProfileNote` says why. The mug values are only demo values, so you'll need to use your print partner's template for best results.

The core turns each print area into a page. The page is the finished size, and the bleed is the page margin:

```typescript
// packages/core/src/spec.ts
export function applyPrintArea(
  engine: CreativeEngine,
  page: number,
  area: PrintArea
): void {
  engine.block.setName(page, area.id);
  engine.block.setWidth(page, area.trim.width);
  engine.block.setHeight(page, area.trim.height);
  engine.block.setBool(page, 'page/marginEnabled', area.bleed > 0);
  engine.block.setBool(page, 'page/safetyEnabled', area.safeZone > 0);
  for (const side of ['top', 'bottom', 'left', 'right'] as const) {
    engine.block.setFloat(page, `page/margin/${side}`, area.bleed);
    engine.block.setFloat(page, `page/safetyInset/${side}`, area.safeZone);
  }
}
```

In the print file, the exporter makes the page plus bleed the BleedBox and the page the TrimBox (step 6), and the verify script checks both. The safety inset draws the safe zone in the editor and doesn't make changes to the file.

## Step 2: Embed the Customizer in React and Vue

The shared logic has no framework code: `packages/core` holds the specs, checks and cart code, and `packages/editor` the editor setup. Each example is a thin wrapper around them. In React that is in `ProductCustomizer.tsx`:

```tsx
// examples/react/src/ProductCustomizer.tsx
export interface ProductCustomizerProps {
  productId: string;
  license?: string;
  userId?: string;
  cartEndpoint?: string;
  designUrl?: string;
  onAddToCart?: (item: CartItem) => void;
}
```

`productId` picks the spec, and `license` is your CE.SDK license key, which is needed because without one the editor runs with a watermark. `cartEndpoint` is where Add to Cart posts the design (step 5), and `designUrl` reopens a saved design instead of the template. `onAddToCart` receives the stored cart item, and the example page mounts it like this:

```tsx
// examples/react/src/index.tsx
<ProductCustomizer
  key={editing?.id ?? productId}
  productId={productId}
  license={license}
  userId={USER_ID}
  cartEndpoint={CART}
  designUrl={editing?.design.url}
  onAddToCart={handleAddToCart}
/>
```

The `key` makes React mount a fresh editor when the product changes because every product has its own scene. The Vue example mounts its `ProductCustomizer.vue` with the same props:

```vue
<!-- examples/vue/src/App.vue -->
<ProductCustomizer
  :key="editing?.id ?? productId"
  :product-id="productId"
  :license="license"
  :user-id="userId"
  cart-endpoint="/api/cart"
  :design-url="editing?.design.url"
  @add-to-cart="handleAddToCart"
/>
```

Vue passes the `add-to-cart` event to the component as the same `onAddToCart` prop.

<div class="image-column">

![The React example's 11 oz mug in the CE.SDK editor, with a photo slot and a name, next to a sidebar listing the 200 by 85 mm print area, 2 mm bleed, 5 mm safe zone and a passing print check](https://img.ly/_astro/mug-customizer-react-example.BydIpOsl.webp)

</div>

## Step 3: Lock the Template So Customers Only Edit What You Allow

Each product starts from a template in `packages/core/templates/` with named image and text slots per print area, displayed in millimeters. The core builds them in the Creator role, which ignores locks, switches every scope off, and turns on only what each kind of block allows:

```typescript
// packages/core/src/template.ts
const ALLOWED: Record<'page' | Slot['type'], Scope[]> = {
  // Adding a block to the print area is an edit of the page.
  page: ['editor/add'],
  image: ['editor/select', 'fill/change', 'layer/crop'],
  text: ['editor/select', 'text/edit', 'text/character', 'fill/change'],
};

export function handToShopper(engine: CreativeEngine): void {
  engine.editor.setRole('Adopter');
  for (const scope of engine.editor.findAllScopes()) {
    engine.editor.setGlobalScope(scope, 'Defer');
  }
}
```

The shopper can replace and crop the photo and retype the text, but they can't move, resize or delete the slots, or change the print area. Global scopes are set to Defer after the role change because setting the role resets them, and a global Allow would override the block locks.

Anything that the shopper adds, like their own text or a sticker, stays fully editable. Step 4 catches anything that goes wrong with it.

<div class="image-column">

![A Wish You Were Here postcard with its large photo slot selected, an upload strip below the card and only Replace and Crop offered for the slot](https://img.ly/_astro/larkhollow-postcard-photo-slot.CLjwzIhN.webp)

</div>

_A locked photo slot in Larkhollow Print, where the shopper can only replace or crop the photo._

## Step 4: Add Live Preflight Checks for Resolution and Print Area

`packages/core/src/preflight.ts` checks the design as it is and returns one warning per problem. It warns when a photo is below the spec's DPI, a block is past the print area, text is inside the safe zone, or a block reaches the cut line but stops short of the bleed. The resolution check divides the image's pixels by the size that it is drawn at, which is the frame size times the crop scale:

```typescript
// packages/core/src/preflight.ts
const drawnWidth =
  engine.block.getWidth(block) * engine.block.getCropScaleX(block);
const drawnHeight =
  engine.block.getHeight(block) * engine.block.getCropScaleY(block);
const dpi = Math.min(
  size.width / (drawnWidth / 25.4),
  size.height / (drawnHeight / 25.4)
);
if (dpi >= minDpi) return;
```

The pixel size comes from the image's source set, which is what the uploads carry, or from the image itself, read through the engine for a reopened design. The component reruns the checks on every change that gets made:

```tsx
// examples/react/src/ProductCustomizer.tsx
useEffect(() => {
  if (!cesdk) return;
  return watchPreflight(cesdk.engine, product.spec, setIssues);
}, [cesdk, product]);
```

In the example, a 480 by 320 pixel upload in the postcard's photo slot sends a warning at 73 DPI. The checks warn instead of blocking the action, and clicking a warning selects the block so you can see where the issue is.

<div class="image-column">

![Four print warnings on the postcard front: a photo at 73 DPI, text 1 mm from the edge, a sticker 9 mm past the print area and a shape 3 mm short of the bleed](https://img.ly/_astro/postcard-print-check-warnings.t_dC5bgG.webp)

</div>

## Step 5: Save the Design With the Cart Item

Add to Cart saves the scene as an archive, and that archive bundles the images that the design uses, including the shopper's uploads. This is important because until then, they only exist in the browser tab (which is not an ideal storage location). The worker renders the print file from it, and the shopper can reopen it again to keep editing. A PNG preview per print area goes with it:

```typescript
// packages/core/src/cart.ts
const archive = await engine.scene.saveToArchive();

const previews: Record<string, Blob> = {};
for (const page of engine.scene.getPages()) {
  // An editor may outline the page; the preview shows the print only.
  const stroke = engine.block.isStrokeEnabled(page);
  engine.block.setStrokeEnabled(page, false);
  try {
    const ratio = engine.block.getHeight(page) / engine.block.getWidth(page);
    previews[engine.block.getName(page)] = await engine.block.export(page, {
      mimeType: 'image/png',
      targetWidth: PREVIEW_WIDTH,
      targetHeight: Math.round(PREVIEW_WIDTH * ratio),
    });
  } finally {
    engine.block.setStrokeEnabled(page, stroke);
  }
}
```

`addToCart` posts the archive, the previews, the cart line and any open print warnings to the cart endpoint, all in one request. If warnings are open, the shopper needs to confirm them first. This is the cart item that the example's endpoint stored for a postcard:

```json
{
  "id": "3b27df4d-300c-41f0-b761-22ff594d993f",
  "createdAt": "2026-10-13T06:58:03.026Z",
  "productId": "postcard-a6",
  "color": "white",
  "quantities": { "A6": 1 },
  "totalQuantity": 1,
  "totalPrice": 1.49,
  "design": {
    "url": "/api/cart/3b27df4d-300c-41f0-b761-22ff594d993f/design",
    "bytes": 2248275
  },
  "previews": {
    "front": "/api/cart/3b27df4d-300c-41f0-b761-22ff594d993f/preview/front",
    "back": "/api/cart/3b27df4d-300c-41f0-b761-22ff594d993f/preview/back"
  },
  "preflight": []
}
```

<div class="image-narrow">

![Larkhollow Print's proof step for an A6 postcard: no print check problems, a PDF/X-4 print file that passes its check, a quantity picker and an approval checkbox before Add to basket](https://img.ly/_astro/larkhollow-proof-step.RYycSYRA.webp)

</div>

_The proof step in Larkhollow Print, before the basket._

## Step 6: Render a Print-Ready PDF on Order With the Headless Engine

`worker/src/render.ts` opens the saved archive in the headless engine, `@cesdk/node`, and exports one print file per print area with `exportPDFX` from IMG.LY's [PDF/X exporter](https://img.ly/docs/cesdk/node/plugins/print-ready-pdf-iroalu.md), `@imgly/pdfx-exporter`:

```typescript
// worker/src/render.ts
await engine.scene.loadFromArchiveURL(archiveUrl, false, true);
const spec = getSpec(engine.block.getMetadata(engine.scene.get()!, 'product'));
await mkdir(outDir, { recursive: true });

const files: PrintFile[] = [];
for (const area of spec.areas) {
  const [page] = engine.block.findByName(area.id);
  // The editor outlines the print area; the print file must not.
  engine.block.setStrokeEnabled(page, false);
  const pdf = await exportPDFX(engine, {
    blocks: [page],
    outputProfile: spec.outputProfile,
    title: `${spec.label}, ${area.label}`,
  });
  const file = `${area.id}.pdf`;
  await writeFile(join(outDir, file), Buffer.from(await pdf.arrayBuffer()));
  files.push({
    areaId: area.id,
    file,
    bytes: pdf.size,
    trim: area.trim,
    bleed: area.bleed,
  });
}
```

`exportPDFX` exports the page with its bleed in any scene layout and converts it to CMYK with the spec's profile, as PDF/X-4 by default. The example uses FOGRA39 for all three products, but DTG and sublimation shops often want RGB files: `outputProfile: 'srgb'` gives an RGB file with the same boxes, which is not PDF/X compliant. Larkhollow saves its t-shirts as 300 DPI PNGs.

The exporter needs Node.js 22 or later. In the example, running on Windows, an order took 3.3 to 5.4 seconds including the engine start.

## Step 7: Connect the Order Endpoint to Your Fulfillment

Next, you can call the order endpoint when the payment goes through, with the cart item id from step 5 and your own order id as `orderRef`. Abandoned carts never expend a render, and a repeated call doesn't print twice. In the example, the cart panel's Place order button is the equivalent of your checkout.

```typescript
// worker/src/server.ts
const { cartItemId, orderRef } = JSON.parse(await readBody(req));

// A webhook that fires twice, or a retry after a timeout, must not print
// the order twice. Only a failed render is tried again.
const existing = (await allOrders()).find(
  (o) =>
    o.orderRef === orderRef &&
    o.cartItemId === cartItemId &&
    o.status !== 'failed'
);
if (existing) return sendJson(res, 200, existing);
```

When the files are ready, the worker posts a handoff to `FULFILLMENT_URL`. A test webhook received this for a postcard:

```json
{
  "orderRef": "1043",
  "productId": "postcard-a6",
  "quantities": { "A6": 1 },
  "files": [
    {
      "areaId": "front",
      "url": "https://files.example.com/api/orders/2670b7ed-8918-40f3-b8e0-408e6cfe69ba/files/front.pdf",
      "trim": { "width": 148, "height": 105 },
      "bleed": 3
    },
    {
      "areaId": "back",
      "url": "https://files.example.com/api/orders/2670b7ed-8918-40f3-b8e0-408e6cfe69ba/files/back.pdf",
      "trim": { "width": 148, "height": 105 },
      "bleed": 3
    }
  ]
}
```

**Your own checkout.** You can call the order endpoint from the code that shows that an order was paid, and you can do this once per customized line item. You can also keep the design archive with the order, and a reprint requires another render of it.

**A commerce platform that sends order webhooks.** Save the cart item id in the line item's custom data, and on the order-paid webhook you'll need to verify the signature and call the order endpoint with the platform's order id as `orderRef`.

**A print partner with an order API.** Point the `FULFILLMENT_URL` at a small adapter that turns the handoff into your partner's order format. Serve the files from storage that hands out expiring links instead of from the worker.

**Your own production floor.** Send the handoff to your production system, or write the PDFs straight into a prepress hot folder. The trim and bleed values in the handoff let you check each file against the job ticket.

Each order carries the print warnings that the shopper accepted. You will need to decide if a warning warrants holding the order for review, but the example doesn't have this step: it just sends the print.

## How to Verify the PDF Is Print-Ready

Check the files before the first order goes to a printer, and again when you change a spec or upgrade CE.SDK. The repo's preflight script checks every print file of an order against the product spec:

```bash
npm run verify -- 2cbcac7e-55c0-423b-a40c-2f6def6eef82
```

<div class="image-column">

![The verify script's output for a postcard order: front.pdf and back.pdf each pass the PDF/X, page size, color, fonts and PDF/X rules checks](https://img.ly/_astro/verify-script-postcard-order.DEI1edrD.webp)

</div>

_The preflight on a postcard order: both print files pass all five checks._

- **PDF/X.** The XMP metadata names the PDF/X version, and the output intent carries the ICC profile set by the spec's `outputProfile`.
- **Page size.** The TrimBox is the finished size, and the BleedBox adds the bleed on every side. The engine snaps pages to whole device pixels, which the exporter's docs put at up to 0.5 points at 300 DPI (148.17 by 105.16 mm for a 148 by 105 mm card), so the script allows 0.25 mm.
- **Color.** No RGB color space or color operator is anywhere on the page, including inside text glyphs, and the photo comes out as a CMYK image.
- **Fonts.** Every font is embedded. In the example's files, text comes out as Type 3 fonts where the glyphs are vector outlines.
- **PDF/X rules.** `checkPDFX` from the exporter checks the file against its PDF/X standard.

The script fails on bad files too. A plain engine export, which is what the editor's download link makes, fails four of the five checks, and an `srgb` export fails the color check and `checkPDFX`.

<div class="image-column">

![The postcard's front.pdf rendered with its TrimBox at 148 by 105 mm in blue and its BleedBox 3 mm outside it in red, with the photo running to the bleed edge](https://img.ly/_astro/postcard-front-trim-and-bleed-boxes.CuicYg6L.webp)

</div>

_The postcard's front.pdf rendered at 300 dpi. The TrimBox (blue) and BleedBox (red) are drawn from the values in the file. The photo runs to the edge of the bleed._

IMG.LY's [exporter docs](https://img.ly/docs/cesdk/node/plugins/print-ready-pdf-iroalu.md) suggest checking every exported file with `checkPDFX` against the standard it was exported to, which is the script's fifth check.

## When Not to Use an Embedded Product Customizer

**You sell through a hosted storefront and personalize a few products.** If shoppers only add a name or a photo to a handful of items then a storefront personalization app is quicker to set up, and you don't have to run a worker.

**Your print partner hosts the designer and takes the file from there.** If your partner offers its own designer and prints straight from it, you get a file it already accepts without building a render pipeline.

**The shopper doesn't design anything.** If every order is a fixed layout with variable data, like names and numbers for team shirts, then render the files in a batch from a template and a data file instead of putting an editor on the product page.

## FAQ: Product Customizers and Print-Ready Files

### Does the shopper need an account?

No. The design is saved with the cart item, and its id is all the worker needs. Sign-in is your shop's decision for completing the transaction.

### Where does the design live between design and order?

Within the cart item is the scene archive, a preview per print area and the open warnings. The example keeps them in folders but a real online store uses its database and object storage.

### Can I use my own fulfillment or print partner?

Yes. The worker posts a handoff with the file URLs, trim and bleed to any webhook you set, and the color profile is a field in each product spec.

### Which frameworks are supported and is the API the same in each?

The repo has React and Vue examples, and both pass the same properties to a component built on the same framework-free packages. CE.SDK's web editor also supports Angular, Svelte, SvelteKit, Next.js, Nuxt.js and plain JavaScript.

### Does it work on Shopify or WooCommerce?

This build targets your own front end. On a commerce platform, save the cart item id on the line item and call the order endpoint from the platform's order webhook (step 7).

### How do I stop low-resolution uploads?

The editor's resolution check warns as soon as a photo prints below the spec's DPI. To block instead of warn, disable Add to Cart while a resolution warning is open. IMG.LY's sample print shop, [Larkhollow Print](https://kq0qod49.imgly.dev/), works this way: Add to basket refuses a design while a print check finds an error, and lets warnings through.

<div class="image-narrow">

![Larkhollow Print's print check dialog listing a low-resolution error and a placeholder-text warning for the unchanged message, with a note that errors must be fixed before the postcard goes in the basket](https://img.ly/_astro/larkhollow-print-check-dialog.CtjvdXX_.webp)

</div>

### Can the shopper come back and edit a saved design?

Yes. Pass the saved archive's URL as `designUrl`, and the customizer reopens it with the uploads, text and locks. The cart's Edit design link does this.

## Glossary: Print Customizer Terms

**Bleed.** Artwork that runs past the finished edge so no white shows after cutting. Written into the PDF as the BleedBox.

**Safe area.** The band inside the finished edge where text shouldn't go, because cutting can drift. The spec calls it the safe zone.

**Print area.** The part of a product that gets printed, such as a shirt front or a mug wrap. Each one is a page in the scene.

**DTG.** Direct to garment: printing onto fabric with an inkjet, with nothing cut afterwards, which is why the t-shirt has no bleed.

**Cart payload.** What Add to Cart sends: the cart line, the design archive, a preview per print area and the open print warnings.

**Placeholder.** A template slot the shopper fills, such as the photo that Replace swaps out. See [placeholder](https://img.ly/glossary/placeholder.md).

**Scene.** CE.SDK's document: the pages, blocks and settings of one design. See [scene](https://img.ly/glossary/scene.md).

**Headless engine.** CE.SDK's engine without the editor interface, here rendering print files on a server. See [headless mode](https://img.ly/glossary/headless-mode.md).

## Next Steps

Clone the [product customizer repository on GitHub](https://github.com/imgly/blog-product-customizer) and run the React or Vue example and the worker with your own license key. To see the same flow in a working shop first, design a t-shirt or a postcard in the [Larkhollow Print demo store](https://kq0qod49.imgly.dev/). The [PDF/X exporter](https://img.ly/docs/cesdk/node/plugins/print-ready-pdf-iroalu.md) and [T-Shirt Designer](https://img.ly/docs/cesdk/js/starterkits/t-shirt-designer-jwinqr.md) docs cover the export and the kit this build starts from. To try CE.SDK before you have a key, you can [get started](https://img.ly/get-started.md) straight away, with no sign-up or trial key required.

---

## More Resources

- **[IMG.LY Website](https://img.ly/index.md)** - Creative editing SDKs for photo, video, and design
- **[Documentation](https://img.ly/docs/cesdk/)** - CE.SDK developer documentation
- **[Build with a coding agent](https://img.ly/SKILL.md)** - A skill that adds a CE.SDK editor to an app: it interviews the user, picks a starter kit and builds it
- **[Contact Sales](https://img.ly/forms/contact-sales.md)** - Get a custom quote. A public JSON API accepts the request directly, no account or key needed. Ask your user for consent and their details first.
