Syncing an App's Settings
palbase apps:sync writes into an app's repository what the app is built with in each environment it is set in: the environment's address, the app's publishable key, what the modules tell a client, and a typed client of each backend the environment runs. It writes under palbase/ and touches nothing outside it. The build reads those files, so nothing reaches the network when the app is built, and a URL or key is never typed into the app.
Running it
From the repository's root, name the apps it builds:
palbase apps:sync shop-web
The apps named join the checkout's, which palbase/project.json records. After that, palbase apps:sync with no name syncs them all. One checkout may build several apps: a web app and its admin site, or an iOS and an Android app side by side.
Run it again whenever the project changes: the app set in a new environment, its key replaced, a machine added, a backend's routes or gateways changed. Each file is made whole from what the project gives now and compared with the one on disk; only a file that differs is written. A file it made that the project no longer gives, such as an environment the app left or a machine removed, is deleted. Commit what it writes.
Each run prints what it added, changed and removed, and notes what needs you: an app in no environment yet, a machine with no contract to make a client from, the keychain entitlement an iOS app needs.
What it writes
palbase/
├── project.json
├── shop-web.ts web: the editor's environment
├── shop-web.main.ts web: one per environment
├── shop-web.staging.ts
├── shop.palbase iOS: every environment, as Swift
├── palbe/
│ ├── backend.ts web: the editor's environment
│ ├── backend.main.ts
│ └── backend.staging.ts
└── environments/
├── main/
│ ├── shop-web.json
│ ├── shop-web.ts
│ ├── shop.json
│ ├── contracts/backend.openapi.json
│ └── palbe/backend.ts
└── staging/
└── …
| File | For | What it holds |
|---|---|---|
palbase/project.json | every checkout | The project's id and name; the checkout's apps (id, slug, name, platform); the editor's environment (types_environment); the backends' names in the apps (clients) |
palbase/environments/<env>/<app>.json | every app | The app's settings in that environment |
palbase/environments/<env>/contracts/<machine>.openapi.json | an environment with a backend | The contract the machine's serving build answers |
palbase/environments/<env>/palbe/<machine>.ts | a web app | The backend's typed client, in TypeScript |
palbase/environments/<env>/palbe/<machine>.kt | an Android app | The backend's typed client, in Kotlin |
palbase/<app>.palbase | an iOS app | Each environment's configuration and its backends' clients, in Swift: one file, which the Xcode build rule reads for the build's environment |
palbase/environments/<env>/<app>.ts | a web app | The app's settings module: settings, connect() and pb, the client typed with the environment's backends |
palbase/<app>.ts, palbase/palbe/<machine>.ts | a web app | The editor's environment, re-exported |
palbase/<app>.<env>.ts, palbase/palbe/<machine>.<env>.ts | a web app | Each environment's, which a build made for it is type-checked against |
palbase/environments/ and palbase/palbe/ are the sync's own: a file there that it would not write is removed, except a .storekit file, which palbase purchases:storekit makes. At the top of palbase/ it touches only project.json, .palbase files and files that start with its header, // Code generated by palbase apps:sync. When palbase/, or a folder or file in it, is a symbolic link, it writes nothing.
The app's settings
palbase/environments/<env>/<app>.json is the document palbase apps:sdkconfig <app> -E <env> prints:
{
"schema": 1,
"project": { "id": "0b9c4e7a-…", "name": "Shop" },
"environment": { "ref": "k3xq81w4m", "slug": "staging", "name": "Staging", "production": false },
"app": { "id": "4f6c2d10-…", "name": "Shop Web", "slug": "shop-web", "platform": "web", "web": { "origins": ["https://staging.shop.example.com"] } },
"url": "https://k3xq81w4m.palbase.studio",
"publishable_key": "pb_k3xq81w4m_c…",
"auth": { "rp_id": "k3xq81w4m.palbase.studio", "associated_domains": ["webcredentials:k3xq81w4m.palbase.studio"] },
"realtime": { "url": "wss://k3xq81w4m.palbase.studio/realtime/v1" },
"backends": [{ "machine": "backend", "url": "https://k3xq81w4m.app.palbase.studio", "contract_digest": "9e1f…" }],
"digest": "sha256:5b2a…"
}
| Field | What it is |
|---|---|
environment.production | true for the project's first environment, main |
app | The app, with what it is in this environment (ios, android or web) |
url, publishable_key | What the app's client is made with: the environment's address and the app's own key |
keychain_group | An iOS app's: <App ID prefix>.<bundle>.palbase, the keychain access group the app and its extensions keep the sign-in in |
auth | The passkeys' relying party, and the iOS app's Associated Domains entry |
backends | The environment's machines: each one's address, and the digest of the contract its serving build answers (none while it is unread) |
digest | The SHA-256 of the document without this field: a build made with another digest is not current |
An iOS app needs the Keychain Sharing capability with the group $(AppIdentifierPrefix)$(PRODUCT_BUNDLE_IDENTIFIER).palbase in every target that uses Palbase: without it the SDK keeps no session, and sign-in fails with keychain_unavailable. The sync reminds you each run.
--format env prints the address and key alone, for a build that does not read palbase/ (another framework, a game engine):
palbase apps:sdkconfig shop-web --format env -E staging
PALBASE_URL=https://k3xq81w4m.palbase.studio
PALBASE_PUBLISHABLE_KEY=pb_k3xq81w4m_c…
The publishable key is public: it ships in the app. A secret key never does, and no file the sync writes holds one.
A build and its environment
The settings of every environment are in the checkout; the build picks one.
- Web. An app imports
pbfrompalbase/<app>.ts. For the editor, that is one environment's:--types <env>names it,palbase/project.jsonremembers it, and without one it is the first environment the app is in that is notmain. A build imports its own environment's instead: the Vite plugin frompalbase/web/vite,palbase({ app: "shop-web", environments: { development: "staging", production: "main" } }), maps eachvite --modeto an environment and type-checks the app against that environment's backends first. See Calling Your Backend. - iOS. The Xcode build rule extracts the Swift of the environment its configuration's
PALBASE_ENVIRONMENTnames frompalbase/<app>.palbase; a configuration that names none, or one the app is not set in, stops the build. See iOS SDK. - Android. The app's
build.gradle.ktsmaps each build variant to an environment and compiles that environment's<app>.jsonand Kotlin clients into the variant's sources; a variant that names no environment, or one the app is not set in, stops the build. See Android SDK.
Backends
Each machine of the environment becomes pb.<name> in its apps, typed from the contract its serving build answers: its routes, their models and typed errors, and its gateways. The name is the machine's camelCased, pb.shopApi for the machine shop-api, unless --as shop-api=shop names it otherwise. A name is the checkout's, the same in every environment, kept in palbase/project.json, and never one of the client's own (auth, flags, storage, …).
- A machine whose contract has not been read yet gets no client, and the run says so. A contract is read once the build's version alone serves: a
palbase/backendapp writes its own; for another program,palbase machines:update <machine> --openapi-path <path>names where it serves one. - When a push's version did not roll out (its contract was refused, its tests failed), the client is made from the build that serves, and the run says so.
- Two backends of one environment that declare the same type, both with a
HealthControllersay, cannot share one Swift or Kotlin target: the run stops (backends_collide) and names it.
--backend [<machine>=]<dir> makes a backend's client from its checkout on this computer instead, to call a route before any machine serves it. The CLI runs bun --no-env-file run src/main.ts there with PALBASE_CONTRACT_OUT set: the palbase/backend app writes its contract and exits, connecting to nothing and reading no .env file. Sync again without --backend once the backend is deployed.
In CI
palbase apps:sync --check
--check writes nothing. It exits 1 when the committed files are not what the project gives now, and lists each file as missing, differing or no longer given.
An environment token sees its own environment alone, so a sync made with one writes that environment's files and counts every other environment's as no longer given. For a checkout of several environments, run the check with an organization's access token of the member role.
When it refuses
| Code | Why |
|---|---|
another_project | The checkout's palbase/ is another project's. Run it for that project (-P), or in this project's own checkout. |
app_deleted | The app is deleted; palbase apps:restore brings it back. |
no_such_app | The project has no app by that slug or id; the answer lists its apps. |
environments_collide | Two environments' slugs differ only in letter case: their folders would be one on macOS and Windows. |
linked_path | palbase/ or a folder in it is a symbolic link. |
unusable_contract | A backend's contract makes no client in the app's language: fix the backend, deploy it, and sync again. |
invalid_backend_name | An --as name is a word the client or the language keeps. |
Related
- Client Apps: registering an app and setting it in an environment
- Calling Your Backend and Web Gateways:
pb.<backend>in a web app - CLI reference:
apps:syncandapps:sdkconfig