Documentation

Configuration

Everything whitehall.toml accepts. Only the top-level keys and [screen] are required; every other table has defaults.

Top level

KeyMeaning
nameProject name. Also the source of the default app icon.
versionApp version.
packageApplication ID, e.g. com.example.myapp
whitehallOptional. The oldest Whitehall this project is written for — a floor, like cargo's rust-version
name = "my-app"
version = "0.1.0"
package = "com.example.myapp"
whitehall = "0.1.3"

The check runs before anything else. A compiler older than the floor stops with the version it needs and how to install it, instead of building a tree it half understands. It is a floor, not a range: a newer compiler is always fine.

[screen]

Required — no file on disk turns Gradle on. Its presence is what tells the build there is a Compose screen at all, the way cargo's [workspace] does.

[screen]
routes = "src/routes"
tabs = ["/", "/search", "/me"]

routes is where the screens live. tabs is optional: the top-level destinations in tab-bar order. Given the order, a move to a lower index plays the back transition instead of the forward one. Routes not in the list are unaffected. tabs belongs here; under [android] it is an error.

[android]

KeyDefaultMeaning
min_sdk24Oldest Android supported
target_sdk—API level the app targets
compile_sdkderivedOverride only. Whitehall picks the floor the bundled AndroidX libraries need; setting it lower fails in Gradle's AAR metadata check
icon—Path to a PNG. Omit for the default icon
notification_icon—Status bar icon. See App Icon
predictive_backtrueAndroid's back preview gesture. See Navigation Hooks
deep_link—URL scheme (myapp:///…). See Deep Links
app_link—An https App Link (https://example.com/…). See Deep Links
permissions[]Extra manifest permissions. $permission already declares what it names
manifest_additions—Raw XML spliced into AndroidManifest.xml, for what nothing else covers
[android]
min_sdk = 24
target_sdk = 36
deep_link = "myapp"
app_link = "example.com"

[android.icons]
default = "static/icon-default.png"
dark = "static/icon-dark.png"
permissions is for what cannot be inferred — permissions that follow from runtime behavior, or that get granted out of band with pm grant. Anything $permission() names is added to the manifest for you, so there is nothing to declare twice.

[theme]

KeyMeaning
preset"shadcn", "material3", or "none". Chooses the component library and what paints it — see Theming
base_colorWhich neutral ramp the palette is built on. "shadcn" only
filePath to the theme stylesheet. Defaults to theme.css beside whitehall.toml, found without being named — a web app's own app.css works as is
mode"light", "dark", or "auto" (default). The scheme <App> uses when nothing is bound at runtime
font"geist", "system", or a path to a font file
[theme]
preset = "shadcn"
base_color = "olive"
mode = "auto"

Tokens themselves live in theme.css — CSS custom properties, shadcn's own format:

/* theme.css, beside whitehall.toml */
:root {
  --radius: 0.625rem;
  --primary: oklch(0.205 0 0);
}

.dark {
  --primary: oklch(0.922 0 0);
}

See Theming for the full token list and the radius scale.

[icons]

Icon sets, by Iconify prefix and pinned version. Only the icons a source file names are compiled in.

[icons]
lucide = "1.2.121"

See Icons.

[signing]

One keystore, committed with the project, so every machine signs the same way. Written by whitehall keystore init; the password is read from the environment variable password_env names, never from the file.

[signing]
keystore = "keys/app.keystore"
alias = "myapp"

See Signing.

[toolchain]

Pin the versions Whitehall downloads and builds with:

[toolchain]
java = "21"
gradle = "8.4"
agp = "8.2.0"
kotlin = "2.0.0"

Omit the table entirely to track Whitehall's tested set. See CLI.

[build]

KeyDefaultMeaning
output_dir".whitehall/android"Where the generated Gradle project goes
optimize_level—Release optimization level

[env]

KeyMeaning
debug / releaseEnv file for each build type
flavorsNamed env files beyond debug and release
public_prefixThe prefix that marks a variable safe to compile in. Defaults to PUBLIC_
[env]
debug = ".env.debug"
release = ".env.release"

See $env — only prefixed variables compile into the app, and reading any other from .wh is a build error.

[wear]

[wear]
min_sdk = 30
target_sdk = 36

[wear.dependencies]
"androidx.wear.compose/compose-material" = "1.3.0"

See Wear OS.

[native]

A screen's logic in Rust, C, C++ or Zig, in a sibling file (+screen.rs, an app-scoped module under src/lib/). Renamed from the old [ffi] — a project still carrying [ffi] is an error naming the table to rename.

KeyMeaning
abisABIs to build for, stated once for the whole app
[native.rust] profileCargo profile for the generated crate
[native.rust.dependencies]Crates, copied verbatim into the generated Cargo.toml
[native.module.<name>]Per-module C/C++ settings, keyed by the name $native.<name> already uses: include, link, standard, flags
[native]
abis = ["arm64-v8a"]

[native.rust]
profile = "release"

[native.rust.dependencies]
serde = "1"

[native.module.codec]
include = ["vendor/codec/include"]
link = ["z"]
standard = "c++17"

A link line belongs to the module that needs it, not to every module sharing its language — [native.module.codec] only affects $native.codec.

Dependencies

Four tables, by when the dependency is needed:

[dependencies]
"io.coil-kt/coil-compose" = "2.6.0"

[build-dependencies]
"com.google.devtools/ksp" = "2.0.0-1.0.21"

[dev-dependencies]
"io.mockk/mockk" = "1.13.9"

[android-dev-dependencies]
"androidx.test.espresso/espresso-core" = "3.5.1"
TableGoes to
dependenciesimplementation
build-dependenciesAnnotation processors and Gradle plugins
dev-dependenciestestImplementation — JVM tests
android-dev-dependenciesandroidTestImplementation — instrumented tests

Prefer whitehall deps add over editing these by hand — it resolves the version for you.

See Also