Targ Apps Docs
Chroma

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.

TopicBehavior
Import input.zip with index.html at the archive root (package.zip: true in chroma.json)
App URLhttp://127.0.0.1:<port>/<app-id>/index.html, not file://
Library server port47823 on 127.0.0.1 by default (stable IndexedDB origin)
Develop modeSkips the library and loads the chroma dev URL
File storageChroma-Fs over a postMessage bridge
API routesSame 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:

  1. The dev server starts in the background on dev.port (default 4173).
  2. chroma-desktop --develop --url http://127.0.0.1:<port>/<entry> --title <name> opens.
  3. The library is skipped and the app loads your dev URL immediately.
  4. 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 .zip onto the window, or
  • Click Import .zip on the library screen.

What happens:

  1. Anything that is not a .zip is rejected.
  2. The display name comes from the zip file name; unsafe characters become -.
  3. 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.
  4. Entries with absolute paths or .. are rejected.
  5. If index.html is 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:

ModeTypical URLIndexedDB origin
Library (imported zip)http://127.0.0.1:47823/<app-id>/index.htmlhttp://127.0.0.1:47823
Develop modehttp://127.0.0.1:<dev.port>/<entry>The dev server origin
Browser (chroma dev --no-desktop or a static host)Top-level pageThe 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.1 only 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

SymptomCauseFix
'<file>' is not a .zip fileA non-zip file was selectedImport the .zip produced by chroma build with package.zip: true
the zip does not contain an index.html at its rootindex.html is nested in a subfolder or missingRebuild with chroma build instead of zipping the project by hand
Dropped file does nothingThe drop handler only accepts .zip and skips other files silentlyUse Import .zip to see the real error
App is blank or its assets 404The build is incomplete or was built for another base pathServe dist/ locally (Server) to check it, fix the build, then re-import
Re-importing does not update an open appEach import is a new entry and never overwritesOpen the new entry, and delete the old folder from the apps directory if you do not need it
IndexedDB data is missing after a restartPort 47823 was busy, so the origin changedFree the port and relaunch, or restore from a backup
Data in the browser but not in DesktopDifferent origins, so different IndexedDBExport from one and import into the other
Window edges do not resizeOld build, or a Wayland compositor issueUpdate 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.

On this page