Your First Mobile App

This guide takes a standard Wails desktop app and runs it on iOS Simulator or Android Emulator. You do not need to change your Go code. The same main.go builds for all targets.

Time to complete: 15–30 minutes (most of that is toolchain installation on first run)

Start from a desktop project

If you don’t have one yet, create a fresh project:

bash
wails3 init -n mymobileapp
cd mymobileapp

Confirm the desktop app works first:

bash
wails3 dev

Once it opens, quit and move on. Everything that runs on desktop also runs on mobile — you won’t need to touch main.go or any Go code for this guide.


Choose your platform

Requirements

  • macOS (iOS builds are macOS-only)
  • Full Xcode — not just command-line tools. Install from the App Store, then:
bash
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
sudo xcodebuild -license accept
  • Go 1.25+ and npm (already installed if you ran wails3 init)

Run wails3 doctor to verify — it lists the iOS SDKs it can find.

Run on the Simulator

1

Launch the app

bash
wails3 task ios:run

That’s it — this builds your app, boots a simulator if one isn’t already running, and launches it.

When it launches, your unmodified desktop app is running on the iOS Simulator — same main.go, same frontend:

A default Wails app running on the iOS Simulator

2

Stream logs

In a separate terminal:

bash
wails3 task ios:logs:dev

This tails the simulator log, filtered to your app. fmt.Println and log.Println output appears here.

3

Inspect the WebView

In Safari: Develop → Simulator → your app. The full Web Inspector works — console, debugger, network panel, everything.

4

Make a change

Edit any frontend file (frontend/src/main.js, index.html, etc.) and re-run wails3 task ios:run. Wails rebuilds the frontend and relaunches the app.

For Go changes: also re-run wails3 task ios:run. Go recompilation is incremental, so only changed packages rebuild.

Open in Xcode (optional)

bash
wails3 task ios:xcode

This opens build/ios/ in Xcode. You can use Xcode for device deployment, advanced profiling, or managing provisioning profiles. Wails regenerates the Xcode project on each build, so don’t modify the generated files directly.


Understanding what happened

Your main.go didn’t change at all. Wails handled everything:

  • Build systemTaskfile.yml in your project contains ios:* and android:* tasks that drive the platform-specific toolchain.
  • Go cross-compilationGOOS=ios or GOOS=android with the appropriate GOARCH and sysroot.
  • Native host — a generated Xcode project (iOS) or Gradle project (Android) that embeds your compiled Go code and hosts the WebView.
  • Asset serving — your frontend/dist/ is embedded in the Go binary and served in-process. No localhost server is needed.

Make your app mobile-aware

Your app already works, but it looks like a desktop app on a phone screen. A few small changes make a big difference.

Responsive CSS

Mobile screens are narrower and use different input patterns. In frontend/public/style.css (or equivalent):

/* Prevent horizontal scrolling */
body {
  overflow-x: hidden;
}

/* Touch-friendly tap targets */
button {
  min-height: 44px;
  min-width: 44px;
}

/* Respect the iOS safe area (notch, home indicator) */
body {
  padding-top: env(safe-area-inset-top);
  padding-bottom: env(safe-area-inset-bottom);
  padding-left: env(safe-area-inset-left);
  padding-right: env(safe-area-inset-right);
}

Detect the platform in Go

Use build tags to add platform-specific behaviour without cluttering shared code.

Create mobile_ios.go for iOS-only code:

mobile_ios.go
//go:build ios

package main

import "github.com/wailsapp/wails/v3/pkg/application"

func platformOptions() application.IOSOptions {
    return application.IOSOptions{
        DisableBounce: true,
    }
}

Create mobile_android.go for Android-only code:

mobile_android.go
//go:build android

package main

import "github.com/wailsapp/wails/v3/pkg/application"

func platformOptions() application.AndroidOptions {
    return application.AndroidOptions{}
}

Create mobile_desktop.go as a stub so the shared code compiles on desktop too:

mobile_desktop.go
//go:build !ios && !android

package main

type mobileOptions struct{}

func platformOptions() mobileOptions { return mobileOptions{} }

Detect the platform in JavaScript and gate mobile-only UI

The IOS.* and Android.* runtime objects only exist on their respective platforms. Calling them on desktop throws. The right pattern — used by the Kitchen Sink — is to detect the platform once and hide mobile-only controls entirely:

// Detect platform from the bridge the host injects into the WebView
const platform = (() => {
  if (typeof window.wails?.platform === 'function') return window.wails.platform(); // Android
  if (window.webkit?.messageHandlers?.external) return 'ios';
  return 'desktop';
})();

const isIOS     = platform === 'ios';
const isAndroid = platform === 'android';
const isMobile  = isIOS || isAndroid;

// Hide any element marked as mobile-only
document.querySelectorAll('.mobile-only').forEach(el => {
  el.style.display = isMobile ? '' : 'none';
});

Then in your HTML:

<section class="mobile-only">
  <button id="btnHaptic">Haptic feedback</button>
</section>

This way mobile-only buttons are never rendered on desktop, and you never need to guard every individual call with an if (isMobile) check.

On the Go side, pair this with a build-tag stub so the event handlers are only registered on the platforms that need them:

native_desktop.go
//go:build !ios && !android

package main

import "github.com/wailsapp/wails/v3/pkg/application"

// No-op on desktop — mobile tabs are hidden in the frontend so these
// events are never emitted.
func registerNativeFeatures(app *application.App) {}
native_ios.go
//go:build ios

package main

import "github.com/wailsapp/wails/v3/pkg/application"

func registerNativeFeatures(app *application.App) {
    app.Event.On("common:haptic", func(e *application.CustomEvent) {
        // only compiled and called on iOS
        application.IOS.Haptic("medium")
    })
    // ... other handlers
}

This is the exact pattern the Kitchen Sink uses — see native_features_stub.go, native_features_ios.go, and native_features_android.go.

Add haptic feedback (iOS)

import { IOS } from '@wailsio/runtime';

async function onButtonTap() {
  if (isIOS) {
    await IOS.Haptics.Impact({ style: 'medium' });
  }
  // ... rest of your handler
}

Add vibration (Android)

import { Android } from '@wailsio/runtime';

async function onButtonTap() {
  if (isAndroid) {
    await Android.Haptics.Vibrate(50); // 50ms
  }
}

Build for production

Simulator build (for testing on simulator, no signing needed):

bash
wails3 task ios:package
wails3 task ios:deploy-simulator

Device build (requires a signing identity and provisioning profile):

bash
wails3 task ios:package \
  IOS_PLATFORM=device \
  CODESIGN_IDENTITY="Apple Development: You (TEAMID)" \
  PROVISIONING_PROFILE=path/to/profile.mobileprovision

wails3 task ios:deploy-device   # installs via xcrun devicectl

Distribution IPA (for App Store or TestFlight):

bash
wails3 task ios:package:ipa IOS_PLATFORM=device \
  CODESIGN_IDENTITY="..." \
  PROVISIONING_PROFILE=path/to/distribution.mobileprovision

Troubleshooting

wails3 task ios:run fails with “no iOS SDKs found”

Full Xcode must be installed and selected:

bash
sudo xcode-select -s /Applications/Xcode.app/Contents/Developer
xcode-select -p  # should print the Xcode path

wails3 task android:run fails with “SDK not found”

Ensure ANDROID_HOME is set and exported. Verify with:

bash
echo $ANDROID_HOME
ls $ANDROID_HOME/platform-tools/adb

Simulator doesn’t boot

List available simulators and boot one manually:

bash
xcrun simctl list devices available
xcrun simctl boot "iPhone 16"

chrome://inspect shows no targets

The WebView must be in debug mode (the default for android:run). Make sure you’re running a debug build, not a production one. Also confirm adb devices shows the emulator as connected.

Safe area insets not applied

Make sure your HTML includes the viewport meta tag:

<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">

Explore the Kitchen Sink

Once your first app is running, the Kitchen Sink example is the fastest way to learn what else is possible. It’s a complete Wails app that runs on iOS, Android and desktop from a single codebase, covering haptics, geolocation, biometrics, local notifications, secure storage, and more:

bash
git clone https://github.com/wailsapp/wails.git
cd wails/v3/examples/mobile

wails3 task ios:run        # iOS Simulator
wails3 task android:run    # Android Emulator
wails3 task run            # Desktop

Browse the source at v3/examples/mobile — the native_features_ios.go and native_features_android.go files are particularly useful as copy-paste starting points for platform-specific features.

What’s next

iOS Guide

Full reference: configuration options, native tabs, WKWebView toggles, device builds, signing.

iOS Guide →

Android Guide

Full reference: configuration, toasts, Play Store packaging, NDK details.

Android Guide →

Kitchen Sink source

Haptics, geolocation, biometrics, notifications, secure storage — all in one runnable app.

View on GitHub →

Edit page

Last updated: