AI and templates
How AI is wired into the template system — component generation, page editing, landing pages, the validation and repair pipeline, and the kit's agent pack.
AI touches the template system at four points: generating custom components, editing whole pages, generating product landing pages, and AI-assisted authoring in the kit. This page maps those surfaces for people building tooling on top of them. General AI API concerns (auth, billing, model routing) live in the AI API docs.
All server endpoints below are authenticated RPC actions under
/api/v1/actions/... (POST, except the GET job-status poll), permission-checked per
store — component generation requires template write access (store_templates.write or
template_components.write); the filter generator only needs read access.
| Endpoint | Produces |
|---|---|
generateCustomComponentCode | One custom component: code + propsSchema + props (+ optional slots) |
editTemplatePageUsingAi | A full page structure (props + sections) for the editor |
generateAiLandingPage | Async job → a complete product landing page |
getAiLandingPageJobStatus | Job polling (status, landingPageId) |
generateProductLandingPageTemplateData | Landing-page TemplateData from an admin-curated landing template |
generateListFilterUsingAi | Natural language → flat filter or filterator JSON |
Component generation
generateCustomComponentCode is the workhorse — it powers the "generate with AI" and "edit
with AI" buttons on every custom block in the store editor.
Request essentials:
{
"storeId": "…",
"input": "A hero with a countdown and two CTAs",
"mode": "create",
"attachments": [
{ "type": "image", "value": "https://…", "label": "match this layout" },
{ "type": "url", "value": "https://example.com/inspiration" },
{ "type": "product", "value": "<product-id>", "prompt": "use its photos as style reference" }
],
"styleContext": "…compact digest of neighboring components…"
}attachments— a unified array of{ type, value, label?, prompt? }: reference images (valueis a URL, fetched and inlined into the prompt), web pages (HTML excerpt or image), and store products (valueis the product id; its catalog images are pulled server-side). Labels and per-attachment prompts ride along.styleContext— a compact JSON digest of the sibling components on the same page, so a generated block matches its neighbors' palette, spacing and tone instead of being designed in a vacuum.- Edit mode adds the current state:
currentCode,currentPropsSchema,currentProps, and — critically —currentSlotsSchema/currentSlots/currentSlotsLayout. The prompt instructs the model to apply a minimal patch and to either echo slot fields unchanged or omit them entirely (omission means "keep what the merchant has"). Without that contract, unrelated edits used to wipe slot children.
Response envelope:
{
"success": true,
"code": "function App() { … }",
"propsSchema": { "heading": { "type": "string", "name": "Heading" } },
"props": { "heading": "…" },
"slotsSchema": { "body": { "name": "Body", "maxChildren": 4 } },
"slots": { "body": [ { "type": "custom", "title": "…", "code": "…" } ] },
"slotsLayout": { "md": { "type": "row", "children": ["body"] } },
"title": "Hero with countdown",
"message": "OK"
}The invariants match the data model: slot fields are
top-level (never inside props), every slot child carries type and title, and
slotsLayout leaves must be keys of slotsSchema. The service actively repairs the common
model mistakes — lifting slots that were misplaced under props, backfilling missing slot
arrays in create mode, dropping layout nodes that point at non-existent slot ids, and
inferring type/title on bare slot children.
The validation and repair pipeline
Generated code is never trusted as-is. Each response runs a gauntlet before it reaches the client, and blocking failures are fed back to the model as corrective turns (up to two retries; token usage accumulates across attempts for billing):
Static validation (errors block, warnings advise) enforces the react-live sandbox contract from custom components:
- Code parses as JSX; exactly one top-level
function App()with no parameters and no other top-level function declarations. - No
import/export/require()— the sandbox pre-injects all globals. - No bare hooks:
useState(...)is rejected,React.useState(...)required. - Every
.map(...)returning JSX must setkeyon the outermost element. - Unknown bare identifiers (not in the sandbox scope, never declared) warn — they would
throw
ReferenceErrorat render time. propsdefaults without a matchingpropsSchemaentry warn (the merchant could not edit them).
Security scan rejects eval, Function, require, process, globalThis, Buffer,
dynamic import(), javascript: URLs, dangerouslySetInnerHTML, while (true) loops,
and sources over 120k characters. The same scanner gates
marketplace releases: any public or paid publish walks the
entire TemplateData tree (pages, sections, children, slots) and scans every code string.
When retries are exhausted but the result still parses, the API returns it anyway with the
unresolved issue codes in message — the editor preview surfaces the runtime error rather
than the merchant losing the generation.
Page-level AI editing
editTemplatePageUsingAi runs the same idea one level up: instead of one component, the
model plans a whole page.
- Input:
storeId,pageId,mode(create/edit),promptand/orattachments, plus context —currentPage,templateSchemaSummary,templateDefaultsPage,templatePromptInstructions, and policy listsallowedSectionKeys/allowedComponentTypes. - The structure pass returns
{ page: { props, sections } }(optionally aglobalPropsPatch). It is validated hard: sections must be non-empty arrays, section keys must be inallowedSectionKeys, and every component type in the tree must be inallowedComponentTypes. - In edit mode the model sees a compact outline of the current page — custom
codeis stripped and replaced with ahasCodemarker so small models get a stable page skeleton instead of a multi-kilobyte payload. - Surgical merge: prompts that read like small deltas ("add", "insert", "keep",
"only" — and nothing destructive like "rebuild" or "from scratch") trigger a merge of the
AI page with the current page keyed by
instanceId, so untouched blocks survive verbatim. "Add a scroll button to the hero" cannot wipe the hero. - Custom blocks in the planned structure arrive as placeholders — no
code, justprops._customComponentDescription(and_title). The server then materializes each placeholder by running component generation recursively (with an editor-style prompt so children match the parent stylistically), including placeholders nested inside slot trees.
Landing-page generation
generateAiLandingPage turns a short idea plus a product into a complete standalone
landing page. It is asynchronous:
POST generateAiLandingPage with storeId, productId, optional name, and prompt
and/or attachments. Returns a job id immediately.
Prompt enhancement — a first model call expands the idea ("Classic luxury theme") into a full design brief using the store and product context.
One-shot generation — a second call produces a design spec plus TemplateData defaults.
The output is hard-validated to contain exactly one page: pages.landing_page — anything
else is rejected.
Placeholder filling — empty slots get description placeholders which are materialized through the same component-generation pipeline (validation, security scan, retries included).
Poll GET getAiLandingPageJobStatus?jobId=… (the merchant app polls every ~10 s) until it
returns a terminal status and the created landingPageId. A notification also fires.
A sibling endpoint, generateProductLandingPageTemplateData, customizes an
admin-curated landing template (templateId + storeId + productId + prompt) instead
of designing from scratch — same validation ideas, but the model fills a known-good
structure.
Natural-language filters
generateListFilterUsingAi translates "bestsellers under 5000 still in stock" into either
flat params or a filterator JSON tree, driven by a
capability schema the client sends (so the model only uses fields the target list actually
supports). The merchant app uses it for the orders and products list screens; it needs only
read scopes.
How the store editor uses AI
The merchant app's template editor is the main consumer of these endpoints:
- Per-block generate/edit —
generateCustomComponentCodewithmode, attachments andstyleContext; edit mode sends the block's current fields so slots survive. - Page assistant —
editTemplatePageUsingAifor "build me a home page like…" and surgical page tweaks. - Landing page wizard —
generateAiLandingPage+ status polling. - The editor applies responses using the omission semantics above: absent
slots*fields in an edit response mean "keep the current values".
AI-assisted authoring in the kit
The other half of the story is local: the template kit treats coding
agents (Cursor, Claude Code, …) as first-class authors. Every feeef template init — and
every marketplace unpack — ships an agent pack in the theme folder:
| Shipped | Purpose |
|---|---|
AGENTS.md | The authoring contract: mental model, hard rules, commands — the first thing an agent reads |
docs/ | A curated subset of the canonical template docs (custom components, order form, filterator, PageSpeed, currency, design system, …) with a task index |
.cursor/skills/ | feeef-theme and feeef-filterator skills — task-triggered playbooks |
.cursor/rules/ | Always-on rules (design system, order form, PageSpeed, filterator, theme source) |
design-system.md | Starter visual contract the theme must fill in — see conventions |
scripts/check-perf.mjs | The static performance scanner |
types/*.d.ts + tsconfig.json | Teach tsserver the react-live globals (FeeefLivePropsOf, scope hooks) so agents get real type feedback |
The pack is copied fill-missing-only — re-running init or unpacking a purchased theme never overwrites your customizations, but does backfill docs added since the theme was scaffolded. The net effect: pointing a coding agent at a theme folder gives it the same contracts the server-side generation pipeline enforces, so locally-authored and AI-generated components obey one set of rules.
Building on top
If you are wiring these endpoints into your own tooling:
- Treat the response envelope as authoritative — apply
code,propsSchema,propstogether, keep slot fields top-level, and honor omission-means-keep in edit flows. - Reproduce the client-side contract from custom components when rendering previews; anything that passes the server validator runs in the sandbox.
- Send
styleContextwhenever you generate into an existing page — it is the difference between a matching block and a jarring one. - For auth, model selection and billing details, see the AI API.