| title | CLI |
|---|---|
| path | getting-started-cli |
| summary | Add Byline to an existing TanStack Start application with 'byline init', provision and seed a hand-wired application with 'byline setup', and check an installation with 'byline doctor'. |
Companions:
- Getting started — the two routes into a running Byline instance, and where to go next.
- Development environment and example application — clone this repo and run
apps/webapp, the reference application the installer's scaffold mirrors. - Upgrading from 3.21 to 4.x — the application-side migration guide when you move an existing installation to 4.x.
The Byline CLI (@byline/cli) installs Byline into an existing TanStack Start
application. It prompts for installation and configuration options (where to
mount the admin UI, whether to use PostgreSQL or MySQL, how to connect to that
database, and whether to include the example collections) and summarises those
choices, along with a diff of every file it plans to write, before installing
anything. When it finishes you have a working admin UI, a provisioned database
with the release's fresh-install baseline, a seeded super-admin account, and a
byline/ configuration directory ready to edit.
If you are evaluating Byline rather than adding it to a project, the development environment runs a fully configured example application without touching your own code.
| Command | What it does |
|---|---|
byline init |
The full installer. Detects what is already in place, then runs the remaining phases. |
byline setup |
Database provisioning and seeding only, for an application you wired by hand. Touches no project files. |
byline doctor |
Read-only. Reports which installation phases are complete. |
All three run through your package runner without a global install: for
example npx @byline/cli@latest init or pnpm dlx @byline/cli@latest init.
- Node
>=20.9.0. - A TanStack Start application using the Nitro (agnostic) adapter, the configuration the CLI is currently tested against.
- Ideally, an application already under Git. The installer warns and asks for confirmation outside a Git repository, because Git is how you roll an installation back.
- A running PostgreSQL server or MySQL 8.0.14 or later, with administrator credentials. PostgreSQL is the default. MariaDB is not supported.
- Choose the existing-server option when asked; the bundled-Docker option is not yet implemented.
- pnpm, npm, Yarn, or Bun.
Skip this step if you already have one.
npx @tanstack/cli@latest create
# or
pnpm dlx @tanstack/cli@latest createSelect the Nitro (agnostic) adapter:
◆ Select deployment adapter:
│ ○ None
│ ○ Cloudflare
│ ○ Netlify
│ ● Nitro (agnostic)
│ ○ Railway
└
From the application directory:
npx @byline/cli@latest init
# or
pnpm dlx @byline/cli@latest initThe installer works through a fixed sequence of phases: preflight,
prompts, host, db, db-init, env, deps, wire, routes,
scaffold, seed-admin, seed-docs, ui. Each phase first checks whether its
work is already done, then summarises what it will change; phases that write
files show the diff and wait for your confirmation. Re-running init is safe:
completed phases are detected and skipped, so an interrupted installation
resumes where it stopped. These phase names are the values --only, --from,
and --to accept.
| Prompt | Default | Notes |
|---|---|---|
| Where should the admin UI be mounted? | /admin |
May be nested, such as /internal/cms. |
| Where should the sign-in page be mounted? | /sign-in |
May be nested, such as /staff/login. |
| Which database should Byline use? | PostgreSQL | Choose PostgreSQL or MySQL 8.0.14 or later. Pass --database postgres or --database mysql to make this choice noninteractively. |
| How will Byline connect to the database? | — | Choose the existing-server option, then give an administrator connection URL, a database name (default byline), and an application role or user (default byline). |
| Include the example collections, blocks, and fields? | yes | The overlay that mirrors apps/webapp/byline in this repo. |
| Include the markdown → Byline import example script? | no | Only asked when you kept the examples. |
The administrator URL must use the same host string and port that the
application will use. The CLI applies the baseline to the exact endpoint it
inspected and writes that endpoint into the application environment, so it
rejects mismatches such as localhost versus 127.0.0.1, an SSH-tunnel port
versus the service port, or a container hostname that only one side can
resolve.
Start your application's dev server and open the admin path you chose (/admin
by default). Sign in with the super-admin credentials the installer wrote to
.env.local:
BYLINE_SUPERADMIN_EMAIL=...
BYLINE_SUPERADMIN_PASSWORD=...If the admin UI does not come up, run byline doctor for a status grid of every
installation phase. Where a phase stopped with a manual instruction, compare
against the reference application in the Byline CMS main repo under
apps/webapp.
The installer adds two application scripts. Run the first after any change to a collection or block schema, and the second in CI:
pnpm byline:generate # regenerate byline/generated/collection-types.ts
pnpm byline:generate:check # fail without writing when the artifact is missing or staleFor AI-assisted editing, add the relevant provider API keys described in
apps/webapp/.env.local.example.
:::warning[Keep public and admin layouts separate]
The installer places Byline routes under the pathless routes/_byline layout.
Put the public application's top-level layout under a separate pathless route,
such as routes/_frontend or routes/_public. Move public styling, headers,
footers, and other layout concerns out of __root.tsx and into that public
layout's route.tsx. This prevents public application styles from affecting the
Byline dashboard.
:::
See the TanStack Router docs for File-Based Routing and Virtual File Routes for more information.
byline init --dry-run # show every change but write nothing
byline init --apply # skip per-phase confirmations (still prints diffs)
byline init --database mysql # select MySQL without the database prompt
byline init --only db-init # run a single phase
byline init --from wire # resume from a phase and continue to the end
byline init --apply -y # apply detected safe scaffold upgrades noninteractivelyRun byline init --help for the full list.
If you configured Byline by hand (copying the byline directory, .env,
start, server, __root.tsx, and vite.config.ts settings from the example
application) and only need to provision the database and seed it, use setup
instead of init. It runs the database and seed phases only and never modifies
project files:
# Provision the DB and seed both the super-admin and example docs (default)
byline setup
# Select MySQL when no database choice is already recorded
byline setup --database mysql
# Provision the DB and seed the super-admin only
byline setup --no-seed-docs
# Provision the DB and seed example docs only
byline setup --no-seed-admin
# Provision the DB without running either seed
byline setup --no-seed-admin --no-seed-docs
# Re-run every phase even if recorded as complete. The database must still be
# empty; a fresh baseline is never used as an upgrade.
byline setup --force
# Full nuke-and-pave: drop and recreate the database, then re-run every phase
byline setup --force --reset --i-mean-itsetup skips phases already recorded as complete; --force re-runs them
against fresh state without weakening database or file safety. It selects and
connects to the database before checking the selected adapter's packages and
environment key. If database selection or connection is declined, setup
stops instead of continuing to initialization or seeds.
The database-drop step asks for confirmation unless you pass --i-mean-it, and
on an installation already recorded complete reset must be combined with
--force. The --force confirmation is separate; add -y as well for a fully
noninteractive rebuild. Reset is intentionally different from the ordinary
safety path: it skips occupied-database inspection, drops the named database
if present, and recreates it from the baseline. Use it only when discarding all
existing document data is intended. Before running, setup checks that the
required @byline/* packages and env files are in place: it does not install
or upgrade packages; use byline init for that.
The selected adapter controls the dependency, environment variable, and server configuration generated by the CLI:
| Selection | Database package | Connection variable | Generated server configuration |
|---|---|---|---|
| PostgreSQL (default) | @byline/db-postgres |
BYLINE_DB_POSTGRES_CONNECTION_STRING |
pgAdapter() and @byline/db-postgres/admin |
| MySQL | @byline/db-mysql |
BYLINE_DB_MYSQL_CONNECTION_STRING |
mysqlAdapter() and @byline/db-mysql/admin |
When you include the examples, the scaffold also installs and registers the
matching @byline/search-postgres or @byline/search-mysql provider. The
minimal scaffold does not install a search provider. The unselected database
package and connection variable are not added.
Every Byline release carries one squashed Drizzle SQL baseline per database adapter inside the CLI. The CLI applies that baseline only to a missing or empty target database. It is a description of the target release's complete schema, not a history that can upgrade an earlier Byline installation.
Before any database mutation or application-password prompt, the ordinary initialization path inspects the target:
- A missing or empty database can receive the fresh baseline.
- A database with Byline tables is refused. Apply the numbered native SQL
scripts from
packages/db-postgres/sqlorpackages/db-mysql/sqlat the Git tag for the release you are upgrading to. - A database with unrelated tables or views is refused. Byline requires a dedicated empty database rather than merging into occupied application storage.
--force re-runs detection but does not bypass these refusals. The CLI does
not apply native SQL upgrades automatically. Search providers own a separate,
disposable numbered migration stream and may apply it from the generated
example configuration; search migrations are not part of the database
baseline.
For an intentional rebuild, use
byline setup --force --reset --i-mean-it. This path skips the inspection
above, drops the database, reapplies the fresh baseline, and loses all existing
content.
You do not need this section to install Byline. It documents what the installer does under the hood, for when a phase stops with a manual instruction or you are upgrading an older installation.
Admin and sign-in paths may be nested; every segment must match
[a-z][a-z0-9-]*. The installer rejects admin, API (resolved from
byline/routes.ts), and sign-in trees that overlap, requires sign-in to remain
outside both, and rejects roots that collide with locale prefixes,
_serverFn, _build, uploads, static, or public, plus any segment named
index or route, which is special to TanStack file routing.
Your answers are applied to both byline/routes.ts and the physical route
files below src/routes/_byline/: for example /internal/cms produces
src/routes/_byline/internal/cms/.... Changing byline/routes.ts alone does
not remount TanStack file routes, and src/routeTree.gen.ts must be
regenerated by TanStack rather than edited.
The CLI never imports or executes your application config. It reads route and
locale configuration with a restricted static evaluator that understands
literals, object and array literals, same-file const references, static
spreads, as const / satisfies, and the directly imported resolveRoutes()
helper from @byline/core. Anything outside that subset (imported values,
process.env, arbitrary function calls) makes the evaluator fail closed: the
routes phase plans no writes rather than guessing.
The installer uses the package manager declared by the workspace that owns the
application. In a monorepo, run it from the application directory: app files
stay there, while lockfiles and workspace-level settings are handled at the
owning workspace root. A --pm choice that conflicts with the owning
workspace is rejected; a noninteractive package.json-only workspace with no
manager metadata must pass --pm.
For pnpm, the installer creates or patches the owning pnpm-workspace.yaml
and merges the build approvals Byline's dependencies need (@google/genai,
esbuild, protobufjs, sharp) rather than asking you to stop for
pnpm approve-builds. Existing YAML is preserved; invalid YAML or a non-map
allowBuilds value is left for manual repair, and stale
pnpm.onlyBuiltDependencies configuration is removed because pnpm 10+ no
longer reads it.
byline init structurally re-detects every phase on each run, so old completion
flags cannot hide files or configuration required by a newer CLI. Missing
generated artifacts and recognized generated predecessors are upgraded or
migrated automatically, including moving the admin or sign-in mount, which is
planned as one atomic change set. Divergent user-owned scaffold, route, Vite,
Turbo, and CI files are never deleted or overwritten: the affected change is
blocked or skipped and reported with a manual note instead.
Before applying a planned create, patch, or delete, the CLI re-checks every target against the previewed state; if anything changed underneath the plan, none of that write set is applied.
The CLI derives package compatibility from its own version. For CLI version
x.y.z, the selected database adapter is pinned exactly to x.y.z because its
schema must match the bundled baseline. Other registry-backed @byline/*
packages must stay within >=x.y.z <(x+1).0.0-0; missing or incompatible
declarations are planned at ^x.y.z. Unbounded ranges and tags such as
latest are not accepted. workspace:*, workspace:^, and workspace:~
links are never replaced: the linked workspace package must resolve locally
and satisfy the same range, otherwise the dependency phase blocks for manual
repair.