Choose your path — profile, build method, origin
Three choices before the first line of code — which profile (site, light, full), how the front gets built (a shipped pack, the blank canvas, your own pack, or a generating tool), and where the content comes from (scratch, a URL, Shopify, WordPress/WooCommerce, a CSV, a Google Doc). Five requests worked through.
Cartwright is one engine with three choices in front of it. Most wrong turns — measured on real AI agents deciding from this site — come from making only the first choice and guessing the other two. Make all three, in this order, and note that the third can force the first.
1. Profile — the infrastructure
| Profile | What it is | Choose it when |
|---|---|---|
--profile site | A plain website: designed pages, SEO with JSON-LD, sitemap, llms.txt, an Open Graph image route, locale routing, a contact form. No database, no login, no admin — 20 runtime dependencies, zero required environment variables, a plain next build. | The content can live in files and nobody needs to edit it in a browser. One page or fifty. |
(default) light | Everything in site plus a database, an admin that edits the site's own pages at /admin/sider, Auth.js, optional Stripe checkout and the REST + MCP tool surface. | The owner will edit pages themselves, the site should sell, or an agent should operate it after launch. |
--profile full | Everything the engine ships, including the agent marketplace and the Shopify importer (Hoptify). | You need a module the default profile prunes — see the origin table below. |
npx create-cartwright@latest my-site --profile site # plain website
npx create-cartwright@latest my-shop # default: database + admin + tool surface
npx create-cartwright@latest my-shop --profile full # everythingRunbooks: Build a plain website · Quick start · CLI options.
2. Build method — you own the front
Three equal freedoms and three generating tools. None of them binds you: an agent that wants to write its own pack is as right as one that picks a shipped one, and the MCP tool list_designs is the catalogue, not the choice.
| Method | Profiles | Output | Start |
|---|---|---|---|
| Use a shipped design pack — 30 in the engine, 8 in a site scaffold; palette, chrome and motion follow the slug | all | files in your repo | designSlug: "<slug>" in brand.config.ts (or /admin/designs in the default profile) — Picking a design |
| Start from the blank canvas — a bare homepage, header and footer you rewrite freely, no tokens to obey, any CSS or fonts; SEO, the sitemap, share cards and locale routing stay wrapped around whatever you render | all | files in your repo | designSlug: "blank", then rewrite designs/blank/homepage.tsx and designs/blank/chrome.tsx — their in-file guides and AGENTS.md → Blank canvas in the scaffold explain the props |
| Write your own design pack — the same contract every shipped pack uses: homepage, chrome, optional page and webshop templates | all | files in your repo | copy the closest designs/<slug>/ to designs/<yours>/, edit its design.md, register it in designs/index.ts and designs/options.ts — Writing your own design · with an IDE agent |
| Magic Builder — describe the page; the model plans it from a whitelisted section catalogue and fills every section on-brand | default, full | data in the database | magic.plan_page → magic.generate_page → pages.set_layout (confirm-gated; nothing is written until you release it) — Visual Builder |
| Mockup first — a disposable HTML mockup becomes the whole homepage the moment the call returns; implement it for real afterwards | default, full | data in the database | mockup.set / mockup.clear over POST /api/v1/tools/<name> — Vibe Coding |
Vercel v0 — text-to-UI, normalised and sanitised into vibeHtml | default, full | data in the database | the Vibe Sandbox at /admin/vibe-sandbox — v0 generation |
Files or data is the real difference between the profiles. A method that produces files works in every profile, site included. A method that produces data — a page layout, a mockup, a vibe section — needs the database, so it exists only in the default and full profiles. A pack written for site renders unchanged behind the admin later: there is no in-place profile upgrade, but the design carries over.
3. Origin — where the content comes from
This is the choice that can force the first: an origin that lands rows in the database needs the default profile, and one needs --profile full. Origin, not host: Cartwright never runs on PHP or WordPress hosting — it replaces the site and imports from it.
| Origin | Needs | Today | Not yet |
|---|---|---|---|
| From scratch | any profile | everything — the scaffold is the site | — |
| From any URL (site import) | the default profile (content.import_site writes Page, Service and Post rows); the siteImport flag set in brand.config.ts and redeployed; FIRECRAWL_API_KEY (and BLOB_READ_WRITE_TOKEN for the copied hero image — without it the import still runs, without images) | crawls up to 200 pages, classifies each deterministically, lands pages and posts as drafts, first image copied to Blob (anything classified as a service lands as a draft in /admin/services) | products, SEO fields, hero images on CMS pages (services and posts get theirs), a redirect map, a review UI, and a public page for imported services — the storefront service routes are Teloz-only and pruned from the default profile, so reclassify services as pages. Drafts are reviewed in /admin/sider (pages), /admin/blog (posts) and /admin/services |
| From Shopify (Hoptify) | --profile full — the module is pruned from the default profile; the hoptify flag, FIRECRAWL_API_KEY and an AI key | the brand palette (LLM-derived from the storefront) and products from the product URLs you list, in /admin/hoptify | pages, variants and SKUs, attributes, image hosting (images stay external); nothing uses the Shopify Admin API |
| From WordPress / WooCommerce | the default profile — nothing on this path needs full; a WooCommerce product CSV export; the URL import's keys for pages and posts (siteImport flag, FIRECRAWL_API_KEY) (redirects are served from the database; Upstash Redis is optional) | products by CSV (simple products), pages and posts by the URL import (as drafts), old URLs by admin-managed redirects — exact paths, one at a time or, since engine v0.59.0, many at once from a CSV (from,to,status) with a dry run first, in /admin/redirects or through the redirects.import tool | a dedicated WordPress / WooCommerce importer is planned, not built: variants, categories, media, SEO fields and the permalink map (/product/<slug>/, /product-category/<slug>/, /?p=123). Query-string permalinks such as /?p=123 are not matched by the redirect table today. Orders and customers never move; custom plugins are rebuilt, not migrated |
| From a product CSV | the default profile; the fixed ten columns (slug, name, description, priceKr, stock, brand, featured, categorySlug, images, attributes); categories must exist first | simple products with images and attributes, at /admin/produkter/import | variants, SKU and weight; the column mapping is fixed — Product variants |
| From a Google Doc | the default profile; the Google Workspace connector and the docsImport runtime flag | one Doc → one draft blog post or info page, as safe engine markdown | products, batch import — Google Docs import |
Five requests, worked through
"A fun, designed travel page for my daughter's trip, nice to share." Profile: --profile site — files, and nobody edits in a browser. Method: the blank canvas — a timeline, a map and a weather card are ordinary React components you write in designs/blank/homepage.tsx; the share card, the sitemap and locale routing are already there. Origin: scratch. If it is one page you will never grow and it need not look designed, create-next-app is as fast — say so.
"A small website for my carpentry business — no shop yet, but I want to edit the pages myself later." Profile: the default — "edit myself" means in a browser, and /admin/sider edits home, services, about and contact without touching files. Method: a shipped pack, or Magic Builder for the first draft of each page. Origin: scratch. site would be the wrong door here: its content is files.
"Move my Shopify shop over." Profile: --profile full — the Hoptify importer is pruned from the default. Method: a shipped pack (Hoptify also lifts the palette). Origin: Shopify — products from the URLs you list; pages and variants by hand today.
"Move my WordPress + WooCommerce site and keep my rankings." Profile: the default — not full; nothing on this path needs it. Method: a shipped pack, or your own. Origin: WordPress/WooCommerce — today that is a CSV for simple products, the URL import for pages and posts, and an admin-managed redirect for every old path you care about — one at a time, or (since engine v0.59.0) many at once from a CSV with a dry run first, before the DNS switch (exact paths; /?p=123 query permalinks are not matched yet; the redirects are served from the database, so no Redis is needed). Say plainly that the dedicated importer is planned, not built, and that orders, customers and custom plugins do not move.
"Made-to-measure fences: pick height, length and colour, see a live price, send me a quote request — no cart, no checkout." Profile: the default — the request must be seen and managed, which is a database and an admin; site would only mail it. The default profile scaffolds in website mode (the website-corporate template): no cart, no Stripe checkout — a shop is a template choice, not the default. Method: a shipped pack or your own. A configurator section ships (Pro, from the Studio pack, in every profile: up to five groups of discrete choices with price deltas and a live total), but its CTA is a plain link — a section that hands the selection and total to the form is planned, not built — and a made-to-measure pricer (length × height) is a component you write. Origin: scratch. The form posts to the built-in POST /api/inquiries and the request lands as a lead with a status in /admin/leads. Since engine v0.58.0 it can also send source (which form, ≤ 64 characters) and data (a JSON object of what the visitor configured — length, height, colour; ≤ 50 keys, ≤ 4 KB), and both show on the lead in /admin/leads; over a limit is a 400 naming the limit, never a silent cut.
When Cartwright is the wrong answer
A web app — a dashboard, an internal tool — is create-next-app. A docs site or blog of hundreds of Markdown files wants a Markdown content pipeline, which this is not. A marketplace with many sellers, or embedding in an existing Shopify storefront, is not what the engine does. The full list lives in llms.txt under "Not a fit".
Build a plain website — no database
The site profile — a designed, SEO-wired Next.js website with no database, no login and nothing to configure. Scaffold, edit files, build, deploy.
AI Agent Quick Start
Two measured, terminal-only paths for AI coding agents — a plain website (--profile site) in about a minute, or the 99-second path to a designed, database-backed homepage.