Projects & Linking
palbase project is how you create, list, inspect and delete the projects your account can reach, and it has exactly four subcommands — create, delete, list and status. Everything else on this page hangs off what palbase project list prints — each project, and its environments under it: palbase link binds a directory to a project, palbase env lists, selects, creates and deletes its environments, palbase clone downloads one environment's source into a new directory, palbase apikey reads and rotates an environment's two keys, and palbase members says who else can reach it. A golden test in the CLI pins that surface with no aliases and no shims, so a verb that is not on this page is not in the binary.
Cloud automatically archives idle projects and wakes them when needed. There is no manual archive or wake command in the CLI.
Quick example
palbase init
palbase project create "Todo App"
# Created Todo App — j06bwtuum (Running)
#
# Link it with:
# palbase link 'Todo App'
palbase link 'Todo App'
# ▸ no client app here (…) — linking the backend only
# no contract yet: https://j06bwtuum.palbase.studio cannot describe itself: nothing is deployed yet — a backend is what makes a contract, so this ends with `palbase push`
# the link is recorded; `palbase spec` fills the contract in once something answers
#
# linked to Todo App (proj_01)
# contract read from main; each verb resolves its own environment
# commit palbase/project.json
palbase plan
palbase push
Five commands, in the order a project actually happens: scaffold the code, create the project, bind this directory to it, measure what a push would change, ship. Creation is synchronous — create returns once the project is running — so the project can be linked in the next breath.
Projects and environments
A project is the product boundary — what a checkout is linked to. It is addressed by its name or its proj_… id, and it holds one or more environments. An environment is the thing that runs: its own database, files and keys, addressed by its ref at https://<ref>.palbase.studio. A new project starts with one environment, main; palbase env create adds more.
| Identifier | Shape | Where you see it |
|---|---|---|
| project name | free text, 1–64 characters, mutable | the PROJECT column of project list |
| project id | proj_… | palbase/project.json, and project list --json |
| environment name | main, staging, … | the ENVIRONMENT column, and palbase env list |
| ref | ^[a-z0-9]{4,24}$ — nine characters as minted | the REF column, and the environment's address |
palbase project status <ref> and palbase project delete <ref> take a ref and act on the environment it names. A ref is 8 random characters from [a-z0-9] plus the letter m — nine characters, carrying no part of the name. Creating a project called todoapp does not mint todoappm; it mints whatever the server returned, which is what create prints. See Environments.
Scaffolding the code — palbase init
palbase init
# ▸ installing @palbase/backend@<version>
# AGENTS.md
# CLAUDE.md
# db/public.ts
# modules/digest/digest.job.test.ts
# modules/digest/digest.job.ts
# modules/digest/digest.module.ts
# modules/health/health.controller.ts
# modules/health/health.module.ts
# modules/notes/dto/create.ts
# modules/notes/dto/list.ts
# modules/notes/dto/note.ts
# modules/notes/dto/update.ts
# modules/notes/notes.controller.ts
# modules/notes/notes.e2e.test.ts
# modules/notes/notes.module.ts
# modules/notes/notes.service.test.ts
# modules/notes/notes.service.ts
# package.json
# test-users.json
# tsconfig.json
# .gitignore
# ▸ resolving the project's dependencies
#
# ▸ @palbase/backend <version>
# palbase start run it on this machine
init takes no arguments and no flags. It does not take a project name, it talks to nothing, and it creates nothing in the cloud — creating a project is palbase project create, and these are separate steps on purpose.
The scaffold is not embedded in this binary. init asks npm which @palbase/backend is newest, installs it, and copies the template out of the installed package — so the scaffold and the SDK that compiles it are the same version by construction. It cannot ask for the latest dist-tag: that tag is deliberately frozen on the v1 line, so init asks the question latest used to answer, the newest non-prerelease version. With no network it refuses rather than guessing:
could not ask the registry which @palbase/backend to install (…) —
`npm view @palbase/backend versions` is the question, and it needs a network
It also refuses to scaffold over your work. A .git directory, a .gitignore, a README.md, a LICENSE and a .DS_Store do not count as content — cloning an empty repository and scaffolding into it is the ordinary way to start — but anything else stops the command:
this directory already has controllers, package.json in it — `palbase init`
scaffolds into an empty one so it cannot overwrite your work
Note: The scaffold is a worked vertical, not a bare minimum:
notesruns fromdb/public.tsthroughmodules/notes/dto/,modules/notes/note.service.ts(with its test) tomodules/notes/notes.controller.ts, all named by the module that declares them, so the layout is something you read rather than something you are told. It also writesCLAUDE.mdbesideAGENTS.md, because Claude Code reads the first name and every other coding assistant reads the second. See Architecture.
palbase project
All four are account-level rather than project-relative, so they need a cloud session (palbase login) or a PALBASE_ACCESS_TOKEN. They are not target-relative: list and create work in any directory, linked or not, and status and delete take the ref as an argument rather than reading it from a link file.
| Command | Arguments | Flags |
|---|---|---|
create <name> | exactly one | --tier, --json |
list | none | --json |
status <ref> | exactly one | --json |
delete <ref> | exactly one | --yes |
project create <name>
palbase project create "Todo App"
# Created Todo App — j06bwtuum (Running)
# waiting for j06bwtuum to answer…
#
# Link it with:
# palbase link 'Todo App'
palbase project create api --tier pro
# Created api — 4pt9z0k1m (Running)
# waiting for 4pt9z0k1m to answer…
#
# Link it with:
# palbase link api
create returns once the new environment answers, which is usually a few minutes after it is placed. If it has not answered after ten minutes the command fails and says so; the project still exists, and palbase project status <ref> shows Reachable yes once it does. The waiting line is printed on stderr, so --json leaves stdout carrying only the document.
The positional argument is the name. Quote it if it contains spaces. The only flags are --tier (default free) and --json.
| Flag | Default | Meaning |
|---|---|---|
--tier | free | the new Environment's compute envelope: free, pro or scale |
--json | false | emit the raw {ref, name, phase} record instead of the human lines |
There is no --name, no --region, no --github-account, no --repo, no --async and no --organization. --name in particular is not merely ignored: cobra rejects the command outright, because the flag is not declared.
Two limits are checked before anything is created: the plan's compute envelope, then how many Projects it allows. A free Organization may only open the free envelope and may hold 2 Projects; pro and scale are unlimited on both. A refusal therefore leaves nothing behind to clean up. See Limits.
The CLI sends no Organization, so a new Project lands in your Personal Organization — and your Personal Organization is created lazily, on first need, rather than at signup. A deploy ticket cannot create a Project: a machine identity bound to one project is refused. See Organizations and palbase login.
The Link it with: line names the project the way palbase link will read it: by its name when exactly one of your projects answers to that word — names, ids and environment refs counted together — and by the new environment's ref otherwise, as when two of your projects share the name or the name contains ://. A name that needs quoting is quoted, and one that starts with a dash comes after --. The line never suggests an address.
Note: A brand-new project can answer
503for a short while after creation.pushandlinkretry a 503 for up to 3 minutes at 6-second intervals; every other status is answered on the first try. So linking immediately is fine, but acurlof the address in that window is not a sign that anything is wrong.
project list
palbase project list
# PROJECT ENVIRONMENT REF STATUS
# Todo App main j06bwtuum Running
# staging p8vn2c4rm Running
# analytics main q7wd3n5vm Running
The project is printed once, with its environments on the rows under it — repeating the name on every row read as two projects. A project with no environments prints (none) in the ENVIRONMENT column, and a project with no name is listed by its proj_… id. --json emits each project's id, name and environments. With nothing to list:
No projects yet — create one with `palbase project create <name>`.
Every value in the PROJECT and REF columns is something palbase link takes, and every one of them binds the whole project. See Linking a Checkout.
project status <ref>
palbase project status j06bwtuum
# Name Todo App
# Ref j06bwtuum
# Phase Running
# Reachable yes
The argument is the ref, and it is required — status reads no link file and has no notion of "the current project". It accepts --json and emits that environment's {ref, name, phase, reachable} record. Reachable says whether the environment answers right now: an environment that was just placed reads Running before it does.
For what is actually running in a project — the live digest, the endpoint count, the SDK version and the credential this machine is using — the command is palbase status in a linked checkout, which is a different command with a different job. See Deploying.
project delete <ref>
palbase project delete j06bwtuum
# This deletes j06bwtuum and its data permanently.
# Type the ref to confirm: j06bwtuum
# Deleted j06bwtuum
Confirmation is typing the ref, not y. Anything else aborts before the request:
aborted — "todoapp" does not match "j06bwtuum"
--yes skips the prompt for scripts, and is the only flag. It destroys the Environment's database, its files, its vault and its keys.
Warning: Deletion is permanent. Cloud's automatic archiving manages idle projects while they exist; it does not provide an undo for
project delete.
Linking a checkout
palbase link binds a directory to a project, and it is the central verb of the CLI. It writes the committed palbase/project.json, and that file is what makes a directory act on a project — every later push, plan, pull, status, deploys, rollback, spec, secret, run, storage, flags, egress, notifications, auth, test-user and apikey list reads it. In a checkout with a client app, the same run writes the config and the contract of every environment.
palbase link "Todo App" # by name, as project list prints it
palbase link p8vn2c4rm # by any environment's ref — the same project
palbase link https://127.0.0.1 --insecure # a stack on this machine, by address
Code reaches an environment one way: palbase link, then palbase push. The mechanics — what link writes, which environment it reads from, the 503 retry, --token-stdin for a stack you host yourself, and why nothing under palbase/ is gitignored — are in Linking a Checkout. palbase unlink removes the binding, and re-running palbase link rebinds.
There is no palbase project use: two mechanisms for "which project is this directory" is how somebody pushes to the wrong one.
Environments — palbase env
A linked checkout acts on one of its project's environments at a time, and choosing which never touches a committed file. palbase env has four verbs, and each acts on the project this checkout is linked to:
| Command | Arguments | Flags |
|---|---|---|
env list | none | — |
env use <name> | exactly one | — |
env create <name> | exactly one | --tier, --yes |
env delete <name> | exactly one | --yes |
palbase env list
# NAME REF STATUS
# * main j06bwtuum Running
# staging p8vn2c4rm Running
palbase env use staging
# ▸ Todo App/staging
* marks the environment this machine has selected. palbase env use remembers the choice for this checkout on this machine only — in ~/.palbase/checkouts/<hash>/selection.json, never in the repository — so a colleague's clone is never switched by yours. A name that is not one of the project's environments is refused with the list.
For one command, pass --env <name> — a name or a ref — and the command acts on that environment without remembering it. The PALBASE_ENV variable does the same:
palbase deploys --env staging
PALBASE_ENV=staging palbase status
With neither, a command acts on a stack palbase start runs here, then this machine's selection, then the project's only environment; with several environments and none selected, it refuses and lists them. See Which environment a command acts on.
palbase env create staging --tier free
# This creates a new environment "staging" under Todo App.
# compute envelope free
# billing …
# Type the name to confirm: staging
# Created staging — p8vn2c4rm (…)
# waiting for p8vn2c4rm to answer…
#
# palbase env use staging
An environment bills for what it uses out of your organisation's pooled quota, so create prints that consequence before it asks. Like project create, it returns once the new environment answers. Confirmation is typing the name, and --yes skips it. --tier names the compute envelope; without it, the smallest one your plan allows is used. In an app checkout, palbase link with no target then writes the new environment's config and contract.
palbase env delete staging
# This deletes Todo App/staging (p8vn2c4rm) and its data permanently.
# Type the ref to confirm: p8vn2c4rm
# Deleted staging (p8vn2c4rm)
Confirmation for a delete is typing the ref, and --yes skips it. It destroys that environment's database, files, vault and keys.
Downloading a project — palbase clone
palbase clone "Todo App"
# ▸ Todo App/main
# ✓ pulled environment https://j06bwtuum.palbase.studio (41283 bytes)
# ▸ Todo App/main
palbase clone p8vn2c4rm --dir todoapp-staging
palbase clone takes what palbase project list prints — a project's name, its proj_… id, or one of its environments' refs — resolved against the same listing palbase link reads. A ref downloads that environment. A name or an id downloads the environment the one default rule picks — the only available one, else this machine's selection, else main, else the first by name — and --from-env <name> names another. A ref and a --from-env that disagree are refused with both named, and an environment that is not running has no source to download:
p8vn2c4rm is the ref of Todo App/staging, but --from-env names main — drop one of them.
A value that answers to none of your projects is refused with the list of the ones you have: no project of yours is called "Todo Ap", and none of your environments has that ref. Yours:.
clone downloads the deployed source of that environment into a new directory named after the project, writes palbase/project.json there, and remembers the environment it cloned as that checkout's selection — so push, pull and spec there act on the environment it came from. It is not a git clone and there is no repository behind it: a Project has no repository column, no repository binding and no connect-repo or disconnect-repo command.
| Flag | Default | Meaning |
|---|---|---|
--dir | the project's name | directory to clone into |
--from-env | (the default rule above) | the environment to take the source from |
A clone that fails removes the directory it created when nothing arrived in it, so an empty directory named after the project never looks like a clone still running; a directory that already existed is left as it was.
Note:
cloneneeds a cloud session so it can resolve the project, and a credential for the environment so it can read the source. An environment that has never deployed has nothing to clone, and says so:<address> has no source to pull: ….
API keys — palbase apikey
Every Environment is minted with two keys, and they are the only two it will ever have: publishable pb_project_c… (role anon) and service-role pb_project_s… (role service_role). There are three verbs, no arguments: palbase apikey list | reveal | rotate. create and revoke were removed — a v2 project has no arbitrary named-key set to create or revoke from.
palbase apikey list
# ▸ Todo App/main
# publishable pb_project_cA1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6
# service-role (hidden — `palbase apikey reveal` prints it)
palbase apikey reveal
# ▸ Todo App/main
# publishable pb_project_cA1b2C3d4E5f6G7h8I9j0K1l2M3n4O5p6
# service-role pb_project_sZ9y8X7w6V5u4T3s2R1q0P9o8N7m6L5k4
| Command | Door | Notes |
|---|---|---|
list | the project itself | prints the publishable key; masks the service-role key |
reveal | your account | prints both |
rotate | your account | replaces both |
The split is a rule rather than an accident: a command touching one project is target-relative; one that needs your account is not. list asks the stack about itself and therefore works against a stack you host yourself; reveal and rotate need your cloud session and refuse anything that is not a project of this cloud:
https://127.0.0.1 (local) is not a project on this cloud — its keys are its own
All three accept --json. apikey list --json emits {"publishable": "…"} and nothing else — the masked key is masked in both modes. reveal --json and rotate --json emit the cloud's key document, {"anonKey": "…", "serviceRoleKey": "…"}.
The two keys differ by one character — the c or s right after pb_project_. The ref half is the stack's own identity, not your address; the constant is project, so every Environment's keys read pb_project_…. The ref is not in the key; a client is configured with an explicit url and apiKey. See the two API keys.
Warning: The blast radius of a leaked service-role key is the whole Environment, its data, its secrets and its running code. Never ship it in a browser bundle, a mobile app, or anything a user can read. The publishable key is the one clients get.
Rotation
palbase apikey rotate
# This replaces j06bwtuum's keys. Anything holding the old key stops working, and the project restarts.
# Type the ref to confirm: j06bwtuum
# Rotated j06bwtuum
# publishable pb_project_cQ4r5S6t7U8v9W0x1Y2z3A4b5C6d7E8f9
# service-role pb_project_sM1n2O3p4Q5r6S7t8U9v0W1x2Y3z4A5b6
#
# The project is restarting; give it a moment before the new key answers.
Rotation replaces both keys — atomic, no grace period, the project restarts, confirmation is typing the ref. There is no window in which the old and new keys are both valid, so roll the new publishable key out to your clients as part of the same piece of work. --yes skips the prompt.
Nothing is written to disk when you rotate. The CLI brokers the service-role key per command and caches nothing, so a rotation cannot leave a stale copy behind.
Members — palbase members
palbase members list
# ROLE EMAIL ID
# owner you@example.com usr_…
# member teammate@acme.com usr_…
palbase members add teammate@acme.com
# Added teammate@acme.com to j06bwtuum
palbase members remove usr_…
# Removed usr_… from j06bwtuum
Three verbs — list, add <email>, remove <user-id>. list accepts --json and emits {userId, email, role} rows; add and remove take no flags.
There is no invitation. No members invite, no members role, no members invitations, no members accept. There is no invitation e-mail, so a member is an account that already exists, added by the address it signed up with. If add cannot find that address, the person has to sign up first.
A project has one owner — a column on the project itself, not a membership row — plus a flat set of members. That is why remove cannot take the last person off a project: its own help says "Remove a member (never the owner)". Changing the owner, and everything about Organization membership, is a Studio surface; the CLI does not know about Organizations at all. See the Studio.
The commands need this checkout linked to a cloud project, because a membership is something your account knows:
https://127.0.0.1 (local) is not a project on this cloud — it has no membership
An account with no email recorded prints (address not recorded yet) in the EMAIL column rather than a blank one.
Commands this page used to document
None of these exist in the binary. They are listed so a search for them lands somewhere useful rather than in a shell error.
| Gone | What to run instead |
|---|---|
palbase project use <id> | palbase link <project> |
palbase project connect-repo / disconnect-repo | nothing — a Project has no repository; deploy with palbase push |
palbase github status / accounts / connect | nothing — there is no GitHub integration and no webhook deploy |
palbase apikey create / revoke | palbase apikey list | reveal | rotate |
palbase members invite / role / invitations / accept | palbase members add <email> — the account must already exist |
palbase mode, palbase endpoints | palbase doctor shows the configured addresses and checks the local environment |
| a flag that picks a project for one command | palbase link <project> in the directory; --env <name> picks one of its environments |
There is likewise no Palbase "branch" resource, no --branch flag anywhere in the tree, and no branch-to-project mapping. Your Git branches are yours and the platform never sees them: a branch that needs its own database is its own project — create one with palbase project create and link that checkout to it.
Related
- Linking a Checkout —
palbase linkin full: what it writes, which environment a command acts on, and how to rebind - Overview — installing the CLI, logging in, and the whole command surface
- Deploying —
palbase push,plan,deploys,rollbackandpull - Stack Settings — storage, flags, egress and notification senders, written straight to the stack
- Introduction — the two keys an Environment is minted with, and what each opens