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): void0.0 to 1.0 for this window, negative restores system brightness.
FreeApp.fullscreen(on: boolean): voidHide system bars (immersive).
FreeApp.keepAwake(on: boolean): voidKeep the screen on.
FreeApp.notify(title: string, body?: string): voidInstant local notification (asks permission on Android 13+).
FreeApp.orientation(value: 'portrait' | 'landscape' | 'sensor_portrait' | 'sensor_landscape' | 'sensor' | 'unspecified'): voidFreeApp.print(): voidPrint / save the page as PDF.
FreeApp.scheduleNotification(title: string, body: string, delayMs: number, opts?: {id?: number, url?: string, repeatMs?: number, exact?: boolean}): numberReturns the id.
FreeApp.speak(text: string, lang?: string): voidNative text-to-speech, lang like 'en-US'.
FreeApp.statusBar(hexColor: string): voidE.g. '#2563EB'.
FreeApp.stopSpeaking(): voidFreeApp.toast(message: string): voidShort native toast.
FreeApp.vibrate(ms?: number | number[]): voidOne-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(): booleanFreeApp.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): booleanFlashlight, 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) | nullReturns a stop function.
FreeApp.location(opts?: {maxAge?: number}): Promise<{lat, lng, accuracy, altitude, speed, provider, time, fromLastKnown}>FreeApp.maps(query: string): voidOpen 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) | nullReturns a stop function, or null if unavailable.
FreeApp.sensors.stop(type: string): voidFreeApp.watchLocation(cb: (p) => void, opts?: {interval?: number, distance?: number}): () => voidReturns 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 | nullThe deep link that opened the app.
FreeApp.network(): 'wifi' | 'cellular' | 'ethernet' | 'vpn' | 'other' | 'none' | 'unknown'FreeApp.on('deeplink', (d: {url}) => void): () => voidLinks opened while the app is running.
FreeApp.online(): booleanFiles, sharing & storage
FreeApp.copy(text: string): voidCopy to clipboard.
FreeApp.open(url: string): voidOpen in the external browser / app.
FreeApp.paste(): stringRead 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.store.get(key: string): any | nullFreeApp.store.set(key: string, value: any): voidPersistent 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): booleanFreeApp.openSettings(): voidOpen 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'): voidLoad 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(): voidOpens Google's Ad Inspector on test devices.
FreeApp.ads.on(cb: (event) => void): () => voidAd 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): booleanWhat 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): PromiseUses 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(): booleanTrue 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): voidOpens Google Play's subscription screen, where users cancel or change their plan.
FreeApp.billing.on(cb: (event) => void): () => voidPurchase 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.