Skip to content

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.

  • The Android SDK with platform-tools, an SDK platform (API 35), build-tools and the NDK (26.3.x) — wails3 doctor shows what it finds
  • A JDK (e.g. OpenJDK 21) for Gradle; set JAVA_HOME if java is not on your PATH
  • Go 1.25+ and npm
  • ANDROID_HOME (or ANDROID_SDK_ROOT) pointing at the SDK

Install the SDK pieces with the command-line tools:

Terminal window
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_7

From your project directory:

Terminal window
wails3 task android:run

This 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:

Terminal window
wails3 task android:logs # stream the app's logcat output

In debug builds the WebView is inspectable from Chrome at chrome://inspect.

Terminal window
wails3 task android:package # production release APK
wails3 task android:deploy-emulator # install + launch it
wails3 task android:bundle # production release AAB (Android App Bundle)
wails3 task android:bundle:fat # release AAB containing all ABIs
wails3 task android:run:device # debug install + launch on a physical device
wails3 task android:deploy-device # install + launch on a physical device
DEVICE_ID=<serial> wails3 task android:run:device
DEVICE_ID=<serial> wails3 task android:deploy-device

Production 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.

Without a keystore, release builds are signed with the Android debug keystore so they install for testing. To sign with your own keystore, set:

Terminal window
ANDROID_KEYSTORE_FILE=/path/to/release.jks \
ANDROID_KEYSTORE_PASSWORD=... \
ANDROID_KEY_ALIAS=... \
ANDROID_KEY_PASSWORD=... \
wails3 task android:package

The 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.

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.

AreaStatus
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, toastAndroid.* runtime API
Emulator + physical device buildsandroid:run, android:run:device, android:deploy-emulator, android:deploy-device
Window geometry, menus, system trayIntentional no-ops
Multiple windowsOnly the first window is displayed
  • Desktop code compiles unchanged under GOOS=android; geometry/menu/tray calls become no-ops because Android apps are fullscreen.
  • android implies the linux build tag (Android is a Linux kernel): desktop-Linux-only files need //go:build linux && !android, and at runtime runtime.GOOS is "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=1 and the NDK; the non-cgo path exists only so tooling such as wails3 generate bindings can load the package.
  • Design the frontend responsively; the Screens work area excludes the status and navigation bars.