3U

Publishing Themes

How a theme gets from your repo to the ThreeU marketplace: bundle, submit, review, integrate.

Publishing Flow

  1. Buildnpm run build produces preview-dist/index.html (relative asset URLs, base: './'). The reviewer renders this folder in a sandbox without API access, so the theme must boot on demo data (see [Dev & Publish](/docs/cli-dev)).
  2. Describethreeu.json with kind, key, name, version and a full `manifest` (≥ 3 pages, non-empty locales). See [Project Setup](/docs/cli-create).
  3. Validatenpx threeu publish --dry-run bundles without uploading. Check the tarball contains preview-dist/ and no .env file.
  4. Submitnpx threeu publish (token via THREEU_DEVELOPER_TOKEN). Creates a submission with status uploaded.
  5. Review — ThreeU runs automated checks (bundle present and ≤ 50 MB, preview_bundle, manifest_present, manifest_pages, manifest_locales, no external API calls, …) then an editorial review.
  6. Approve & integrate — an approved theme is registered in the marketplace catalog and merged into the hosted storefront by ThreeU. Merchants then pick it in their dashboard (public) or it is assigned to a brand (exclusive).

Review Requirements

  • All pages render without errors; responsive 360 → 1920 px, no horizontal scroll
  • Both declared locales actually render (AR: RTL layout, Arabic-capable font); never declare a locale the theme does not ship
  • Editable components use clear, descriptive names; no hardcoded brand-specific data
  • No API calls outside the SDK — every fetch goes through threeu-sdk
  • Public Storefront Token used correctly (no secret tokens in a theme)
  • Accessibility basics (alt text, semantic HTML, keyboard nav), WCAG AA contrast
  • Product pages emit Product JSON-LD; FAQ pages emit FAQPage
  • preview-dist/index.html present and renders on demo data
  • Clean, maintainable code; no admin/back-office or auth pages (ThreeU owns those flows)

Mergeability Contract

Approval is half the pipeline. To be integrated into the hosted storefront, the bundle must let ThreeU mount your theme inside its own brand-scoped provider:

  1. Named `App` export from src/index.{js,jsx,ts,tsx} — e.g. export { default as App } from './App.jsx'.
  2. `App` is provider-free. The host wraps <App/> in its own <ThemeProvider> with real brand data. If App mounts a provider too, it double-wraps and falls back to demo data. Pattern: an injectable provider prop that defaults to a pass-through; your standalone main.jsx injects the demo provider, the host injects nothing.
  3. `src/styles.css` is compiled, self-contained CSS. The host does not run your Tailwind/PostCSS. Copy Vite's emitted CSS to src/styles.css in a postbuild step.
  4. `@/` aliases are fine — the integration step rewrites them to the host's path.
  5. Import only what the SDK exports — the host's threeu-sdk must export every hook/component you use.
Provider-free App
TSX
// src/App.jsx
const PassThrough = ({ children }) => children;
export default function App({ Provider = PassThrough }) {
  return (
    <Router>
      <LanguageProvider>
        <Provider><Routes /></Provider>
      </LanguageProvider>
    </Router>
  );
}

// src/index.jsx (host-merge entry)
export { default as App } from './App.jsx';

// src/main.jsx (standalone / preview)
createRoot(root).render(<App Provider={ThemeRoot} />);

Limits & Statuses

ItemValue
Bundle size≤ 50 MB after exclusions (node_modules, dist, .git, .env* are dropped; preview-dist/ is kept)
Projects per accountLimited per developer account (new versions of an existing key do not count)
Key ownershipA key owned by another developer → 422
Statusespending_uploaduploadedapproved / rejected
List submissionsGET /api/developer/submissions

Each npx threeu publish creates a new submission; superseded ones can be ignored.

Common Failures

SymptomCause / fix
401 on publishToken invalid/expired, or a production token used against sandbox
422 theme key takenPick a new key in threeu.json
preview_bundle failspreview-dist/index.html missing — set build.outDir: 'preview-dist', base: './'
manifest_present failsthreeu.json has no manifest object
manifest_pages / manifest_locales failDeclare ≥ 3 pages / a non-empty locales array
Rejected: external API callsA non-ThreeU fetch survived (e.g. a base44 call) — grep and remove
Token found in the bundleSecret file was not named .env* — rename to .env.threeu, rotate the token