Targ Apps Docs
Chroma

Browser plugins by mode

Portable (Web) vs Executable (Chroma Desktop) behavior for chroma-fs, chroma-router, chroma-style, and chroma-t2t.

First-party plugins are browser ESM packages vendored under plugins/<name>/. Runtime modes change the host around them, not the plugin manifest format. Registration is always:

  1. List the id in chroma.json → plugins.
  2. At startup, plugins/loader.js fetches each chroma.plugin.json, imports entry, runs hooks.styles / hooks.bootstrap, and returns loadPlugins() keys (fs, router, style, t2t).

The loader passes router bootstrap options from options.router (default manifest ./api/routes-manifest.json). Everything else uses plugin defaults unless your app wraps loadPlugins({ ... }).

Summary table

PluginloadAsHooksPortable (Web)Executable (Desktop)Primary entry points
chroma-fsfsbootstrap → bootstrapFsFull with IndexedDB (default)Full; auto-defaults to filesystem when platform is desktopchroma-fs.json, detectPlatform(), iframe detection
chroma-routerrouterbootstrap → bootstrapRouterFullFullhijackFetch, api/routes-manifest.json
chroma-stylestylestyles → injectStylesFullFullApp calls bootstrapStyle({ cva, cx })
chroma-t2tt2tbootstrap → bootstrapT2tPartialPartialexposeGlobal, runNodeTests (Node only)

chroma-fs

Package: @chroma/fs. Docs: Chroma-Fs, Backends.

Portable (Web)

CapabilityStatus
readFile / writeFile / list with default configFull via IndexedDB
storage.backend: "indexeddb"Full
storage.backend: "filesystem"Fails in a normal top-level tab — throws unless you pass a custom adapter (tests) or embed in a host that provides one
Data survives refreshFull for the same origin (URL + port)
Sharing data with DesktopDifferent partition — export/import manually

Platform detection (detectPlatform()): no __TAURI__, not in Desktop iframe → "static" → default backend indexeddb when storage.backend is omitted.

Executable (Chroma Desktop)

CapabilityStatus
App in library iframeFull — iframe is treated as "desktop" even without Tauri globals in the child
Default backend when omittedfilesystem (postMessage bridge to shell)
storage.backend: "filesystem"Full via chroma-fs channel on window.parent
storage.backend: "indexeddb"Full but keyed to loopback origin (47823 or dev port)
Develop mode (chroma dev + Desktop)Different origin from browser --no-desktop dev — separate IndexedDB
On-disk pathsUnder <app-data>/apps/<app-id>/data/ — see Desktop storage

Entry points:

  • chroma-fs.json — explicit storage.backend, basePath, IndexedDB names.
  • createStorage({ adapter }) — forces filesystem with in-memory or test adapters (any mode).
  • Host: Chroma Desktop index.html parent handles { channel: "chroma-fs", op, ... }.

chroma-router

Package: @chroma/router. Docs: Routing.

Both modes: Full

BehaviorPortableExecutable
Load api/routes-manifest.jsonYesYes
Register routes from api/**/<METHOD>.jsYesYes
Hijack globalThis.fetch for /api/*YesYes
Real network to your backendOnly if URL does not match local routesSame

The router does not detect Desktop. It runs entirely in the iframe JavaScript context. Simulated endpoints are not a production API server in either mode.

Entry points:

  • chroma.plugin.json → routing.manifestPath (default ./api/routes-manifest.json).
  • loadPlugins({ router: { manifestUrl, hijackFetch: false } }) to disable fetch hijack.
  • globalThis.__CHROMA_DEBUG__ for verbose logging (both modes).

chroma-style

Package: @chroma/style. Docs: Styling, Chroma-Style.

Both modes: Full

BehaviorPortableExecutable
injectStyles() loads tokens.css, utilities.cssYesYes
bootstrapStyle({ cva, cx }) presetsYesYes
Theme via data-theme / CSS variablesYesYes
Dependency on host file APIsNoneNone

The styling pipeline is pure DOM + CSS. No compile flag switches style between Portable and Executable.

Entry points:

  • Plugin hook styles → injectStyles (loader runs before bootstrap).
  • App entry must call bootstrapStyle once with engine cva / cx (templates in src/main.chr).
  • FOUC and <link> ordering: Chroma-Style — Runtime hosts.

chroma-t2t

Package: @chroma/t2t. Docs: Chroma-t2t.

Both modes: Partial (by design)

The plugin is loaded in the browser context in Portable and Desktop. Node APIs are not available inside the WASM/ESM bundle.

APIPortable (Web)Executable (Desktop)
bootstrapT2t / globalThis.__chromaT2tFullFull
smokeSnapshot / assertPersistRoundTrip (configured adapters)FullFull — persist tests follow your storage adapters (often Fs-dependent)
runNodeTests / runAll({ includeNodeTests: true })Skipped (process undefined)Skipped — run npm test or node plugins/chroma-t2t/node/run-tests.mjs on the dev machine
Production bundlesUsually omit plugin from pluginsSame recommendation

Persist round-trip tests validate app-provided adapters (export/import/flush), not the host mode directly. When adapters use live Chroma-Fs, round-trip behavior differs between IndexedDB and filesystem backends — configure tests per runtime mode.

Entry points:

  • bootstrapT2t({ persist, snapshot, node, exposeGlobal, globalKey }).
  • options.node.command / npmTestScript for documented CLI test entry (runs outside the page).

WASM screen router (not a plugin)

Exported from pkg/chroma.js, not plugins/.

FeaturePortableExecutable
createRouter, matchRoute, hash/historyFullFull
Interaction with API routerOrthogonal — screen vs /api fetchSame

See Routing — screen router.


Legacy libraries

Not plugins: entries in chroma.json legacyLibraries and <script> tags in index.html. chroma add vendors files; dev / build do not rewrite HTML automatically.

BehaviorPortableExecutable
CDN or vendored UMD/globalsFullFull
Mode-specific loadingN/A — same HTML in dist/Same zip import

Editor support for legacy libs is limited; see Legacy libraries.


Custom plugins

Your chroma.plugin.json follows the same contract in both modes:

  • Avoid Node built-ins and native modules.
  • If you need disk, delegate to @chroma/fs or feature-detect detectPlatform() from @chroma/fs rather than calling Tauri from the iframe.
  • Use capabilities for tooling tags only; nothing in the loader branches on Portable vs Executable.

On this page