Features Apograph CMS on GitHub

Command line

The two binaries an installed app is built, run and migrated with.

Documents 0.5.2 Updated Edit this page Report a problem

On this page

Two binaries, both new in 0.4.0.

create-apograph-app scaffolds an app. apograph builds, runs and migrates one — it is a devDependency of every generated app, and the counterpart to the Nx targets this project’s own monorepo uses.

Before 0.4.0 everything needed to operate an app lived in a private package that will never be published, which meant an app installed from npm had no way to migrate its database at all.

Scaffolding an app

npx create-apograph-app my-cms
FlagWhat it does
--yesAccept every default, asking nothing
--media <id>Storage adapter. Default media-local
--copilot <ids>Comma-separated copilot backends, or none
--sso <ids>Comma-separated identity providers — sso-oidc, sso-github, sso-saml — or none. Default none; the usage text names only sso-oidc as its example
--protocols <ids>Protocols beyond REST — graphql, mcp — or none
--no-installSkip installing dependencies
--no-gitSkip initialising a git repository
-h, --helpShow the usage message

--yes, and any non-TTY, takes the defaults without asking. A scaffolder that blocks on a prompt in CI hangs the job until it times out.

The four questions

QuestionKindDefault
Where should uploads be stored?one ofLocal filesystem
AI copilot — which model backends?any ofnone — there is no offline stand-in, so the copilot stays off
How should people sign in?any ofemail and password only
Which protocols should the content API speak?any ofREST, which is locked on

Both multi-selects default to nothing extra, deliberately. A hosted copilot provider sends workspace content to a third party, and an endpoint nobody asked for is still an endpoint.

REST is shown ticked and dimmed rather than left out of the question: “which protocols does this app speak?” is a more useful thing to answer than “do you want these two extras”, and the answer reads as a complete set only if the one you always get is in it. --protocols none still yields REST.

The scaffolder's version is the set

Every @apograph/* dependency is stamped with the scaffolder’s own version, and the release is lockstep — so npx create-apograph-app@0.4.0 generates a 0.4.0 app, and a generated app is internally consistent by construction.

That is also what makes an old version reproducible: nothing resolves latest from the registry.

Building and running

npx apograph <command>
CommandWhat it runs
apograph devtsc --watch, node --watch and Vite in one terminal
apograph buildtsc for the server, Vite for the admin
apograph startRuns the built server from dist/server/main.js
apograph migrateBuilds the server, then applies every plugin’s migrations
apograph generate --name=<n>A migration for this app’s own content tables
apograph studioDrizzle Studio on the app’s database
FlagApplies to
--serverbuild, dev — the server only, skipping the admin
--adminbuild — the admin bundle only
--host, --portstudio
-h, --help, helpAnywhere — apograph --help, apograph help, apograph migrate --help all print the usage and exit 0
-v, --versionThe installed version. Checked before --help, so apograph --version --help answers with the version

A bare apograph with no command prints the usage too. It used to be that a bare apograph --help was read as the command --help and exited 1; 0.5.0 checks for the help and version flags before the command position, and added --version.

apograph migrate compiles first

It builds the server and then reads the compiled config and plugin list, rather than reading your TypeScript directly. A generated app has its own build step, so the whole problem of running decorated Nest classes through a TypeScript loader stays out of the consumer path.

Migrations are applied in plugin order, with no transaction spanning plugins. That is why the order of the array in plugins.ts matters — see the plugin catalogue.

.env is loaded by the CLI

Nothing else loads it. A generated app has no task runner, so without this every command would fail on a DATABASE_URL sitting in a file two lines away.

Variables already exported are not overwritten, which is the precedence a deployment needs.

The app's layout is a convention, not configuration

apograph start expects dist/server/main.js, and apograph migrate expects the compiled config beside it. Those paths come from the rootDir settings the generated tsconfig files carry.

Change a rootDir without changing the rest and apograph start reports a missing entry point rather than a moved one.

Nothing is bundled

Every plugin locates its migrations relative to its own package root inside node_modules. A bundler that flattened those files would break it, which is why a generated app compiles with tsc alone and keeps node_modules on disk.

See installation for the first run, and the database for what migrate does in a deployment.