Project Setup (threeu.json)
How the CLI identifies a theme or plugin project, and what the manifest must contain.
Scaffolding a Project
Use your usual toolchain — the CLI does not generate projects. Published themes are Vite + React apps:
npm create vite@latest my-theme -- --template react-ts
cd my-theme
npm install threeu-sdkthreeu.json
The CLI detects the project from a threeu.json file at the project root (or a "threeu" field in package.json). It reads the file verbatim — it does not execute defineTheme() — so the manifest must be written here by hand:
{
"kind": "theme",
"key": "modern-luxury",
"name": "Modern Luxury",
"version": "1.0.0",
"manifest": {
"type": "theme",
"name": "Modern Luxury",
"slug": "modern-luxury",
"version": "1.0.0",
"visibility": "public",
"locales": ["ar", "en"],
"default_locale": "ar",
"pages": ["home", "products", "product", "cart", "checkout", "about", "contact"],
"editable_fields": [
{ "name": "hero.heading", "kind": "heading", "label": "Hero heading" },
{ "name": "hero.image", "kind": "image", "label": "Hero background image" }
],
"capabilities": {
"catalog": { "required": true, "features": { "products": true, "collections": true }, "modes": ["dynamic"] },
"checkout": { "required": false, "features": { "delivery": true, "payments": true, "coupons": true }, "modes": ["dynamic"] },
"leads": { "required": true, "features": { "contact": true }, "modes": ["dynamic"] }
}
}
}Fields
| Field | Rule |
|---|---|
kind | "theme" or "plugin" |
key | Lowercase letters/numbers with - or _; unique per developer. Another developer's key → 422 on publish |
name | Display name |
version | Semver; re-publishing the same key+version creates a new submission |
manifest | Required for themes. A bare {kind,key,name,version} publishes with manifest: null and fails review |
The reviewer's blocking manifest checks are exactly three: `manifest_present` (non-empty object), `manifest_pages` (≥ 3 entries), `manifest_locales` (non-empty array). Keep manifest.pages identical to the list you pass to defineTheme({ pages }). Plugin submissions must use the slug of a plugin you already own in the Developer Console.
Vite Alias & Preview Build
Two vite.config settings the review pipeline depends on: build to preview-dist/ with relative asset URLs (the reviewer renders that folder in an iframe), and declare the @ → src alias if your code uses it (an alias a generator once provided is not provided by the CLI):
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { fileURLToPath, URL } from 'node:url'
export default defineConfig({
plugins: [react()],
resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } },
base: './',
build: { outDir: 'preview-dist' },
})