Android
Wails v3 applications run on Android as native apps: an Android WebView
renders the frontend, assets are served in-process through a
WebViewAssetLoader backed by the Go asset server (no localhost server, no
open ports), and the standard @wailsio/runtime works unchanged — service
bindings, events, dialogs and the clipboard route through the Go message
processor.
The same main.go builds for desktop and Android. The Go code is compiled as
a C shared library (libwails.so, GOOS=android + the NDK toolchain) and
loaded by a small Java host. Android-specific behaviour lives in per-platform
Go files guarded by //go:build android.
Requirements
Section titled “Requirements”- The Android SDK with platform-tools, an SDK platform (API 35),
build-tools and the NDK (26.3.x) —
wails3 doctorshows what it finds - A JDK (e.g. OpenJDK 21) for Gradle; set
JAVA_HOMEifjavais not on yourPATH - Go 1.25+ and npm
ANDROID_HOME(orANDROID_SDK_ROOT) pointing at the SDK
Install the SDK pieces with the command-line tools:
sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0" \ "ndk;26.3.11579264" "emulator" \ "system-images;android-35;google_apis;arm64-v8a"avdmanager create avd --name wails \ --package "system-images;android-35;google_apis;arm64-v8a" \ --device pixel_7Running on the Emulator
Section titled “Running on the Emulator”From your project directory:
wails3 task android:runThis boots an emulator if none is running, generates the bindings, builds the
frontend, compiles your Go code to libwails.so for the emulator’s ABI,
assembles a debug APK with Gradle, then installs and launches it.
Useful companions:
wails3 task android:logs # stream the app's logcat outputIn debug builds the WebView is inspectable from Chrome at chrome://inspect.
Packaging
Section titled “Packaging”wails3 task android:package # production release APKwails3 task android:deploy-emulator # install + launch itwails3 task android:bundle # production release AAB (Android App Bundle)wails3 task android:bundle:fat # release AAB containing all ABIswails3 task android:run:device # debug install + launch on a physical devicewails3 task android:deploy-device # install + launch on a physical deviceDEVICE_ID=<serial> wails3 task android:run:deviceDEVICE_ID=<serial> wails3 task android:deploy-deviceProduction builds use -tags production,android, are stripped, and compile
out the framework’s internal diagnostics. wails3 task android:package:fat
builds both arm64-v8a and x86_64 into a single APK.
Google Play requires the Android App Bundle (.aab) format for new app
submissions, and new submissions must target Android 15 (API 35) or higher;
the project template sets compileSdk and targetSdk to 35 in
build/android/app/build.gradle. wails3 task android:bundle:fat produces
bin/<AppName>.aab with both ABIs included; Google Play generates
optimised per-device APKs from it, so the fat bundle is the right artifact
for store uploads. APKs remain the quickest route for local and emulator
testing, because an .aab cannot be installed directly with adb.
android:run and android:deploy-emulator are emulator-oriented tasks. For a
physical Android device, use android:run:device for a debug APK or
android:deploy-device for a release APK. Both build for arm64, select the
first connected non-emulator entry from adb devices, install it, and launch
com.wails.app.MainActivity. Pass DEVICE_ID=<serial> to target a specific
device.
Signing & release builds
Section titled “Signing & release builds”Without a keystore, release builds are signed with the Android debug keystore so they install for testing. To sign with your own keystore, set:
ANDROID_KEYSTORE_FILE=/path/to/release.jks \ANDROID_KEYSTORE_PASSWORD=... \ANDROID_KEY_ALIAS=... \ANDROID_KEY_PASSWORD=... \ wails3 task android:packageThe same variables sign App Bundles: run wails3 task android:bundle:fat
with them set to produce a Play-ready .aab. Without them the bundle is
signed with the debug keystore and Google Play will reject it, so the task
prints a warning.
Configuration
Section titled “Configuration”The frontend drives Android features at runtime through the Android runtime
object: Android.Haptics.Vibrate(durationMs), Android.Device.Info(),
Android.Toast.Show(message). The package name is controlled by APP_ID in
the build tasks.
What works, what doesn’t
Section titled “What works, what doesn’t”| Area | Status |
|---|---|
WebView + in-process assets (WebViewAssetLoader) | ✅ |
| Service bindings, events (both directions) | ✅ |
| Message dialogs | ✅ AlertDialog with button callbacks |
| Open file / files dialogs | ✅ Storage Access Framework (files imported as cache copies) |
| Open directory / save file dialogs | ❌ Return an error — write inside the app sandbox instead |
| Clipboard | ✅ ClipboardManager |
| Screens API | ✅ WindowMetrics incl. system-bar work area |
Lifecycle events (events.Android.*) | ✅ |
| Haptics, device info, toast | ✅ Android.* runtime API |
| Emulator + physical device builds | ✅ android:run, android:run:device, android:deploy-emulator, android:deploy-device |
| Window geometry, menus, system tray | Intentional no-ops |
| Multiple windows | Only the first window is displayed |
Porting notes
Section titled “Porting notes”- Desktop code compiles unchanged under
GOOS=android; geometry/menu/tray calls become no-ops because Android apps are fullscreen. androidimplies thelinuxbuild tag (Android is a Linux kernel): desktop-Linux-only files need//go:build linux && !android, and at runtimeruntime.GOOSis"android".- Replace save-file and choose-directory dialogs with writes into the app sandbox plus an intent share flow. Open-file dialogs work and import the chosen documents as cache-directory copies, so you get real filesystem paths.
- A real app is always built with
CGO_ENABLED=1and the NDK; the non-cgo path exists only so tooling such aswails3 generate bindingscan load the package. - Design the frontend responsively; the
Screenswork area excludes the status and navigation bars.