The host no longer carries a runtime product catalog. products.js shrinks to a legacy single-product stub; the canonical catalog lives in template-manager's productsStore, seeded ONCE on first boot from the new seedProducts.js (after that the store under templates/ is authoritative and the seed is ignored — intentional, so restarts never rewrite an admin-managed catalog). phoneModels.js and templateCategories.js are the supporting constant sets, and docs/template-schema.md documents the v4 schema. templates/ is the admin-managed store itself (_products_index.json + products/<id>/product.json, JSON only) — committed so the configured catalog (sizes, pricing, options built up in the admin UI) survives a fresh checkout rather than regressing to the seed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
7.8 KiB
7.8 KiB
Template JSON Schema
This document describes the template structure for creating custom t-shirt design templates.
Note (2026-07-06): this doc predates the goods-editor extraction and the v4 multi-view template schema. The authoritative element schema lives in
goods-editor-module/CLAUDE.md("Element schema") and the accepted payload shape intemplate-manager/src/server/lib/validation.js. The property tables below are still accurate for the fields they list.
Template Structure
{
"id": "string (unique identifier)",
"name": "string (display name)",
"category": "string (Sports|Music|Quotes|Animals|Abstract|Vintage|Nature|Tech)",
"description": "string (short description)",
"elements": [
{
"type": "text" | "image",
"text": "string (for text elements only)",
"x": "number (x position in canvas units)",
"y": "number (y position in canvas units)",
"fontSize": "number (for text elements)",
"fontFamily": "string (Google Font name)",
"fill": "string (hex color)",
"rotation": "number (degrees, -180 to 180)",
"width": "number (for image elements)",
"height": "number (for image elements)",
"src": "string (image URL or path)"
}
]
}
Element Properties
Text Element
| Property | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Must be "text" |
text |
string | Yes | The text content to display |
x |
number | Yes | X position on canvas (0-300) |
y |
number | Yes | Y position on canvas (0-300) |
fontSize |
number | Yes | Font size in pixels (12-120) |
fontFamily |
string | No | Google Font name (default: "DM Sans") |
fill |
string | No | Hex color (default: "#0f172a") |
rotation |
number | No | Rotation in degrees (default: 0) |
arc |
number | No | Curve amount (default: 0 = flat). > 0 arcs up (rainbow), < 0 arcs down (smile). See "Curved text" below. |
align |
string | No | left | center | right (default: center; multi-line) |
lineHeight |
number | No | Line-box ratio of fontSize (default: 1.2) |
letterSpacing |
number | No | Absolute tracking in canvas units (default: 0; suspended while curved) |
Curved text (arc)
- The editor's Curve slider writes values in −100..100; hand-authored templates should stay in that range.
- The stored value up to ±200 is honored by the renderer and the print
exporter (
ARC_MODEL_MAXin the goods-editor module, 2026-07-06): the editor itself derives values above 100 when preserving an authored curve on long customer text whose font size auto-shrank. Don't hand-author above ±100 — the headroom exists for derived values. - What is visually preserved across customer edits is the curve's
density (
arc · fontSize / width), not the raw number: the editor rescalesarcas text, font, or size changes so the perceived sweep the template author designed stays constant. Expect saved customer designs to carry arc values that differ from the authored one. letterSpacinghas no effect while|arc| > 0.5(suspension rule); the stored value survives and reapplies when the text goes flat.
Image Element
| Property | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | Must be "image" |
src |
string | Yes | Image URL or server path |
x |
number | Yes | X position on canvas (0-300) |
y |
number | Yes | Y position on canvas (0-300) |
width |
number | Yes | Width in canvas units (min: 20) |
height |
number | Yes | Height in canvas units (min: 20) |
rotation |
number | No | Rotation in degrees (default: 0) |
Advanced Template Features
Template with Background and Overlay
For templates that include background images and overlay elements:
{
"id": "team-sport",
"name": "Team Sport",
"category": "Sports",
"description": "Classic team jersey with number and text",
"background": {
"type": "color" | "image",
"color": "string (hex, if type is color)",
"src": "string (URL/path, if type is image)"
},
"overlay": [
{
"type": "text" | "image",
"nonPrintable": false,
"...": "same properties as regular elements"
}
],
"elements": [...]
}
Slot-Based Templates
For templates with image slots (auto-crop regions):
{
"id": "classic-tee-front",
"name": "Classic Tee - Front",
"slots": [
{
"id": "chest-logo",
"bounds": { "x": 100, "y": 80, "width": 100, "height": 100 },
"aspectRatio": 1.0,
"label": "Chest Logo"
},
{
"id": "sleeve-left",
"bounds": { "x": 20, "y": 100, "width": 60, "height": 60 },
"aspectRatio": 1.0,
"label": "Left Sleeve"
}
],
"elements": []
}
Slot Crop Region
When an image is assigned to a slot, the crop property is applied:
{
"type": "image",
"src": "/uploads/image.png",
"x": 100,
"y": 80,
"width": 100,
"height": 100,
"crop": {
"sx": 0,
"sy": 0,
"sWidth": 500,
"sHeight": 500
}
}
| Property | Type | Description |
|---|---|---|
sx |
number | Source x coordinate for crop |
sy |
number | Source y coordinate for crop |
sWidth |
number | Source width for crop |
sHeight |
number | Source height for crop |
Non-Printable Elements
Elements can be marked as non-printable to exclude them from export:
{
"type": "text",
"text": "Guide Text",
"nonPrintable": true,
"x": 150,
"y": 150,
"fontSize": 12,
"fill": "#cccccc"
}
Example Templates
Minimal Quote Template
{
"id": "minimal-quote",
"name": "Minimal Quote",
"category": "Quotes",
"description": "Simple centered quote design",
"elements": [
{
"type": "text",
"text": "\"Be the change\"",
"x": 150,
"y": 130,
"fontSize": 24,
"fontFamily": "Georgia",
"fill": "#1e293b",
"rotation": 0
},
{
"type": "text",
"text": "you wish to see",
"x": 150,
"y": 160,
"fontSize": 18,
"fontFamily": "Arial",
"fill": "#64748b",
"rotation": 0
}
]
}
Team Sport Template
{
"id": "team-sport",
"name": "Team Sport",
"category": "Sports",
"description": "Classic team jersey with number and text",
"elements": [
{
"type": "text",
"text": "TEAM NAME",
"x": 75,
"y": 80,
"fontSize": 28,
"fontFamily": "Impact",
"fill": "#ffffff",
"rotation": 0
},
{
"type": "text",
"text": "23",
"x": 150,
"y": 150,
"fontSize": 72,
"fontFamily": "Impact",
"fill": "#ffffff",
"rotation": 0
}
]
}
Canvas Coordinate System
- Canvas size: 300x300 units (preview)
- Export size: 4500x4500 pixels (300 DPI, 15"x15")
- Scale factor: 15x (export / preview)
- Origin (0,0): Top-left corner
- X increases: Left to right
- Y increases: Top to bottom
Best Practices
- Keep text readable: Use font sizes between 18-72px for main text
- Respect print area: Keep elements within 0-300 canvas bounds
- Use contrasting colors: Ensure text is visible on shirt colors
- Test at export size: Verify designs look good at 4500x4500px
- Limit elements: 5-10 elements maximum for performance