Overview
The Bayton Web App Generator wraps any URL in a signed Android application, ready for deployment through an EMM platform or direct sideload to devices. Each build produces a unique package name registered to your URL, along with an optional source zip so you can audit or reproduce the build independently.
Android version support
Generated apps target API 37 (Android 17) and require API 28 (Android 9 Pie) or above to install. Devices running Android 8.1 or older are not supported.
Lower API levels are achievable depending on which options you enable - the minSdk in the generator is set to a safe baseline that covers every option offered. If you only need a subset, you can drop the minSdk in app/build.gradle.kts from the downloaded source and rebuild. Android 9 (API 28) is the lowest level that supports every option this generator exposes.
Signing
- Debug
- Used for local testing purposes only, should not be used in production and cannot be deployed through Google Play.
- Release signed
- Release-signed using Bayton's certificate. No setup required - build and deploy immediately. Each build is assigned a unique package name. Use an update code to reuse the same package name for updates. AAB uploads to Managed Google Play are unaffected; Play App Signing takes over after first upload. Not intended for public Google Play Store distribution: under Google's developer verification, the publishing account must own both the signing key and the package name, so Release-signed builds are suitable for Managed Google Play, direct EMM push, and sideload via ADB - see developer verification for what's required to install on non-managed consumer devices. For public Play distribution, use Self signed with a keystore registered to your own verified developer account.
- Self signed
- Sign with your own certificate for full ownership. Upload a JKS or PKCS12 keystore with the key alias and passwords. Always use a unique keystore for builds produced by this generator - never share it with another production app, and never reuse a keystore that already signs anything you can't afford to compromise. If the signing key is ever compromised or lost, you only lose this app; a shared key would cascade the impact across every other app it signs. Credentials are never stored and are purged immediately after the build completes.
Developer verification (Google)
From September 2026, Android enforces a developer verification requirement on certified Android 7+ devices: apps must be registered against a verified Google developer account (Play Console or Android Developer Console) to install on consumer devices. Enforcement begins in Brazil, Indonesia, Singapore, and Thailand, with global rollout from 2027. Managed-device deployments are exempt.
- Release-signed builds (Bayton's key)
- Bayton owns the signing key and the generated package namespace under its own verified account. Works: Managed Google Play (private apps via the EMM iframe) and direct EMM push to managed devices - both exempt as managed-device deployments. Doesn't work: public Google Play Store (a different developer account cannot upload an APK signed under Bayton's certificate) and direct consumer sideload on non-managed certified devices in regulated regions (the end user would need to opt into Android's advanced flow - a one-time setup with a 24-hour wait and biometric confirmation - or install via ADB).
- Self-signed builds (your key)
- You own the key and the package name, so you own the verification path. For public Play Store distribution, the keystore must be registered to a verified Play Console developer account that owns the package name. For consumer sideload on certified devices in regulated regions, the package must be registered against an Android Developer Console account (full distribution, or limited distribution for fleets of up to 20 devices). For managed-device EMM deployment, no verification is required.
- ADB / development install
- ADB installs are not subject to verification and continue to work as-is for any signing mode. This is the path for local testing and engineering builds against attached hardware.
Options & permissions
- Allow external links
- Off by default. The app stays locked to the registered URL and its subdomains; any other web link is silently blocked (the user sees a small "External link blocked" toast). When on, the WebView navigates freely within itself - no URL bar in standalone or fullscreen, so the user can't see what site they're on. Use deliberately.
- Trust user-installed CAs
- On by default. Adds the device's user CA store to the WebView's trust anchors alongside the system store. Required for deployments behind a corporate SSL inspection proxy (Zscaler, Netskope, Forcepoint, etc.) and for internal sites using self-signed certificates distributed via MDM. Turn off only for high-security deployments where you want to restrict trust to the Android system CA store, accepting that the app will reject any HTTPS connection whose certificate chain ends at a user-installed root - including MDM-pushed corporate roots.
- Camera / Microphone / Location
- Off by default. When off, page calls to
getUserMedia() or the Geolocation API fail silently - the underlying Android permission isn't even declared. When on, the runtime permission is requested the first time the page asks. Users can revoke it later via Android Settings without breaking the app. EMM admins can pre-grant any of these permissions via managed-config / app-permission policy (Workspace ONE, Intune, Android Management API permissionGrants) so users never see the runtime prompt - the WebView gets the requested capability immediately.
- Pull to refresh
- Off by default. Adds the standard swipe-down-to-reload gesture, only firing when the page is scrolled to the top so it doesn't fight normal scrolling. Particularly useful in Standalone and Full screen modes, which don't expose a toolbar refresh button.
- JavaScript / Cookies
- On by default - required by virtually all modern web apps. Toggle either off only for static / content-only sites or for kiosk-style locked-down apps where session state is unwanted. Disabling JavaScript breaks anything dynamic; disabling cookies breaks login sessions and most authenticated flows.
- Keep screen on
- Prevents the device dimming or sleeping while the app is foregrounded. Pairs with Full screen display mode for kiosks and digital signage. Applies the
FLAG_KEEP_SCREEN_ON window flag.
- Block screenshots
- Applies
FLAG_SECURE so the system refuses screenshots, screen recording, and shows a blank preview in the app switcher. Use for wrappers over sensitive content - banking, medical, internal tools.
- Orientation
- Default Auto lets the system pick (typically full sensor on phones). Set Portrait or Landscape to lock the activity, useful when the wrapped site only renders correctly in one orientation or for tablet kiosk setups. On Android 16+ (API 36+) larger or windowed devices may ignore the lock: tablets / foldables / desktop mode treat fixed orientation as a hint when the screen has more than ~600dp short edge, leaving the user (or DPC) to choose. Handhelds and small tablets still respect it. For tablet kiosks where the lockdown matters, pair this with EMM lock-task / DPC orientation policy.
- User-Agent override
- Replaces the WebView's default User-Agent string with one you supply. Useful when a site sniffs for "Android" or "WebView" in the UA and serves a degraded mobile experience. Most users should leave this blank.
Capabilities and limitations
- File downloads work - tapping a download link surfaces the standard Android download notification, with the file saved to Downloads. Uses Android's DownloadManager; note its historical security trade-offs if your wrapped site handles particularly sensitive material.
- File uploads work -
<input type="file"> opens the system document picker. WebView cannot vet the selected file; the page receives whatever URI the user picks (WebView file-inclusion guide).
- Service workers and offline caching work - PWA-style sites with offline fallbacks behave as expected inside the wrapper.
- Web push notifications do not work - there's no FCM bridge.
- Cookies don't span apps. The wrapper has its own cookie jar - users logged into the site in Chrome will need to log in again in the wrapped app.
- HTTP target URLs automatically enable cleartext traffic in the network security config for that host only. Modern web is HTTPS; only use HTTP knowingly.
tel: / mailto: / sms: / geo: links open the relevant system app - but only when a real user gesture taps them in the main frame. Sub-frames, scripts, and other schemes (intent://, custom) are blocked silently.
Display mode
- Standalone
- Renders as a standard application with system status and navigation bars visible. Best suited to general enterprise web app replacement.
- Minimal UI
- Adds a minimal toolbar inside the app showing the current URL and a refresh button. Useful when users need basic navigation context.
- Full screen
- Hides all system bars for a fully immersive experience. Best suited to kiosk, digital signage, or dedicated-device deployments.
Output format
- APK
- A ready-to-install package. Suitable for direct sideloading or silent push via an EMM using managed app distribution.
- AAB
- Android App Bundle, the preferred format for Managed Google Play private app distribution. Google Play repackages and signs it through Play App Signing after upload. AAB cannot be sideloaded directly.
Theme colour
Sets the colour used for the splash-screen background (with the app icon centered), the status bar, the navigation bar, and - in Minimal UI mode - the in-app toolbar. Accepts any CSS hex colour (#rrggbb or #rgb). Defaults to white when not provided. Icon and text contrast is computed automatically using WCAG relative luminance, so dark colours receive white icons and light colours receive dark icons.
Update codes
Every new build generates a unique update code returned once in the submission response. Store it securely - it is never shown again. Only a SHA-256 hash is kept server-side, so the raw code cannot be recovered or regenerated; this is the security boundary that lets it work as a one-time recovery secret.
When you need to update an existing app and must keep the same Android package name (required for EMM silent-update flows and Managed Google Play uploads beyond the first), enter the original update code in the Update code field before submitting. The service will reuse the registered package name for the new build.
If you do not have an update code, a new unique package name is assigned. You can replace the app on managed devices by uninstalling the old version first or by pushing the new package as a separate app.
Config export / import
Every successful build offers a Download config button next to Download source - a portable JSON file (web_app_config.json) capturing every form field plus the build's update_code. Use it to skip refilling the form on later rebuilds.
To re-import, click Import config at the top of the form and pick a previously-downloaded JSON. Every field populates, including radio selections and Options toggles. Icon and keystore are not in the JSON (binary / sensitive). The icon is also not stored server-side, so it will need to be provided again on subsequent builds; submit without one and the default is used.
The exported file contains your update code in plaintext, so treat it like the code itself - it's effectively a per-app one-time recovery secret. The file expires alongside the artefact (5 min after first download) so the raw code doesn't linger server-side.
REST equivalent: POST the JSON as the config_json form field on /api/build. Other form fields submitted in the same request override the imported values, so a one-off colour change or output-format swap is one extra field, not a JSON re-edit.
App icon
Upload a 512 × 512 px PNG for best results. The generator produces all required mipmap densities automatically, including an adaptive icon for Android 8.0 and above. Optionally maskable icons with safe-zone padding produce better results in launchers that apply custom shapes. A default Bayton icon is used when no icon is provided.
Icon is not stored, and will need to be provided on subsequent builds. The config export / import flow round-trips every form field but the icon - re-attach it manually when rebuilding from a previously exported web_app_config.json or against a typed update_code. Submit without an icon and the default is used.
Downloads and expiry
Download links expire 5 minutes after first click and build files are purged at that point. All builds are purged after 24 hours regardless of download activity. The source download includes post-substitution Kotlin, XML, and Gradle files so you can audit exactly what was compiled or reproduce the build with your own tooling.
Versioning & provenance
The WAG server itself is versioned (semver). Every APK is stamped with the server version that produced it, plus a build timestamp. Five ways to read the stamp, ranked by ease:
- The download filename — the APK comes back as
<app-name>-wag-v<version>.apk (e.g. my-app-wag-v1.0.0.apk). At-a-glance lookup; lost if the file is later renamed.
- Embedded text file —
unzip -p <file.apk> assets/wag-build.txt returns three lines: builder: wag, version: ..., build_timestamp: .... Universal unzip tool; works after rename.
- Greppable —
strings <file.apk> | grep '^builder:' surfaces the same three lines without any extraction.
- On-device:
adb shell dumpsys package <pkg> | grep org.bayton.wag reads the manifest meta-data tags.
- From the build status / live server: the
api/status/<id> response includes builder_version; GET api/version returns the current WAG release.
Quote the version in any support ticket so the issue can be tied to the exact build of WAG that produced your artefact.
Best practices
- For Managed Google Play distribution, use Release signed or Self signed with AAB output.
- For direct EMM app push, use Release signed or Self signed with APK output.
- Use Full screen display mode for kiosk and digital signage; Standalone for typical app replacements.
- HTTP target URLs automatically have cleartext traffic enabled in the app's network security configuration.
- Package names are unique per build and permanently logged. Use an update code to rebuild with the same package name. Once uploaded to Google Play a package name cannot be changed.
- Download the source alongside your build to retain an auditable record of what was compiled.