Desktop
Install Chroma Desktop, run your app in it with chroma dev, import a built zip, and understand where its data is stored.
Chroma Desktop is the executable runtime mode for Chroma: a native app that hosts the same WASM bundle you ship to the web. See Runtime modes for how Portable (web) and Desktop differ while exposing the same APIs.
You build an app with chroma build, import the .zip into a small library, and open it in the window. During development, chroma dev can open your project in Desktop instead of a browser tab.
| Topic | Behavior |
|---|---|
| Import input | .zip with index.html at the archive root (package.zip: true in chroma.json) |
| App URL | http://127.0.0.1:<port>/<app-id>/index.html, not file:// |
| Library server port | 47823 on 127.0.0.1 by default (stable IndexedDB origin) |
| Develop mode | Skips the library and loads the chroma dev URL |
| File storage | Chroma-Fs over a postMessage bridge |
| API routes | Same as the browser: client-side /api in the app iframe |
Each import gets its own folder, and imported apps stay imported across launches.
Install
Chroma Desktop is distributed to licensed customers together with the Chroma installer package. Targ Apps provides the installers for your platform when your license is activated; see Licensing.
chroma install also provides a chroma-desktop binary for develop mode, copied from the CLI bundle. If you installed the CLI and have no chroma-desktop, use chroma dev --no-desktop or install Desktop from the package Targ Apps provided.
Develop mode
Develop mode is chroma dev with Chroma Desktop as the window. You keep editing .chr files on disk, and the app loads from the same dev server a browser tab would use.
It starts when the app is dynamic (including the standard template) and chroma.json has dev.desktop: true:
- The dev server starts in the background on
dev.port(default4173). chroma-desktop --develop --url http://127.0.0.1:<port>/<entry> --title <name>opens.- The library is skipped and the app loads your dev URL immediately.
- Closing Desktop stops the dev session.
To use the browser instead, run chroma dev --no-desktop. If chroma-desktop cannot be found, chroma dev stops with an error that suggests chroma install or --no-desktop.
You can also launch develop mode by hand:
chroma-desktop --develop --url http://127.0.0.1:4173/index.html --title "My app"In develop mode, Chroma-Fs uses the app id develop, so files land in <app-data>/apps/develop/data/, and IndexedDB is keyed to the dev server origin.
Import an app
Import installs a finished build (the zip from chroma build), not your src/ folder. The archive must look like dist/: index.html at the top, plus the bundle, WASM, and optional api/.
- Drag the
.ziponto the window, or - Click Import .zip on the library screen.
What happens:
- Anything that is not a
.zipis rejected. - The display name comes from the zip file name; unsafe characters become
-. - The zip is extracted into its own folder. If the name is taken, Desktop adds
-2,-3, and so on, so re-importing never overwrites an existing app. - Entries with absolute paths or
..are rejected. - If
index.htmlis missing at the root, the import fails and the partial folder is removed.
A successful import opens the app. A failed one shows a message at the bottom of the window and leaves the library unchanged.
cd my-app
chroma build
# import the generated .zip by drag and drop or with "Import .zip"The library
On launch, Desktop shows every imported app in a grid, sorted by name, with an Import .zip card first. Select a card to open the app full-window, and use the back arrow to return.
Storage
How your app persists data depends on its Chroma-Fs backend.
Filesystem backend
Apps in the iframe cannot call Tauri directly. When chroma-fs.json uses backend: "filesystem", Chroma-Fs talks to Desktop over window.postMessage and Desktop reads and writes under the app's own data/ folder. You never call postMessage yourself:
Files live at <app-data>/apps/<app-id>/data/, for example ~/.local/share/com.chroma.desktop/apps/my-app/data/notes/hello.txt on Linux.
IndexedDB backend
IndexedDB data is stored per origin, and the port is part of the origin:
| Mode | Typical URL | IndexedDB origin |
|---|---|---|
| Library (imported zip) | http://127.0.0.1:47823/<app-id>/index.html | http://127.0.0.1:47823 |
| Develop mode | http://127.0.0.1:<dev.port>/<entry> | The dev server origin |
Browser (chroma dev --no-desktop or a static host) | Top-level page | The dev server or host origin |
Desktop binds 127.0.0.1:47823 so the origin stays stable across restarts. If that port is busy, it falls back to a random free port and logs a warning, and data stored under the stable origin will not be visible until the port is free again.
Browser dev and Desktop do not share IndexedDB, because their origins differ. To move data between them, export it from one and import it into the other.
Security notes
- The bundled server listens on
127.0.0.1only and is not reachable from the network. - Zip extraction rejects entries that would write outside the app's folder, and the server refuses paths that escape the apps directory.
- The app window has a narrow permission set: window controls and a file picker, with no direct filesystem, shell, or HTTP permissions for the frontend.
Desktop does not sandbox JavaScript inside an imported app beyond loopback-only networking and the protections above. Only import bundles you trust, as you would with any native program.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
'<file>' is not a .zip file | A non-zip file was selected | Import the .zip produced by chroma build with package.zip: true |
the zip does not contain an index.html at its root | index.html is nested in a subfolder or missing | Rebuild with chroma build instead of zipping the project by hand |
| Dropped file does nothing | The drop handler only accepts .zip and skips other files silently | Use Import .zip to see the real error |
| App is blank or its assets 404 | The build is incomplete or was built for another base path | Serve dist/ locally (Server) to check it, fix the build, then re-import |
| Re-importing does not update an open app | Each import is a new entry and never overwrites | Open the new entry, and delete the old folder from the apps directory if you do not need it |
| IndexedDB data is missing after a restart | Port 47823 was busy, so the origin changed | Free the port and relaunch, or restore from a backup |
| Data in the browser but not in Desktop | Different origins, so different IndexedDB | Export from one and import into the other |
| Window edges do not resize | Old build, or a Wayland compositor issue | Update to the latest installer; on Wayland try GDK_BACKEND=x11 chroma-desktop to confirm |
Experimental mobile targets
Compiling Chroma apps for Android and iOS (the same chroma build output wrapped in a mobile shell) is experimental.
Mobile builds are not production-ready. Toolchains, flags, and permissions may change without notice. Use them only as previews.
Desktop (Linux and Windows) is the supported native path, and the web, via chroma build and static hosting, is the recommended path for predictable releases. See Limitations.
Next
- Runtime modes — canonical Portable vs Executable comparison
- CLI to scaffold, serve, and build the app you import
- Server for how the dev server and Desktop serve files
- Chroma-Fs backends for the full storage matrix
- Plugins — same loader in the iframe; host-specific storage
Chroma Desktop is proprietary software. See Licensing.