configure

App config

Everything about your app that is not code lives in [tool.pyxpo] in pyproject.toml: name, icons, splash screen, permissions and more. pyxpo writes the native files for you.

A full example

pyproject.toml
[tool.pyxpo]
name = "Notes"
slug = "notes"
version = "1.0.0"
bundle_id = "com.example.notes"
scheme = "notes"
orientation = "portrait"
user_interface_style = "automatic"
icon = "assets/icon.png"
modules = ["file-system", "sharing"]

[tool.pyxpo.splash]
image = "assets/splash.png"
image_width = 200
background_color = "#ffffff"
dark = { background_color = "#0c0e12" }

[tool.pyxpo.ios]
supports_tablet = true
info_plist = { NSCameraUsageDescription = "Take photos for your notes" }

[tool.pyxpo.android]
permissions = ["CAMERA"]
adaptive_icon = { foreground_image = "assets/adaptive.png", background_color = "#3776ab" }

[tool.pyxpo.extra]
api_url = "https://api.example.com"

Run pyxpo config to validate the file and print the final config for each platform. pyxpo config --schema prints a JSON Schema you can give to your editor.

Top-level keys

KeyMeaning
nameThe name on the home screen.
slugA short id used for folders and defaults.
versionThe user-facing version, for example 1.2.0.
build_numberThe build number for the stores.
bundle_idDefault iOS bundle id and Android package.
schemeURL scheme for deep links, for example demo://.
orientationportrait, landscape or default.
user_interface_stylelight, dark or automatic.
background_colorRoot background color.
iconPath to a 1024×1024 PNG app icon.
modulesNative modules to compile in.
pluginsConfig plugins to run during the native build.
abisAndroid CPU types to ship. Default: every one with a runtime, arm64-v8a, armeabi-v7a (phones with a 32-bit Android) and x86_64. A requirement with no armeabi-v7a wheel leaves that one out with a warning; list it here to make that an error.

Sections

TableKeys
[tool.pyxpo.splash]image, image_width, resize_mode, background_color, and a dark table for dark mode.
[tool.pyxpo.status_bar]Style and visibility of the status bar.
[tool.pyxpo.navigation_bar]Style and visibility of the Android navigation bar.
[tool.pyxpo.ios]bundle_identifier, supports_tablet, deployment_target, icon (light, dark, tinted), associated_domains, info_plist, entitlements.
[tool.pyxpo.android]package, min_sdk, target_sdk, version_code, permissions, blocked_permissions, adaptive_icon, intent_filters, software_keyboard_layout_mode, allow_backup.
[tool.pyxpo.build_properties.android]enable_minify_in_release_builds, enable_shrink_resources_in_release_builds, extra_proguard_rules, packaging_options.
[tool.pyxpo.extra]Any values you want to read at runtime through Constants.

Reading config at runtime

python
from pyxpo import Constants

api_url = Constants.extra["api_url"]   # extra ships in plain text: no secrets

Config plugins

When a setting has no key, write a config plugin. A plugin is a Python function plugin(config, props) that changes the native project during the build: Info.plist, entitlements, Xcode build settings, AndroidManifest.xml, Gradle files and Android resources.

plugins/camera.py
import xml.etree.ElementTree as ET
from pyxpo.config_plugins import android_name, with_android_manifest, with_info_plist

def plugin(config, props):
    reason = props.get("reason", "Take photos")
    with_info_plist(config, lambda plist: plist.update({"NSCameraUsageDescription": reason}))

    def manifest(root):  # root is the <manifest> element
        ET.SubElement(root, "uses-feature", {
            android_name("name"): "android.hardware.camera",
            android_name("required"): "false",
        })

    with_android_manifest(config, manifest)
    return config
pyproject.toml
[tool.pyxpo]
plugins = [["./plugins/camera.py", { reason = "Scan receipts" }]]
HelperChanges
with_info_plistiOS Info.plist
with_entitlementsiOS entitlements
with_xcconfigXcode build settings
with_android_manifestAndroidManifest.xml
with_gradle_propertiesgradle.properties
with_app_gradleapp/build.gradle.kts
with_android_res_valuesAndroid string, color and other value resources
with_dangerous_modAny file in the native project, as a last resort