YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
Introduction▾
Tutorials▾
Core Concepts▾
Architecture▾

App Config

akan.config.ts is the app-level settings file. You do not need to understand every option on day one. Start with an empty file, then add only the fields your app actually needs.
apps/minimal/akan.config.ts
This is the whole key set. Every one of them has a default that a working app can live with, and the slides below cover the ones you are most likely to change:
routesAkanRouteConfig[]
Public domains for the app, optionally split per client with basePath.
api{ prefix, websocketPrefix }default /api, /ws
Where signal endpoints and the websocket upgrade are mounted. Baked into every client bundle.
webboolean | { csr: boolean }default true
Which web surfaces the build produces and the app mounts at boot.
i18n{ defaultLocale, locales }default en, ["en", "ko"]
The locale segment every route sits under. defaultLocale must be one of locales.
nativeAkanNativeAppConfig
The iOS, Android and desktop app: its identity, platform settings and targets.
imagesAkanImageConfigdefault webp, quality 75
Allow-list, sizes, and limits for the image optimizer. A remote host not listed is refused.
publicEnvstring[]default []
Extra process.env names the browser build may inline, beyond the built-in AKAN_PUBLIC_* pattern.
secretsstring[]default []
Globs for files that cannot live inside env.server.*.ts. Shipped by upload-env and git-ignored.
assets{ pruneFonts, keepFonts }default true, []
How akan build trims the public/ copy it ships. Source trees are never touched.
syncPageLibsstring[] | booleandefault false
Which library page folders this app mounts as its own routes.
pluginsAkanPlugin[]default []
Akan plugins this app contributes, read live by the CLI.
pipelineAppPipelineConfigdefault build → docker → deploy
The stages Akan Cloud runs to build and deploy this app, in order.
dockerstring | DockerImageConfigdefault oven/bun:1-slim
A whole Dockerfile as a string, or the parts akan build assembles one from.
database{ modes: DatabaseMode[] }default { modes: ["single"] }
The modes the build can run in; each deployment picks one with AKAN_DATABASE_MODE.
externalLibsstring[]default []
Packages kept as production runtime dependencies instead of being bundled.
trustedDependenciesstring[]default []
Packages whose install scripts bun install --production runs, in the image and in a desktop app's server.
binRecord<string, { [platform]: AkanBinSource }>default {}
Executables every desktop build carries, per platform, first on its PATH; the image ignores it.
barrelImportsstring[]default akanjs + workspace
Barrel paths Akan flattens while scanning and bundling.
optimizeImportsstring[]default built-in list
Extra packages whose imports the client build rewrites to the exact source file.
Start small: Most defaults are already prepared, so an empty config is valid.
Add only what changes: Define only the parts your app actually needs to customize.
One source of truth: CLI commands, production builds, and native app commands all read this file.

Config Shape

The default export can be a plain object or a function. Use an object for most apps. Use a function only when the config needs app metadata while it is being loaded.
Object config
Function config

Application Env

akan.config.ts describes how the app is built and routed. The env/ folder describes the actual values the app uses at runtime, such as public client keys, server-only options, and environment-specific service settings.
env/env.client.local.ts
env/env.server.local.ts
env.client.*
Public-safe values for client code, such as map keys, site keys, or feature switches.
env.server.*
Server-only values: server options, connection settings, private service configuration.
localtestingdebugdevelopmain
Suffixes chosen by AKAN_PUBLIC_ENV: your machine, tests, two shared stages, production.
env.*.type.ts
The shape of env values, so a missing or misspelled setting is caught while coding.

Server Option

lib/option.ts is where the app configures its server. env/ holds the values, akan.config.ts holds the build, and this file wires them into the runtime: use objects, signal middleware, adaptor overrides, web proxies, the MCP server, the agent relay's access policy, and the LLM that relay speaks to. Every library the app depends on brings its own option.ts, read in mount order with the app's last — so an app tightens what a library declared without restating it.
lib/option.ts
setLlm
apiKey, model, and host for whichever adaptor holds LlmAdaptorRole.
setAgentAccess
Guards (ANDed) for spending the LLM key via runAgentTurn; with none, every call is refused.
setMcp
MCP server settings such as instructions, readOnly, and auth.
useapplyMiddlewareapplyAdaptorapplyWebProxy
Register env-derived use<T>() singletons, signal middleware, adaptor overrides, web proxies.

Routes and Domains

routes is where you list the public domains for the app. If your app has several clients, each route can also name the client with basePath. The multi-client page explains that structure in detail; here we focus on the config fields.
apps/myapp/akan.config.ts
basePathstring
The client this route opens and its first page folder; without one, the route is the app.
domainsRecord<branch, string[]>default {}
Hosts that open this route, keyed by branch: debug, develop, main, or any key you add.

Web Surfaces And Prefixes

web decides which web surfaces the build produces, and api decides where the server mounts its endpoints. Both are declared here rather than only in main.ts, because both are baked into the client bundles: a prebuilt CSR shell or a native app never reaches a server that could tell it otherwise.
apps/myapp/akan.config.ts
webboolean | { csr: boolean }default true
true builds SSR and CSR, false is API-only, and { csr: false } drops only the CSR shell.
api.prefixstringdefault /api
Where signal endpoints are mounted; read it back with getApiPrefix() from akanjs/base.
api.websocketPrefixstringdefault /ws
Where the websocket upgrade sits; read it back with getWsPrefix().
Never write either prefix as a literal; new AkanApp({ prefix, websocketPrefix }) still overrides both for the server and every page it renders.
AKAN_SSR and AKAN_CSR narrow the same choice at boot, and can only narrow it: a deployment cannot switch on a surface the build left out. akan start ignores web entirely, so the dev surface stays whole.

Native Apps

native describes the app the Android, iOS and desktop commands build from this app's CSR client: its name, package id, version, permissions and plugins. A value only one platform reads sits in that platform's section, ios, android or desktop.
Native config
An app without basePaths leaves basePath out, and an app that ships one native app needs no targets. So the shortest config for a desktop app that carries the app's server is this:
native without basePath
When the first page is not /, add indexPath beside it: native: { indexPath: "/board", desktop: { server: true } }. When one platform starts elsewhere, give that platform section its own: native: { indexPath: "/mobile", desktop: { indexPath: "/", server: true } } opens the phones on /mobile and the desktop app on /.
Targets
targets builds several native apps from one Akan app, such as a store app and an admin app that each open their own basePath. A target takes every field of native but targets, and its own values win: objects (deepLinks, updates, ios, android, desktop and the objects inside them) merge key by key, while lists, icon, splash and every other value are replaced, so a target's permissions replace native's instead of adding to them. Without targets the app has one target, named default, or named after the app and opening that basePath when routes declares one with the app's name.
native.targets
basePathstring
The client the app opens, a basePath routes declares. An app without basePaths leaves it out.
indexPathstringdefault /
Start path, and where a deep link's stack and a back with no history fall back to. ios.indexPath, android.indexPath and desktop.indexPath win on their platform.
appNamestringdefault the app name
Display name of the native app.
appIdstring | { default?, ios?, android?, macos?, windows?, linux? }default com.<repo>.<app>
Android applicationId and iOS bundle id; one per platform when the store listings already differ.
fileNamestringdefault the app folder name
Name of the executables and archives: letters, digits, ., _ and -.
versionstringdefault 0.0.1
User-facing app version, written to Android versionName and iOS MARKETING_VERSION.
buildNumnumberdefault 1
Store build number, written to Android versionCode and iOS CURRENT_PROJECT_VERSION.
iconstring | { image, backgroundColor? }
A square PNG relative to the app folder, or it with the color behind its transparent areas.
splashstring | { image?, backgroundColor?, autoHide?, timeout? }
A PNG shown centered at launch, or the launch screen's image, color and when it hides.
backgroundColorstring | { light, dark }default white / black
What a window and an external view show before their page paints, and the splash's default color.
permissionscamera | contacts | location | push | speechdefault []
Native permission hints; each activates the matching plugin's native configuration.
pluginsstring[]default []
Runtime plugins beyond the ones the permissions bring, by builtin id (iap) or absolute folder.
deepLinks.schemesstring[]
Custom URL schemes such as example://.
deepLinks.domainsstring[]
App-link and universal-link hosts, normalized to the bare host.
updates{ url, publicKeys, revokedKeys?, channel?, readyTimeout?, report? }
Where installed apps find new releases: a phone updates itself, a desktop app when it calls updates.
updates.urlstring
A static base URL, such as a storage bucket, holding what akan publish-update writes; https in a release build.
updates.publicKeysstring[]
1–3 keys from akan update-keygen, signing key first; a key revokes only those before it, and a desktop app trusts the first. publicKey is the one-key form, never beside it.
updates.revokedKeysstring[]
Up to 16 keyIds every phone release revokes, kept here so a release from a fresh checkout revokes them too. A phone build trusts none of them.
updates.channelstringdefault the --env it is built with
The channel the app follows; unset, only releases of the env it was built with. A pilot target names its own.
updates.readyTimeoutnumberdefault 10000
How long, in ms, a release on trial has to mount its first page before it is rolled back.
updates.report{ url: string }
Where a phone posts what became of each release, such as a rollback, so rollback rates are visible. Unset sends nothing.
devtoolsbooleandefault false
The web inspector in a release build too: internal test targets only.
ios.teamIdstring
Apple Developer Team ID for apple-app-site-association; universal links need it.
ios.infoPlistRecord<string, AkanNativeValue>
Info.plist keys added to the iOS app.
ios.entitlementsRecord<string, AkanNativeValue>
Entitlements added to the iOS app.
ios.privacy{ tracking?, trackingDomains?, collectedDataTypes?, accessedApis? }
The app's part of the privacy manifest, PrivacyInfo.xcprivacy, which an App Store upload requires.
ios.filesRecord<string, string>
Files copied into the app bundle, keyed by their path there; the value is app-relative.
android.sha256CertFingerprintsstring[]
assetlinks.json signing fingerprints: debug for a local build, release for Play Store.
android.googleServicesstring
The google-services.json FCM push reads, relative to the app folder.
android.push{ channel?, smallIcon?, color? }
The channel pushes arrive in, the status bar icon (an app-relative PNG) and the accent color.
android.autoplaybooleandefault false
Media plays with sound without a tap first, as it does on iOS and the desktop.
android.manifeststring[]
XML added at the <manifest> level, with the applicationId placeholder filled in.
android.applicationstring[]
XML added inside <application>.
android.activitystring[]
XML added inside the app's activity.
android.filesRecord<string, string>
Files copied into the app, keyed res/<type>/<file> or assets/<path>; the value is app-relative.
desktop.serverboolean | { omit?, profile?, modules?, env?, restart?, watchdog? }default false
Carries the app's server on loopback, the only backend its pages call; switching takes a reinstall. The object form carries it with some changes, the keys below.
desktop.server.omitstring[]
Packages only the image needs, left out with what only they pull in; the server's own bundle must not import them.
desktop.server.profilestring
Bundles env/env.server.<env>.<profile>.ts in place of the environment's own file; the build refuses one holding a secret-named value.
desktop.server.modulesstring[]
What the server mounts, as AKAN_MODULES: module names, or @edge for every module whose serve() lists edge.
desktop.server.envRecord<string, string>
Variables the server starts with over the image's, readable by anyone on the computer; secret-named ones and the stage's own are refused.
desktop.server.restart"forever" | numberdefault 5
Crashes in a row before the app gives up on its server; "forever" for a screen nobody attends.
desktop.server.watchdogboolean | { intervalMs?, failures? }default false
Asks the server's health route every intervalMs (10000) and restarts it after failures (3) missed answers.
desktop.recovery"errorPage" | "reload"default "errorPage"
"reload" reloads a crashed page every time, relaunches the app and opens no dialog, share sheet, download or link for a page; "errorPage" shows an error page.
desktop.window{ fullscreen?, skipTaskbar?, frameless? }
Opens the main window from its first frame fullscreen, without a taskbar button (Windows, Linux), or without a title bar and border (frameless).
desktop.screenCapture"picker" | "auto"default "picker"
"auto" (Windows) shares the first screen without a picker; leave it off in an app that asks for a camera.
desktop.webview2"bootstrapper" | "standalone"default "bootstrapper"
Windows setup: "bootstrapper" downloads the WebView2 Runtime where none is installed; "standalone" carries the whole runtime (~200 MB) for a PC offline at install.
desktop.publisherstringdefault the app name
Windows: who publishes the app, as the installed app's Publisher and its executable's company name.
targetsRecord<string, AkanNativeSettings>default { default: {} }
Several native apps from one Akan app; each takes the fields above, without targets.

Images And Public Env

images controls the allow-list for optimized remote images. publicEnv is an allow-list for extra browser-visible environment variables beyond the built-in AKAN_PUBLIC_* pattern.
images
publicEnv

Secret Files

Some private values cannot live inside env.server.*.ts, such as service-account JSON, TLS certificates, or private key files. The secrets field lists glob patterns for these files so Akan ships them together with the env/ folder.
akan upload-env archives every matched file, and akan download-env restores them. Patterns are resolved relative to the app directory, and one declaration both deploys and git-ignores the files.
secrets

Build And Runtime

The rest of the config is for the build system and the production image. Most apps never touch it, but it is where a package stays external, a font survives pruning, a library's routes join the app, and the image gains a system dependency.
Build and runtime fields
externalLibsstring[]default []
Unbundled packages, installed in production at the workspace-pinned version.
trustedDependenciesstring[]default []
Packages allowed to run their install scripts, for an addon that builds itself at install.
binRecord<string, { [platform]: AkanBinSource }>default {}
{ url, sha256, file? } or { path, file? } per platform, carried in a desktop app and put first on its PATH.
optimizeImportsstring[]default built-in list
Extra packages the client build imports by exact file, so an icon set does not ship whole.
barrelImportsstring[]default akanjs + workspace
Extra barrels to flatten while scanning and bundling, for ones outside the workspace.
database.modes("single" | "multiple" | "cluster")[]default ["single"]
Every declared mode's drivers ship: multiple adds bullmq and ioredis, cluster also postgres.
assets.pruneFontsbooleandefault true
Drops unreferenced fonts from dist's public/ copy; an optimize-on font's source goes too.
assets.keepFontsstring[]default []
Font globs kept whatever the scan concludes, such as a URL assembled at runtime.
syncPageLibsstring[] | booleandefault false
true mounts every dependency lib with a page folder, an array only those; false unlinks all.
pluginsAkanPlugin[]default []
Read live by the CLI for runtime packages, native project setup, and public/ assets.
dockerstring | DockerImageConfigdefault oven/bun:1-slim
A whole Dockerfile, or its parts: image, preRuns and postRuns around bun install, command.
A library contributes to five of these: its own externalLibs, trustedDependencies, docker.preRuns and docker.postRuns, and assets.keepFonts carry into every app that mounts it, and its bin into the apps that depend on it. The generated image installs ca-certificates and tzdata and nothing else, which is why an app that needs ffmpeg or a headless browser declares it.
  • One image, several deployments. With ["single", "cluster"] the same image runs an edge site and a cloud cluster, and each deployment names its mode with AKAN_DATABASE_MODE.
  • A desktop app carries its own executables. It gets none of the image's docker steps, so bin puts ffmpeg, or anything else its server or a native plugin spawns, into every desktop build for the computer it is built on, first on the app's PATH and in a plugin's ctx.binDir. Carry a static LGPL build: a --enable-nonfree build may not be redistributed.

Pipeline

pipeline lists the stages Akan Cloud runs for this app, in order. Without it the app runs build, docker and deploy; once you declare it, the list is the whole pipeline, so write those actions in it too.
apps/web/akan.config.ts
stagesAppPipelineStageInput[]
This app's stages in order, the built-in actions included.
envRecord<string, string>
Environment for every step of this app.
afterstring[]
Apps whose deploy comes before this one's; apps outside the build are ignored.
  • A stage runs after the action before it. A stage written before any action runs before the apps build, and one after build runs once every app's build is done.
  • Actions keep their order. docker needs a build before it, since the image is made from that build. When an app's action is skipped or fails, its later actions do not run.
  • A function sees the build. pipeline: (ctx) => ({ … }) reads ctx.app, ctx.branch, ctx.trigger and the build's other facts.
Stage Keys
A stage is a built-in action (use) or a shell stage (name with sh or steps). The tags say which kind takes each key.
use"build" | "docker" | "deploy" | "ota"use
A built-in action. Each appears at most once per app, in this order; ota runs only when listed.
targetsstring[]use
ota only: native targets to pack. Default: every target with native.updates.
platforms("ios" | "android")[]use
ota only: phones to pack for. Default: both.
namestringshelluse
The name shown for the stage; a shell stage needs one.
shstringshell
A bash script run as one step. Give sh or steps, not both.
steps(string | PipelineStepInput)[]shell
Commands run one after another, each its own step.
envRecord<string, string>shell
Environment for this stage's steps, over the app's and the workspace's.
secretsstring[]shell
Pipeline secrets the steps receive as environment variables of the same name.
whenPipelineWhenshelluse
Run the stage only when every condition given holds.
timeoutPipelineDurationshelluse
Time limit for the whole stage: seconds, or 30s, 10m, 2h.
retrynumber | { attempts, delay? }shell
Total attempts for each step without a retry of its own, with an optional delay between them.
retrynumberuse
Total attempts of the action; a number only, with no delay.
allowFailurebooleandefault falseshelluse
A failure of this stage does not fail the build.
milestonePipelineMilestoneshelluse
Progress label: prepare, build, verify, package, deploy or finish.
idstringshelluse
A stable id; it defaults to a slug of the name, or to the action.
descriptionstringshelluse
A longer note shown with the stage.
Step Keys
An entry of steps is a command string or an object with these keys:
shstring
The bash command; a plain string in steps is this alone.
namestring
The name shown for the step.
cwdstring
Directory to run in, relative to the workspace root and never above it.
envRecord<string, string>
Environment for this step, over the stage's.
secretsstring[]
Secrets this step receives on top of the stage's.
timeoutPipelineDuration
Time limit for this step.
retrynumber | { attempts, delay? }
Total attempts, with an optional delay between them.
allowFailurebooleandefault false
A failure of this step does not fail its stage.
skipExitCodesnumber[]
Exit codes that end the step as skipped, along with the rest of its stage.
idstring
A stable id within the stage.
When A Stage Runs
branchstring | string[]
Patterns the build's branch must match.
triggerPipelineTrigger | PipelineTrigger[]
What may have started the build: push, redeploy, initial, rebuild or manual.
changedstring | string[]
Path patterns, one of which changed since the branch's last good build.
  • Patterns. * matches within one path segment, ** across segments and ? one character. A leading ! excludes, and case matters.
  • When each is decided. branch and trigger are decided when the plan is made, and changed just before the stage runs. With no earlier good build to compare against, every path counts as changed.
Environment Every Step Gets
AKAN_CI_BRANCHAKAN_CI_ENVAKAN_CI_TRIGGERAKAN_CI_BUILD_NUMAKAN_CI_COMMIT
The build's branch, env, trigger, number and commit.
AKAN_CI_APPSAKAN_CI_APP
The apps the build covers, and the app a stage runs for.
AKAN_CI_PHASEAKAN_CI_STAGEAKAN_CI_STEP
The ids of what is running.
AKAN_CI_RESULT
How the build ended, for a post step.
CIAKAN_CI
Set on every step, so a script can tell it runs in a build.
  • The narrower value wins. A step's env beats its stage's, which beats the app's, which beats the workspace's.
  • Some names are reserved. CI, every AKAN_CI_* and shell names such as PATH or HOME cannot be set in env or used for a secret.

Workspace Config

The akan.config.ts at the workspace root holds what spans every app: stages around all of them, the steps that close a build, the cloud the CLI talks to, and the repositories it mirrors apps to (subspace). It is typed WorkspaceConfig, and app or library settings stay in their own files.
akan.config.ts
beforeWorkspacePipelineStageInput[]
Stages that run before any app stage, one phase each.
afterWorkspacePipelineStageInput[]
Stages that run after every app's last stage.
post{ success, failure, canceled, always }
Steps that close the build, picked by how it ended; always runs last.
envRecord<string, string>
Environment for every step of the build, app stages included.
timeoutPipelineDuration
Time limit for the whole build.
Like an app's, the root pipeline may be a function of the build, (ctx) => ({ … }), without ctx.app.
cloud.hoststringdefault Akan Cloud
The cloud every cloud command talks to when it names no --host, such as a self-hosted one.
subspace.pushableBranchesstring[]default main, develop, debug
Branches akan subspace push mirrors from; another is refused, so a feature branch never reaches a mirrored repo.
Root Stage Kinds
ShapeRuns
{ name, sh | steps }A shell stage, run once, as in an app.
{ name, parallel: [...] }Shell stages side by side; maxParallel caps them, failFast stops the rest on a failure.
{ name, matrix: "apps", sh }One copy per app of the build with AKAN_CI_APP set; when.app narrows the apps.
Built-in actions belong to apps, so a root stage never takes use.
Order Of A Build
Phases run one after another, and each waits until every stage of the one before has ended:
phaseRuns
beforeEach root before stage, one phase apiece.
before-buildApp stages written before build.
buildEvery app's build, side by side.
after-buildApp stages written between build and docker.
dockerEvery app's image.
after-dockerApp stages written between docker and deploy.
deployEvery app's deploy, one at a time, in after order.
after-deployApp stages written after deploy.
otaOver-the-air packs of apps that list ota.
after-otaApp stages written after ota.
afterEach root after stage.
postThe steps for how the build ended, then always.

Defaults And Rules

Akan resolves the final app config by merging your file with framework defaults. For a first app, keep these rules in mind before adding advanced options.
Environment values: Put runtime values in env/ before adding config fields. Use client env for public values and server env for private server options.
Routes: Skip routes until you need custom domains or multiple clients.
Native apps: appName defaults to the app name, appId defaults to com.<repoName>.<appName>, version defaults to 0.0.1, and buildNum defaults to 1. Pin a real reverse-DNS appId before you ship: a placeholder such as com.example.app has almost always been claimed in Apple's portal already.
Images: Remote images are blocked unless remotePatterns allow them. WebP and quality 75 are used by default.
i18n: Locales default to en and ko with en first. Change it only to move the default locale or to serve a different set — defaultLocale must be one of locales.

Released under the MIT License

Connect your AI to these docs

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js All rights reserved.System managed bybassman