Skip to content

Configuration (pythonnative.toml)

Every PythonNative project is described by a single pythonnative.toml file at its root. It is the one source of truth for your app's identity (bundle/application id, name, version), the device permissions it requests, its icon and splash, the packages it bundles, and signing. pn init scaffolds one for you, and every other command (pn run, pn build, pn doctor, pn app-id) reads it.

[app]
id = "com.example.myapp"        # reverse-DNS id (required)
name = "myapp"                  # short project name (required)
display_name = "My App"         # home-screen label (defaults to name)
version = "1.0.0"               # marketing version
build = 1                       # integer build number
python_version = "3.13"         # embedded CPython version (3.13 or 3.14)
orientation = "portrait"        # portrait | landscape | all
entry_point = "app/main.py"     # module whose `App` is mounted
url_schemes = ["myapp"]         # deep-link schemes the app handles

[permissions]
camera = "Scan receipts with your camera."
location_when_in_use = "Show nearby stores."
notifications = true

[assets]
icon = "assets/icon.png"        # 1024x1024 source icon
splash = "assets/splash.png"    # splash / launch image

[requirements]
packages = ["humanize", "httpx", "numpy"]
# extra_index_urls = ["https://wheels.example.com/simple"]

[plugins]
# paths = ["native/my_plugin"]  # project-local Swift/Kotlin plugins

[ios]
deployment_target = "13.0"
development_team = "ABCDE12345"
privacy_manifest = "PrivacyInfo.xcprivacy"  # optional app declarations
# bundle_id = "com.example.myapp"

[ios.signing]
export_method = "development"   # development | ad-hoc | app-store | enterprise
# provisioning_profile = "My App Distribution"

[android]
min_sdk = 24
target_sdk = 36
# abi_filters = ["arm64-v8a", "x86_64"]   # the default; add 32-bit ABIs if needed

[android.signing]
# keystore = "release.keystore"
# key_alias = "myapp"

Paths (icon, splash, keystore) are resolved relative to the project root. Invalid configuration fails fast with a specific, actionable error; run pn doctor to validate at any time.


[app]

Core identity, shared by both platforms.

Key Type Default Notes
id string required Reverse-DNS id with at least two segments, e.g. com.example.myapp. Each segment must be lowercase, start with a letter, and avoid Java/Kotlin reserved words. Becomes the default Android application id and iOS bundle id.
name string required Short project name. Used for the Gradle/Xcode project name and as the default display_name.
display_name string name The label shown under the icon on the home screen.
version string "1.0.0" Marketing version (CFBundleShortVersionString / versionName). One to four dot-separated numbers.
build integer 1 Build number (CFBundleVersion / versionCode). Must be a positive integer; bump it for every store upload.
python_version string "3.13" Embedded CPython version. One of 3.13, 3.14; every listed version has a pinned, checksum-verified iOS runtime and a matching Chaquopy build. Packages are resolved for this version, not your host's (see PyPI packages).
orientation string "portrait" portrait, landscape, or all.
url_schemes list of strings [] Custom deep-link URL schemes (e.g. ["myapp"] handles myapp://…). Wired into CFBundleURLTypes on iOS and a VIEW intent filter on Android; inbound URLs reach pn.Linking.
entry_point string "app/main.py" The module whose top-level App component is mounted. app/main.py → imported as app.main.

Per-platform id overrides

By default both platforms use app.id. To diverge, set [android].application_id and/or [ios].bundle_id. The resolved value for either platform is available from pn app-id android / pn app-id ios.


[permissions]

Declare the device capabilities your app needs by name; PythonNative expands each into the right iOS Info.plist usage keys and Android <uses-permission> entries.

[permissions]
camera = "Scan receipts with your camera."   # string → iOS prompt text
notifications = true                          # true → sensible default
location_always = false                       # false → disabled

A value can be a string (used verbatim as the iOS permission-prompt text), true (use the capability's built-in default reason), or false (disable it without deleting the line). Unknown capability names are rejected at validation time.

See the dedicated Permissions guide for the full catalog and how each capability maps to native artifacts.


[assets]

Key Type Notes
icon string Path to a 1024×1024 PNG source icon. Resized into every iOS idiom and Android density at build time.
splash string Path to a splash/launch image used for the iOS launch screen and the Android 12+ splash.

Asset generation requires Pillow, the pythonnative[build] optional dependency (pip install 'pythonnative[build]'). If Pillow isn't installed, the template's default assets are kept and the build still succeeds; pn doctor reports whether Pillow is available. See Building for release.


[requirements]

Key Type Default Notes
packages list of strings [] Third-party pip requirements bundled into the app. Resolved per device target with --only-binary, so binary packages need a wheel for each target.
extra_index_urls list of strings [] Additional package indexes searched after PyPI and the platform indexes (BeeWare for iOS, Chaquopy for Android). Must be http(s) URLs.
[requirements]
packages = ["humanize", "httpx>=0.27", "numpy"]
extra_index_urls = ["https://wheels.mycompany.example/simple"]
  • iOS: the CLI resolves and installs one app_packages.<sdk> slice per SDK (device and Simulator); the Xcode run script bundles the matching one.
  • Android: written into the staged template's requirements.txt (with the index options) and installed by Chaquopy into the APK at build time, once per ABI.

Run pn deps to see how each requirement resolves for every target before building. See PyPI packages for what works and why.

Don't list pythonnative

The CLI bundles the installed pythonnative package directly, so listing it here would install a second copy and confuse imports. Validation rejects it.

C-extension packages need wheels built for the target architectures (arm64-v8a/armeabi-v7a on Android; arm64/x86_64 for the iOS Simulator). Many popular extensions have no upstream mobile wheels yet.


[plugins]

Key Type Default Notes
paths list of strings [] Project-relative directories containing a native plugin (pn_plugin.json plus ios/ and android/ sources).
[plugins]
paths = ["native/badge"]

Native plugins add Swift and Kotlin component managers and native modules to the app. Installed packages contribute theirs through the pythonnative.plugins entry point group automatically; paths is for native code that lives in the app repository itself. At build time each plugin's ios/*.swift is copied into PythonNativeKit and its android/**/*.kt into the pythonnative Gradle module, and the generated registration file calls every plugin's register. See Custom native components.


[ios]

Key Type Default Notes
deployment_target string "13.0" Minimum iOS version. Must be at least 13.0, the floor of BeeWare's CPython builds and of every iOS wheel on PyPI.
development_team string Apple Developer Team ID used for signing.
bundle_id string app.id Override the iOS bundle identifier.
extra_info_plist table {} Arbitrary extra Info.plist keys merged verbatim into the generated plist.

[ios.signing]

Key Type Default Notes
export_method string "development" One of development, ad-hoc, app-store, enterprise. Controls how the archive is exported into an .ipa.
provisioning_profile string Provisioning profile name or UUID for manual signing.
[ios]
deployment_target = "13.0"
development_team = "ABCDE12345"
privacy_manifest = "PrivacyInfo.xcprivacy"  # optional app declarations

[ios.signing]
export_method = "app-store"
provisioning_profile = "My App Distribution"

[android]

Key Type Default Notes
min_sdk integer 24 Minimum API level. Must be at least 24 (Chaquopy 17 requirement).
target_sdk integer 34 Target API level. Must be ≥ min_sdk.
compile_sdk integer 34 SDK level the project compiles against.
application_id string app.id Override the Android application id (and package).
abi_filters list of strings ["arm64-v8a", "x86_64"] Native ABIs to include: arm64-v8a (devices) and x86_64 (emulators). CPython 3.13+ on Chaquopy and PEP 738 wheels are 64-bit only, so 32-bit ABIs are rejected. Drop x86_64 for a smaller release APK.
permissions list of strings [] Extra raw Android permission strings appended to the ones derived from [permissions].

[android.signing]

Key Type Default Notes
keystore string Path to the release keystore (relative to project root).
key_alias string Key alias within the keystore.
store_password_env string PN_ANDROID_KEYSTORE_PASSWORD Env var holding the keystore password.
key_password_env string PN_ANDROID_KEY_PASSWORD Env var holding the key password.

Passwords stay out of the file

Only the env-var names live in pythonnative.toml; the passwords themselves are read from the environment at build time. See Building for release.


How the config flows into a build

flowchart LR
    T["pythonnative.toml"] --> C["AppConfig (parsed + validated)"]
    C --> A["Android configurator<br/>package · gradle · manifest"]
    C --> I["iOS configurator<br/>Info.plist · xcodebuild · export"]
    C --> AS["Icon / splash generation"]
    A --> B["pn run / pn build"]
    I --> B
    AS --> B

Because parsing and validation happen once, up front, the platform configurators and builder always work from a fully-defaulted, valid config, so pn run and pn build behave consistently. For the build mechanics, continue to Building for release.