The Developer Workspace
What It Is
A workspace is one git repository for one tenant. It holds the tenant's apps and its configuration, and the jetstack CLI connects it to the tenant's two environments: development, where work happens, and production, which gets the tested result.
acme/
jetstack.json the environments, and where apps and configuration live
AGENTS.md a guide for AI assistants (the CLI keeps its section up to date)
CLAUDE.md includes AGENTS.md for Claude Code
apps/
crm/ one app: jetstack.app.json, server/, public/
portal/
tenant/ the tenant's configuration: data model, roles, permissions, automations, …
.jetstack/ generated: schemas, types, build output, sync state
A customer with several tenants has one workspace per tenant.
Why It Matters
- One command to start.
jetstack connectfinds both environments from the tenant itself, so nobody types their addresses, and the first commit already holds the tenant's configuration. - Everything in one place. Apps and tenant configuration are versioned together, reviewed together and promoted together.
- Made for AI assistants too. An assistant opened in the workspace reads
AGENTS.md: what is where, which rules hold (work on development; production only from git), and which commands to use.
How It Works
Environments And Modes
jetstack.json lists the environments, each with a mode:
| Mode | Environment | Meaning |
|---|---|---|
sandbox | development | Direct changes are expected. jetstack dev applies an app's data model and other configuration on its own. |
protected | production | Every change shows its plan and asks first. Without a terminal (CI, an assistant), commands need --yes. |
Commands use development unless told otherwise (defaultEnv). No command defaults to production.
How The Tenant Is Found
Every environment describes itself at /.well-known/jetstack.json: whether it is production or development, the address of its paired environment, and its platform version.
- Isolated environments take this from their setup.
- Other tenants take it from the settings
environmentRoleandpeerEnvironmentUrl.
jetstack connect reads it from either environment.
Apps In The Workspace
Each folder of apps/ holding a manifest is an app; its folder name is its slug. App commands act on one app: the one named with --app <slug>, or the one whose folder the command runs in. --all runs a command for every app.
The Tenant's Configuration
tenant/ holds what Configuration As Code describes: the tenant's data model, roles and permissions, queries, automations and the rest, except what apps own (an app's own types, roles and permissions live in its manifest).
- Getting changes. Changes made in the tenant's UI or with the CLI come into the workspace with
jetstack tenant pull --env <env>: each entry of the environment's change history becomes its own commit, by the person who made it. - Reaching production. Production gets the committed folder, together with every app's tested release, by promotion:
jetstack promote --to prod.
How To Use It
Start A Workspace
- Install the CLI from the tenant. A tenant administrator or superuser opens Command-line tool in the tenant's system menu (
https://acme.jetstack.app/cli/install) and runs the install command it shows, for macOS, Linux or Windows (see Installing). The console's Develop card links to that page for each tenant. - In an empty folder, run
jetstack connect https://acme.jetstack.app(either environment's address works). - Sign in to development and production when the browser opens.
- Run
jetstack doctorwhenever something does not work; it names the fix for each problem.
Build An App
jetstack app new crm # apps/crm/: a data type, a menu item, handlers, a page
jetstack dev --app crm # run it from this computer against development
jetstack deploy --app crm # build, plan and deploy it to development
Work With An AI Assistant
Open the workspace folder in Claude Code, VS Code, Cursor or another assistant. It reads AGENTS.md (Claude Code through CLAUDE.md). Ask for what you need, such as "add an app for tracking support tickets, visible to each customer's own staff only". The assistant scaffolds, deploys to development and shows you the plans.
The CLI's section of AGENTS.md tells the assistant how to work: which commands to use, to check its work with jetstack validate and the plans, and where to read more. The assistant reads the documentation with jetstack docs and jetstack docs search, and the commands with jetstack help --json; the PHAR carries both, matching the platform version.
Keep the team's own notes for assistants below the CLI's section of AGENTS.md; jetstack agents refresh updates only that section, and jetstack agents status says whether it is current.
Three more steps make an assistant better at this work:
jetstack agents setuplets it use the tenant's own tools throughjetstack mcp: the data model, records, permissions and history, as you (see MCP).jetstack agents setup --skillsgives it step-by-step skills for the common jobs: adding a data type, setting permissions, writing a data migration, shipping to production, restoring a state and debugging a handler.jetstack agents setup --devcontainerwrites a dev container with the CLI and the assistant set up, for a team that works in containers or Codespaces.
Move An Older Project Into A Workspace
In a project with jetstack.config.json, run jetstack workspace upgrade. The app moves to apps/<slug>/, jetstack/ becomes tenant/, and the environments keep their addresses; commit the result.
Example
A team starts working on the tenant of Acme.
- A developer runs
jetstack connect https://acme.jetstack.appin an empty folder. The workspace knowsacme-dev.jetstack.appas development andacme.jetstack.appas production, and its first commit holds Acme's configuration. - They push the repository to the team's git host; colleagues clone it and run
jetstack login. - An assistant adds
apps/tickets/and deploys it to development. - A colleague adds a field to the client type in development's UI.
jetstack tenant pull --env devbrings it intotenant/as a commit by that colleague. - After review,
jetstack promote --to prodtakes the app and the configuration to production from git, as one change.
Best Practices
- Keep one workspace per tenant, and push it to the team's git host.
- Commit
.jetstack/sync.jsonwithtenant/: it records where the repository stands with each environment. - Set production's change policy to Reviewed, so nothing reaches production unseen, and pull production before applying to it.
- Never put tokens or passwords in the workspace; sign-ins stay in each person's own config folder, and CI uses
JETSTACK_TOKEN_<ENV>. - Run
jetstack doctorafter onboarding a colleague: it shows missing permissions before they cause errors.
Tenant Settings
environmentRole
Whether this tenant is a production (default) or a development environment. /.well-known/jetstack.json reports it, so jetstack connect knows which side it reached.
Isolated environments take it from their setup (JETSTACK_ENVIRONMENT) and ignore the setting.
peerEnvironmentUrl
The address of this tenant's paired environment: development's address on the production tenant, production's on the development tenant. With it, jetstack connect finds both environments from either address.
Empty by default; then connect needs both addresses (--dev <url> --prod <url>). Isolated environments take it from their setup (JETSTACK_PEER_URL) and ignore the setting.
devOutboundAllowlist
On a development environment, the hosts its calls to other systems may reach. Automations (the webhook and JSON stream steps), connectors, $api->http in code, OAuth connections and the MCP servers it uses keep to them. Everything else is held back, and the Event Log says what and where.
Write a host as api.example.com, or as .example.com for the domain and all its subdomains. An empty list allows nothing.
While it is set:
- a held call fails like any other failed call: the automation takes its error path, the code gets an exception;
- redirects are not followed, because they could lead elsewhere;
- PHP code may not use
curl_*, sockets or Guzzle directly;$api->httpis the way out.
Unset by default: development calls anything, as production does. Production ignores the setting.
devSchedulerEnabled
Whether a development environment's scheduler runs its tasks. Set it to false to hold them; a person can still run them from the Scheduler screen.
On by default. Production ignores the setting.