Package format
The files of a feeef package: feeef.json, app/ routes and layouts, components, content, locales and limits.
Developers write code, tools and merchants write JSON. Code files (app/, components/,
lib/, styles/) are written by hand. JSON files (feeef.json, content/, locales/) are
also written by the editor, the CLI and the AI.
Files
Limits: 400 files, 2 MiB in total, 256 KiB per file, 5000 blocks per page and 64 levels deep. Other paths are rejected.
feeef.json
| Field | Rule |
|---|---|
kind | template or component |
engine | helium@20 |
theme | A props map (see props). Templates only. Read with useTheme(). |
dependencies | Marketplace component packages, exact versions. Templates only. See Marketplace. |
A component package holds only feeef.json, components/, lib/, styles/ and locales/.
Routes
A route is app/<folder>/page.tsx; its id is the folder path (index, products/[slug],
contact, not-found). A template needs index, products/[slug], cart, checkout,
thanks and not-found. Any other folder is a free page with a fixed path; only the commerce
routes take [slug].
Routes and layouts render on the server only and ship no JavaScript. A component they import
becomes its own interactive island. Exports: default (a function; routes get { params },
layouts get { children }) and an optional static metadata {title, description, noindex}.
<Slot name title /> marks an area the merchant fills; names are literals.
app/pages/[slug]/page.tsx frames merchant store pages. It must have
<Slot name="main" /> and has no content file. Without it, store pages render their main
slot straight inside the layout.
Layouts
| Layout | Renders |
|---|---|
app/layout.tsx | Every page, unless its content picks another layout. Content: content/layout.json. |
app/layouts/<name>.tsx | Pages that pick <name>. Content: content/layouts/<name>.json. |
blank | Always there: the default layout with its slots empty, so the CSS and theme stay. Ship app/layouts/blank.tsx to replace it. |
Names match ^[a-z][a-z0-9-]{0,39}$; default is not a name. app/layouts/ is not a route.
Content
content/pages/<route>.json, content/layout.json and content/layouts/<name>.json say what
each slot holds:
| Field | Rule |
|---|---|
layout | Pages only, optional: blank or a named layout. Absent = the default layout. |
id | Unique on the page and stable; different from the ids in its layout's content. |
use | A local component (hero), a dependency component (reviews/list) or a core block (core/box). |
props | Overrides only. Missing props use the component defaults. |
slots | Named lists of child blocks. |
hydrate | Optional, not on core blocks. See hydration. |
content/theme.json holds only the theme values the merchant changed; the defaults come from
feeef.json. Locales are nested JSON read with t('section.key').
Core blocks
| Block | Use |
|---|---|
core/box | Layout: direction, columns, gap, padding, maxWidth and phone variants. Slot main. No JavaScript. |
core/product | Product scope (productId, or the page's product). Slot main. |
core/product-title, -price, -description, -gallery, -variants, -offers | Parts of the product in scope. |
core/order-form | The order form for the product in scope. Slot main holds its fields. Its frame is set by props: title, gap, padding, border, borderColor, radius, background, maxWidth. |
core/order-name, core/order-phone, core/order-address, … core/order-submit | Fields of the order form. The editor's block list shows them all. |
core/order-country | The country. Hidden by default (hidden: true): orders go to the store's country. Shown, it lists the countries the store ships to. |
core/order-state, core/order-city | The state (showCode: "16 - Alger", sort: code or name) and the city list that follows it. Place both or neither. |
core/order-gps | Fills state and city from the shopper's location: style icon (a square beside other fields) or button (full width). Needs state and city. |
core/order-button | A floating button that brings the shopper to the order form. |
Build
Publishing (and feeef template check) builds the package. Errors come back as
{code, message, file, line, column}. The result is an immutable artifact: code and content
are hashed apart, so a content-only change reuses the code build. All stores on one version
share it.