Murasaki
Building & Distribution

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:

murasaki.config.ts
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

FieldTypeDescription
appIdstringRequired. Portable reverse-DNS identifier (e.g. com.example.my-app; letters, digits, dots, and hyphens) — becomes CFBundleIdentifier on macOS.
productNamestringRequired. 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.
versionstring?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.
descriptionstring?Short description shown in the native "About <app>" panel.
copyrightstring?Copyright notice shown in the About panel.
homepagestring?Homepage URL shown in the About panel.
authorsstring[]?Author names shown in the About panel (Windows/Linux only).
aboutAboutConfig?Opt into a configurable macOS About panel with custom size, paragraphs, detail rows, and external-link buttons — see below.
windowWindowConfig?Window shape — see below.
windowsRecord<string, SecondaryWindowConfig>?Secondary windows keyed by stable label — see below.
webviewWebviewConfig?Application-wide User-Agent, private session, and unauthenticated proxy settings — see below.
capabilitiesNativeCapabilityGrant[]?Primary-window native renderer grants. Used only when window.capabilities is omitted. Default is deny-all.
backendCapabilitiesBackendCapability[]?Primary-window renderer-to-Node/API grants. Used only when window.backendCapabilities is omitted. Default is deny-all.
systemPermissionsSystemPermissionsConfig?Host-OS consent declarations and packaged macOS launch prompts — see below.
mainfalse | object?Long-lived Node Main entry and shutdown bound — see below.
bundleobject?Node dependency and non-code resource packaging — see below.
pluginsMurasakiPlugin[]?Trusted build-time extensions for Vite, CLI hooks, dependencies, and resources — see below.
buildobject?Pre-build command and public client env prefixes — see below.
securityobject?Renderer Content Security Policy — see below.
updaterUpdaterConfig?Auto-update source — see below.
localesstring[]?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.
devPortnumber?Vite dev server port during murasaki dev. Defaults to 5178 (auto-increments if taken).
targetsTarget[]?Build targets. Defaults to the host platform. One or more of darwin-arm64, darwin-x64, win32-x64, win32-arm64, linux-x64, linux-arm64.
iconstring?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.
protocolsProtocolConfig[]?Custom URL schemes registered by packaged macOS apps and Windows installers — see below.
fileAssociationsFileAssociationConfig[]?Document extensions registered by packaged macOS apps and Windows installers — see below.
installerobject?macOS DMG and Windows installer options — see below.
signobject?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.

murasaki.config.ts
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' },
  ],
},
FieldTypeDescription
namestring?Panel heading. Defaults to productName.
widthnumber?Content width in logical pixels, 360–900. Default 480.
heightnumber?Content height in logical pixels, 320–1000. Auto-sized when omitted.
paragraphsstring[]?Up to 8 centered body paragraphs. Falls back to description.
paragraphSpacingnumber?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

FieldTypeDescription
titlestring?Window title bar text, set once at launch.
width / heightnumber?Initial window size.
minWidth / minHeightnumber?Minimum window size.
resizableboolean?Whether the window can be resized.
transparentboolean?Transparent window background.
vibrancy'hud' | 'sidebar' | 'popover' | nullmacOS translucent window vibrancy.
consoleboolean?Windows only: show the backend Node console window. Defaults to false.
routestring?Same-origin path loaded in this window. Defaults to /; full/protocol-relative URLs are rejected.
visibleboolean?Initial visibility. Defaults to true for main, false for secondary windows.
capabilitiesNativeCapabilityGrant[]?Per-window native grants. Secondary windows default to deny-all.
backendCapabilitiesBackendCapability[]?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 fieldTypeDescription
createOnLaunchboolean?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.

murasaki.config.ts
webview: {
  userAgent: 'AcmeDesktop/1.4 Murasaki',
  incognito: true,
  proxy: {
    protocol: 'socks5',
    host: '127.0.0.1',
    port: 1080,
  },
},
FieldTypeDescription
userAgentstring?Complete custom User-Agent value. Must be trimmed, contain no control characters, and be at most 512 UTF-8 bytes.
incognitoboolean?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.hoststringASCII DNS hostname, IPv4 address, or bracketed IPv6 literal, at most 253 bytes. It is not a URL.
proxy.portnumberInteger 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-proxy feature 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/.deb builds 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.' },
  },
},
FieldTypeDescription
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
FieldTypeDescription
entrystring?Path relative to the project root. Defaults to src/main.ts.
shutdownTimeoutMsnumber?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' },
]
FieldTypeDescription
schemestringRequired. 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.
namestring?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',
  },
]
FieldTypeDescription
extensionsstring[]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.
namestring?Document type name. Defaults to `${productName} document`.
descriptionstring?Description stored in package/Windows registration metadata. Defaults to name.
role'viewer' | 'editor' | 'shell' | 'none'macOS document role. Defaults to viewer.
mimeTypestring?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 },
  ],
}
FieldTypeDescription
externalstring[]?Stage packages in the app's node_modules. Add computed/dynamic package loads here. Prefer this for native addons and packages with runtime data.
noExternalstring[]?Force packages into the compiled server bundle. Use for JS-only packages without runtime assets.
resourcesArray<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.

murasaki.config.ts
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],
})
FieldTypeDescription
namestringRequired stable lowercase identifier. Duplicate names fail configuration validation.
vitePluginOption?Vite contribution appended after Murasaki core plugins. Nested arrays and async Vite options follow Vite semantics.
bundleBundleConfig?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_'],
}
FieldTypeDescription
beforestring?Shell command run once before the client and Node builds. A non-zero exit stops packaging. Use it for workspace prerequisites and code generation.
envPrefixstring[]?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.local

Prefix renderer values with Murasaki's MURASAKI_PUBLIC_ namespace and read them through import.meta.env:

.env.local
MURASAKI_PUBLIC_API_ORIGIN=https://api.example.com
ACCOUNT_API_TOKEN=keep-this-private
src/app/page.tsx
const apiOrigin = import.meta.env.MURASAKI_PUBLIC_API_ORIGIN

Unprefixed 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:

src/main.ts
const token = process.env.ACCOUNT_API_TOKEN

MURASAKI_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 },
ValueBehavior
omittedUse Murasaki's production or development default.
stringCompletely replace the default; directives are not merged.
falseDisable 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.

FieldTypeDescription
backgroundstring?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).
iconSizenumber?Icon size in the DMG window. Default 128.

installer.windows

FieldTypeDescription
installMode'perUser' | 'perMachine'NSIS installation scope. Defaults to perUser; MSI is always per-machine.
publisherstring?Installer/Add-Remove Programs publisher. Falls back to authors, copyright, then productName.
upgradeCodestring?Stable MSI GUID. Defaults to a deterministic value derived from appId; do not change it after release.
iconstring?.ico for installer/uninstaller and Add/Remove Programs. Defaults to the generated app icon.
bannerstring?Wizard header BMP. NSIS expects 150×57; MSI expects 493×58.
sidebarstring?Welcome/finish BMP. NSIS expects 164×314; MSI expects 493×312.
licensestring?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

FieldTypeDescription
identitystring?Signing identity, e.g. "Developer ID Application: Name (TEAMID)". Defaults to $MURASAKI_SIGN_IDENTITY, then the first "Developer ID Application" identity in your keychain.
entitlementsstring?Custom .plist for the main app executable. Defaults to minimum host permissions derived from systemPermissions. Missing/invalid files fail the build.
helperEntitlementsstring?Custom .plist for the bundled Node helper. Defaults to Node's JIT/library-loading hardened-runtime rights. Missing/invalid files fail the build.
appSandboxboolean?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.

FieldTypeDescription
certificateFilestring?PFX/P12 path. Its optional password comes only from $MURASAKI_WINDOWS_CERTIFICATE_PASSWORD.
certificateSubjectNamestring?Subject-name selector for a certificate already imported into the Windows My store.
certificateSha1string?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.
timestampUrlstring | falseRFC 3161 timestamp URL; false disables timestamping. Defaults to DigiCert, or Microsoft's service with Artifact Signing.
signToolPathstring?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.

Next

Improve this page on GitHub

On this page