Downstream Customization¶
Open Cowork is designed to be repackaged as an internal build without forking the code. Branding, providers, agents, skills, MCPs, and bundled prompts are all expressed in files the app reads at runtime, and a small set of environment variables lets a downstream distribution point the app at its own copy of those files.
This page is the reference for that model.
The workspace ownership, sync, status/reason, and non-sync guarantees that downstream builds must preserve are defined in Product Contract. The public product names, package/image names, release-channel language, and legacy Gateway migration policy are defined in OSS Packaging and Gateway Migration. The versioned downstream configuration, branding, packaging, template hygiene, and extension contract is defined in Downstream Contract. Current public config files use contractVersion: 1.
Distribution modes¶
Downstream operators should choose the product surface intentionally:
- Local-only desktop sets
cloudDesktop.enabled=false. Users get the current private desktop behavior with local OpenCode runtime ownership, local project directories, local stdio MCPs, local settings, and no cloud dependency. - Cloud-enabled desktop sets
cloudDesktop.enabled=trueand can allow user-added cloud connections. Local and cloud workspaces appear side by side, but local threads remain local and cloud threads sync through Open Cowork Cloud. - Managed-org-only desktop sets
cloudDesktop.enabled=true,allowUserAddedConnections=false,requireManagedOrg=true, andpreconfiguredConnections[]. This is the right mode for internal company builds that must pin users to approved cloud orgs. - Gateway-enabled deployment runs Open Cowork Gateway next to a cloud org. The gateway is a headless client for approved channels; it uses cloud service tokens and channel bindings but does not spawn OpenCode or own a second control plane.
Open Cowork also supports two commercial/operational packaging modes without changing the OSS boundary:
- Internal enterprise distribution ships a branded desktop config, pins
cloudDesktop.preconfiguredConnectionsto the company's cloud, runs cloud behind company OIDC, and deploys gateway inside the company's infrastructure. - Managed BYOK SaaS distribution ships the same open desktop/cloud/gateway code while the operator hosts cloud, billing, BYOK, object storage, and managed channel bindings for paying orgs.
Branding, providers, skills, MCPs, agents, cloud connections, gateway credentials, and telemetry are configurable, but the workspace contract is not: no build should implicitly upload local threads, local project files, provider keys, local stdio MCP commands, or machine runtime config into cloud. Use cloud-safe remote MCPs, explicit artifact upload, or admin-managed profiles when a capability must be available in cloud.
For a complete placeholder deployment that covers desktop config, cloud Helm values, gateway Helm values, OIDC, branding, and profile/tool/agent allowlists, see examples/downstream/example-org/.
Mental model¶
Everything downstream-customizable goes through two layers:
- Configuration — a JSON file validated against
open-cowork.config.schema.jsonand the versionedcontractVersion: 1downstream contract. The app merges the bundled config with up to three additional layers in a fixed order. - Content — skill bundles (
skills/<name>/SKILL.md) and MCP packages (mcps/<name>/dist/index.js) that the config can reference. The app resolves these from a stack of roots, with the downstream root winning over the bundled one.
A downstream distribution typically ships a directory with this layout:
example-cowork/
├── config.json # or open-cowork.config.json
├── skills/
│ ├── example-reports/
│ │ └── SKILL.md
│ └── example-tickets/
│ └── SKILL.md
└── mcps/
└── example-crm/
└── dist/
└── index.js
Then launches the app with OPEN_COWORK_DOWNSTREAM_ROOT=/etc/example-cowork.
Config merge order¶
flowchart LR
D["DEFAULT_CONFIG<br/>compiled-in"]
B["bundled<br/>open-cowork.config.json"]
P["OPEN_COWORK_CONFIG_PATH<br/>single file override"]
R["OPEN_COWORK_DOWNSTREAM_ROOT<br/>or CONFIG_DIR"]
U["per-user<br/>~/.config/<dataDirName>/config.json"]
S["managed system<br/>/etc/<dataDirName>/config.json"]
Active["Active config<br/>(deep-merged)"]
D --> B --> P --> R --> U --> S --> Active
style D fill:#e0e7ff,stroke:#6366f1
style B fill:#e0e7ff,stroke:#6366f1
style P fill:#fef3c7,stroke:#f59e0b
style R fill:#fef3c7,stroke:#f59e0b
style U fill:#dcfce7,stroke:#10b981
style S fill:#dcfce7,stroke:#10b981
style Active fill:#fae8ff,stroke:#a855f7,stroke-width:2px Layers later in the chain override earlier ones via deep merge — so a downstream layer only carries the keys it wants to change. Indigo layers are baked in; amber layers come from environment variables; green layers are user/admin overlays.
When the app starts, apps/desktop/src/main/config-loader.ts builds the active config by merging these sources in order (later entries override earlier ones):
DEFAULT_CONFIGcompiled into the app.- The bundled
open-cowork.config.jsonshipped inside the package. OPEN_COWORK_CONFIG_PATH— a single JSON file, if set and it exists.OPEN_COWORK_CONFIG_DIRorOPEN_COWORK_DOWNSTREAM_ROOT— a directory containingconfig.jsonoropen-cowork.config.json, if set and it exists.~/.config/<dataDirName>/config.json— per-user config./Library/Application Support/<dataDirName>/config.json(macOS),C:\ProgramData\<dataDirName>\config.json(Windows), or/etc/<dataDirName>/config.json(Linux) — managed system config.
deepMerge runs at each step, so downstream layers only have to carry the keys they want to change. <dataDirName> comes from branding.dataDirName in the active config.
Cloud, desktop, and gateway knobs¶
Downstream deployments should keep one deployer-facing config file as the source of product policy and then let infrastructure manifests inject secrets. The public schema now covers the three product surfaces:
| Surface | Config keys | What belongs here |
|---|---|---|
| Desktop | branding, cloudDesktop | App name, sidebar/home copy, managed cloud URLs, cache mode, and whether users may add their own cloud orgs. |
| Cloud Web/control plane | cloud.publicBranding, cloud.auth, cloud.storage, cloud.features, cloud.profiles, cloud.projectSources, cloud.abuse, cloud.billing | Public dashboard name/logo/legal links, OIDC metadata, database/object-store/secret refs, profile allowlists, project-source policy, quotas, and self-host or managed billing mode. |
| Gateway | gateway.branding, gateway.server, gateway.providers, gateway.metrics, gateway.diagnostics | Headless channel branding, public gateway URL, provider bindings, and operator endpoints. Cloud URL and gateway service token belong in env or deployment secrets. |
Use Downstream Contract for the full field inventory and to distinguish runtime config, packaging-time config, infrastructure config, and private downstream config.
Gateway can load the same central file as Desktop and Cloud through OPEN_COWORK_CONFIG_PATH, OPEN_COWORK_CONFIG_DIR, or OPEN_COWORK_DOWNSTREAM_ROOT. Gateway-specific env and OPEN_COWORK_GATEWAY_CONFIG / OPEN_COWORK_GATEWAY_CONFIG_JSON remain available as overrides. Gateway config JSON intentionally ignores gateway.cloud.baseUrl, gateway.cloud.serviceToken, and gateway.cloud.allowInsecureHttp, plus gateway.timeouts.cloudRequestMs; use OPEN_COWORK_CLOUD_BASE_URL, OPEN_COWORK_GATEWAY_SERVICE_TOKEN, OPEN_COWORK_GATEWAY_ALLOW_INSECURE_HTTP, and OPEN_COWORK_GATEWAY_CLOUD_REQUEST_TIMEOUT_MS from your deployment secret manager instead. This lets an internal deployment keep branding, cloud.publicBranding, cloudDesktop, and gateway.branding in one audited file while Kubernetes, Compose, or a VPS process manager supplies endpoint bindings and secrets from the local secret manager.
Production safety rules are enforced in config validation:
- public cloud, desktop, gateway, telemetry, update, and logo/legal URLs must be HTTPS, except loopback/local development URLs
cloud.billing.provider=noneorstubkeeps self-host deployments free of commercial billing dependencies- gateway provider enablement is explicit; no provider is started unless it is configured through
gateway.providers, gateway provider env, or the local fake-provider development flag - public gateway metrics or diagnostics require
gateway.server.adminToken - webhook gateway ingress requires a shared secret
- the fake gateway provider cannot be exposed from a public bind unless
OPEN_COWORK_GATEWAY_ALLOW_PUBLIC_FAKE_PROVIDER=trueis set deliberately for a self-hosted demo - public
cloud.auth.mode=headerdeployments require a header auth secret and signed timestamped identity headers from the trusted proxy - environment placeholders such as
{env:OPEN_COWORK_GATEWAY_SERVICE_TOKEN}only resolve when listed inallowedEnvPlaceholders
Managed BYOK SaaS deployments should keep downstream-specific prices, project ids, legal URLs, and brand assets outside this public repository. The public repo should contain only generic examples and these portable supply points:
| Area | Public config/env surface | Secret source |
|---|---|---|
| Billing mode | cloud.billing.enabled, cloud.billing.provider, OPEN_COWORK_CLOUD_BILLING_ENABLED, OPEN_COWORK_CLOUD_BILLING_PROVIDER, OPEN_COWORK_CLOUD_BILLING_DEFAULT_PLAN | None for none/stub; provider credentials through refs for managed SaaS. |
| Stripe | cloud.billing.stripe.*, OPEN_COWORK_CLOUD_STRIPE_API_KEY_REF, OPEN_COWORK_CLOUD_STRIPE_WEBHOOK_SECRET_REF, OPEN_COWORK_CLOUD_STRIPE_PRICE_ID, OPEN_COWORK_CLOUD_STRIPE_SUCCESS_URL, OPEN_COWORK_CLOUD_STRIPE_CANCEL_URL, OPEN_COWORK_CLOUD_STRIPE_PORTAL_RETURN_URL | Platform secret manager or env refs for API key and webhook secret. |
| BYOK | cloud.profiles, cloud.billing.plans.*.entitlements.allowedProviders, provider validators in cloud process wiring | Provider keys enter only through /api/byok; plaintext is never stored in config. |
| Quotas | cloud.abuse.*, OPEN_COWORK_CLOUD_MAX_* env overrides | No secrets. |
| Public URLs | cloud.publicBranding, cloudDesktop.preconfiguredConnections, Gateway publicUrl | Use generic URLs here; downstream managed repo owns real domains. |
Self-host OSS deployments can leave cloud.billing.enabled=false and cloud.billing.provider=none; hosted SaaS deployments should require signed billing webhooks, BYOK validator/override evidence, quotas, and launch readiness gates before public traffic.
Managed BYOK SaaS can live in a separate managed/private repo that supplies config overlays, billing adapters, object-store and secret refs, and operations evidence. That repo should not fork Open Cowork runtime behavior or require billing for the OSS self-host path.
Cloud Web theming is intentionally narrow. Downstream overlays can change cloud.publicBranding names, logo/legal/support URLs, dashboard copy, token labels, and public theme colors, including the expanded dark-token keys documented in Design Tokens. They should not fork Cloud Web layout CSS, add a separate build pipeline, replace Mona/Schibsted font serving, or change the Cloud Web cloud-client-only architecture. The shared structural tokens in packages/shared/src/design-tokens.ts keep Desktop and Cloud Web aligned across spacing, radius, typography scale, shadows, and control density.
Downstream deployment recipes must also preserve these production contracts:
- images are pinned by immutable release tag or digest;
latest,stable, and other mutable aliases are not acceptable defaults - local/demo Compose defaults are replaced before shared use: auth, public URLs, cookie/internal/service tokens, object-store credentials, and fake providers
- multi-worker Cloud requires checkpointing plus a shared object store for artifacts, uploaded snapshots, workspace snapshots, and runtime checkpoints
- Kubernetes overlays own HPA/KEDA policy, PodDisruptionBudgets, and topology spread constraints; they should not change cloud runtime or gateway code
- self-host OSS keeps a billing-free path with
cloud.billing.provider=noneor the stub provider
Extension Points And Ownership¶
Downstream deployments should extend Open Cowork through the surface that owns the product concept. Do not patch core execution code to add deployer-specific behavior. OpenCode owns execution; Open Cowork owns composition, policy, projection, channel adapters, and deployment ergonomics.
| Extension point | Owning modules | Contract |
|---|---|---|
| Gateway providers | packages/gateway-provider-*, packages/gateway-channel, apps/gateway/src/provider-registry.ts | Implement the provider contract and register capabilities. Gateway providers send/receive channel messages only; they do not spawn OpenCode, import Cloud stores, or own execution state. |
| Deployment recipes | deploy/, helm/, docker-compose*.yml, scripts/validate-deployment-configs.mjs | Compose public templates from config/env refs. Keep real project ids, domains, account ids, customer values, prices, and secrets in private deployment repos. |
| Billing adapters | packages/cloud-server/src/billing-adapter.ts, stripe-billing-adapter.ts, stub-billing-adapter.ts | Add provider-specific billing behind the adapter. Core entitlement and quota logic consumes provider-neutral subscription records. |
| Object-store adapters | packages/cloud-server/src/object-store.ts, deployment object-store config | Add storage providers behind the object-store interface. Artifact, upload, snapshot, and checkpoint callers should not branch on cloud provider names. |
| Secret adapters | packages/cloud-server/src/secret-adapter.ts, BYOK secret store, config refs | Resolve or protect secrets behind refs. Raw provider keys, OAuth tokens, cookies, channel secrets, and signed URLs must never enter renderer state, cache, diagnostics, or public templates. |
| Worker pool modes | docs/managed-workers.md, managed-worker-types.ts, services/managed-worker-service.ts | Add worker modes only after the trust model is documented. Customer-hosted workers remain deferred until a separate review covers updates, liability, networking, and data residency. |
| Runtime profiles and policy packs | cloud.profiles, cloud.runtime, cloud-config.ts, runtime-config-builder.ts | Cloud profiles own feature flags and allowlists. Machine runtime config, arbitrary local stdio MCPs, and host project directories stay disabled unless explicitly reviewed and allowlisted. |
| Cloud Web feature modules and admin panels | packages/app/src, packages/app/src/browser/cowork-api.ts, docs/cloud-web-workbench.md | Cloud Web is the unified renderer running in the browser over the cloud HTTP/SSE shim. It must not import server-only stores, runtime adapters, secret adapters, or provider-specific internals. |
| BYOK validation and injection hooks | byok-secret-store.ts, runtime-config-builder.ts, opencode-runtime-adapter.ts, cloud-config.ts | Provider keys enter OpenCode through runtime config provider options. They never enter process env, logs, renderer state, diagnostics, cache, or read APIs. |
| Cloud event and projection contract | packages/shared/src/cloud-session-projection.ts, opencode-runtime-adapter.ts, session-projection-service.ts, gateway renderers | Runtime translation happens once at the worker/runtime boundary. Desktop, Web, and Gateway consume canonical Cloud events/projections rather than raw SDK events. |
Module changes should stay narrow:
- route modules validate/auth/parse and delegate to services
- services own orchestration and policy decisions
- store/domain modules persist atomically and do not own channel or renderer behavior
- package-boundary tests guard against clients importing server-only Cloud internals
- source-size budgets are regressions gates, not targets to grow into
Environment variables¶
| Variable | Purpose | Default |
|---|---|---|
OPEN_COWORK_DOWNSTREAM_ROOT | Root directory for a downstream distribution. Supplies config, skills, and MCPs. | unset |
OPEN_COWORK_CONFIG_PATH | Absolute path to a single override config file. Takes priority over CONFIG_DIR / DOWNSTREAM_ROOT config discovery. | unset |
OPEN_COWORK_CONFIG_DIR | Directory containing config.json or open-cowork.config.json. Merged after the single-file override. | unset |
OPEN_COWORK_GATEWAY_CONFIG | Gateway-only JSON config file. Overrides the shared gateway section when running apps/gateway. | unset |
OPEN_COWORK_GATEWAY_CONFIG_JSON | Inline gateway-only JSON config. Highest-priority gateway override for process managers and tests. | unset |
OPEN_COWORK_SANDBOX_DIR | Root directory where sandbox threads create workspaces. | ~/Open Cowork Sandbox |
OPEN_COWORK_CHART_TIMEOUT_MS | Main-process chart render timeout. Clamped to [250, 10000] ms. | 1500 |
OPEN_COWORK_CUSTOM_SKILLS_DIR is set by the app itself when spawning the bundled skills MCP and is not intended for downstream use. The skills MCP rejects relative paths, filesystem roots, and the user's home directory; keep custom skill storage inside the app-managed runtime home.
Skills overlay¶
Skill bundles are resolved from these roots, in order (first match wins):
$OPEN_COWORK_DOWNSTREAM_ROOT/skills/<name>/<cwd>/skills/<name>/— the repo's ownskills/directory during development.<app>/Contents/Resources/skills/<name>/— skills shipped with the packaged app.
This means a downstream distribution can either replace a bundled skill (by shipping a directory with the same name) or add new skills that the downstream config references.
Skills only become visible to the runtime when they are listed under skills in the active config — a skill directory that nobody references is ignored.
See packages/runtime-host/src/runtime-content.ts and packages/runtime-host/src/effective-skills.ts for the resolution code.
MCPs overlay¶
MCP packages are resolved from:
$OPEN_COWORK_DOWNSTREAM_ROOT/mcps/<name>/dist/index.js<resourcesPath>/mcps/<name>/dist/index.js— MCPs shipped with the packaged app (mcps/agents,mcps/charts,mcps/clock,mcps/skills,mcps/workflows).
As with skills, the MCP must be declared in the active config (mcps section) before the runtime spawns it.
See packages/runtime-host/src/runtime-mcp.ts.
Environment placeholders in config¶
Config strings may reference environment variables using {env:NAME}.
For safety, placeholders only resolve when the variable name is explicitly listed in allowedEnvPlaceholders:
{
"allowedEnvPlaceholders": ["EXAMPLE_LLM_GATEWAY_URL"],
"providers": {
"custom": {
"example-gateway": {
"options": {
"baseURL": "{env:EXAMPLE_LLM_GATEWAY_URL}"
}
}
}
}
}
Unknown placeholders fail config loading with a clear error. This is deliberate: Open Cowork does not implicitly pull arbitrary secrets from the host environment, so downstream configs have to opt each variable in.
Provider credentials the user enters in the app's UI take a separate path (through the app settings store, settings.enc in production, and provider.options.<runtimeKey> in the built OpenCode config) and do not need to be listed in allowedEnvPlaceholders.
Worked example¶
A downstream distribution that brands the app "Example Cowork", ships one custom provider, one internal MCP, and one internal skill would look like:
/etc/example-cowork/
├── config.json
├── skills/
│ └── example-tickets/
│ └── SKILL.md
└── mcps/
└── example-crm/
└── dist/
└── index.js
config.json:
{
"contractVersion": 1,
"allowedEnvPlaceholders": ["EXAMPLE_GATEWAY_URL", "EXAMPLE_GATEWAY_KEY"],
"branding": {
"name": "Example Cowork",
"appId": "com.example.cowork",
"dataDirName": "example-cowork",
"helpUrl": "https://internal.example.com/cowork",
"sidebar": {
"top": {
"variant": "logo-text",
"logoAsset": "branding/example-logo.svg",
"mediaSize": 36,
"mediaFit": "vertical",
"mediaAlign": "center",
"title": "Example AI",
"subtitle": "Private workspace"
},
"lower": {
"text": "Example internal build",
"secondaryText": "Support from Data Platform.",
"linkLabel": "Get help",
"linkUrl": "https://internal.example.com/cowork-help"
}
},
"home": {
"greeting": "What should {{brand}} work on today?",
"subtitle": "Ask a question or delegate to an approved agent.",
"composerPlaceholder": "Ask {{brand}} anything",
"suggestionLabel": "Start with",
"statusReadyLabel": "Online"
}
},
"providers": {
"available": ["example-gateway"],
"descriptors": {
"example-gateway": {
"runtime": "custom",
"name": "Example Gateway",
"description": "Internal LLM gateway.",
"defaultModel": "example-large",
"credentials": []
}
},
"custom": {
"example-gateway": {
"name": "Example Gateway",
"defaultModel": "example-large",
"options": {
"baseURL": "{env:EXAMPLE_GATEWAY_URL}",
"apiKey": "{env:EXAMPLE_GATEWAY_KEY}"
},
"models": {
"example-large": { "name": "Example Large" }
}
}
},
"defaultProvider": "example-gateway",
"defaultModel": "example-large"
}
// ...tools, skills, mcps, agents, permissions as needed
}
Launch the packaged app with:
OPEN_COWORK_DOWNSTREAM_ROOT=/etc/example-cowork \
EXAMPLE_GATEWAY_URL=https://llm.internal.example.com \
EXAMPLE_GATEWAY_KEY="$(cat /run/secrets/example-gateway-key)" \
./Example\ Cowork
Rebranding the packaged app¶
The build reads three env vars to set the product identity without forking electron-builder.yml:
| Variable | Default | What it controls |
|---|---|---|
APP_PRODUCT_NAME | Open Cowork | macOS menu bar + Dock name |
APP_ID | com.opencowork.desktop | Bundle identifier / reverse-DNS |
APP_ARTIFACT_PREFIX | Open-Cowork | macOS / Linux artifact filenames |
Example downstream build:
APP_PRODUCT_NAME="Example Cowork" \
APP_ID="com.example.cowork" \
APP_ARTIFACT_PREFIX="Example-Cowork" \
pnpm --dir apps/desktop dist:ci:mac
The in-app brand name (window title, first-run copy, log prefixes) is driven separately by branding.name inside open-cowork.config.json or the downstream config overlay — see the Configuration section above.
Downstream builds can also configure sidebar and Home copy under branding.sidebar and branding.home. Those fields only affect UI surfaces: they do not change OpenCode runtime providers, agents, permissions, MCPs, or skills. For logo-backed sidebar variants, place image files under the repo-level branding/ directory and reference them with branding.sidebar.top.logoAsset, for example branding/example-logo.svg. The app rejects absolute paths, traversal, remote URLs, and unsupported extensions.
Sidebar top branding can also tune the rendered media with branding.sidebar.top.mediaSize (16-96 pixels, default 28), mediaFit (vertical or horizontal; unset keeps the legacy square bounding box), and mediaAlign (start, center, or end for icon-only or logo-only placement).
The repo name, bundle identifier, and project namespace are separate concerns. Upstream now uses the public repo name open-cowork, but retains com.opencowork.desktop and .opencowork/ as the default internal identifiers for back-compat. Downstreams should only change APP_ID or branding.projectNamespace when they intentionally want a new install identity and are prepared to migrate existing state.
Localization (i18n)¶
Open Cowork ships with inline English strings. The config schema has an optional i18n overlay so downstream forks can localize without forking the codebase:
{
"i18n": {
"locale": "de-DE",
"strings": {
"settings.language.label": "Sprache",
"chat.awaitingApproval": "Wartet auf Freigabe"
}
}
}
Two things happen when this is set:
-
Date formatting (
formatDatefrompackages/app/src/helpers/i18n.ts) switches to the configured locale. Dates render as31.12.2026inde-DEand12/31/2026inen-US. -
String catalog —
t('settings.language.label', 'Language')looks up the configured translation and falls back to the inline English default when no translation exists. Partial catalogs are fine; untranslated keys stay in English.
Launch posture: English-first, honestly partial elsewhere¶
The public build is English-first. The built-in non-English catalogs (ar, de, es, fr, hi, it, ja, ko, pt, ru, zh) currently translate roughly a third of the renderer's strings; everything else renders its inline English fallback. That state is deliberate policy, not drift:
- Untranslated keys are tracked in the documented allowlist (
tests/i18n-english-only-allowlist.json); a newt()key that is neither translated nor allowlisted fails CI, so the backlog can only shrink or be made visible — never grow silently. - Strings are never machine-translated in bulk; catalog entries are added deliberately so a native reader can trust what ships.
- The language picker shows the honest coverage figure per locale ("Deutsch — zu 32 % übersetzt"), generated from the live backlog by
node scripts/i18n-coverage.mjs --write-statusand kept in sync by thei18n:checkgate.
The upstream source hasn't migrated every string to the catalog yet — only the highest-visibility ones (see the focused roadmap in docs/roadmap.md). Downstream forks that need broader coverage can either:
- Submit a PR migrating more strings to
t(key, fallback)in the renderer source, then translate the key in their config. - Fork and translate inline strings directly.
locale flows into every Intl formatter without string-catalog migration, so even an untranslated fork sees locale-appropriate numbers / dates / currencies.
Telemetry forwarding¶
Every in-app event tracked by packages/runtime-host/src/telemetry.ts (app launch, auth login, session creation, perf-slow, error) is written to a local NDJSON file by default — no data leaves the user's machine. Downstream installs that want their own telemetry collector (PostHog, Mixpanel, an internal HTTP endpoint) set:
{
"allowedEnvPlaceholders": ["EXAMPLE_TELEMETRY_TOKEN"],
"telemetry": {
"enabled": true,
"endpoint": "https://events.example.com/ingest",
"headers": {
"Authorization": "Bearer {env:EXAMPLE_TELEMETRY_TOKEN}"
}
}
}
Each tracked event is POSTed to the endpoint as JSON — fire-and- forget with a 2-second timeout. Failures are silent; the local NDJSON file stays the source of truth. headers is passed to fetch after normal config placeholder resolution, so auth tokens, CSRF tokens, or routing hints go through unchanged. Use {env:VAR} placeholders for secrets and list each variable in allowedEnvPlaceholders so the config file itself stays safe to commit.
Upstream distributions ship with telemetry disabled by default — no remote calls happen unless a downstream opts in.
Signing, notarization, and distribution¶
This repository ships unsigned artifacts by default. Downstream distributions that ship to end users should add:
- macOS code signing and notarization (via the standard
CSC_LINK,CSC_KEY_PASSWORD,APPLE_ID,APPLE_APP_SPECIFIC_PASSWORD,APPLE_TEAM_IDenvironment variables that electron-builder reads) - Linux package signing as required by the target repositories
- any internal release approval, artifact mirror, or provenance requirements
See Packaging and Releases for the upstream release flow those hooks plug into.