Configuration
Full reference for murasaki.config.ts.
murasaki.config.ts (or .js / .mjs) at your project root describes your
app's identity, window, and build settings:
import { defineConfig } from 'murasaki'
export default defineConfig({
appId: 'app.murasaki.example',
productName: 'Murasaki App',
version: '0.1.0',
icon: 'assets/icon.png',
window: {
title: 'Murasaki App',
width: 1000,
height: 700,
backendCapabilities: ['api:POST:/api/documents'],
},
capabilities: ['dialog:openFile', 'clipboard:writeText'],
})defineConfig returns the same object for type inference and validates
declarative window labels/routes synchronously. There is nothing to await.
Top-level fields
| Field | Type | Description |
|---|---|---|
appId | string | Required. Portable reverse-DNS identifier (e.g. com.example.my-app; letters, digits, dots, and hyphens) — becomes CFBundleIdentifier on macOS. |
productName | string | Required. Portable 1–120 character display/file name — the .app filename, Dock/menu-bar label, and DMG volume name. Path characters, Windows device names, edge whitespace, and trailing dots are rejected. |
version | string? | Strict semantic version (for example 1.2.3 or 1.2.3-beta.1; no v prefix). Shown in the native About panel and artifact filenames. Defaults to 0.0.0. |
description | string? | Short description shown in the native "About <app>" panel. |
copyright | string? | Copyright notice shown in the About panel. |
homepage | string? | Homepage URL shown in the About panel. |
authors | string[]? | Author names shown in the About panel (Windows/Linux only). |
about | AboutConfig? | Opt into a configurable macOS About panel with custom size, paragraphs, detail rows, and external-link buttons — see below. |
window | WindowConfig? | Window shape — see below. |
windows | Record<string, SecondaryWindowConfig>? | Secondary windows keyed by stable label — see below. |
webview | WebviewConfig? | Application-wide User-Agent, private session, and unauthenticated proxy settings — see below. |
capabilities | NativeCapabilityGrant[]? | Primary-window native renderer grants. Used only when window.capabilities is omitted. Default is deny-all. |
backendCapabilities | BackendCapability[]? | Primary-window renderer-to-Node/API grants. Used only when window.backendCapabilities is omitted. Default is deny-all. |
systemPermissions | SystemPermissionsConfig? | Host-OS consent declarations and packaged macOS launch prompts — see below. |
main | false | object? | Long-lived Node Main entry and shutdown bound — see below. |
bundle | object? | Node dependency and non-code resource packaging — see below. |
plugins | MurasakiPlugin[]? | Trusted build-time extensions for Vite, CLI hooks, dependencies, and resources — see below. |
build | object? | Pre-build command and public client env prefixes — see below. |
security | object? | Renderer Content Security Policy — see below. |
updater | UpdaterConfig? | Auto-update source — see below. |
locales | string[]? | BCP-47 UI languages your app supports (e.g. ['en', 'ja']). Feeds the macOS bundle's CFBundleLocalizations and constrains Murasaki's default native menu translations. Defaults to every language Murasaki ships menu translations for: en, ja, zh-Hans, ko, es, fr, de. |
devPort | number? | Vite dev server port during murasaki dev. Defaults to 5178 (auto-increments if taken). |
targets | Target[]? | Build targets. Defaults to the host platform. One or more of darwin-arm64, darwin-x64, win32-x64, win32-arm64, linux-x64, linux-arm64. |
icon | string? | Path to a square source PNG (1024px recommended). On macOS with full Xcode, Murasaki compiles Assets.car so the OS applies its current mask, and retains .icns as a legacy fallback. Windows/Linux resources come from the same source. |
protocols | ProtocolConfig[]? | Custom URL schemes registered by packaged macOS apps and Windows installers — see below. |
fileAssociations | FileAssociationConfig[]? | Document extensions registered by packaged macOS apps and Windows installers — see below. |
installer | object? | macOS DMG and Windows installer options — see below. |
sign | object? | macOS Developer ID and Windows Authenticode signing — see below. |
about
Omit about to keep the operating system's compact standard About dialog. On
macOS, declaring it opts into a native, customizable AppKit panel. Windows and
Linux currently continue to use their standard metadata dialog.
about: {
name: 'My App',
width: 520,
height: 680,
paragraphs: [
'A focused desktop workspace.',
'Built with Murasaki.',
],
paragraphSpacing: 16,
details: [
{ label: 'Build', value: '15212' },
{
label: 'Commit',
value: '332b2aefc',
href: 'https://github.com/example/app/commit/332b2aefc',
},
],
buttons: [
{ label: 'Docs', href: 'https://docs.example.com' },
{ label: 'GitHub', href: 'https://github.com/example/app' },
],
},| Field | Type | Description |
|---|---|---|
name | string? | Panel heading. Defaults to productName. |
width | number? | Content width in logical pixels, 360–900. Default 480. |
height | number? | Content height in logical pixels, 320–1000. Auto-sized when omitted. |
paragraphs | string[]? | Up to 8 centered body paragraphs. Falls back to description. |
paragraphSpacing | number? | Gap between paragraphs in logical pixels, 0–48. Default 12. |
details | { label, value, href? }[]? | Up to 12 label/value rows. A Version row is added automatically unless supplied. |
buttons | { label, href }[]? | Up to 6 ordered native buttons at the bottom. |
External destinations are restricted to absolute, credential-free http,
https, and mailto URLs. The application icon is resolved by macOS from the
installed bundle, so the About panel uses the same masked icon as Finder and
the Dock.
window
| Field | Type | Description |
|---|---|---|
title | string? | Window title bar text, set once at launch. |
width / height | number? | Initial window size. |
minWidth / minHeight | number? | Minimum window size. |
resizable | boolean? | Whether the window can be resized. |
transparent | boolean? | Transparent window background. |
vibrancy | 'hud' | 'sidebar' | 'popover' | null | macOS translucent window vibrancy. |
console | boolean? | Windows only: show the backend Node console window. Defaults to false. |
route | string? | Same-origin path loaded in this window. Defaults to /; full/protocol-relative URLs are rejected. |
visible | boolean? | Initial visibility. Defaults to true for main, false for secondary windows. |
capabilities | NativeCapabilityGrant[]? | Per-window native grants. Secondary windows default to deny-all. |
backendCapabilities | BackendCapability[]? | Per-window Node Main, Server Action, API Route, updater, event, and diagnostics grants. Secondary windows default to deny-all. |
On macOS, vibrancy installs the matching semantic NSVisualEffectView
material and automatically makes both the native window and WebView
transparent. Keep the renderer background transparent where the material
should remain visible. Other platforms ignore this macOS-only option.
windows
window is the primary window with the reserved label main. Declare
secondary windows in windows; each entry accepts the same shape except for
the primary-only console option and gets its own route, initial visibility,
and command allowlist:
window: {
route: '/',
capabilities: ['window:open', 'window:list', 'window:manage'],
backendCapabilities: ['api:POST:/api/settings'],
},
windows: {
settings: {
route: '/settings',
width: 720,
height: 560,
createOnLaunch: false,
capabilities: [],
backendCapabilities: ['api:GET:/api/settings'],
},
},Labels are 1–64 safe characters and main is reserved. Secondary windows use
createOnLaunch: true by default and are created hidden. Set it to false to
keep a declaration dormant until Node Main calls windows.create(label). The
primary is always created on launch. Secondary windows inherit no top-level
permissions. A secondary console field is rejected because that option
controls the Windows backend console for the whole application. See
Windows & permissions for lifecycle and renderer APIs.
| Secondary-only field | Type | Description |
|---|---|---|
createOnLaunch | boolean? | Create the declared secondary during startup. Defaults to true; false keeps it as a trusted Node Main template. |
webview
These settings apply to every native WebView in development and packaged macOS/Windows applications. Window-specific overrides are not supported.
webview: {
userAgent: 'AcmeDesktop/1.4 Murasaki',
incognito: true,
proxy: {
protocol: 'socks5',
host: '127.0.0.1',
port: 1080,
},
},| Field | Type | Description |
|---|---|---|
userAgent | string? | Complete custom User-Agent value. Must be trimmed, contain no control characters, and be at most 512 UTF-8 bytes. |
incognito | boolean? | Use Wry's non-persistent/private data store instead of the app's persistent profile. Default false. |
proxy.protocol | 'http' | 'socks5' | http is an HTTP CONNECT proxy; socks5 is SOCKSv5. |
proxy.host | string | ASCII DNS hostname, IPv4 address, or bracketed IPv6 literal, at most 253 bytes. It is not a URL. |
proxy.port | number | Integer from 1 through 65535. |
Proxy URLs and authentication fields are deliberately unsupported: schemes,
paths, user@host, usernames, passwords, and unknown proxy fields fail config
validation. If a proxy requires credentials, configure it outside Murasaki or
use a local unauthenticated forwarding proxy. Configuration is passed to Wry's
native WebKit/WebView2 builder and is also validated by the Rust host.
Platform constraints:
- macOS proxy support uses Wry's
mac-proxyfeature and requires macOS 14 or newer. A configured proxy fails WebView startup explicitly on older macOS. Private sessions use WebKit's non-persistent data store. - Windows proxy support uses WebView2 browser arguments. Custom User-Agent requires WebView2 86.0.616.0 or newer; private mode requires 101.0.1210.39 or newer. WebView2 ignores those settings on older runtimes.
- Linux development and packaged AppDir/AppImage/
.debbuilds pass these options to Wry/WebKitGTK. Test the behavior against the WebKitGTK version shipped by each target distribution.
Incognito controls local WebView persistence only. It does not anonymize network traffic, hide the client from a proxy/server, or make app-level Node data private. Murasaki isolates browser profiles by native window so cookies, storage, and workers do not cross per-window backend authority. The primary keeps the historical app profile. Secondary profiles persist on Windows/Linux and macOS 14+; macOS 11–13 uses isolated non-persistent stores. Session sharing between windows is therefore intentionally unsupported; use Main/API state for explicit sharing. See Security.
capabilities
Native renderer APIs are deny-by-default. Add only the commands your app uses; unknown permissions do not grant anything:
capabilities: [
'app:quit',
'autostart:read',
'autostart:write',
'dialog:openFile',
'clipboard:readText',
'clipboard:writeText',
'notification:show',
'shell:openExternal',
'window:setTitle',
]The complete typed list is NativeCapability. The primary window uses
window.capabilities ?? capabilities ?? []; each secondary uses its own list
or []. Grants are evaluated for the calling renderer, and off-origin pages
cannot use the native bridge. Window management adds window:getLabel,
window:open, window:list, and window:manage. Programmatic application
shutdown requires app:quit; enabling the built-in updater grants it to the
primary window for the verified restart handshake.
Login startup is split into read and write authority: use autostart:read for
status() and reserve autostart:write for settings UI that calls
enable() or disable().
Use a structured NativeCapabilityGrant when a target-bearing command must be
limited. deny takes precedence over allow; a trailing /** is the only
wildcard form:
capabilities: [
{ permission: 'shell:openExternal', allow: { urls: ['https://example.com/help/**'] } },
{ permission: 'shell:showItemInFolder', allow: { paths: ['/Users/me/Documents/**'] } },
{ permission: 'window:manage', allow: { windows: ['settings', 'preview'] } },
{ permission: 'systemPermission:request', allow: { permissions: ['camera'] } },
{ permission: 'secureStorage:get', allow: { keys: ['account:*'] } },
{ permission: 'webview:writeCookies', allow: { urls: ['https://app.example.com/**'] } },
]String grants remain unrestricted for compatibility. URL, path, key, window, and OS-permission scopes are validated while loading config and enforced again by the Rust host.
backendCapabilities
Renderer access to local Node resources is separately deny-by-default. The
primary window uses window.backendCapabilities ?? backendCapabilities ?? [];
secondary windows inherit nothing and use their own list or [].
backendCapabilities: [
'main:src/backend/account.ts#loadAccount',
'action:src/actions/save.ts#saveDocument',
'api:POST:/api/documents/*',
'updater:check',
'events:sync.*',
'diagnostics:renderer-error',
]Resources match exactly unless the grant ends in one trailing *, which is a
prefix wildcard. API resources include the uppercase method. Broad grants
such as main:*, action:*, api:*, updater:*, and events:* are valid,
but exact resources are safer for production. Native shutdown, activation,
and window-control endpoints are not grantable to renderers. See
Security.
systemPermissions
This config describes host-OS consent, not which renderer commands are trusted.
For packaged macOS apps, Murasaki writes purpose text to Info.plist and can
request most permissions at launch:
systemPermissions: {
macOS: {
camera: {
usageDescription: 'Use your camera for video calls.',
requestOnLaunch: true,
},
microphone: {
usageDescription: 'Use your microphone for voice calls.',
},
screenRecording: { requestOnLaunch: false },
accessibility: { requestOnLaunch: false },
inputMonitoring: { requestOnLaunch: false },
location: {
usageDescription: 'Show nearby points of interest.',
mode: 'whenInUse',
},
fullDiskAccess: { requestOnLaunch: false },
photos: { usageDescription: 'Attach a photo to your post.' },
contacts: { usageDescription: 'Find your friends.' },
calendar: { usageDescription: 'See your schedule.' },
reminders: { usageDescription: 'See your reminders.' },
speechRecognition: { usageDescription: 'Transcribe your voice memos.' },
bluetooth: { usageDescription: 'Find nearby devices.' },
appleEvents: { usageDescription: 'Automate another app on your behalf.' },
localNetwork: { usageDescription: 'Discover devices on this network.' },
},
},| Field | Type | Description |
|---|---|---|
macOS.camera | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSCameraUsageDescription; optionally prompts during packaged launch. |
macOS.microphone | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSMicrophoneUsageDescription; optionally prompts during packaged launch. |
macOS.screenRecording | { requestOnLaunch?: boolean } | Optionally asks for screen-capture consent at packaged launch. |
macOS.accessibility | { requestOnLaunch?: boolean } | Optionally opens the macOS accessibility trust prompt at packaged launch. |
macOS.inputMonitoring | { requestOnLaunch?: boolean } | Optionally asks for Input Monitoring (HID listen event) consent at packaged launch. |
macOS.location | { usageDescription: string; mode?: 'whenInUse' | 'always'; requestOnLaunch?: boolean } | Writes NSLocationWhenInUseUsageDescription (and, for mode: 'always', also NSLocationAlwaysAndWhenInUseUsageDescription); optionally prompts during packaged launch. |
macOS.fullDiskAccess | { requestOnLaunch?: boolean } | Guidance-only — there is no TCC request API, so requestOnLaunch opens the Full Disk Access pane in System Settings instead of showing an in-app prompt when status isn't already granted. |
macOS.photos | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSPhotoLibraryUsageDescription (read-write library access). |
macOS.contacts | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSContactsUsageDescription. |
macOS.calendar | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSCalendarsUsageDescription and NSCalendarsFullAccessUsageDescription (both always written, so one build stays correct on macOS 11 through 14+). |
macOS.reminders | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSRemindersUsageDescription and NSRemindersFullAccessUsageDescription (same reasoning as calendar). |
macOS.speechRecognition | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSSpeechRecognitionUsageDescription. |
macOS.bluetooth | { usageDescription: string; requestOnLaunch?: boolean } | Writes NSBluetoothAlwaysUsageDescription. CoreBluetooth has no explicit request call — consent is determined implicitly the first time a central manager is created. |
macOS.appleEvents | { usageDescription: string } | Writes NSAppleEventsUsageDescription. Declaration-only — no requestOnLaunch; automation consent is per-target-app, so status() always reports unknown and request() only opens System Settings' Automation pane as guidance. |
macOS.localNetwork | { usageDescription: string } | Writes NSLocalNetworkUsageDescription. Declaration-only — no query/request API exists at all; macOS prompts automatically on first local-network access. |
Prefer a contextual systemPermission.request() over launch-time prompts when
the user can first understand the feature. Runtime calls additionally require
systemPermission:status / systemPermission:request in that renderer's
capability list. Windows unpackaged desktop consent is usage-driven, so it has
no equivalent generic launch prompt; all fifteen permission kinds above are
macOS-only, since Windows/Linux expose no OS-level equivalent of these
TCC-gated prompts. Test macOS TCC behavior from a packaged app, not the
terminal/Node identity used by murasaki dev.
Entitlements and the App Sandbox
Murasaki currently supports hardened-runtime signing, not the macOS App
Sandbox. With sign.appSandbox left at its default false, Murasaki derives
the Hardened Runtime resource-access entitlements for camera, microphone,
location, photos, contacts, calendar/reminders, and Apple Events from
systemPermissions.macOS. A signed build needs both those host entitlements
and the corresponding Info.plist purpose strings. Bluetooth and speech
recognition only need their purpose strings under this non-sandboxed posture.
The bundled Node helper alone receives its hardened-runtime JIT rights; native
add-ons receive no executable entitlement. sign.appSandbox: true is rejected
fail-closed because Apple's inherited sandbox helper rules are incompatible
with the current embedded Node/JIT architecture. Custom sign.entitlements
and sign.helperEntitlements files are used verbatim, and configured paths
must exist and pass plutil validation. See
Native APIs → Entitlements and the App Sandbox
for the full per-kind mapping.
main
Murasaki loads src/main.ts when it exists. Use main to choose another
entry, change the graceful cleanup deadline, or disable Main discovery:
main: {
entry: 'src/backend/main.ts',
shutdownTimeoutMs: 15_000,
}
// main: false| Field | Type | Description |
|---|---|---|
entry | string? | Path relative to the project root. Defaults to src/main.ts. |
shutdownTimeoutMs | number? | End-to-end limit for beforeQuit() plus shutdown() before host exit. Defaults to 10_000. |
See Node Main for lifecycle hooks and 'use main'.
protocols
Register custom URL schemes and receive matching URLs through Node Main's
openRequested() hook:
protocols: [
{ scheme: 'violet', name: 'Violet Link' },
]| Field | Type | Description |
|---|---|---|
scheme | string | Required. RFC 3986-style scheme, such as violet in violet://open/42. It is trimmed and normalized to lowercase. Must be 1–63 valid scheme characters. Browser/OS schemes including blob, file, http, https, javascript, mailto, ms-settings, tel, and murasaki are reserved. |
name | string? | Human-readable handler name used in package metadata. Defaults to `${productName} URL`. |
Duplicate or invalid schemes fail the build. macOS writes the registration into
the packaged .app's Info.plist. Windows NSIS/MSI installers write the OS
registration; a portable Windows archive does not. Linux writes the matching
MimeType entries into the .desktop file; installing the .deb registers
that file and refreshes the desktop database. A manually extracted AppDir or
AppImage does not perform an OS-level registration step.
fileAssociations
Register one or more extensions as a document type:
fileAssociations: [
{
extensions: ['vnote', 'violet-note'],
name: 'Violet Note',
description: 'A note created with Violet',
role: 'editor',
mimeType: 'application/x-violet-note',
},
]| Field | Type | Description |
|---|---|---|
extensions | string[] | Required. At least one extension. A leading dot is accepted and removed; values are normalized to lowercase. Each extension must be 1–32 letters, digits, underscores, or hyphens and must start with a letter or digit. |
name | string? | Document type name. Defaults to `${productName} document`. |
description | string? | Description stored in package/Windows registration metadata. Defaults to name. |
role | 'viewer' | 'editor' | 'shell' | 'none' | macOS document role. Defaults to viewer. |
mimeType | string? | Optional MIME type stored with the document metadata. |
Extensions must be unique across all entries; invalid extensions and MIME types
fail the build. Matching files are delivered to Node Main's openRequested()
hook. The same artifact limits as protocols apply: packaged macOS .app,
installed Windows NSIS/MSI, and installed Linux .deb packages register the
association. Portable Windows archives, manually extracted AppDir/AppImage
artifacts, and murasaki dev do not install OS-level associations.
See Deep links and file associations for delivery semantics, testing, and untrusted-input requirements.
bundle
Server/Main code is compiled for production. Static bare npm imports are detected and staged automatically; use these options for dependencies and assets whose runtime behavior cannot be discovered statically:
bundle: {
external: ['computed-plugin'],
noExternal: ['small-js-only-package'],
resources: [
'prisma/schema.prisma',
{ from: 'prisma/migrations', to: 'database/migrations' },
{ from: 'bin/indexer', to: 'sidecars/indexer', executable: true },
],
}| Field | Type | Description |
|---|---|---|
external | string[]? | Stage packages in the app's node_modules. Add computed/dynamic package loads here. Prefer this for native addons and packages with runtime data. |
noExternal | string[]? | Force packages into the compiled server bundle. Use for JS-only packages without runtime assets. |
resources | Array<string | { from: string; to?: string; executable?: boolean }>? | Copy files/directories into packaged resources. String entries use the source basename; object entries choose a relative destination. Mark every app-owned executable sidecar with executable: true so macOS/Windows signing seals it before the outer artifact. Executable directories are rejected. |
Do not place secrets in resources; everything in an app bundle is readable by
the user. Test native addons on every target architecture.
plugins
Use defineMurasakiPlugin to declare trusted build-time extensions. Plugins
run with the same Node.js privileges as murasaki.config.ts; install only code
you trust.
import { defineConfig, defineMurasakiPlugin } from 'murasaki'
import inspect from 'vite-plugin-inspect'
const assetsPlugin = defineMurasakiPlugin({
name: 'acme.assets',
vite: inspect(),
bundle: {
external: ['native-addon'],
resources: [{ from: 'models', to: 'models' }],
},
hooks: {
async before({ command, target, projectRoot, config }) {
// Generate/check files before dev, build, or bundle.
},
async after(context) {
// Runs serially after a successful command.
},
},
})
export default defineConfig({
appId: 'app.murasaki.example',
productName: 'Murasaki App',
plugins: [assetsPlugin],
})| Field | Type | Description |
|---|---|---|
name | string | Required stable lowercase identifier. Duplicate names fail configuration validation. |
vite | PluginOption? | Vite contribution appended after Murasaki core plugins. Nested arrays and async Vite options follow Vite semantics. |
bundle | BundleConfig? | Adds external, noExternal, and resources in plugin declaration order. Exact duplicates are removed; noExternal wins when the same package is also external. |
hooks.before / hooks.after | (context) => void | Promise<void> | Serial CLI hooks. A throw/rejection stops the command and reports the plugin name. after runs only after success. |
Hook context contains a deeply frozen configuration snapshot (without the
plugin objects), absolute projectRoot, command (dev, build, or
bundle), and the concrete target for bundle commands. Plugin objects and
functions are never serialized into murasaki-meta.json.
This SDK extends the build pipeline only. It is not a native Rust ABI, a dynamic library loader, or a renderer/runtime plugin sandbox. Native features still belong in Murasaki's native host and capability model.
build
build: {
before: 'pnpm --filter @acme/database build && pnpm prisma generate',
envPrefix: ['MURASAKI_PUBLIC_', 'ACME_PUBLIC_'],
}| Field | Type | Description |
|---|---|---|
before | string? | Shell command run once before the client and Node builds. A non-zero exit stops packaging. Use it for workspace prerequisites and code generation. |
envPrefix | string[]? | Environment prefixes that may be exposed to renderer code. Defaults to MURASAKI_PUBLIC_; set this only to add or replace prefixes. |
Only prefixed variables are client-public, but never put secrets under those prefixes: Vite replaces their values into shipped JavaScript.
Environment variables and .env
The Murasaki CLI automatically loads the following files from the project root.
Later files have higher priority, while variables already supplied by the terminal
or CI override every .env file.
.env
.env.local
.env.development # murasaki dev
.env.development.local
.env.production # build / bundle / installer
.env.production.localPrefix renderer values with Murasaki's MURASAKI_PUBLIC_ namespace and read them
through import.meta.env:
MURASAKI_PUBLIC_API_ORIGIN=https://api.example.com
ACCOUNT_API_TOKEN=keep-this-privateconst apiOrigin = import.meta.env.MURASAKI_PUBLIC_API_ORIGINUnprefixed values never enter the renderer bundle. In murasaki.config.*, plugin
hooks, Node Main, Server Actions, and API Routes, read them as ordinary Node
environment variables:
const token = process.env.ACCOUNT_API_TOKENMURASAKI_PUBLIC_ values are embedded into shipped JavaScript at build time, so
never use that prefix for secrets. Packaged Node Main inherits the launcher's
environment, but Murasaki does not copy .env files into the app. For installed
app secrets or end-user runtime settings, use the deployment environment, OS
credential store, secureStorage, or context.paths.data.
security
Murasaki injects one environment-specific Content Security Policy into both
the framework shell and a user-owned index.html. Configure a complete
replacement, or explicitly opt out:
security: {
csp: "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; connect-src 'self' https: wss:; object-src 'none'; base-uri 'none'; frame-src 'none'",
},
// Escape hatch when another layer owns the policy:
// security: { csp: false },| Value | Behavior |
|---|---|
| omitted | Use Murasaki's production or development default. |
string | Completely replace the default; directives are not merged. |
false | Disable framework injection. An existing user-authored CSP is left untouched. |
If index.html already contains a CSP <meta http-equiv> tag, Murasaki keeps
that user-owned policy and moves it to the beginning of <head> so it applies
before scripts or resources. Configure the policy in exactly one place:
combining an existing tag with a security.csp string is a build error.
Configured policies must be non-empty, single-line strings without control
characters, double quotes, <, or >. See Security
for defaults, migration guidance, and limitations of meta-delivered CSP.
updater
true is a complete, working setup for a normal OSS app — the GitHub repo is
inferred from package.json's repository field, the public key from
.murasaki/update-key.pub, channel defaults to 'stable', checked once at
launch. The object form only needs to override what doesn't fit those
defaults:
type UpdaterConfig =
| boolean
| {
repo?: string // "owner/repo" — defaults to package.json#repository
endpoint?: string // self-hosted manifest URL — mutually exclusive with repo, must be https (loopback http allowed for local testing)
channel?: string // release channel, default 'stable'
checkOnStart?: boolean // check once at launch, default true
checkInterval?: string | false // e.g. '6h' — re-check on a timer, default '6h'
publicKey?: string // base64 Ed25519 public key — defaults to .murasaki/update-key.pub
publicKeys?: string[] // additional pinned keys for rotation (max 4 total with publicKey)
maxManifestAgeDays?: number // reject a manifest older than this many days, default 90
allowLegacyManifestsWithoutGeneratedAt?: boolean // migration escape hatch, default false
}There is deliberately no provider field — GitHub vs. self-hosted is
inferred from whether repo or endpoint is set, so it can't drift out of
sync with the rest of the config. There is also no way to disable signature
verification, and a self-hosted endpoint must be credential-free https: (an http:
endpoint is only accepted for loopback hosts — 127.0.0.1, localhost,
[::1] — for local testing), enforced both here and again at fetch time.
generatedAt is required by default for replay protection. The legacy escape
hatch should only be enabled while migrating an older signed manifest.
checkOnStart and checkInterval are driven by the update engine itself, so
they apply whether or not anything in your UI ever calls check(). You can
still call useUpdate().check() by hand — an in-flight check is never started
twice concurrently.
Consumed by useUpdate() and <UpdateButton /> (both from murasaki) —
see the full Auto-update guide for the manifest
format, signing, and publishing releases.
installer
macOS DMG styling and Windows NSIS/MSI installer options. Omit entirely to use Murasaki's defaults.
| Field | Type | Description |
|---|---|---|
background | string? | Path (relative to the project root) to a custom DMG background PNG. Overrides Murasaki's default. |
window | { width: number; height: number }? | DMG window content size in points. Default { width: 640, height: 420 } (matches the default background). |
iconSize | number? | Icon size in the DMG window. Default 128. |
installer.windows
| Field | Type | Description |
|---|---|---|
installMode | 'perUser' | 'perMachine' | NSIS installation scope. Defaults to perUser; MSI is always per-machine. |
publisher | string? | Installer/Add-Remove Programs publisher. Falls back to authors, copyright, then productName. |
upgradeCode | string? | Stable MSI GUID. Defaults to a deterministic value derived from appId; do not change it after release. |
icon | string? | .ico for installer/uninstaller and Add/Remove Programs. Defaults to the generated app icon. |
banner | string? | Wizard header BMP. NSIS expects 150×57; MSI expects 493×58. |
sidebar | string? | Welcome/finish BMP. NSIS expects 164×314; MSI expects 493×312. |
license | string? | NSIS .txt/.rtf or MSI .rtf license file. |
When the built-in updater is enabled, Windows distribution is intentionally
NSIS per-user only: MSI generation is skipped and installMode: 'perMachine'
is rejected. MSI remains available when updater is disabled for deployments
managed through MSI major upgrades. The installer command fails if the
required platform packaging tool produces no installer; use murasaki bundle
when you only need the portable ZIP.
sign
Murasaki signs with your certificate/provider — it ships none of its own. Notarization credentials and PFX passwords are read from environment variables, never from this config — see Distribution → Signing.
macOS fields
| Field | Type | Description |
|---|---|---|
identity | string? | Signing identity, e.g. "Developer ID Application: Name (TEAMID)". Defaults to $MURASAKI_SIGN_IDENTITY, then the first "Developer ID Application" identity in your keychain. |
entitlements | string? | Custom .plist for the main app executable. Defaults to minimum host permissions derived from systemPermissions. Missing/invalid files fail the build. |
helperEntitlements | string? | Custom .plist for the bundled Node helper. Defaults to Node's JIT/library-loading hardened-runtime rights. Missing/invalid files fail the build. |
appSandbox | boolean? | Reserved. true is rejected fail-closed by the current bundled-Node architecture; use the default hardened-runtime signing. Default false. |
sign.windows
murasaki bundle --sign --target win32-x64 signs the app executable before
the portable ZIP is made. murasaki installer --sign --target win32-x64
also signs each generated installer (NSIS and, when the updater is disabled,
MSI), then verifies every signature
with the Authenticode policy. This step runs on Windows with SignTool.
| Field | Type | Description |
|---|---|---|
certificateFile | string? | PFX/P12 path. Its optional password comes only from $MURASAKI_WINDOWS_CERTIFICATE_PASSWORD. |
certificateSubjectName | string? | Subject-name selector for a certificate already imported into the Windows My store. |
certificateSha1 | string? | 40-character certificate thumbprint in the Windows My store. Mutually exclusive with the file/subject selectors. |
certificateStore | 'currentUser' | 'localMachine' | Store scope for subject/thumbprint/automatic selection. Default currentUser. |
timestampUrl | string | false | RFC 3161 timestamp URL; false disables timestamping. Defaults to DigiCert, or Microsoft's service with Artifact Signing. |
signToolPath | string? | Explicit signtool.exe; otherwise PATH and installed Windows SDKs are searched. |
artifactSigning | { dlib: string; metadata: string }? | Microsoft Artifact Signing provider paths (Azure.CodeSigning.Dlib.dll and its non-secret account/profile metadata JSON). Mutually exclusive with certificate selectors. |
All selectors also have CI environment overrides; see Windows installers and signing.