A customizable merch catalog that merges a locked master design, per-customer brand overrides, and per-product catalog data into personalized print plates and mockup previews. This page explains the three-layer model, how to run the Vite demo, batch-render the catalog, and extend customers or products.
Built with CreativeEditor SDK (CE.SDK) by IMG.LY.
- Three-layer merge: Master, customer, and product stay separated so brand rules and SKU data do not overwrite each other.
- Functional
extends: The customerextendsvalue selects the master template thatresolveMasterTemplateloads. It is not metadata alone. - Print vs mockup split: Print is ink-ready artwork. Mockup is the sales/preview composition on a product backdrop.
- Fail-closed licensing: Headless render throws if a license is missing or rejected, unless you explicitly allow evaluation mode.
- Demo UI: The Vite app lets you select a customer, run the same batch path as the CLI, and browse result tiles.
- One master change (such as
masterOnly.complianceText) can propagate to every customer mockup on the next render without editing customer files. - One customer logo change affects only that customer’s outputs.
- Product copy and artwork placement scale across the catalog without polluting brand overrides.
Template inheritance keeps brand control and product scale in separate layers. The master owns locked layout and a master-only compliance footer. Each customer file overrides only brand fields (logo, palette, contact, legal) and points at the master through an extends field. Each product supplies campaign copy, a heroImage, and an artworkLocation.
At resolve time, resolveScene in src/resolve.ts loads the master named by extends, applies the customer override, re-applies master-only compliance so customers cannot override it, then applies product data. The batch renderer walks every SKU for a chosen customer and writes two PNG files per print area.
| Output | Role |
|---|---|
print.png |
Production plate. Contains HeroImage only, sized to artworkLocation. It excludes BrandLogo, headline, body, CTA, contact, legal, and master compliance. |
mockup.png |
Marketing preview. Contains the full composition (logo, palette, hero, copy, contact, legal, compliance) composited onto the product photo. |
templates/master.json → locked layout + masterOnly.complianceText
templates/customers/*.json → logo, palette, contact, legal (via extends)
catalog/brand-campaign.json → default HeroImage campaign art per customer id
catalog/products.json → heroImage, headline/body/cta, artworkLocation (50 SKUs)
src/resolve.ts → merge master → customer → product
scripts/render-catalog.ts → headless Node batch (@cesdk/node)
Before you install or run the project, confirm you have:
- Node.js 20+ and npm (
engines.nodeinpackage.json) - A CE.SDK license key from the IMG.LY free trial form
- The included assets under
public/(brand images, product mockups, and engine assets as shipped in this repository)
- Install dependencies.
- Copy the environment example file to
.env. - Paste your CE.SDK license key into
.env.
npm install
cp .env.example .envLicense keys belong in a local .env file or your shell environment. .env is gitignored.
| Variable | Used by |
|---|---|
CESDK_LICENSE |
Preferred by headless npm run render:catalog |
VITE_CESDK_LICENSE |
Fallback for the batch renderer; available if a browser engine path is added |
# .env
VITE_CESDK_LICENSE=your_key_here
# or
CESDK_LICENSE=your_key_hereIf the license is missing or CreativeEngine.init({ license }) fails, the batch renderer throws. It does not silently continue in evaluation mode.
To allow watermarked evaluation output on purpose:
npm run render:catalog -- --customer bean-there-bean-good --allow-evaluation-modeThe Vite API accepts the same option as JSON: { "allowEvaluationMode": true }. In the demo UI, you can append ?allowEvaluationMode=1 to the page URL.
Warning: Evaluation mode produces watermarked exports. Use a valid license for review or production-like runs.
- Start the Vite development server:
npm run dev- Open the local URL printed in the terminal (often
http://localhost:5173). - Select a customer in the Select customer section.
- Click Render catalog.
- Review the print and mockup tiles in the results grid.
Render catalog calls POST /api/render-catalog, which runs the same headless path as the CLI. Result images are served from /output/….
To render only the first two products while testing the UI, open the app with /?limit=2.
| Number | Meaning |
|---|---|
| 50 products | SKUs in catalog/products.json |
| 65 areas | Print locations (some SKUs have front and back) |
| ~130 images | Up to two PNGs per area |
- Ensure
.envcontains a valid license key. - Run the batch for one customer id.
- Inspect files under
output/catalog/<customer-id>/.
npm run render:catalog -- --customer bean-there-bean-goodnpm run render:catalog -- --customer bean-there-bean-good --limit 3 --areas first
npm run render:catalog -- --customer scoop-there-it-is --out output/scoop-runAvailable customers: bean-there-bean-good, scoop-there-it-is, bun-intended.
| Flag | Meaning |
|---|---|
--customer <id> |
Required. Stem of templates/customers/<id>.json |
--out <dir> |
Output root (default: output/catalog) |
--color <id> |
Product color id for {{color}} mockup tokens |
--limit <n> |
First n products only |
--areas first|all |
First area only, or every area (default: all) |
--allow-evaluation-mode |
If licensed init fails (or no key), retry without a license |
output/catalog/<customer-id>/<product-id>/<area-id>/
print.png # HeroImage-only plate at artworkLocation size
mockup.png # marketing composition + product backdrop
resolved.json
output/catalog/<customer-id>/manifest.json
Customer files hold brand data only. Keep product copy and hero artwork out of this layer.
- Create
templates/customers/<id>.jsonwith brand fields and"extends": "../master.json". - Place logo and related assets under
public/so paths such as/images/logo-acme.pngresolve. - Optionally add campaign art for the customer in
catalog/brand-campaign.jsonunderbyCustomerId. - Register the customer import in
src/app/customer-catalog.ts. - Confirm
extendsis registered inMASTER_TEMPLATES_BY_EXTENDSinsidesrc/resolve.ts(default:../master.json). - Render a short smoke batch for the new customer.
{
"id": "acme-merch",
"name": "Acme Merch Co.",
"extends": "../master.json",
"variables": {
"brandName": "Acme Merch Co.",
"logoUri": "/images/logo-acme.png",
"primaryColor": "#111111",
"secondaryColor": "#F5F0E8",
"contactName": "Acme Merch Co.",
"contactPhone": "+1 (555) 010-9999",
"contactEmail": "hello@acme.example",
"contactAddress": "1 Market St, San Francisco, CA",
"legalDisclaimer": "© Acme Merch Co. All rights reserved."
}
}Warning: Do not put
heroImage,headline,body,cta, or master-only compliance fields in a customer file. Those campaign fields belong on the product (or brand-campaign map) so one brand does not freeze the same art and copy across every SKU. Compliance stays on the master so customers cannot override shared legal/policy text.
npm run render:catalog -- --customer acme-merch --limit 2 --areas first- Append an entry to the
productsarray incatalog/products.json. - Include
artworkLocation, aheroImageobject withuri,width, andheight(forsetSourceSet), andheadline,body, andcta. - Add mockup PNGs under
public/if you wantmockup.pngoutput. - Update
countincatalog/products.jsonif you maintain that field. - Smoke-test with a limited batch render.
npm run render:catalog -- --customer bean-there-bean-good --limit 1 --areas firstThe shared design lives in templates/master.json (sceneString plus metadata).
- Locked brand structure: logo slot, palette swatches, contact block, legal line
- Master-only compliance footer (
MasterCompliance/masterOnly.complianceText), which must not appear in customer files - Placeholders for
HeroImageand campaign text variables (headline,body,cta) filled from the product layer
| Change | Propagates when you re-render? | Edit customers? |
|---|---|---|
masterOnly.complianceText |
Yes — all customers’ mockups | No |
Locked layout / sceneString |
Yes | No |
Customer logoUri / colors |
Only that customer | Yes (that file only) |
| Product headline / hero | Only that SKU | Edit catalog/products.json |
exportModes.print documents the HeroImage-only plate. exportModes.marketing documents the full composition. Print exports omit BrandLogo on purpose: brand chrome belongs on mockups, not on the production ink plate.
After you change the master:
npm run render:catalog -- --customer bean-there-bean-good --limit 3 --areas first| Command | Purpose |
|---|---|
npm run dev |
Vite UI and /api/render-catalog |
npm run render:catalog -- --customer <id> |
Headless batch render |
npm run test:unit |
Smoke: one product licensed render and print size check |
npm run check:all |
Typecheck, format, and lint |
Demo code in this repository is provided for evaluation with CE.SDK. CE.SDK requires an IMG.LY license. Do not commit .env.