The dukkan.app.toml file
Every key of the app configuration file, how the CLI syncs it with the portal, and what happens when the two disagree.
On this page
This page is for anyone whose app carries a dukkan.app.toml. By the end you know what every key means, which values the portal validates, and how dukkan app config push and pull keep the file and your draft version in step.
What the file is#
dukkan.app.toml is the declarative description of your app: its name, the scopes it asks for, the exact redirect URIs, the webhook topics, and the paths the SDK mounts. Three things read it:
@dukkan.one/nuxtat build time, to mount the install, callback and webhook routes.dukkan app config push, to turn it into the draft version of your app in the portal.dukkan app dev, for the port and the tunnel provider.
Secrets never live in it. The client secret, the session secret and your database URL come from the environment; see The Nuxt module.
A complete file#
config_version = 1
[app]
name = "Order notifier"
client_id = "dukkan_app_1f3c9e2a"
api_version = "v1"
[auth]
scopes = ["orders:read", "orders:write"]
redirect_uris = [
"https://app.example.com/dukkan/callback",
"http://localhost:3000/dukkan/callback",
]
[webhooks]
topics = ["order.created", "order.paid", "app.uninstalled"]
[urls]
app_url = "https://app.example.com"
install_path = "/dukkan/install"
callback_path = "/dukkan/callback"
webhook_path = "/dukkan/webhooks"
[dev]
port = 3000
tunnel = "cloudflared"Keys#
| Key | Required | Meaning |
|---|---|---|
config_version |
yes | Always 1 today. A later revision of the file format bumps it. |
app.name |
yes | Display name in the portal, 1 to 120 characters. |
app.client_id |
no | The OAuth client id. dukkan app config pull fills it in; @dukkan.one/nuxt uses it as the default for NUXT_DUKKAN_CLIENT_ID. |
app.handle |
no | The marketplace handle once the app is listed: lowercase letters, digits and hyphens. |
app.api_version |
no | v1. The dated revision the SDK speaks is pinned by the package, not by this file. |
auth.scopes |
yes | At least one scope from the scope reference. The consent screen shows exactly these. |
auth.redirect_uris |
yes | 1 to 10 exact URLs. https anywhere, http only on localhost, 127.0.0.1 or [::1]; no credentials, no fragment. |
webhooks.topics |
no | Topics from the topic table. Every topic's required scope must be in auth.scopes; the file is rejected otherwise. |
urls.app_url |
no | The public origin of the deployed app; the default for NUXT_DUKKAN_APP_BASE_URL. |
urls.install_path |
no | Where the marketplace sends merchants. Default /dukkan/install. |
urls.callback_path |
no | The OAuth redirect path. Default /dukkan/callback. Its full URL must appear in auth.redirect_uris. |
urls.webhook_path |
no | Where the platform posts deliveries. Default /dukkan/webhooks. |
dev.port |
no | The port dukkan app dev starts the app on. Default 3000. |
dev.tunnel |
no | cloudflared (default) or none. With none, pass --tunnel-url or run with --no-tunnel. |
Paths start with / and contain only letters, digits, /, _ and -.
Validation#
The same schema runs in three places: the CLI before a push, the portal when it receives one, and the Nuxt module at build time. A JSON Schema is published at /contracts/v1/dukkan.app.schema.json for editor completion and for other languages; see The contract for other languages.
A failing file prints every problem with its path:
✗ dukkan.app.toml is invalid:
auth.redirect_uris.0: Redirect URIs must be https, or http on localhost
webhooks.topics: These topics need scopes the app does not request: order.paidPush and pull#
dukkan app config push sends the file's surface (scopes, redirect URIs, topics) to your app's draft version and records the file as the source. The portal then shows the version as Managed by dukkan.app.toml with the time of the push. A push never activates a version; releasing stays in the portal.
Updating draft version 4:
+ scope: orders:write
+ redirect: https://app.example.com/dukkan/callback
- topic: product.updated
✓ Updated draft version 4; the portal now shows it as managed by dukkan.app.toml.dukkan app config pull writes the portal's draft (or the active version when there is no draft) back into the file, including app.client_id.
Warning
Removing a scope narrows what future installs consent to; existing installs keep what they granted. The CLI asks before pushing a removal unless you pass --yes.
When the portal and the file disagree#
Editing the draft in the portal after a push marks the version as edited by hand. The portal shows a Portal and dukkan.app.toml disagree notice with the pull command, and the next push refuses:
✗ The portal draft changed since your last pull (last push 2026-09-08T14:02:11Z, then edited in the portal). Run: dukkan app config pull, or push again with --force to overwrite it.Pull to adopt the portal's edits into the file, or push with --force to overwrite them. A draft under marketplace review is frozen either way; withdraw the submission first.