Free HTML to APK
Developers

JavaScript API

Apps built with Free HTML to APK inject a native bridge as window.FreeApp. Call it from plain JavaScript, from React or Vue, or from a game engine. Your page keeps working in a normal browser if you guard the calls.

Getting started

The bridge can arrive a moment after your scripts run, so do native setup in the freeappready event, and also immediately if window.FreeApp already exists. Always provide a fallback for the browser.

function setup() {
  FreeApp.statusBar('#E2552A');
  FreeApp.toast('Hello from Android');
}
if (window.FreeApp) setup();
else window.addEventListener('freeappready', setup, { once: true });

document.querySelector('#buzz').onclick = () =>
  window.FreeApp ? FreeApp.vibrate(40) : console.log('Not in the app');

Calls that need a permission (camera, microphone, location, notifications) ask the user the first time. Turn the matching permission on in the builder's Permissions step; templates switch on what their code uses.

Feedback & UI

FreeApp.brightness(level: number): void

0.0 to 1.0 for this window, negative restores system brightness.

FreeApp.fullscreen(on: boolean): void

Hide system bars (immersive).

FreeApp.keepAwake(on: boolean): void

Keep the screen on.

FreeApp.notify(title: string, body?: string): void

Instant local notification (asks permission on Android 13+).

FreeApp.orientation(value: 'portrait' | 'landscape' | 'sensor_portrait' | 'sensor_landscape' | 'sensor' | 'unspecified'): void
FreeApp.print(): void

Print / save the page as PDF.

FreeApp.scheduleNotification(title: string, body: string, delayMs: number, opts?: {id?: number, url?: string, repeatMs?: number, exact?: boolean}): number

Returns the id.

FreeApp.speak(text: string, lang?: string): void

Native text-to-speech, lang like 'en-US'.

FreeApp.statusBar(hexColor: string): void

E.g. '#2563EB'.

FreeApp.stopSpeaking(): void
FreeApp.toast(message: string): void

Short native toast.

FreeApp.vibrate(ms?: number | number[]): void

One-shot buzz (default 50 ms) or an [off, on, off, on…] pattern.

Camera & scanning

FreeApp.camera.capture(opts?: {maxSize?: number, quality?: number, mirror?: boolean}): Promise<{dataUrl, facing}>

Still photo from the running preview.

FreeApp.camera.start(opts?: {facing?: 'back' | 'front', element?: string | Element /* place the preview over this element */, behind?: boolean /* draw under a transparent page so HTML controls float on top */}): Promise<{facing}>
FreeApp.camera.switch(): Promise<{facing}>; FreeApp.camera.torch(on: boolean): boolean; FreeApp.camera.stop(): void; FreeApp.camera.isOpen(): boolean
FreeApp.compressImage(dataUrl: string, opts?: {maxSize?: number, quality?: number}): Promise<{dataUrl, width, height, bytes}>

Native, fast on low-end phones.

FreeApp.saveToGallery(dataUrl: string, fileName?: string): Promise<{uri}>

Saves an image to Photos.

FreeApp.scanCode(opts?: {formats?: string[] /* e.g. ['QR_CODE','EAN_13','CODE_128'] */, facing?: 'back' | 'front'}): Promise<{text, format}>

Full-screen native QR / barcode scanner with torch; rejects with 'cancelled'.

FreeApp.torch(on: boolean): boolean

Flashlight, returns false if no flash.

Location & sensors

FreeApp.address(lat: number, lng: number): Promise<{address, city, country, countryCode, postalCode, street}>
FreeApp.compass(cb: (d: {heading: number /* degrees */, pitch: number, roll: number}) => void): (() => void) | null

Returns a stop function.

FreeApp.location(opts?: {maxAge?: number}): Promise<{lat, lng, accuracy, altitude, speed, provider, time, fromLastKnown}>
FreeApp.maps(query: string): void

Open a place or "lat,lng" in the maps app.

FreeApp.sensors.start(type: 'accelerometer' | 'gyroscope' | 'magnetometer' | 'light' | 'proximity' | 'pressure' | 'gravity' | 'linear_acceleration' | 'rotation' | 'humidity' | 'temperature' | 'steps' | 'step_detector', cb: (d: {type, values: number[], x, y, z, accuracy, t}) => void, intervalMs?: number): (() => void) | null

Returns a stop function, or null if unavailable.

FreeApp.sensors.stop(type: string): void
FreeApp.watchLocation(cb: (p) => void, opts?: {interval?: number, distance?: number}): () => void

Returns a stop function.

Device & app

FreeApp.app(): {packageName, versionName, versionCode, name, firstInstall}
FreeApp.battery(): {level: number /* 0-100 */, charging: boolean}
FreeApp.biometric(title: string, subtitle?: string): Promise<boolean>

Fingerprint / face unlock.

FreeApp.device(): {manufacturer, model, brand, androidVersion, sdkInt, language, screenWidth, screenHeight, density, isTablet, darkMode}
FreeApp.launchUrl(): string | null

The deep link that opened the app.

FreeApp.network(): 'wifi' | 'cellular' | 'ethernet' | 'vpn' | 'other' | 'none' | 'unknown'
FreeApp.on('deeplink', (d: {url}) => void): () => void

Links opened while the app is running.

FreeApp.online(): boolean

Files, sharing & storage

FreeApp.copy(text: string): void

Copy to clipboard.

FreeApp.open(url: string): void

Open in the external browser / app.

FreeApp.paste(): string

Read clipboard text.

FreeApp.pickFile(opts?: {accept?: string[], multiple?: boolean}): Promise<{name, mime, size, dataUrl}>
FreeApp.saveFile(name: string, mime: string, data: string /* base64 or data: URL */ | Blob): Promise<boolean>

Saves to Downloads; FreeApp.on('saved', d => …) gives {name, ok, location}.

FreeApp.share(text: string, title?: string): void

Android share sheet.

FreeApp.shareFile(fileName: string, mimeType: string, base64OrDataUrl: string): boolean

Share a real file.

FreeApp.store.get(key: string): any | null
FreeApp.store.set(key: string, value: any): void

Persistent native key/value (JSON).

NFC

FreeApp.nfc.read(): Promise<{tagId, techs: string[], writable, maxSize, records: Array<{type: "text" | "uri" | "mime" | "unknown", value, mime?, lang?}>}>

Waits for a tag; FreeApp.nfc.stop() cancels.

FreeApp.nfc.status(): 'ready' | 'disabled' | 'unsupported' | 'not_enabled_in_app'
FreeApp.nfc.write(records: string | Array<{type: 'text' | 'uri' | 'mime', value: string, mime?: string}>): Promise<{written}>

Permissions

FreeApp.hasPermission(name: string): boolean
FreeApp.openSettings(): void

Open this app's system settings page.

FreeApp.permissionStatus(name: 'camera' | 'microphone' | 'location' | 'notifications' | 'activity'): 'granted' | 'prompt' | 'blocked' | 'unavailable'
FreeApp.requestPermission(name: 'camera' | 'microphone' | 'location' | 'notifications'): Promise<boolean>
FreeApp.requestPermissions(names: string[]): Promise<{[name]: boolean}>

Ads

Available when AdMob is turned on in the builder. In test mode, Google's test ads are shown.

FreeApp.ads.loadBanner(unit?: string, position?: 'top' | 'bottom'): void

Load and show a banner. Without arguments it uses the unit and position set in the builder.

FreeApp.ads.showBanner(position?) / hideBanner() / isBannerLoaded()

Show, hide or check the banner.

FreeApp.ads.loadInterstitial(unit?) / showInterstitial() / isInterstitialLoaded()

Full-screen ads between screens. A minimum gap between full-screen ads is enforced.

FreeApp.ads.loadRewarded(unit?) / isRewardedLoaded()

Preload a rewarded ad.

FreeApp.ads.showRewarded(): Promise<{ok, amount, type}>

Shows a rewarded ad; resolves when the user earned the reward, rejects if they closed it early.

FreeApp.ads.privacyOptionsRequired(): boolean / showPrivacyOptions()

Lets users change their consent choices (required in some regions).

FreeApp.ads.adapterStatus(): {[network]: {installed, configured, ready, description, latencyMs}}

With mediation: which partner networks are inside the app and which your AdMob account has set up.

FreeApp.ads.openAdInspector(): void

Opens Google's Ad Inspector on test devices.

FreeApp.ads.on(cb: (event) => void): () => void

Ad events such as initialized, bannerLoaded, interstitialDismissed or rewardedFailed. Returns an unsubscribe function.

With a Remove ads purchase, banner and full-screen ads stop and the adFree event fires. Rewarded ads keep working, because people choose to watch them.

In-app purchases

Available when Purchases is turned on in the builder. Sell one-time products, consumables like coins, and subscriptions through your own Google Play account. Create the products in Play Console first, with the same IDs. Purchases work in a copy installed from Google Play, so test with a testing track and a licence tester.

const billing = window.FreeApp && FreeApp.billing;
if (billing && billing.isOwned('pro_unlock')) unlockPro();

// Unlock in the purchase event: it also arrives for purchases finished while the app was closed.
billing && billing.on(e => { if (e.event === 'purchase' && e.id === 'pro_unlock') unlockPro(); });

buyButton.onclick = async () => {
  try { await billing.buy('pro_unlock'); }
  catch (err) { if (err.code === 'pending') show('Payment pending'); else if (err.code !== 'cancelled') show('Purchase failed'); }
};
FreeApp.billing.products(ids: string[]): Promise<Product[]>

Google Play's live details for your products: title, local price, currency, and for subscriptions the billing period and offers. IDs that Play does not know are left out.

FreeApp.billing.buy(id: string, opts?: {offer?: string}): Promise<Purchase>

Opens Google Play's payment sheet. For a subscription, offer picks a base plan or offer ID. Rejects with an error whose code is cancelled, pending, not_found, unavailable, busy, network or developer_error.

FreeApp.billing.owned(): string[] / isOwned(id): boolean

What the user owns, saved on the phone, so it answers at once and offline. Use it at startup to unlock paid features.

FreeApp.billing.restore(): Promise<{purchases, owned}>

Asks Google Play again for everything the user owns, for a Restore purchases button.

FreeApp.billing.consume(idOrToken: string): Promise

Uses up a one-time product so it can be bought again. Products listed as consumable in the builder are used up for you.

FreeApp.billing.isAdFree(): boolean

True when the user owns one of the Remove ads products set in the builder. The app then stops banner and full-screen ads by itself.

FreeApp.billing.manageSubscriptions(id?: string): void

Opens Google Play's subscription screen, where users cancel or change their plan.

FreeApp.billing.on(cb: (event) => void): () => void

Purchase events: purchase (unlock here), pending, owned (the owned list changed: a purchase, a refund or an expired subscription), failed and unavailable. Returns an unsubscribe function.

Compatibility layer

Pages written for other HTML-to-APK tools that call window.AndroidAPI keep working: the same method names, callback IDs and event names are supported, and ad calls are safe no-ops in apps without ads.