This chapter takes you from nothing to a working site on your own computer. You install two tools, create a site from a template, start it and sign in to the admin. It takes about ten minutes, most of it waiting for downloads.
At the end you have a folder you can edit, keep in git and later run on a server. When you are ready for that, the next chapter is Run it on a server.
You do not need Docker yet. You need it on a server, and the end of this chapter shows how to try the Docker setup on your own computer if you have it.
Check whether Node is already installed:
node --versionIt should print v24 or higher. If it prints a lower number, or says the command is not found, install Node.js 24. On macOS and Windows, download the installer from nodejs.org. On Linux, or if you like to switch between Node versions, use nvm. The number in this address is the nvm release; its page lists the current one.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.8/install.sh | bashOpen a new terminal window so that nvm is available, then install Node and check it:
nvm install 24
node --versionNow install pnpm with npm, which came with Node, and check it:
npm install --global pnpm
pnpm --versionIf npm says it is not allowed to write to a system folder, you installed Node with your system's package manager. Installing Node with nvm, as above, avoids the problem.
my-site is the name of the folder it makes; use your own name.npm create @jamiemcconnell/cms-app@latest my-sitey.| Question | What it sets |
|---|---|
| Site name | The name shown in the browser tab, in emails and in the admin. The folder name is offered. |
| Locale | Language and region, such as en-GB or en-US. It decides how dates and numbers are written. |
| Include Blog posts? Forms? Shop? Appointments? Polls? Questionnaires? | The optional features. Answer n to all of them for a plain content site. You can add one later. |
| Currency and Tax rate | Asked only if you include the Shop. Tax is one rate for the whole site; 0 means no tax. |
| Admin email | The email address of your first admin account. |
When it finishes it prints where the site is and your sign-in details:
Done. Your site is in /home/you/projects/my-site
cd my-site
pnpm dev # site at http://127.0.0.1:4321, admin at /admin
Admin sign-in: you@example.com / 95jW43GVVhLp (shown once — change it or add users with `pnpm cms user:create-admin`)Copy the password now. It is generated for you and shown only once.
Every question is also a flag, which is useful in a script or when you have done this before. With npm, put -- before the flags. Without it, npm keeps the flags for itself and the create command never sees them.
npm create @jamiemcconnell/cms-app@latest my-site -- --yes --name "My Site" --admin-email you@example.com--yes takes the default answer to everything you do not give. The default is a content-only site with no features, in en-GB, with the admin email admin@example.com, so give --admin-email yourself. A site with features and a chosen password looks like this:
npm create @jamiemcconnell/cms-app@latest my-shop -- --yes \
--name "My Shop" --features posts,forms,shop --currency GBP --tax-rate 20 \
--admin-email you@example.com --admin-password 'choose-something-long'| Flag | What it does |
|---|---|
--yes | Do not ask; use the defaults for anything not given. |
--name, --locale | The site name and locale. |
--features | A comma list of posts, forms, shop, appointments, polls and questionnaires. --features none means content only. |
--currency, --tax-rate, --prices-exclude-tax | Shop settings. --prices-exclude-tax means the prices you enter do not include tax. --tax-rate 0 turns tax off, and nothing then shows a tax line. Giving one of these adds the shop for you. |
--admin-email, --admin-password | Your first admin account. Without a password, one is generated and printed. |
--port | The port the site uses. The default is 4321. |
--mcp | Turn on the connection for AI assistants, with the browser service that takes screenshots for them (AI assistants (MCP)). |
--no-install, --no-seed | Skip installing the packages, or skip loading the starter content and creating the admin. |
cd my-site
pnpm devLocal with an address, http://127.0.0.1:4321/. Open it in your browser to see the starter site./admin to the address, http://127.0.0.1:4321/admin, and sign in with the email and password the create command printed.You land on the dashboard. The menu on the left has Dashboard, Content, Collections, Media, Users, API tokens and Settings. Pages are under Content. Pages and blocks shows how to build one.
Press Ctrl+C in the terminal to stop the server. It listens on 127.0.0.1 only, so only your computer can open it. Everything you change in the admin is saved in the data/ folder, not in your code.
If you lose the password, make another admin account with a different email address:
pnpm cms user:create-admin --email other@example.com --password 'a-long-password'site.config.ts is the one file that wires the site together: its name, address and language, the database, email, storage, the theme, the blocks and the features. Secrets are never in it; they come from .env.theme.ts holds the colours, fonts and spacing the site starts with. Once an admin saves a change under Settings → Theme, the saved theme is used instead.blocks/ is your own block package: a custom Hero variant, a Counter block to copy and the site's stylesheet.package.json and pnpm-lock.yaml list the packages. @jamiemcconnell/cms brings the platform with it, and there is one more package for each feature you chose. Keep the lock file in git: the server's build needs it.astro.config.mjs adds the cms to Astro, the framework underneath, with integrations: [cms()]. The Node adapter is included..env holds CMS_AUTH_SECRET, which was generated for you, and CMS_SITE_URL. Never commit it; .gitignore already leaves it out. .env.example lists every setting with a comment.data/ holds the SQLite database and the uploaded media. It is left out of git. CMS_DATA_DIR moves both, and the Docker image sets it to /data.Dockerfile, compose.yaml, Caddyfile and .dockerignore run the site as containers. Run it on a server explains them.Run these in the site folder.
pnpm seed # load the starter content again; it skips what already exists
pnpm cms user:create-admin --email editor@example.com --password 'a-long-password' --role editor
pnpm cms migrate # list the database migrations, applying any that are pending
pnpm cms jobs list # the scheduled jobs; `pnpm cms jobs run expire-orders` runs one now
pnpm cms media optimise --dry-run # shrink stored images with the settings under Settings → Media; --dry-run writes nothing
pnpm cms generate block Feature --island # a new block in ./blocks, with an island for interactive parts
pnpm cms generate hero Splash # a hero variant, listed under Hero as "Custom: Splash"
pnpm cms generate collection team # a source for "List of…" blocks
pnpm cms generate module work # a module: table, routes, admin screen, page and collection
pnpm test # your blocks' tests
pnpm build && pnpm start # a production build, run as a Node server--role takes admin, editor or customer. Running user:create-admin for an email that already has an account changes nothing and prints user exists.
Posts, forms, the shop, appointments, polls and questionnaires are features. With none turned on, a site is a content CMS: pages, blocks, media, a theme and users. Sign-in, protected pages and the Users screen are always there, but public sign-up stays off until an admin turns it on under Settings → Users.
To turn a feature on later, install its package, list its factory under features in site.config.ts and restart. These are the packages and factories:
| Feature | Package | Factory |
|---|---|---|
| Blog posts | @jamiemcconnell/cms-posts | posts() |
| Forms | @jamiemcconnell/cms-forms | forms() |
| Shop | @jamiemcconnell/cms-shop | shop() |
| Appointments | @jamiemcconnell/cms-appointments | appointments() |
| Polls | @jamiemcconnell/cms-polls | polls() |
| Questionnaires | @jamiemcconnell/cms-questionnaires | questionnaires() |
The shop is the example here:
pnpm add @jamiemcconnell/cms-shopThen add the import at the top of site.config.ts and the factory under features. The shop needs a few options:
import { fakePayments, shop } from '@jamiemcconnell/cms-shop';
export default defineConfig({
// ...
features: [
shop({
payments: [fakePayments()], // a pretend payment page; swap in Stripe before you go live
currency: 'GBP',
pricesIncludeTax: true,
taxRate: 20,
}),
],
});No migration is needed. Every site already has the tables, so a feature you turn on later works at once, and a feature you never install leaves no trace: no menu, no settings tab and no route.
This step is optional and needs Docker. The site folder can also run as containers, which is how it runs on a server. The scaffold sets it up to serve plain http on 127.0.0.1 only.
pnpm dev with Ctrl+C. Both use port 4321.docker compose up -d --buildhttp://127.0.0.1:4321 when the command finishes. It says “Page not found” at first. The Docker copy keeps its data in a Docker volume, apart from the data/ folder that pnpm dev uses, so it starts empty.docker compose run --rm app pnpm cms seed --starter
docker compose run --rm app pnpm cms user:create-admin --email you@example.com --password 'choose-a-long-password'Stop the containers again with docker compose down. It keeps the data. Add -v to delete the data too.