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

text
feeef.json                    manifest
app/
  layout.tsx                  default layout (optional)
  layouts/<name>.tsx          named layouts a page can pick (optional)
  page.tsx                    /
  products/page.tsx           /products
  products/[slug]/page.tsx    /products/:slug          the product
  categories/[slug]/page.tsx  /categories/:slug        the category
  cart/page.tsx               /cart
  checkout/page.tsx           /checkout
  thanks/page.tsx             /thanks
  pages/[slug]/page.tsx       frame of store pages (optional)
  contact/page.tsx            /contact                 a free page
  not-found.tsx               404
components/<id>.tsx           one component per file (or components/<id>/index.tsx)
lib/**  styles/**             helpers and CSS
content/
  theme.json                  theme values the merchant changed
  layout.json                 slot content of app/layout.tsx
  layouts/<name>.json         slot content of app/layouts/<name>.tsx
  pages/<route>.json          slot content of a route: index, products/[slug], contact, …
locales/<locale>.json         translations
README.md  AGENTS.md          notes for people and agents
package.json  tsconfig.json   your own npm tooling (feeef ignores them)

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

json
{
  "kind": "template",
  "engine": "helium@20",
  "title": "Alishop",
  "description": "Flash sales, countdowns and rich product cards",
  "locales": ["ar", "en", "fr"],
  "defaultLocale": "ar",
  "theme": {
    "accent": { "type": "string", "title": "Accent color", "ui": "color", "default": "#e0261a" }
  },
  "dependencies": { "reviews": { "package": "<component package id>", "version": 3 } }
}
FieldRule
kindtemplate or component
enginehelium@20
themeA props map (see props). Templates only. Read with useTheme().
dependenciesMarketplace 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].

tsx
// app/products/[slug]/page.tsx
import { Slot } from '@feeef/template'

export const metadata = { title: 'Product' }

export default function ProductPage() {
  return (
    <main>
      <Slot name="main" title="Product page" />
    </main>
  )
}

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

tsx
// app/layouts/landing.tsx
import { Slot } from '@feeef/template'

export const config = {
  title: 'Landing',
  description: 'Logo only, no menu, cart or footer: for product landing pages and ads',
}

export default function LandingLayout({ children }: { children?: any }) {
  return (
    <>
      <Slot name="header" title="Top bar" />
      {children}
    </>
  )
}
LayoutRenders
app/layout.tsxEvery page, unless its content picks another layout. Content: content/layout.json.
app/layouts/<name>.tsxPages that pick <name>. Content: content/layouts/<name>.json.
blankAlways 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:

json
{
  "slots": {
    "main": [
      { "id": "home-hero", "use": "hero", "props": { "title": "Eid sale" } },
      { "id": "home-products", "use": "products" }
    ]
  }
}
FieldRule
layoutPages only, optional: blank or a named layout. Absent = the default layout.
idUnique on the page and stable; different from the ids in its layout's content.
useA local component (hero), a dependency component (reviews/list) or a core block (core/box).
propsOverrides only. Missing props use the component defaults.
slotsNamed lists of child blocks.
hydrateOptional, 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

BlockUse
core/boxLayout: direction, columns, gap, padding, maxWidth and phone variants. Slot main. No JavaScript.
core/productProduct scope (productId, or the page's product). Slot main.
core/product-title, -price, -description, -gallery, -variants, -offersParts of the product in scope.
core/order-formThe 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-submitFields of the order form. The editor's block list shows them all.
core/order-countryThe 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-cityThe state (showCode: "16 - Alger", sort: code or name) and the city list that follows it. Place both or neither.
core/order-gpsFills 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-buttonA floating button that brings the shopper to the order form.
json
{ "id": "order", "use": "core/order-form", "slots": { "main": [
  { "id": "order-name", "use": "core/order-name" },
  { "id": "order-phone", "use": "core/order-phone" },
  { "id": "order-country", "use": "core/order-country" },
  { "id": "order-place", "use": "core/box", "props": { "direction": "row", "phoneDirection": "same", "gap": 12 }, "slots": { "main": [
    { "id": "order-state", "use": "core/order-state" },
    { "id": "order-city", "use": "core/order-city" },
    { "id": "order-gps", "use": "core/order-gps" }
  ] } },
  { "id": "order-submit", "use": "core/order-submit" }
] } }

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.