Skip to main content
khal-app.json is the manifest every pack ships at its repo root. The platform reads it once at install time and from then on knows your pack’s identity, the permissions it requires, the frontend bundle to load, and the backend service (if any) to run. The schema is defined by @khal-os/types and validated with Zod, so mistakes are caught at build time rather than in production.

Required fields

Every pack manifest must declare these fields. The build will fail if any are missing or the wrong shape.
FieldTypeWhat it is
idstringStable identifier — lowercase, hyphen-separated. Used for NATS subjects, install paths, and log lines.
namestringHuman-readable name shown in the launcher and window title.
versionstringSemantic version of the pack (1.0.0). Bumped on every release.
iconstringRelative path to the pack icon (SVG recommended).
descriptionstringShort blurb shown in the launcher and the marketplace listing.
authorstringThe name or organization shipping the pack.
permissionsstring[]The capabilities the pack needs (nats:publish, files:read, etc.). Declare the minimum — the platform enforces the list at runtime.
For a pack with a single frontend view (the common case), you’ll also set frontend.package to the npm name your pack publishes. It’s technically optional in the schema because bundle packs use apps[] instead — but you’ll include it in 95% of packs.

Optional fields

These fields unlock additional pack shapes. You only declare what your pack needs.
FieldWhen to use it
frontend.packageThe npm package your pack publishes (@khal-os/pack-<name>). Required for single-app packs.
services[]Declare long-running processes your pack runs. Each entry names a process, its entry point, health check, and ports.
windows[]Default window sizes and titles for the shell. Override per-view dimensions here.
backendFor packs that ship a container image — names the image, Helm chart, env vars, and ports.
apps[]For bundle packs that ship multiple frontend apps in one repo. Each entry has its own id, name, and frontend.package. When present, the root frontend is ignored.
sandboxFor packs that require a per-user container on install. Declares CPU, memory, and volume mounts.
Every optional field has a deep dive in the exhaustive khal-app.json schema reference — bookmark it when you start declaring services or sandboxes.

Worked examples

The minimum viable manifest — what you get from pack-template after renaming. No backend, no services, one frontend package.
khal-app.json
{
  "$schema": "https://raw.githubusercontent.com/khal-os/app-kit/dev/packages/types/src/khal-app-schema.json",
  "id": "<name>",
  "name": "<Name>",
  "version": "1.0.0",
  "icon": "./package/src/assets/icon.svg",
  "description": "A KhalOS pack",
  "author": "KhalOS Core Team",
  "permissions": [],
  "frontend": {
    "package": "@khal-os/pack-<name>"
  }
}
Source: the scaffold in pack-template.
Annotated khal-app.json with arrows from each field to the runtime component it configures — id to launcher, permissions to capability gate, frontend.package to the shell loader, backend to the Kubernetes workload.

Permissions: declare the minimum

permissions is where you tell the platform what your pack intends to do. The platform enforces the list at runtime — a pack without nats:publish cannot publish to NATS, period.
Principle of least privilege. Start with an empty permissions array and add entries only when the build surfaces a denied capability. Packs that over-declare permissions are flagged in review.
Common permissions include nats:publish, nats:subscribe, files:read, files:write, pty:spawn, http:fetch, system:clipboard, and system:notifications. The full list and the semantics of each lives in the khal-app.json schema reference.

Secrets: declare, don’t embed

Never put secrets directly in khal-app.json. The manifest is committed to git and published as part of your pack. Instead, declare what your pack needs and let the platform provide the values at runtime — through the backend.env block (for non-sensitive config) or via the platform’s secret store (for credentials).

Validation

The toolchain validates your manifest on every build. If a required field is missing, a type is wrong, or a permission name is unrecognized, you’ll see a clear Zod error with the field path before the build proceeds. There’s no way to publish a pack with a malformed manifest.

Next steps

Full schema reference

Every field, every type, every enum value — the exhaustive reference for writing non-trivial manifests.

Anatomy of a pack

Step back out to the directory-level view and see where the manifest sits relative to everything else.