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| Flag | What it does |
|---|---|
--yes | Accept 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-install | Skip installing dependencies |
--no-git | Skip initialising a git repository |
-h, --help | Show 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
| Question | Kind | Default |
|---|---|---|
| Where should uploads be stored? | one of | Local filesystem |
| AI copilot — which model backends? | any of | none — there is no offline stand-in, so the copilot stays off |
| How should people sign in? | any of | email and password only |
| Which protocols should the content API speak? | any of | REST, 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>| Command | What it runs |
|---|---|
apograph dev | tsc --watch, node --watch and Vite in one terminal |
apograph build | tsc for the server, Vite for the admin |
apograph start | Runs the built server from dist/server/main.js |
apograph migrate | Builds the server, then applies every plugin’s migrations |
apograph generate --name=<n> | A migration for this app’s own content tables |
apograph studio | Drizzle Studio on the app’s database |
| Flag | Applies to |
|---|---|
--server | build, dev — the server only, skipping the admin |
--admin | build — the admin bundle only |
--host, --port | studio |
-h, --help, help | Anywhere — apograph --help, apograph help, apograph migrate --help all print the usage and exit 0 |
-v, --version | The 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.