Skip to content

Installation

One command writes a working project. The scaffolder asks up to four questions, installs dependencies, creates a local database and leaves you with a dev server and an admin waiting for its first passkey.

create-plumix-app assembles a project rather than copying a fixed template. It reads the runtime you pick and the plugins you tick, then splices their imports, their config slots and their registrations into one plumix.config.ts. Choosing the media or the OG plugin is what puts storage: r2({ binding: "MEDIA" }) in the config and an r2_buckets entry in wrangler.jsonc. Both declare a storage requirement, and it is fulfilled once however many of them you tick. Choosing neither leaves both out.

You need one of npm, pnpm, yarn or bun. No published Plumix package declares an engines range, so no Node version is enforced at install time. The repository is developed and tested on Node 24, which is what its .nvmrc pins.

The post-scaffold install uses the package manager that invoked the scaffolder. The scaffolder reads it from the npm_config_user_agent string every manager sets. When that string is missing or names a manager the scaffolder does not know, it falls back to npm. Pass --pm pnpm, --pm yarn or --pm bun to choose one explicitly.

The target directory must not exist, or must be empty, and its parent must exist. Its last path segment becomes the project name, so it has to match npm’s package grammar: lowercase letters, digits, and -, _ or ., up to 214 characters. The name is spliced into package.json, into wrangler.jsonc and into the passkey relying-party name, so the scaffolder rejects an out-of-grammar name up front rather than emitting a project that will not build.

Run the scaffolder with whichever package manager you use:

Terminal window
pnpm create plumix-app my-site

Then work through the rest:

  1. Answer the wizard. It asks only for what the command line did not already supply, so the command above, which names the directory, gets three questions rather than four. Those are which runtime to target, which plugins to include, and which sign-in methods to add. Run pnpm create plumix-app with no directory and it asks where to create the project first. Node is the default runtime, running as a plain process against a local SQLite file; pass --runtime cloudflare for a Worker against D1 instead. Passkeys are always on, so the sign-in question is about what to layer beside them. The plugin question opens with SEO and Feeds ticked, so a new site serves head meta, robots.txt, a sitemap and feeds from the first request; nothing else is preselected. Untick either and the generated project has no import and no registration for it. One pairing is worth knowing before you do: og declares SEO as a peer and needs it registered to reach og:image, so unticking SEO while keeping OG installs the package without wiring it up.

  2. Let the post-scaffold steps run. The scaffolder installs dependencies, runs plumix migrate against the local database, then initializes a git repository and makes one commit. Each step is skippable with a flag, and none of them throws, so a failure never leaves you without a project.

    The three do not report failure equally. A failed install and a failed database setup are both named in the output. The printed next steps carry pnpm install when the install did not run, but never the database step, because plumix dev applies the migrations itself when it starts. The migration step runs the installed plumix binary, so it is skipped whenever the install did not succeed, with no message saying it was skipped. Git reports nothing either way. A failed git init, or a commit rejected for want of a configured git identity, prints no warning and no recovery step, and git init is skipped outright when the target directory is already inside a repository.

  3. Start the dev server. On the default Node runtime, the scaffolded config pins the passkey origin to port 3000:

    Terminal window
    cd my-site
    pnpm dev -- --port 3000

    Vite serves the site at http://localhost:3000, against a local SQLite file. On --runtime cloudflare, run plain pnpm dev instead — it serves at http://localhost:5173, against a local D1 database in .wrangler.

  4. Claim the admin account. Open http://localhost:3000/_plumix/admin (:5173 on Cloudflare). With no users in the database, the admin sends you to “Create admin account”. Enter an email and an optional name, submit, and the browser runs the WebAuthn ceremony. The account you create this way is an admin. The role is decided inside the insert statement itself, so two people submitting the form at the same moment cannot both be elected.

  5. Confirm the public site renders. Open http://localhost:3000 (:5173 on Cloudflare). The scaffolded theme greets you and links back to the admin.

Before the first deploy, edit the placeholders the generated files carry comments for. On Node and Bun that is the passkey rpId and origin in plumix.config.ts. On Cloudflare there are two. accountSubdomain in the cloudflareDeployOrigin call ships as your-account. The database_id in wrangler.jsonc ships as local-development-only until you paste in the id wrangler d1 create prints.

Registration closes after the first account

Section titled “Registration closes after the first account”

The passkey registration endpoint counts the users table before it issues a challenge. Zero rows means bootstrap, and the request is allowed without a session. One row or more means registration is closed, and an unauthenticated request is refused with registration_closed. An already-signed-in user hitting the same endpoint is adding a device instead, and the email in the request has to match their own.

So the first person to reach a fresh deploy owns it. Deploy and claim the account in the same sitting, or gate the deploy until you have.

Every prompt has a flag, and --yes accepts the defaults for whatever you did not pass. That combination is what makes the scaffolder usable from a script or a CI job, where there is no terminal to prompt into. The wizard only runs on a real TTY and never under CI.

Terminal window
pnpm create plumix-app my-site --plugins blog,pages,media,seo,feeds --auth oauth --pm pnpm --yes

The flags are:

  • --runtime <id> picks the runtime, node, bun or cloudflare. Defaults to node. A bun project installs with Bun and runs every plumix command as bun --bun plumix ….
  • -p, --plugins <ids> takes a comma-separated list. The ids are blog, pages, menu, comments, media, audit-log, og, seo and feeds. Leave the flag off and you get seo and feeds. The flag replaces that pair rather than adding to it, so --plugins blog scaffolds blog alone, and --plugins= scaffolds none.
  • --auth <ids> adds sign-in methods to passkeys, as a comma-separated list: oauth for GitHub, magic-link, and cfAccess for Cloudflare Access on the cloudflare runtime. Leave it off and you get passkeys alone. An unknown id exits with the list of methods the runtime offers.
  • --pm <name> picks npm, pnpm, yarn or bun. Without it you get the manager that ran the scaffolder, or npm when the scaffolder cannot tell which one that was. An unrecognised name exits with an error rather than falling back. --runtime bun always installs with Bun, so it exits with an error beside a --pm naming another manager.
  • --no-install skips the dependency install.
  • --no-db skips applying migrations to the local database.
  • --no-git skips git init and the first commit.
  • -y, --yes accepts defaults for everything not flagged.

Skipping the database step skips no work you have to do yourself. Every start of plumix dev applies the migrations core ships, then those of each plugin that carries its own tables, to the local database: the SQLite file on Node and Bun, the local D1 copy on Cloudflare. Adding a plugin that carries its own tables means restarting plumix dev, nothing more.

Tables of the site’s own, declared by a plugin inside the project, get their own history in the project’s migrations/ folder. When you change one, the next plumix dev writes the migration for the change into migrations/, applies it, and prints the file to commit. plumix migrate generate does the same without starting the server.

The wizard’s sign-in question saves you an edit. It locks nothing in. Picking GitHub OAuth adds a github(...) provider to the auth block and writes GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET into .env. Picking magic links adds a magicLink block and no mailer: plumix dev catches each sign-in mail for you, and plumix build fails until you set a top-level mailer that sends through a real transport. Both are edits to plumix.config.ts you can make by hand afterwards.

Cloudflare Access appears in the same list when the runtime is Cloudflare. It replaces the request authenticator rather than adding a login form, so the identity provider in front of the worker decides who the user is.

Project Structure is the file-by-file account of what the scaffolder just wrote. Configuration is the reference for the config file it assembled. Passkeys covers the WebAuthn ceremony, the relying-party id and adding a second device. Secrets covers .env, EnvInput and production secrets. Plugins covers installing and configuring the nine official plugins.

Read Project Structure next, then model something. Content Modelling shows how a local plugin registers an entry type, a taxonomy and a meta box, and it is the page every later section assumes you have read. When the model is settled, Deploy Your Site takes the project to a live URL.