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,
},
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. Bundle identifier (e.g. com.example.my-app) — becomes CFBundleIdentifier on macOS. |
productName | string | Required. Display name — the .app filename, Dock/menu-bar label, and DMG volume name. |
version | string? | App version. Shown in the native About panel; used in the .dmg filename. 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). |
window | WindowConfig? | Window shape — see below. |
windows | Record<string, SecondaryWindowConfig>? | Secondary windows keyed by stable label — see below. |
capabilities | NativeCapability[]? | Primary-window native renderer commands. Used only when window.capabilities 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. |
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 source PNG (ideally 1024px). murasaki icon / murasaki bundle fan it out to .icns / .ico / a PNG set. |
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. |
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 | NativeCapability[]? | Per-window native command allowlist. Secondary windows default to deny-all. |
vibrancy is declared but not yet applied by the native binding — setting
it is currently a no-op (support is planned).
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'],
},
windows: {
settings: {
route: '/settings',
width: 720,
height: 560,
capabilities: [],
},
},Labels are 1–64 safe characters and main is reserved. Secondary windows are
created hidden and 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.
capabilities
Native renderer APIs are deny-by-default. Add only the commands your app uses; unknown permissions do not grant anything:
capabilities: [
'app:quit',
'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.
systemPermissions
This config describes host-OS consent, not which renderer commands are trusted.
For packaged macOS apps, Murasaki writes camera/microphone purpose text to
Info.plist and can request selected 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 },
},
},| 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. |
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. Test macOS TCC behavior from a packaged
app, not the terminal/Node identity used by murasaki dev.
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 registration is not
implemented.
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 and
installed Windows NSIS/MSI are supported, while portable Windows archives,
Linux, and murasaki dev do not install 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' },
],
}| 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 }>? | Copy files/directories into packaged resources. String entries use the source basename; object entries choose a relative destination. |
Do not place secrets in resources; everything in an app bundle is readable by
the user. Test native addons on every target architecture.
build
build: {
before: 'pnpm --filter @acme/database build && pnpm prisma generate',
envPrefix: ['VITE_', 'NEXT_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 Vite may expose to renderer code. Defaults to VITE_ and NEXT_PUBLIC_. |
Only prefixed variables are client-public, but never put secrets under those
prefixes: Vite replaces their values into shipped JavaScript. Packaged Node
Main inherits the environment of the launcher; Murasaki does not copy .env
files into the app. Put end-user runtime settings under context.paths.data
or provide them through your deployment environment.
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
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
}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.
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. |
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? | Path to a custom entitlements .plist. Defaults to a Node-friendly hardened-runtime set (JIT + unsigned executable memory + disabled library validation). |
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 NSIS setup and 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.