# AppMint & Appwright integration guide - complete Source: https://freewebtoapk.com/docs/ ยท generated from the guide shipped inside the apps ยท 2026-09-09 47 chapters, 357 steps, plus the 138-method bridge API reference. AppMint and Appwright are sibling Android app builders that share one engine; a step that differs between them is given twice, labelled. --- # Free AI App Builder > Describe an app in plain words and get a real APK, with no key and no account. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/free-ai-app-builder - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Build Android apps and games for free - get a key from OpenRouter or Arena AI and pick a free model ### 1. Build your app for free You can create a full Android app or game here without paying anyone - including us. The AI that writes your app runs on your own free account with one of two gateways: OpenRouter or Arena AI. Both hand out an API key for free, and both carry models that cost nothing to use. You bring the key, we do the rest. It takes about two minutes to set up, and you only ever do it once. ### 2. Step 1 - Create your free key Pick either gateway. You only need ONE of them: - OpenRouter - one key unlocks Claude, GPT, Gemini, Llama, DeepSeek and hundreds more. Its free models have ids ending in ":free", and Appwright lists those first so they are easy to spot. - Arena AI - a newer gateway with the same one-key idea and a clean model list. Sign up with an email or a Google account, then open the keys page and create a key. Copy it straight away - most sites show a key only once. [Get a free OpenRouter key](https://openrouter.ai/keys) ### 3. Step 1b - Or use Arena AI instead Prefer Arena AI? Sign up at arena.ai, open the dashboard and create a virtual API key. Paste that key into Appwright exactly the same way as an OpenRouter key - everything after this step is identical. [Get an Arena AI key](https://portal.api.preview.arena.ai/dashboard/keys) ### 4. Step 2 - Tap 'Create with AI' Back on the Appwright home screen, tap 'Create with AI' to open the AI App Studio. This is where you describe your app and watch it get built. ### 5. Step 3 - Add your key In the AI App Studio: 1. Tap the provider name at the bottom of the screen (it shows the current provider). 2. Choose OpenRouter or Arena AI from the list. 3. Paste your key and tap 'Save & Test'. A green tick means the key works. Your key is stored encrypted on your device and is sent only to the gateway you chose - never to us. ### 6. Step 4 - Choose a free model Once the key is accepted, Appwright loads the full model list from your account. On OpenRouter, look for models ending in ":free" - they are sorted to the top and cost nothing. On Arena AI, pick any model from the list. A ๐Ÿ“ท marker means the model can also read images, which is what powers Screenshot โ†’ App. ### 7. Step 5 - Describe it, and build Type what you want in plain words - "a habit tracker with streaks and a dark theme", "a 2D endless runner game" - and tap Generate. Preview it live, ask for changes in the same box, then tap 'Build App' to turn it into a real APK you can install or publish to Google Play. No credits are used and nothing is charged: the whole run happens on your own free key. ### 8. Good to know - Free models are shared by a lot of people, so they can be slower or briefly busy at peak times. Try again in a moment, or switch to another free model. - Bigger apps ask more of the model - if a large app struggles on a free model, a paid model on the same key handles it comfortably. - You can swap providers or keys whenever you like; your projects stay put. - Prefer to skip setup entirely? Appwright needs no key or account at all - see the Appwright section below. --- # Appwright (How Credits Work) > What one Mint buys, what a typical app costs to generate, and why editing is cheaper than creating. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/how-credits-work - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. No setup, no API keys - buy credits and build apps instantly ### 1. What is Appwright? Appwright is the easiest way to build an app - no accounts, no API keys, no setup. You just buy credits and start building. We run the AI for you in the background and you pay only for what you use. ### 2. How credits work Credits are like prepaid balance. Every time the AI builds or edits your app, it uses some credits - and we show you exactly how many ('Used 45 credits ยท 7,910 left'). There is no fixed 'number of apps' because usage depends on how big your app is and how many changes you make. A small app costs little; a large app with lots of edits costs more. You can spend your whole balance on one big app if you want. ### 3. Buying credits _(AppMint only)_ Tap 'Buy Credits' and pick a pack: - Starter - $2.30 (100 credits - approximately 2-3 full apps) - Maker - $5.74 (450 credits - approximately 8 full apps) The 'approximately N apps' figures are estimates only - your real usage is metered. Payment is handled securely by Google Play, or via web checkout (Dodo) which is cheaper than the in-app price; there is no subscription. ### 3. Buying credits _(Appwright only)_ Tap 'Buy Credits' to get the one-time Top-up: - Top-up - $20.00 (1600 Mints) Mints never expire - they're yours until used, and your real usage is metered transparently. Payment is handled securely by Google Play; there is no subscription. ### 4. Standard vs Best Quality Pick a quality level before you build: - Standard - great for most apps and the cheapest. Recommended. - Best Quality - uses a smarter model for tricky apps, but spends credits faster. You can switch any time. Image โ†’ App (turning a screenshot or drawing into an app) works on both. ### 5. Appwright vs Free vs Your Own Key You have three ways to build: - Appwright - best quality, no setup, pay-as-you-go credits, always badge-free. (Recommended.) - Free providers - Groq (free key from console.groq.com) or Cloudflare Workers AI (free tier from dash.cloudflare.com). Great for testing ideas. - Your Own AI Key - bring a Claude, OpenAI, Grok, or DeepSeek key. All providers are available to everyone. Apps built with your own key include a small Appwright badge unless you have a Pro plan. Appwright apps are always badge-free. ### 6. Your balance is always safe Your credit balance is stored on our secure server, not just on your phone - so it follows your purchase. If you reinstall the app, your remaining credits are restored. We never see or store any AI provider keys on your device for Appwright; everything runs server-side. --- # AI App Studio (Bring Your Own Key) > Run the AI pipeline on your own OpenAI, Anthropic or OpenRouter key instead of Mints. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/ai-app-studio-own-key Generate apps with Groq (Free), OpenRouter, Claude, OpenAI, Grok or DeepSeek using your own API key ### 1. What is the AI App Studio? The AI App Studio lets you describe an app in plain words and have an AI build it for you - then preview it live and turn it into an APK, all inside this app. It uses a 'bring your own key' model: you connect your own AI account, so generation costs are billed directly by the AI provider, not by us. ### 2. Choose an AI provider Pick one of the supported providers: - Groq - FREE tier available, very fast (recommended to start) - Cloudflare Workers AI - FREE tier available, open models (Llama, etc.) - Claude (Anthropic) - OpenAI (ChatGPT) - Grok (xAI) - DeepSeek You need an account with that provider. Groq and Cloudflare both offer generous free tiers; the others have pay-as-you-go pricing. ### 3. Get your API key Create an API key in your provider's console, then copy it: - Groq (FREE): console.groq.com/keys - Cloudflare (FREE): dash.cloudflare.com โ†’ AI / Workers AI. Copy your Account ID and create an API token with the Workers AI permission, then enter them in the app as accountId:token. - Claude: console.anthropic.com/settings/keys - OpenAI: platform.openai.com/api-keys - Grok: console.x.ai - DeepSeek: platform.deepseek.com/api_keys โš ๏ธ Keep your key private - treat it like a password. [Open Anthropic Console](https://console.anthropic.com/settings/keys) ### 4. Connect your key in the app In the AI App Studio: 1. Select your provider from the dropdown 2. Paste your API key 3. Tap 'Save & Test' The key is stored encrypted on your device and is sent only to your chosen provider - never to us. You can change or remove it any time. ### 5. Pick a model After the key is validated, the studio fetches the list of available models from your provider. Choose one and it stays fixed for the whole session for consistent results. Models marked ๐Ÿ“ท also support image input (used by Screenshot โ†’ App and Inspire-from-Website). ### 6. Generate your app Choose how to start: - Create from Prompt - describe the app - Improve My App - upload existing HTML/ZIP and refine - Screenshot โ†’ App - turn an image into an app - Inspire from Website - browse a site, capture screens - Remix a Template - start from a ready-made app Tap Generate, then refine with follow-up instructions. Use 'View Live' to preview the running app at any time. ### 7. Build the APK When you're happy with the preview, tap 'Build App'. The generated project is handed to the normal APK wizard - set your icon, name, package and options as usual, then generate. Your APK and AAB appear in the Downloads section like any other build. ### 8. Costs & privacy โœ… You pay your AI provider directly for usage - there is no extra charge from us. โœ… Your API key never leaves your device except to call your provider. โœ… A Mints counter shows usage per session so you can keep costs in check. โœ… Simple-HTML apps are cheapest; React/framework apps use more Mints. ### 9. Free tiers & automatic fallback The free providers have different limits: - Groq - about 8,000 tokens per MINUTE. A large app can hit this mid-build. - Cloudflare - 10,000 Neurons per DAY (resets at 00:00 UTC), with no per-minute cap. If Groq hits its per-minute limit and you have a Cloudflare key saved, the studio automatically retries your request on Cloudflare - so a busy moment on one free provider doesn't block you. For the most consistent results on big apps, add your own paid key (Claude or OpenAI). --- # Import a ZIP > Upload a React, Vue or plain HTML project and have it bundled into an APK unchanged. - **Applies to:** Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/import-a-zip-project Turn an exported web project into a real APK ### 1. When to use it You already have a web project - React, Vite, Next.js (static export), Astro, or plain HTML/CSS/JS - and want it as a real Android app. Export/build it to static files, zip the output folder, and import. ### 2. How Home โ†’ Import a ZIP โ†’ pick your file. Set the app name, icon and package, then build - you get a real signed APK, ready for the Play Store. ### 3. What your ZIP needs An index.html at the top level (or a standard build-output folder like dist/ zipped from inside), with assets referenced by relative paths. If it opens in a browser from the folder, it will work as an app. ### 4. Everything else still applies ZIP-imported apps get the same powers as AI-built ones - notifications, AdMob, In-App Selling, deep links, Remote Update. The guides in this list work for both. --- # Writing HTML for your app > How a page inside the app differs from a page in a browser tab, and the rules that keep it working. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/writing-html-for-your-app - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. What the app runtime exposes to your page - contacts, SMS, notifications, device info, biometrics - and the rules for calling it correctly ### 1. Rule 1 - always check first window.WebToApk only exists inside your built app. In a browser it is missing. So always check before you call. If you do not, your page breaks everywhere except inside the app. Some things need no check at all: navigator.vibrate(), speechSynthesis and Notification already work - the app connects them for you. Just use the normal web code. ``` // GOOD โ€” safe everywhere if (window.WebToApk) { WebToApk.playClick(); } // BAD โ€” this breaks your page in a browser WebToApk.playClick(); ``` ### 2. Rule 2 - some answers come back later Reading contacts, SMS or the call log takes time. So these do not answer straight away. If they did, your app would freeze. You give the call a name (an id). The answer arrives later as an event. You listen for that event. Event names: appmint:contacts, appmint:calllog, appmint:sms, appmint:sms-received, appmint:notification-action. ``` // 1. Listen for the answer window.addEventListener('appmint:contacts', function (e) { if (e.detail.error) { alert('Problem: ' + e.detail.error); return; } console.log(e.detail.contacts); }); // 2. Ask the question ('my1' is any name you choose) WebToApk.listContacts('my1', 50, 0); ``` ### 3. Rule 3 - two switches must be ON A phone feature needs TWO things: 1. You turned it on in Step 3 (Permissions) when you built the app. If not, you get error 'not_enabled' and the user sees nothing. 1. The user said Yes on the phone. If they said No, you get error 'permission_denied'. Always handle both errors so the user knows what happened. Easier way: pickContact() and composeSms() need NO permission at all. Use them when they fit - they do the same job with less trouble. ``` window.addEventListener('appmint:contacts', function (e) { if (e.detail.error === 'not_enabled') { alert('Turn on Contacts when you build the app'); } else if (e.detail.error === 'permission_denied') { alert('Please allow contacts access'); } else { showContacts(e.detail.contacts); } }); ``` ### 4. Notifications with pictures and buttons notify() takes a list of options. You can add a big picture, an icon, buttons, and a progress bar. channel can be: urgent, default, quiet, or ongoing. Use ongoing:true for a notification the user cannot swipe away. Note: they can still remove it from Android settings. No app can make one that is impossible to remove. Use tag to give it a name. Sending again with the same tag replaces the old one instead of adding a new one. ``` WebToApk.notify(JSON.stringify({ title: 'Order shipped', body: 'Arriving Tuesday', channel: 'urgent', image: 'https://mysite.com/box.jpg', actions: [{ id: 'track', label: 'Track' }], tag: 'order-482' })); // Know which button the user pressed window.addEventListener('appmint:notification-action', function (e) { if (e.detail.actionId === 'track') showTracking(); }); ``` ### 5. Phone information and fingerprint getDeviceInfo() needs no permission. It gives you the Android version, the version ID (buildId), the phone model, screen size, battery level, and more. About device ID: there is NO IMEI or serial number. Android blocked this for every app from Android 10. Use getInstallId() instead - a fixed ID for this install that stays the same after updates. ``` var info = JSON.parse(WebToApk.getDeviceInfo()); info.android.release; // "14" info.android.buildId; // "TQ3A.230805.001" <- version ID info.hardware.model; // "Pixel 7" info.runtime.batteryLevel; // 82 // Fingerprint window.__webToApkAuth = window.__webToApkAuth || {}; window.__webToApkAuth['unlock'] = function (json) { var r = JSON.parse(json); if (r.ok) showMyApp(); }; WebToApk.authenticateBiometricEx('unlock', JSON.stringify({ title: 'Unlock', allowDeviceCredential: true })); ``` ### 6. Make your page fill the screen When you turn on Fullscreen, the app hides the Android bars for you. But add this CSS so your content is not hidden under the phone's notch. Important: Fullscreen hides the ANDROID bars (clock, battery, back buttons). Your app's own coloured bar at the top is a different switch called 'Show top bar'. If you still see a bar after turning on Fullscreen, that is the one to turn off. Also: test your app rotated. The notch moves to the side in landscape. ``` body { padding-top: env(safe-area-inset-top); padding-bottom: env(safe-area-inset-bottom); min-height: 100vh; /* old phones */ min-height: 100dvh; /* correct when bars are hidden */ } ``` ### 7. Downloads keep the filename you chose Your normal download code already works. When your page saves a file - a PDF report, a CSV export, a backup - the app catches it and opens the Android 'Save asโ€ฆ' sheet with YOUR filename already filled in. This covers every usual way of doing it: , a.click() from code, html2pdf / jsPDF, FileSaver.js saveAs(), and window.open() on a blob URL. A File object brings its own name with it. If you would rather ask directly instead of building an tag, use AppMint.downloadFile(). One thing to know: the name lives only in your page's JavaScript - Android never sees it on its own. So set download= (or pass a name) every time, or the file is saved as 'download'. ``` // The usual way โ€” saves as Site_Report_2026-08.pdf const blob = await html2pdf().from(el).outputPdf('blob'); const a = document.createElement('a'); a.download = 'Site_Report_2026-08.pdf'; a.href = URL.createObjectURL(blob); a.click(); // Or ask directly AppMint.downloadFile(base64String, 'Ledger_Q3.csv', 'text/csv'); AppMint.saveBlob(myBlob, 'Backup.json'); ``` ### 8. Make the Back button do what YOUR app expects If your app changes screens by showing and hiding elements (no URL changes, no history.pushState), Android's Back button cannot see those screens - its history is empty, so Back exits the app from anywhere. Register a back handler and decide yourself: return true when you handled the press (closed a menu, went back a screen), return false to let the normal behaviour run - page history back, then the exit confirmation, then exit. Apps that use history.pushState for every screen do not need this: Back already walks their history. And don't worry about freezing the app - if your handler ever hangs, the phone's Back keeps working natively after a short moment. ``` AppMint.setBackHandler(() => { if (isMenuOpen) { closeMenu(); return true; } // consumed if (screen !== 'home') { goTo('home'); return true; } return false; // nothing open โ€” normal exit behaviour }); // Or listen instead: e.preventDefault() consumes the press window.addEventListener('appmint:back', e => { if (closeTopmost()) e.preventDefault(); }); ``` ### 9. Receive a file the user opened with your app Turn on 'Open with this app' in the build wizard. Your app then appears in Android's Open with / Share sheet for the file types it registers (JSON, CSV, TXT, XML, Markdown, GPX, TCX, FIT). When someone taps a file, ask for it with AppMint.getOpenedFile(). Ask whenever you are ready - the file waits for you. This matters for React, Vue and Angular apps: they finish starting AFTER the page loads, so an app that only listened for the event used to miss the file completely and just show its home screen. There is no size limit. f.file is always the whole file. f.text is filled in for text formats and f.base64 for small binaries, as a shortcut. ``` // Ask on startup โ€” works however late your app starts const f = await AppMint.getOpenedFile(); // null if opened normally if (f) { console.log(f.name, f.mimeType, f.size); const text = f.text ?? await f.file.text(); importActivity(text); } // Or listen, if you prefer window.addEventListener('appmint:fileopen', e => handle(e.detail)); ``` ### 10. Bluetooth sensors - standard Web Bluetooth Heart-rate straps, cadence and speed pods, power meters, smart trainers, scales - anything that speaks Bluetooth LE. Use navigator.bluetooth, exactly the same code you would write for Chrome on a desktop. Appwright provides it inside the app; a plain Android WebView has none, which is why this code does nothing in other app builders. The device chooser is drawn by the app, like the browser's - your page can only reach the device the user picked. Turn on the Bluetooth permission in Step 3. If your code mentions navigator.bluetooth, Appwright turns it on for you when you pick your ZIP. Not available: getDevices(), watchAdvertisements() and requestLEScan() - they reject with NotSupportedError, so check before using them. ``` const device = await navigator.bluetooth.requestDevice({ filters: [{ services: ['heart_rate'] }] }); const server = await device.gatt.connect(); const service = await server.getPrimaryService('heart_rate'); const chr = await service.getCharacteristic('heart_rate_measurement'); chr.addEventListener('characteristicvaluechanged', e => { const v = e.target.value; // a DataView const bpm = (v.getUint8(0) & 1) ? v.getUint16(1, true) : v.getUint8(1); document.getElementById('bpm').textContent = bpm; }); await chr.startNotifications(); device.addEventListener('gattserverdisconnected', () => showReconnect()); ``` ### 11. {exampleCount} ready-made examples - open them here Working code you can copy: send an SMS, read contacts, show a notification with a picture, fingerprint lock, phone information, saving files, Bluetooth sensors, and more. Each one is short and explained in simple words. Opens inside Appwright. You can search it, and you can select the text to copy it into your page. The same screen also has 'All methods' - the full list of all {methodCount} things your app can call. That list is built from the app runtime itself, so it always matches exactly what your app can do. [Open examples & method list](appmint:api-reference) ### 12. Save the examples to your phone or computer Saves three files to Downloads/Appwright/Guides: - Examples - the {exampleCount} examples above - Method list - all {methodCount} methods - index.html - a ready test page The test page has buttons for device info, vibration, notifications and the contact picker. Build it as an app to see everything working, then change it into your own app. It also opens fine in a normal browser - nothing breaks, it just says it is not inside the app. **Download** (in the app these are saved to your phone by the button on this step): - [Appwright-examples.md](https://freewebtoapk.com/docs/files/hybrid_examples.md) - 22 KB - [Appwright-all-methods.md](https://freewebtoapk.com/docs/files/hybrid_api_reference.md) - 55 KB - [index.html](https://freewebtoapk.com/docs/files/hybrid_starter.html) - 6 KB --- # React & Framework Apps > Build settings, routing and asset paths for framework projects that have to run from a file:// origin. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/react-and-framework-apps Convert React, Next.js, Astro & Vue Projects ### 1. โœ… Supported Frameworks You can convert apps built with these popular frameworks: - React (Vite) - Most popular, recommended - Next.js - React-based, pages & app router - Astro - Fast static sites with React components - Vue.js (Vite) - Vue single-page apps All of these work automatically - just upload your project ZIP! ### 2. ๐Ÿ“ฆ Two Ways to Upload Option A - Upload Source Code (Easiest): Just zip your project folder as-is and upload it. Our cloud will build it for you automatically. Option B - Upload Pre-Built Files: Run 'npm run build' on your computer first, then zip the output folder (dist/, build/, or out/) and upload that. ๐Ÿ’ก Option A is recommended for most users - no terminal or coding needed! ### 3. ๐Ÿ“ How to Prepare Your ZIP 1. Open your project folder on your computer 2. Select ALL files inside (including package.json, src/, public/, etc.) 3. Right-click โ†’ 'Compress' or 'Send to ZIP' โš ๏ธ Make sure package.json is at the root of the ZIP, not inside a subfolder. You can also download your project from GitHub as a ZIP directly. ### 4. ๐Ÿš€ Generate Your APK 1. Select 'Offline ZIP' mode in Step 1 2. Choose your project ZIP file 3. The app will detect your framework automatically 4. Fill in App Name, Icon, Splash Screen, etc. 5. Click 'Generate APK' - done! ๐Ÿ“ถ Internet is required during generation for source code projects. ### 5. ๐ŸŽฏ Tips for Best Results - Keep your app a single-page app (SPA) - it works best - If your app uses a backend/API, keep the backend hosted online - Images and assets in the 'public/' folder are included automatically - Test your app in a browser first to make sure it works ### 6. โŒ What Won't Work - Server-side features (database queries, server APIs built into the app) - Apps that need Node.js or a server to run - WordPress, Shopify, or CMS-based websites - Svelte and Angular projects (coming soon!) If your app works in a browser without a server, it will work as an APK! ### 7. ๐Ÿ“ฅ Download Sample Projects Try these ready-made sample projects to test the feature. Download any ZIP below, then use it in Step 1 with 'Offline ZIP' mode to generate an APK instantly! [โฌ‡๏ธ React (Vite) Sample](https://freewebtoapk.com/samples/vite-react-test-app.zip) ### 8. ๐Ÿ“ฅ Next.js Sample A multi-page Next.js app with routing - great for testing page navigation in your generated APK. [โฌ‡๏ธ Next.js Sample](https://freewebtoapk.com/samples/nextjs-test-app.zip) ### 9. ๐Ÿ“ฅ Astro Sample A fast static site built with Astro and React components - perfect for content-heavy apps. [โฌ‡๏ธ Astro Sample](https://freewebtoapk.com/samples/astro-test-app.zip) ### 10. ๐Ÿ“ฅ Single HTML Sample Minimal single-file offline test (just index.html). Great for quick sanity checks when troubleshooting blank screens. [โฌ‡๏ธ Single HTML Sample](https://freewebtoapk.com/samples/single-html-test.zip) ### 11. ๐Ÿ“ฅ HTML + CSS Sample Simple two-file offline test (index.html + style.css) to verify external CSS loading in generated apps. [โฌ‡๏ธ HTML + CSS Sample](https://freewebtoapk.com/samples/html-css-test.zip) --- # App Icon & Splash Screen > Adaptive icons, the splash screen, and the sizes Android actually uses. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/app-icon-and-splash-screen Image Size Requirements & Best Practices ### 1. App Icon - Required Size The app icon is resized to multiple densities automatically. For the best result: โœ… Size: 512 ร— 512 pixels โœ… Format: PNG with transparency (ARGB) โœ… Shape: Square (1:1 aspect ratio) โš ๏ธ Non-square images are automatically center-cropped before scaling. This means edges and sides outside the center square WILL be cut off. Always use a square image to avoid unwanted cropping. ``` Recommended: 512 ร— 512 px PNG Images larger than 4096 ร— 4096 px are rejected. Non-square images โ†’ center-cropped to square โ†’ scaled to 512ร—512. ``` ### 2. App Icon - What Happens Internally After you upload your icon, the generator creates icons at all required Android densities: - mdpi - 48 ร— 48 px - hdpi - 72 ร— 72 px - xhdpi - 96 ร— 96 px - xxhdpi - 144 ร— 144 px - xxxhdpi - 192 ร— 192 px Starting from a sharp 512ร—512 source ensures all densities are crisp. Using a smaller source (e.g. 96ร—96) will result in blurry icons on high-density screens. ``` Source: 512 ร— 512 px โ†’ mdpi (48px) โœ… crisp โ†’ hdpi (72px) โœ… crisp โ†’ xhdpi (96px) โœ… crisp โ†’ xxhdpi (144px) โœ… crisp โ†’ xxxhdpi(192px) โœ… crisp ``` ### 3. Splash Screen Image - Required Size The splash screen image fills the entire screen background. For the best result: โœ… Size: 1080 ร— 1920 pixels โœ… Aspect ratio: 9:16 (portrait) โœ… Format: PNG โš ๏ธ Images with a different aspect ratio will appear stretched or cropped on most phone screens. A 9:16 image fits nearly all Android phones without distortion. ``` Recommended: 1080 ร— 1920 px PNG (9:16 portrait) Common wrong sizes that cause stretching: โŒ 1080 ร— 1080 (square) โŒ 1920 ร— 1080 (landscape) โŒ 512 ร— 512 (too small / wrong ratio) ``` ### 4. Splash Screen - Design Tips Since the splash image fills the whole screen, follow these design guidelines: - Place your logo/branding in the center of the image - safe zone is roughly 600ร—600 px in the middle - Keep edges simple (solid color or gentle gradient) so they look good if slightly cropped on unusual screen ratios - Avoid putting important text near the very top or bottom - status bar and navigation bar may overlap these areas - Use PNG-24 (24-bit with alpha) for sharp results ### 5. No Splash Image - Color Only Mode If you don't upload a splash image, the splash screen will show: - A solid background using your chosen splash color - Your app icon in the center - Your app name below the icon This is the default mode and looks professional on all screen sizes without any image size concerns. ### 6. Quick Checklist Before Generating Before you tap Generate, confirm: โ˜‘๏ธ Icon is exactly square (equal width and height) โ˜‘๏ธ Icon is at least 512 ร— 512 px โ˜‘๏ธ Icon is saved as PNG โ˜‘๏ธ Splash image is 1080 ร— 1920 px (or any 9:16 ratio) โ˜‘๏ธ Splash image is saved as PNG โ˜‘๏ธ Logo/branding centered in the splash image โ˜‘๏ธ No critical content near splash image edges --- # JSX / React Runtime > Run raw JSX with no build step by loading React and Babel at runtime. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/jsx-react-runtime Run Raw JSX & React Code Directly Without a Build Step ### 1. What Is JSX Runtime? JSX is a syntax extension used by React that lets you write HTML-like code inside JavaScript. Browsers and WebView cannot run JSX directly - it needs to be compiled first. The JSX Runtime feature loads React and Babel Standalone into your app at runtime, so raw JSX code just works - no build tools needed. ### 2. When to Use This Enable JSX Runtime when: - You write React/JSX code in the Custom JS field - Your HTML files contain Babel Standalone automatically finds and compiles these tags. ### 6. Important Notes - Requires internet connection on first load (CDN) - Adds ~200-500ms startup delay for Babel compilation - React 18 and ReactDOM 18 are loaded automatically - Works alongside all other features (ads, push, side menu, etc.) - For production apps with large codebases, consider using the ZIP upload with esbuild bundling instead - it's faster and works offline --- # Connectors - Payments, WhatsApp, Maps & more > Prebuilt wiring for payment gateways, WhatsApp, Maps and other third-party services. - **Applies to:** Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/connectors Ask the AI for a service - it wires the real integration ### 1. What connectors are Ask the AI for Stripe payments, WhatsApp messages, Google Maps, email, calendars - and it wires the real integration: backend functions, typed contracts, and a working keys screen. 14 curated connectors are built in, and anything else (Juspay, MSG91, a niche CRM) gets wired the same way automatically when your app needs it. ### 2. Connect a service in ~2 minutes After generation, Settings โ†’ Connectors shows a card for every service your app uses. Open the service's dashboard (the card links to it), paste your API keys, and tap Test. ### 3. The Test button is the truth A connector only turns Connected after one real authenticated call to that service succeeds. Your keys are stored on your backend - never inside the APK - so they're safe to ship. ### 4. Reuse across apps Once a service is connected, every future app that mentions it reuses the proven setup - you don't configure the same service twice. --- # Stripe Payments > Take card payments for physical goods and services, where Play billing is not required. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/stripe-payments - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Charge users in AI-generated apps with your own Stripe account - keys stay server-side, fulfillment is webhook-verified ### 1. What this gives your app Lets apps you generate charge real money - paywalls, 'Pro' upgrades, buy buttons - through Stripe Checkout. It's YOUR Stripe account: your keys, your money, paid straight to you. Appwright takes no cut and no payment liability (no Stripe Connect). You connect Stripe ONCE in Settings; every app you generate reuses it - you only describe what to sell, never re-enter keys. The AI writes all the payment code. Your secret key never ships inside the app - it lives in your Supabase backend. ### 2. First connect Supabase (required) _(AppMint only)_ Stripe payments need a secure server to hold your secret key and to confirm payments - that server is your Supabase project. So connect and enable Supabase first (Settings โ†’ BACKEND - SUPABASE) - the chapter '๐Ÿ—„๏ธ Supabase Backend (Connect It Once)' at the top of this guide walks through it. If Supabase isn't active you'll see 'Stripe keys saved, but payments also need Supabase active'. AppMint pushes your Stripe secret into your Supabase functions automatically on deploy - it's never put in the generated app. ### 2. First connect Supabase (required) _(Appwright only)_ Stripe payments need a secure server to hold your secret key and to confirm payments - that server is your Supabase project. So connect and enable Supabase first (Settings โ†’ BACKEND - SUPABASE). If Supabase isn't active you'll see 'Stripe keys saved, but payments also need Supabase active'. Appwright pushes your Stripe secret into your Supabase functions automatically on deploy - it's never put in the generated app. ### 3. Get your Stripe keys (start in test) On a computer: 1. Open the Stripe Dashboard and stay in test mode / a sandbox while you build (Stripe now calls the test environment a 'sandbox'; older accounts show a Test/Live toggle). No real charges happen here. 2. Go to Developers โ†’ API keys. 3. Copy the 'Publishable key' (starts with pk_test_โ€ฆ) and reveal + copy the 'Secret key' (starts with sk_test_โ€ฆ). Use the standard Secret key (sk_โ€ฆ), not a Restricted key (rk_โ€ฆ) - Appwright validates and pushes the sk_ secret. No merchant approval is needed to build and test the whole flow before activating live payments. [Open Stripe API keys](https://dashboard.stripe.com/test/apikeys) ### 4. Connect Stripe in Appwright Open AI Studio โ†’ Settings โ†’ scroll to 'PAYMENTS - STRIPE (OPTIONAL)' โ†’ 'CONNECT STRIPE (YOUR ACCOUNT)': 1. Leave 'Live mode' OFF for now (off = Test). 2. Paste your publishable key into the pk_test_โ€ฆ field. 3. Paste your secret key into the sk_test_โ€ฆ field (kept encrypted on this device). 4. Turn ON 'Enable Stripe payments for generated apps'. 5. Tap 'Validate & save Stripe keys'. Appwright checks the format, makes sure both keys are the same mode, and verifies the secret with Stripe. On success you'll see 'โ— Connected (TEST)'. ### 5. What Appwright wires up for you When you generate an app with payments, Appwright does the plumbing automatically - you don't touch Supabase or Stripe dashboards: - Pushes your STRIPE_SECRET_KEY into your Supabase Edge Functions (encrypted, server-side only). - Deploys a 'create-checkout' function (starts a Stripe Checkout session) and a 'stripe-webhook' function (confirms payments). - Registers the webhook with Stripe and stores its signing secret. - Creates an 'entitlements' table so the app knows who paid. The publishable key (pk_) is the only Stripe value that ships in the app - that key is safe to be public. ### 6. Describe your paywall - the AI builds it Just say what you want in plain English. The AI adds the buy button, the checkout call, and gates the paid features for you: - 'Charge $5 one-time to unlock the Pro theme.' - 'A $9/month subscription that removes ads.' - 'Sell a downloadable PDF for $19.' You never paste keys or prices into the chat as config - only the intent. Access is unlocked only after Stripe confirms the payment via the webhook, never from a client-side 'paid' flag, so it can't be faked. ### 7. Test a payment end-to-end While in Test mode, no real money moves. Run the full flow: 1. Open your app and tap the buy/upgrade button. 2. On the Stripe Checkout page, pay with the test card: ``` 4242 4242 4242 4242, any future expiry, any CVC, any ZIP. ``` 1. You're returned to the app and the paid feature unlocks. 2. Check Stripe Dashboard (Test mode) โ†’ Payments - you'll see the test charge. If it unlocks, your webhook + entitlements are wired correctly. ``` 4242 4242 4242 4242 exp: any future date CVC: any 3 digits ``` ### 8. Go Live when you're ready 1. Activate your Stripe account for live payments (Stripe Dashboard โ†’ 'Activate' / complete business details). 2. In Stripe, switch from sandbox/test to live mode and copy your LIVE keys (pk_live_โ€ฆ and sk_live_โ€ฆ) from Developers โ†’ API keys. 3. In Appwright โ†’ Settings โ†’ Payments: turn ON 'Live mode', paste the pk_live_/sk_live_ keys, and tap 'Validate & save'. The badge turns to 'โ— Connected (LIVE)'. 4. Rebuild your app. Appwright blocks mixing test and live keys, and switching mode re-points the deployed secret - test and live never coexist on one app, so you can't accidentally ship test keys. ### 9. If something doesn't work - 'Stripe keys saved, but payments also need Supabase active' โ†’ enable Supabase first (step 2). - 'You selected TEST but the keys are LIVE' (or vice-versa) โ†’ the mode toggle must match your keys' prefix (pk_test/sk_test vs pk_live/sk_live). - 'Stripe rejected the key' โ†’ the secret key is wrong or revoked. Re-copy it from Developers โ†’ API keys. - Payment succeeds but the feature stays locked โ†’ the webhook didn't reach Supabase. Rebuild/redeploy so Appwright re-registers the webhook, and confirm the app's Supabase project is the one you connected. - Can't activate a live account โ†’ Stripe does NOT onboard merchants in some countries (including India). Test mode still works everywhere for building; for live payments there you'd need a Stripe-supported country or a different provider. --- # AdMob Integration > Banner, interstitial and rewarded ads, the consent rules, and the mistakes that stop ads serving. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/admob-ads Monetization Setup ### 1. Create AdMob Account Sign up for a Google AdMob account. You'll need a Google account to get started. AdMob is Google's mobile advertising platform. [Go to AdMob](https://admob.google.com) ### 2. Add Your App In AdMob dashboard: - Click 'Apps' in the sidebar - Click 'Add App' - Select 'No' for 'Is your app listed on a supported app store?' - Enter your app name - Select 'Android' as platform - Click 'Add' ### 3. Get App ID After creating your app: - Copy the App ID shown (format: ca-app-pub-XXXXXXXXXXXXXXXX~XXXXXXXXXX) - This is different from your Ad Unit ID - You'll use this in AndroidManifest.xml ``` App ID format: ca-app-pub-1234567890123456~1234567890 ``` ### 4. Create Ad Unit (Banner) In your AdMob app: - Click 'Ad units' tab - Click 'Add ad unit' - Select 'Banner' - Enter ad unit name (e.g., 'Main Banner') - Click 'Create ad unit' - Copy the Ad Unit ID ``` Banner Ad Unit ID format: ca-app-pub-1234567890123456/1234567890 ``` ### 5. Setup in Web2APK Generator _(AppMint only)_ In the generator app: 1. Enable 'Ads' toggle in Step 3 2. Select ad placement (Top or Bottom) 3. Paste your AdMob Banner Ad Unit ID 4. Continue with other settings and generate your APK ``` Ad Unit ID (not App ID): ca-app-pub-XXXXXXXXXXXXXXXX/YYYYYYYYYY ``` ### 5. Setup in Appwright _(Appwright only)_ In the generator app: 1. Enable 'Ads' toggle in Step 3 2. Select ad placement (Top or Bottom) 3. Paste your AdMob Banner Ad Unit ID 4. Continue with other settings and generate your APK ``` Ad Unit ID (not App ID): ca-app-pub-XXXXXXXXXXXXXXXX/YYYYYYYYYY ``` ### 6. Test Ads in Your App During development, use Google's official test IDs to avoid policy violations. Copy-paste these into the generator: ๐Ÿ“‹ AdMob App ID: ca-app-pub-3940256099942544~3347511713 ๐Ÿ“‹ Banner Ad Unit ID: ca-app-pub-3940256099942544/6300978111 ๐Ÿ“‹ Interstitial Ad Unit ID: ca-app-pub-3940256099942544/1033173712 ๐Ÿ“‹ Rewarded Ad Unit ID: ca-app-pub-3940256099942544/5224354917 ๐Ÿ“‹ Publisher ID: pub-3940256099942544 ๐Ÿ“‹ Developer Website: https://example.com These test IDs show real-looking ads without generating revenue or violating policies. Replace with your real IDs for production. ``` Test IDs for Generator: AdMob App ID: ca-app-pub-3940256099942544~3347511713 Banner Ad ID: ca-app-pub-3940256099942544/6300978111 Interstitial ID: ca-app-pub-3940256099942544/1033173712 Rewarded Ad ID: ca-app-pub-3940256099942544/5224354917 Publisher ID: pub-3940256099942544 Developer URL: https://example.com ``` ### 7. Interstitial Ads Interstitial ads are full-screen ads that cover the entire app interface. Users can close them to return to the app. ๐Ÿ“บ How to Enable: 1. Toggle ON 'Enable Interstitial Ad' in Step 3 2. Enter your Interstitial Ad Unit ID (or leave empty for test ads) 3. Choose a trigger mode: ๐Ÿ”„ After Navigations (default): The ad shows after every N page navigations. For example, if set to 4, the ad appears after the 4th, 8th, 12th page load, and so on. ๐Ÿ“ฑ App Open: The ad shows once immediately after the splash screen, when the app first opens. โš ๏ธ You can enable either Interstitial OR Rewarded - not both at the same time. ``` Example: Action Count = 4 Page 1 โ†’ No ad Page 2 โ†’ No ad Page 3 โ†’ No ad Page 4 โ†’ ๐Ÿ“บ Interstitial Ad shown! Page 5 โ†’ No ad Page 6 โ†’ No ad Page 7 โ†’ No ad Page 8 โ†’ ๐Ÿ“บ Interstitial Ad shown! ... ``` ### 8. Rewarded Ads _(AppMint only)_ Rewarded ads are full-screen video ads that users watch in exchange for a reward. They provide higher revenue than banner or interstitial ads. ๐ŸŽ How to Enable: 1. Toggle ON 'Enable Rewarded Ad' in Step 3 2. Enter your Rewarded Ad Unit ID (or leave empty for test ads) 3. Choose a trigger mode: ๐Ÿ”„ After Navigations (default): Similar to interstitial - the ad shows after every N page loads. Set the 'Action Count' to control frequency. Default is 4. ๐Ÿ“ฑ App Open: The reward video shows once when the app first opens after splash. ๐Ÿ’ก What is Action Count? The number of page navigations between each ad. Lower = more ads (more revenue but worse UX). Higher = fewer ads (better UX but less revenue). Recommended: 3-5. โš ๏ธ Google policy: rewarded ads need the user's OK. In After Navigations and App Open modes your app first shows a 'Watch an ad?' prompt with a 'No thanks' button; the ad plays only if they accept. For a 'watch ad to earn' button with no automatic ads, use On Demand. ``` Revenue comparison (approximate): Banner Ads: $0.10 - $0.50 per 1000 views Interstitial Ads: $1 - $5 per 1000 views Rewarded Ads: $5 - $15 per 1000 views Recommended Action Count: Aggressive: 2-3 (more ads, more revenue) Balanced: 4-5 (recommended) Conservative: 6-10 (fewer ads, better UX) ``` ### 8. Rewarded Ads _(Appwright only)_ Rewarded ads are full-screen video ads that users watch in exchange for a reward. They provide higher revenue than banner or interstitial ads. ๐ŸŽ How to Enable: 1. Toggle ON 'Enable Rewarded Ad' in Step 3 2. Enter your Rewarded Ad Unit ID (or leave empty for test ads) 3. Choose a trigger mode: ๐Ÿ”„ After Navigations (default): Similar to interstitial - the ad shows after every N page loads. Set the 'Action Count' to control frequency. Default is 4. ๐Ÿ“ฑ App Open: The reward video shows once when the app first opens after splash. ๐Ÿ’ก What is Action Count? The number of page navigations between each ad. Lower = more ads (more revenue but worse UX). Higher = fewer ads (better UX but less revenue). Recommended: 3-5. ``` Revenue comparison (approximate): Banner Ads: $0.10 - $0.50 per 1000 views Interstitial Ads: $1 - $5 per 1000 views Rewarded Ads: $5 - $15 per 1000 views Recommended Action Count: Aggressive: 2-3 (more ads, more revenue) Balanced: 4-5 (recommended) Conservative: 6-10 (fewer ads, better UX) ``` ### 9. Consent & Privacy Policy (Required) โš ๏ธ AdMob requires a consent mechanism for users in the EEA, UK, Switzerland, and US states. This is handled automatically by the generated app using Google's User Messaging Platform (UMP). The app will: - Show a consent dialog to EEA/UK/Switzerland users before displaying ads - Show an opt-out dialog to users in applicable US states - Only request ads after consent is obtained What YOU must do: 1. In AdMob console โ†’ Privacy & messaging โ†’ Create a European regulations message 2. In AdMob console โ†’ Privacy & messaging โ†’ Create a US state regulations message 3. Set your Privacy Policy URL in the generator (Step 3 or Step 4) 4. Make sure your privacy policy mentions AdMob advertising โš ๏ธ Without this setup, AdMob may show a policy warning: 'Consent requirement: No CMP' [Open AdMob Privacy & messaging](https://admob.google.com) ### 10. Ad Review Process After publishing your app: - AdMob will review your app (usually takes 24-48 hours) - Initially, you'll see test ads - Once approved, real ads will start showing - Keep your app content policy-compliant ### 11. Enable Payments To receive ad revenue: - Go to 'Payments' in AdMob dashboard - Add payment method (bank account or other options) - Verify your address - Set up tax information - Payments are made monthly after reaching $100 threshold ### 12. Best Practices โœ… Banner Placement: Bottom banner is less intrusive and has better user experience โœ… Interstitial/Rewarded: Choose one, not both. Rewarded ads earn more but require user engagement โœ… Action Count: Start with 4-5 navigations between ads. Too frequent = bad reviews โœ… Test First: Always test with test ad units during development โœ… Monitor Performance: Check AdMob dashboard regularly for earnings and metrics โš ๏ธ Don't Click: Never click your own ads - this violates AdMob policy โš ๏ธ Quality Content: Ensure your app provides value and follows AdMob policies ๐Ÿ’ก Pro Tip: Combine Banner (always visible) + Interstitial or Rewarded (periodic) for maximum revenue ### 13. Troubleshooting Common issues and solutions: - No ads showing: Check if you're using correct Ad Unit ID (not App ID) - Test ads work but real ads don't: Wait for AdMob review to complete - Ad request failed: Verify internet connection and AdMob account status - Account suspended: Review AdMob policies and appeal if needed [AdMob Help Center](https://support.google.com/admob) --- # app-ads.txt Verification > The one file AdMob needs on your domain before it will pay full rates. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/app-ads-txt AdMob Publisher Verification (Required) ### 1. Why app-ads.txt is Required Google AdMob now REQUIRES app-ads.txt file verification for all apps that show ads. Without this verification: โŒ Your ads may not serve properly โŒ You may lose potential revenue โŒ Your AdMob account may face restrictions โœ… With app-ads.txt: Higher ad fill rates and better CPM ### 2. Get Your Publisher ID Your Publisher ID is the first part of your AdMob App ID: - If your App ID is: ca-app-pub-1234567890123456~1234567890 - Your Publisher ID is: pub-1234567890123456 You need to enter this in the generator app. ``` Format: pub-1234567890123456 Example: ca-app-pub-1234567890123456~9876543210 โ†“ pub-1234567890123456 ``` ### 3. Get Your Developer Website You must have a website URL where you'll host the app-ads.txt file. This can be: - Your personal website - Your company website - A simple GitHub Pages site - Any domain you own Note: The website must be publicly accessible and use HTTPS. ``` Valid examples: https://yourwebsite.com https://yourdomain.com https://username.github.io ``` ### 4. Generate app-ads.txt in Generator _(AppMint only)_ In the Web2APK Generator app (Step 3 - AdMob section): 1. Enter your AdMob Publisher ID (pub-XXXXXXXXXXXXXXXX) 2. Enter your Developer Website URL 3. Click 'Generate app-ads.txt' button 4. The file content will be displayed in a dialog 5. For premium users: File is automatically saved to Downloads/WebToApk/{AppName}/app-ads.txt ### 4. Generate app-ads.txt in Generator _(Appwright only)_ In Appwright (Step 3 - AdMob section): 1. Enter your AdMob Publisher ID (pub-XXXXXXXXXXXXXXXX) 2. Enter your Developer Website URL 3. Click 'Generate app-ads.txt' button 4. The file content will be displayed in a dialog 5. For premium users: File is automatically saved to Downloads/Appwright/{AppName}/app-ads.txt ### 5. Upload to Your Website Upload the app-ads.txt file to your website's root directory: - File must be at: https://yourdomain.com/app-ads.txt - Not in a subdirectory - Must be plain text file - Must be publicly accessible (no login required) ``` Correct: https://yoursite.com/app-ads.txt โœ… Incorrect: https://yoursite.com/folder/app-ads.txt โŒ https://yoursite.com/files/app-ads.txt โŒ ``` ### 6. File Content Format Your app-ads.txt file should contain this exact format: ``` google.com, pub-1234567890123456, DIRECT, f08c47fec0942fa0 Replace 'pub-1234567890123456' with YOUR actual Publisher ID ``` ### 7. Verify in AdMob Dashboard After uploading the file: 1. Go to AdMob dashboard 2. Navigate to Settings > Account Information 3. Look for 'app-ads.txt' status 4. It may take 24-48 hours for Google to verify 5. Status should show 'Verified' when done ### 8. Test Your File Before waiting for AdMob verification, test your file: - Open browser and go to: https://yourdomain.com/app-ads.txt - You should see the file content - If you get 404 error, file is not in the correct location - If you see HTML instead of text, wrong file type [AdMob app-ads.txt Guide](https://support.google.com/admob/answer/9787911) ### 9. Common Issues & Solutions โŒ File not found (404): Upload to root directory, not in a folder โŒ Wrong format: Use EXACTLY the format shown (comma-separated, no extra lines) โŒ Wrong Publisher ID: Double-check your AdMob Publisher ID โŒ Website not accessible: Ensure HTTPS and no password protection โœ… Multiple Publisher IDs: Add each on a new line in the file โœ… Subdomain: If using subdomain (app.yoursite.com), file must be there too ### 10. GitHub Pages Example If you don't have a website, use GitHub Pages (FREE): 1. Create a GitHub repository named: yourusername.github.io 2. Upload app-ads.txt file to the root 3. Enable GitHub Pages in repository settings 4. Your file will be at: https://yourusername.github.io/app-ads.txt 5. Use this URL as your Developer Website in the generator [GitHub Pages Setup](https://pages.github.com) --- # Sell In-App Products (IAP) > Sell one-time unlocks and subscriptions from your own page, with live Play prices and offline entitlement checks. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/in-app-purchases - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Google Play purchases from your own website or ZIP app ### 1. ๐Ÿ“ฅ Get the guide + working demo app Everything in this guide is also a PDF you can read offline, plus a complete demo app you can build and run today. Tap below and both are saved to your phone under Downloads/Appwright/Guides: - AppMint-InApp-Purchases-Guide.pdf - the full guide, including the Play Console steps and the React / Next.js examples - iap-demo-index.html - a small game that sells three things: remove ads (one-time), a level pack (one-time) and a monthly season pass To try the demo: zip iap-demo-index.html on its own, upload it in the stepper, and follow the guide. It is the fastest way to see a real purchase work end to end before you touch your own app. **Download** (in the app these are saved to your phone by the button on this step): - [AppMint-InApp-Purchases-Guide.pdf](https://freewebtoapk.com/docs/files/AppMint-InApp-Purchases-Guide.pdf) - 303 KB - [iap-demo-index.html](https://freewebtoapk.com/docs/files/iap-demo-index.html) - 10 KB ### 2. What is this? (in plain words) Your app can sell things - a Pro upgrade, extra levels, a subscription - and Google collects the money and pays it to you every month. WE WILL BUILD ONE EXAMPLE TOGETHER, all the way through this guide: ``` A game that sells two things โ€ข 'Levels 11-20' โ‚น99 paid once, kept forever โ€ข 'Remove ads' โ‚น49 every month ``` You will end up adding four lines to your page. That is the whole job - there is no purchase code to write, and if you have never done this before you will still be fine. You do NOT need to know programming beyond copy-paste, and you do NOT need a server, a database or a payment company account. Google Play does the hard parts: - Shows the payment screen (card / UPI / carrier billing) - Converts your price into every country's currency - Remembers who bought what - even after reinstall - Sends the money to your bank Appwright takes 0% - the sale goes straight to YOUR Google Play account. Works the same for Website โ†’ APK, ZIP โ†’ APK and AI-built apps. ### 3. What you need before you start Just four things: 1. Your website or HTML/ZIP app (the thing you turn into an APK). 2. Something to sell - decide what buyers get (e.g. 'Pro version': no limits, extra content). 3. A Google Play developer account - one-time $25 fee at play.google.com/console. You need this to publish ANY app on the Play Store, not just paid ones. 4. About 1 hour, total, spread over the steps below. Important to know up-front: in-app purchases only work when your app is installed FROM the Play Store. That's a Google rule for everyone, not an Appwright limit. ### 4. Step 1 - Invent your product ID A product ID is just a short name you make up for the thing you sell. You'll type this same name in two places later (your page + Play Console), so keep it simple and write it down. Rules: only lowercase letters, numbers, underscores. Must start with a letter or number. Copy one of these: - pro_unlock - 'Pro version' upgrade (good default - the code below already uses it) - remove_ads - ad-free upgrade - level_pack_2 - extra content pack - coins_100 - game currency pack - premium_monthly - monthly subscription The price is NOT part of the ID - you'll set the price in Step 5, and you can change it any time without touching your code. ### 5. Step 2 - Mark your button (no code to write) You do NOT write any purchase code. Your app already carries the purchase engine - you just label your own HTML so it knows what to sell. Copy the block below into your page and change only: (1) pro_unlock โ†’ your product ID, in data-iap-buy (2) the words on the button and inside the paid section That's it. The button then shows Google's real price in the buyer's own currency, opens Google's payment screen when tapped, reveals the paid section after payment (and again at every launch for people who already bought), takes it back if a subscription lapses or a payment is refunded, and shows a clear message if anything goes wrong. THE FOUR LABELS: - data-iap-buy="id" - makes this the buy button (required) - data-iap-label="โ€ฆ{price}โ€ฆ" - button text; {price} becomes Google's real price - data-iap-owned="โ€ฆ" - button text after buying - data-iap-show - a section that appears only after buying (start it 'hidden') Also available: data-iap-hide (disappears after buying), data-iap-container (a wrapper that only appears inside the app), data-iap-text (put it on the holding the text if your button also has icons). SELLING SEVERAL DIFFERENT THINGS? Add a button for each. They are independent - an app can remove ads AND sell level packs AND run a subscription, and buying one never affects the others. Point a section at one product with data-iap-show="level_pack_2". Only the IDs you type into 'Remove Ads (IAP) โ†’ Product ID' switch the ads off. Everything else you sell unlocks whatever your page does with it, and the ads keep running. TWO PLANS FOR THE SAME THING (monthly vs lifetime)? Give both buttons the same data-iap-group="adfree" - then buying either hides the other, so nobody pays twice. Without a group nothing is hidden, which is what unrelated products need. ```
Upgrade to remove the ads.
``` ### 6. The 4 things people usually sell Pick whichever matches you. The only difference between them is where you create the product in Play Console - your page code is the same shape every time. 1. REMOVE ADS, PAID ONCE (forever) Create it under: In-app products Also type the id into: Remove Ads (IAP) โ†’ Product ID 1. REMOVE ADS, MONTHLY Create it under: Subscriptions Also type the id into: Remove Ads (IAP) โ†’ Product ID 1. UNLOCK LEVELS / CONTENT, PAID ONCE Create it under: In-app products Do NOT type it into the Remove Ads box - your ads keep running. 1. UNLOCK LEVELS / CONTENT, MONTHLY Create it under: Subscriptions Do NOT type it into the Remove Ads box - your ads keep running. You can sell ALL FOUR in the same app, and as many products as you like. Buying one never affects the others. ``` ``` ### 7. Which purchases remove the ads? This is the one thing people get wrong, so it is worth 30 seconds. Your ads are NOT part of your web page. They are real Android ads sitting on top of it, so nothing in your page can switch them off. Only ONE thing can: ``` wizard Step 3 โ†’ Remove Ads (IAP) โ†’ Product ID ``` Whatever id you type in that box removes the ads when someone buys it. Every other id you sell just unlocks whatever your page does with it, and the ads carry on. Selling both an ad-free plan AND level packs? Type only the ad-free id in that box. In our example: ``` Product ID box: remove_ads_monthly NOT in the box: level_pack_2 ``` Offering two ad-free plans (monthly AND lifetime)? Put both in that one box, separated by a comma - owning either one removes the ads: ``` remove_ads_monthly,remove_ads_lifetime ``` Not selling ad removal at all? Leave that toggle off entirely and just use '๐Ÿ’ฐ Sell In-App Products'. ``` Selling levels only: Remove Ads (IAP) OFF Sell In-App Products ON Selling ad removal only: Remove Ads (IAP) ON โ†’ Product ID: remove_ads_monthly Sell In-App Products ON Selling both, with two ad-free plans: Remove Ads (IAP) ON โ†’ Product ID: remove_ads_monthly,remove_ads_lifetime Sell In-App Products ON (level_pack_2 stays out of the box) ``` ### 8. Full example - plain HTML page Here is our whole example app: sells 'Levels 11-20' once, and 'Remove ads' monthly. Copy it, change the two ids and the words, and you are done. Read what each label does: - data-iap-buy โ†’ what this button sells - data-iap-label โ†’ the text; {price} becomes Google's real price - data-iap-owned โ†’ the text after they have paid - data-iap-show="level_pack_2" โ†’ this box appears only after THAT product is bought - data-iap-hide โ†’ this box disappears once anything is bought Notice there is no ``` ### Recipe 4 - subscriptions A subscription is bought exactly like a one-time product; the difference is that it can go away. `type` tells you which kind a product is and `period` gives the billing interval as an ISO-8601 duration - `P1M` monthly, `P1Y` yearly, `P1W` weekly. *A monthly pass that expires on its own* ```js AppMintIAP.onChange(state => { const pass = state.products.season_pass; // undefined until Google answers const active = state.ownedIds.includes('season_pass'); document.body.classList.toggle('has-pass', active); if (pass && !active) { const every = { P1W: 'week', P1M: 'month', P1Y: 'year' }[pass.period] || 'period'; label.textContent = `Season pass โ€” ${pass.price} / ${every}`; } }); // The pass lapsed (not renewed, or refunded): the same handler runs with it gone. // Nothing to write โ€” the class comes off and the paid content hides itself. ``` You do not poll and you do not need an expiry date. Play restores the owned set on every launch, so a lapsed subscription simply stops appearing in `ownedIds`. > **Careful.** Activate **both** the subscription and its base plan in the Play Console. A subscription with an inactive base plan returns no details at all, and the button will report "not available on Google Play yet". ### Recipe 5 - the raw bridge, no helper Everything above is built on six methods and five events. If you want to own the UI completely, use them directly. *The whole surface, in one file* ```js const bridge = window.WebToApk; const enabled = !!(bridge && bridge.purchase); // false in a browser // --- what does Google charge for these, in this buyer's currency? --- const reqId = 'prices-' + Date.now(); window.addEventListener('appmint:products', function onProducts(e) { if (e.detail.requestId !== reqId) return; // not our answer window.removeEventListener('appmint:products', onProducts); for (const p of e.detail.products) { // {productId, type:'inapp'|'subs', title, description, price, currency, period?, owned} render(p); } }); bridge.getProducts(reqId, JSON.stringify(['pro_unlock', 'season_pass'])); // --- what does this buyer already own? instant, and works offline --- const owned = JSON.parse(bridge.getOwnedProducts() || '[]'); const isPro = bridge.isOwned('pro_unlock'); // --- buy --- buyButton.onclick = () => bridge.purchase('pro_unlock'); // --- the ONLY place a purchase is ever granted --- window.addEventListener('appmint:purchase', e => { if (e.detail.productId === 'pro_unlock' && e.detail.owned) unlockPro(); }); // --- and the only place failures show up --- window.addEventListener('appmint:purchase-failed', e => { toast({ not_found: 'This item is not available on Google Play yet.', billing_error: 'Google Play could not start the payment. Please try again.', pending: 'Payment pending โ€” this unlocks automatically once Google confirms.', disabled: 'In-app purchases are not enabled in this app.' }[e.detail.reason] || 'Purchase could not be completed. Please try again.'); }); // --- the launch-time restore finished, or a refund took something away --- window.addEventListener('appmint:owned-changed', () => refreshEverything()); ``` > **Note.** `appmint:purchase` fires again for something already owned, on purpose. Write `unlockPro()` so that running it twice is harmless and you never have to track whether you have already run it. ### Recipe 6 - degrade honestly outside the app The same page usually has to open in a normal browser as well - during development, or because you also publish it on the web. There, `window.WebToApk` does not exist and no purchase can happen. Say so, rather than showing a button that does nothing. *One check, at the top* ```js const inApp = !!(window.AppMintIAP && window.AppMintIAP.available); if (!inApp) { buyButton.disabled = true; buyButton.textContent = 'Available in the Android app'; // and let people get it: storeLink.hidden = false; } ``` ### Reference Everything the page can call, listen for, or mark up. **window.AppMintIAP - the helper the runtime injects** | Call | Does | | --- | --- | | `AppMintIAP.available` | False in a browser, or when in-app purchases were not switched on for this build. Check it before showing anything for sale. | | `AppMintIAP.sell(id, button, opts)` | Wire one product to one button. `opts`: `label`, `owned`, `group`, `show`, `hide`, `labelEl`. Call it once per product. | | `AppMintIAP.start(config)` | Wire several at once: `{plans, show, hide, container, toast, hideOtherPlans}`. Use it when you want your own toast function, or to keep the other plans of a group visible after one is bought (`hideOtherPlans: false`). | | `AppMintIAP.onChange(fn)` | Fires immediately with the current state, then on every change. Returns an unsubscribe function. | | `AppMintIAP.state()` | The same object, once: `{available, adFree, owned, ownedIds, products}`. | | `AppMintIAP.isOwned(id)` | True/false, instantly, offline. | | `AppMintIAP.owned()` | Array of every owned product id. | | `AppMintIAP.buy(id)` | Opens Google's purchase sheet. Grants nothing by itself. | | `AppMintIAP.refresh()` | Re-scan for `data-iap-buy` buttons added since the last pass. Rarely needed - the runtime watches the DOM. | **window.WebToApk - the bridge underneath** | Call | Returns | | --- | --- | | `purchase(productId)` | Nothing. Opens the sheet; the outcome arrives as an event. | | `getProducts(requestId, idsJson)` | Nothing. The answer arrives as `appmint:products` carrying the same `requestId`. | | `getOwnedProducts()` | JSON array of owned product ids, as a string. | | `isOwned(productId)` | Boolean - a one-time product owned, or a subscription currently active. | | `isPremium()` | Boolean - the "remove ads" entitlement specifically. | | `startRemoveAdsPurchase()` | Nothing. Opens the sheet for the FIRST id in the wizard's "Remove Ads" field; owning any id listed there removes the ads. Outcome arrives as `appmint:premium`. | **Events** | Event | detail | When | | --- | --- | --- | | `appmint:purchase` | `{productId, owned}` | Google confirmed. The only place to unlock. Re-fires for something already owned. | | `appmint:purchase-failed` | `{productId, reason}` | The sheet never opened, the flow errored, or the payment is pending. Never fired for a plain cancel. | | `appmint:products` | `{requestId, products[]}` | Reply to a `getProducts()` call. Ids Play does not know are simply absent. | | `appmint:premium` | `{premium}` | The ad-free entitlement changed - purchase, launch restore, refund or expiry. | | `appmint:owned-changed` | `{products[]}` | The launch-time restore finished, or a refund took something away. Carries every owned id. | **HTML attributes** | Attribute | On | Does | | --- | --- | --- | | `data-iap-buy="id"` | button | Makes it buy that product. | | `data-iap-label="โ€ฆ {price}"` | button | Label template. `{price}` becomes Google's live localised price. | | `data-iap-owned="โ€ฆ"` | button | Label once the product is owned. | | `data-iap-group="name"` | button | Marks plans as alternatives to each other. | | `data-iap-show="id|group"` | anything | Shown only when that is owned. | | `data-iap-hide="id|group"` | anything | Hidden once that is owned. | | `data-iap-text` | inside a button | The element whose text gets the label, when the button has more inside it than text. | | `data-iap-container` | wrapper | Shown only inside the app; hidden in a browser, where there is nothing to buy. Wrap the whole offer in it. | | `data-iap-state="owned"` | set by the runtime | On an owned product's button, so you can style it. | **Failure reasons on appmint:purchase-failed** | reason | What actually happened | | --- | --- | | `not_found` | Google does not know this product id - not created, not activated, or the app was not installed from the Play Store. | | `billing_error` | Play could not start the payment. Usually transient; the buyer should try again. | | `pending` | A slow payment method (cash, bank transfer). It unlocks by itself when Google confirms - do not treat this as a failure. | | `disabled` | In-app purchases were not switched on for this build. | | `unknown` | Anything else. | ### Before you ship 1. Every product id in your code exists in the Play Console **and is activated** - for a subscription, the base plan too. 2. The build carries the same ids, and the Base64 RSA licence key from Monetization setup. 3. Your Gmail address is in **Setup โ†’ License testing**, with the response set to RESPOND_NORMALLY. 4. You uploaded the AAB to Internal or Closed testing and installed it **from the Play Store link** - a sideloaded APK can never purchase anything. 5. You bought each product as a licence tester and saw "Test instrument, always approves". 6. You uninstalled and reinstalled, and the purchase came back on its own. 7. You turned off the network and the paid content is still unlocked. 8. You refunded a test purchase in the Play Console and watched the app lock again. ### Mistakes that cost money - **Unlocking in the click handler.** Anyone who opens the sheet and backs out gets the product free. - **Storing the entitlement only in `localStorage`.** Clearing site data takes away something a customer paid for; copying a value gives it away. - **Hard-coding the price in your HTML.** It will be wrong in every other country and out of date the day you change it. Use `{price}` or `products[id].price`. - **Selling physical goods or a real-world service through Play billing.** Google requires the opposite: those must use a normal payment processor. That is what the [Stripe guide](/docs/stripe-payments) is for. - **Testing on a sideloaded APK.** In-app purchases only work when the app was installed from the Play Store. Nothing you write can change that. - **Treating `pending` as a failure.** The buyer paid by a slow method; it unlocks by itself when Google confirms. --- # Firebase Backend (Database + Accounts) > A hosted database and user accounts for an app with no server of its own. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/firebase-backend - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Give AI-generated apps a cloud database and user login using your own free Firebase project ### 1. What this gives your app Normally your generated app stores data only on the phone it's running on. Turning on Firebase gives the app a free cloud database, so the same data is visible from any device and (if you want) people can sign up with email + password. It's BRING-YOUR-OWN - the app talks to YOUR free Firebase project. Appwright hosts nothing and charges you nothing extra. You paste one config once; the AI writes all the code. You do NOT have to write or design anything - no tables, no schema, no SQL. Firebase + Appwright figure it out from your app description. ### 2. Create the Firebase project (5 min, free) On a computer is easiest: 1. Open console.firebase.google.com and sign in with any Google account. 2. Tap 'Add project' โ†’ give it any name โ†’ keep clicking 'Continue' โ†’ skip Google Analytics โ†’ 'Create project'. Wait ~30 sec. 3. In the left menu: Databases & Storage โ†’ Firestore โ†’ 'Create database'. - Pick a location near your users (you can't change this later). - Choose 'Start in production mode' - NOT 'test mode'. Test mode auto-expires after 30 days and your app will silently stop working. 1. In the left menu: Security โ†’ Authentication โ†’ 'Get started' โ†’ tab 'Sign-in method': - Enable 'Email/Password' (turn ON the first toggle, save). - ALSO enable 'Anonymous' (scroll down, turn ON, save) - needed for guest/leaderboard apps. That's it. There's no schema to design. [Open Firebase Console](https://console.firebase.google.com) ### 3. Copy your web config Still in Firebase Console: 1. Tap the โš™๏ธ gear (top left) โ†’ 'Project settings'. 2. Scroll to 'Your apps' โ†’ tap the (web) icon. 3. Give the app any nickname โ†’ tap 'Register app' (skip 'Firebase Hosting'). 4. You'll see a code snippet. Copy ONLY the firebaseConfig object - everything from the opening { to the closing }. The web API key is meant to be public - your data is protected by the security rules in step 6, not by hiding the key. ``` const firebaseConfig = { apiKey: "AIza...", authDomain: "my-app.firebaseapp.com", projectId: "my-app", storageBucket: "my-app.appspot.com", messagingSenderId: "123456789", appId: "1:123:web:abc" }; โ† copy the { ... } part ``` ### 4. Paste it into Appwright (once) Open AI Studio โ†’ Settings โ†’ scroll to 'BACKEND - FIREBASE (OPTIONAL)': 1. Paste the firebaseConfig you copied into the big text box. 2. Turn ON the 'Enable Firebase backend' switch. 3. Tap 'Save Firebase config'. You should see 'Saved Firebase project: '. It's stored encrypted on your device and reused for every app you generate - you only do this once. ### 5. Describe your app - the AI does the rest Just say what you want, in plain English. The AI decides whether to add login screens or not, based on what you ask for: - 'A notes app where each user saves their own notes' โ†’ AI builds signup/login + private notes. - 'A global leaderboard for a tap game, no accounts' โ†’ AI uses silent guest sign-in, public scores, no login UI. - 'A shared bulletin board everyone can post to' โ†’ AI uses guest sign-in, no login UI. If the device is offline or the config is missing, the app automatically falls back to phone-only storage so it never crashes. ### 6. Apply the security rules (REQUIRED) Without this step, the app will get 'permission denied' errors when it tries to read or write. The AI writes the rules FOR YOU; you just paste them in. 1. After generating your app, open the Code view (or unzip the downloaded project) and find the file named 'firestore.rules'. Tap it and copy ALL the text. 2. In Firebase Console: Databases & Storage โ†’ Firestore โ†’ 'Rules' tab โ†’ tap 'Edit rules' โ†’ delete what's there โ†’ paste the AI's rules โ†’ tap 'Publish'. Do this once per app. The rules are tailored to your app - they let users write only their own data, while leaderboards stay publicly readable. ### 7. Verify it works Quick sanity check after installing the APK: 1. Open the app on your phone and do one action that should save data (create a note, submit a score, etc.). 2. Back in Firebase Console: Databases & Storage โ†’ Firestore โ†’ 'Data' tab โ†’ you should see a new collection (e.g. 'notes' or 'scores') with the data you just entered. 3. Security โ†’ Authentication โ†’ 'Users' tab โ†’ if your app uses login, your account should appear here. If nothing shows up: check the 3 most common causes in the next step. ### 8. If something doesn't work The most common errors: - 'permission-denied' or no data saving โ†’ you forgot step 6 (Publish the rules). - 'auth/operation-not-allowed' on signup/login โ†’ you didn't enable Email/Password in step 2. - 'auth/operation-not-allowed' for guest/leaderboard apps โ†’ you didn't enable Anonymous in step 2. - 'auth/operation-not-allowed' for Google sign-in โ†’ you didn't enable Google AND paste your SHA-256 fingerprint (see step 'Add Google Sign-In' below). - Pasted config rejected ('Couldn't find a { ... } config object') โ†’ paste the WHOLE { ... } block including the curly braces, not just the inside. Supported sign-in methods in generated apps: Email/Password, Anonymous (guest), Google Sign-In (via native Credential Manager), and Phone/SMS (via reCAPTCHA). Facebook/Apple/Twitter sign-in is still NOT supported - they require OAuth popups that Google blocks inside Android WebView. ### 9. Add Google Sign-In (optional) Generated apps can use Google Sign-In via Android's native Credential Manager - it works inside the WebView (unlike OAuth popups). One-time setup per app: 1. Firebase Console โ†’ Security โ†’ Authentication โ†’ 'Sign-in method' tab โ†’ enable 'Google'. Save. 2. Firebase Console โ†’ โš™ Project settings โ†’ 'Your apps' โ†’ if you don't already have an Android app registered, tap 'Add app' โ†’ Android โ†’ enter your package name (the one you'll use in Appwright). 3. Build your APK once in Appwright. Open the downloaded '_signkey_info.txt' file - at the top you'll see SHA-1 and SHA-256 fingerprints. 4. Back in Firebase Console โ†’ โš™ Project settings โ†’ Your apps โ†’ Android app โ†’ 'Add fingerprint' โ†’ paste the SHA-256. Tap 'Save'. (You only do this ONCE per app - the same signing key is used for every future build.) 5. Firebase Console โ†’ โš™ Project settings โ†’ General โ†’ scroll to 'Your apps' โ†’ find the Web SDK section โ†’ copy the 'Web client ID' (ends in .apps.googleusercontent.com). 6. Open Appwright Settings โ†’ paste it into 'GOOGLE SIGN-IN - OAUTH WEB CLIENT ID'. Save. 7. Rebuild your app. Google Sign-In now works. ### 10. Add Phone / SMS Sign-In (optional) Phone Auth uses Firebase Web SDK + reCAPTCHA (works inside WebView). The user types their phone number, taps an 'I'm not a robot' check, receives an SMS code, and enters it. Setup: 1. Firebase Console โ†’ Security โ†’ Authentication โ†’ 'Sign-in method' tab โ†’ enable 'Phone'. Save. 2. No SHA needed (we use reCAPTCHA verification, not Play Integrity). 3. Just ask the AI for phone sign-in in your app description (e.g. 'Users sign in with their phone number'). โš ๏ธ Billing: Sending real verification SMS now requires the Blaze (pay-as-you-go) plan - the free Spark plan no longer includes a free SMS quota. Upgrade in Firebase Console โ†’ โš™ Project settings โ†’ Usage and billing. On Blaze, Firebase Authentication allows up to 3,000 verification SMS per day; SMS pricing varies by country. ๐Ÿ’ก To test WITHOUT billing or real SMS: Authentication โ†’ Settings โ†’ 'Phone numbers for testing' lets you add a fixed test number + code (no SMS is sent). [Firebase Auth quotas & pricing](https://firebase.google.com/docs/auth/limits) ### 11. Cost - what to expect Firebase's free 'Spark' plan covers most hobby apps. Free per day: - 50,000 document reads - 20,000 writes - 20,000 deletes - 1 GiB total storage - 10 GiB / month outbound network For reference: a small notes app with 100 active users does ~5,000 reads/day. A viral game leaderboard could blow past 50k reads in an hour. โš ๏ธ Set a budget alert: Google Cloud Console โ†’ Billing โ†’ Budgets & alerts โ†’ set 'Alert me at $1'. You stay safe and Firebase will email you the moment usage costs money. [Set a budget alert](https://console.cloud.google.com/billing/budgets) --- # Google Sign-In (Continue with Google) > A real "Continue with Google" button inside a generated app, and the SHA-1 that makes it work. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/google-sign-in - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Add 'Continue with Google' to AI-generated apps using your own Google account - no shared login, no secret in the app ### 1. What this gives your app Adds a 'Continue with Google' button to apps you generate in AI Studio, so your users sign in with their Google account instead of typing a new email + password. It's BRING-YOUR-OWN: the button uses YOUR Google sign-in, on YOUR consent screen, for YOUR users. Appwright hosts no login and stores nothing - you paste your Google credentials once and every app you generate can reuse them. You don't write any code. The AI adds the button and the sign-in logic when you ask for it; the secret never ships inside the app. ### 2. First connect Supabase (required) _(AppMint only)_ Google sign-in for generated apps rides on Supabase Auth, so you must have Supabase connected and enabled first (Settings โ†’ BACKEND - SUPABASE). If Supabase isn't active, the Google button won't appear. The chapter '๐Ÿ—„๏ธ Supabase Backend (Connect It Once)' at the top of this guide walks through connecting it. Why: your users' accounts live in YOUR Supabase project. AppMint pushes your Google credentials into that project automatically on deploy - you never have to flip the Google switch inside the Supabase dashboard yourself. ### 2. First connect Supabase (required) _(Appwright only)_ Google sign-in for generated apps rides on Supabase Auth, so you must have Supabase connected and enabled first (Settings โ†’ BACKEND - SUPABASE). If Supabase isn't active, the Google button won't appear. Why: your users' accounts live in YOUR Supabase project. Appwright pushes your Google credentials into that project automatically on deploy - you never have to flip the Google switch inside the Supabase dashboard yourself. ### 3. Create a Google OAuth client (5 min, free) On a computer, in the Google Auth Platform console (Google recently moved OAuth setup here from 'APIs & Services'): 1. Open console.cloud.google.com/auth and pick (or create) any project. 2. First time only - configure the consent screen: under 'Branding' add an app name + your support email, and under 'Audience' choose 'External' and either publish the app or add yourself as a test user. 3. Open 'Clients' โ†’ click 'Create client'. 4. Application type: choose 'Web application' (NOT Android - the sign-in happens through Supabase on the web). 5. Name it and click 'Create'. You'll get a Client ID (ends in .apps.googleusercontent.com) and a Client secret. Keep this tab open for the next step. [Open Google Auth Platform โ†’ Clients](https://console.cloud.google.com/auth/clients) ### 4. Add your Supabase callback as a redirect URI This is the step everyone forgets - without it Google shows a 'redirect_uri_mismatch' error. 1. Find your Supabase project ref: it's the '' part of your project URL https://.supabase.co (Supabase dashboard โ†’ Project Settings โ†’ API). 2. Back in the Google OAuth client you just created, under 'Authorized redirect URIs' click 'Add URI' and paste exactly: ``` https://.supabase.co/auth/v1/callback ``` 1. Replace with your real project ref and Save. ``` https://.supabase.co/auth/v1/callback ``` ### 5. Paste your credentials into Appwright In AI Studio, just describe an app that needs accounts and ask for Google sign-in - e.g. 'let users sign in with Google'. The first time, Appwright pops a 'Connect Google sign-in' dialog: 1. Paste your Google OAuth Client ID (โ€ฆapps.googleusercontent.com). 2. Paste your Google OAuth Client Secret. 3. Tap 'Save & continue'. They're stored encrypted on your device and reused for every future app - you only paste them once. (Tap 'Skip Google' if you change your mind; the app still builds, just without the button.) ### 6. How it works on a real phone Google blocks its login screen inside an in-app WebView, so Appwright handles it the safe way automatically - nothing for you to configure: - Tapping 'Continue with Google' opens the real browser for the consent screen. - After the user approves, it returns to your app through a secure deep link and finishes the sign-in. This works in BOTH the live Preview and the installed APK. Each generated app uses its own private return link, so multiple Appwright-made apps never collide. ### 7. Verify it works 1. Open your app (Preview or installed APK) and tap 'Continue with Google'. 2. The browser opens, you pick your Google account, and you're returned to the app, signed in. 3. In your Supabase dashboard โ†’ Authentication โ†’ Users, your Google account now appears. If it works in Preview it will work in the built APK - the flow is identical. ### 8. If something doesn't work - 'redirect_uri_mismatch' โ†’ the redirect URI in step 4 doesn't exactly match. It must be https://.supabase.co/auth/v1/callback with YOUR ref, no trailing slash, no typos. - Button doesn't appear โ†’ Supabase isn't active (step 2), or you tapped 'Skip Google'. Re-open the app description with Google sign-in requested. - 'Access blocked: app not verified' โ†’ your OAuth consent screen is still in Testing. Add your Google account as a test user, or publish the consent screen. - Only Google is offered - Facebook / Apple / X (Twitter) sign-in is NOT supported. They need OAuth popups that Android WebView blocks. Email/password still works alongside Google. - Offline (no-backend) apps get no social login at all - there's no Supabase to authenticate against. --- # Supabase Backend (Connect It Once) > Connect a Postgres database, auth and storage once, and let the AI write against it. - **Applies to:** AppMint - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/supabase-backend Connect your own free Supabase project - cloud database, user accounts and storage for AI-generated apps ### 1. What connecting Supabase gives you By default an AI-generated app keeps its data on the one phone it is installed on - 'Offline' mode. Connect Supabase and the same app gets a real cloud database (Postgres), user accounts, and file storage, so data is shared across devices and users. It is BRING-YOUR-OWN: the app talks to YOUR free Supabase project. AppMint hosts nothing for you and charges nothing extra for it. You do not design tables or write SQL. AppMint works out the schema from your app description, applies it, and keeps it in sync on every build. ### 2. What you need first A free Supabase account - that is all. You do NOT need to create a project first; AppMint can create one for you during sign-in. Worth knowing about the free plan: - 2 projects per organisation. - Projects pause after a week of no traffic and wake on the next request. If you already have a project you want to reuse, that works too - AppMint puts each app in its own schema inside it, so several apps can share one project. ### 3. Connect it (one tap, recommended) In AI Studio, under 'WHERE YOUR APP'S DATA LIVES', tap the 'Supabase' card (it reads 'Tap to connect'). Choose 'Sign in with OAuth'. Your browser opens Supabase, you sign in and approve AppMint, and you land back in the app automatically - there is no token to copy. The same thing is available from the โš™ Settings gear โ†’ 'BACKEND - SUPABASE' โ†’ '๐Ÿ”— Connect Supabase (OAuth)'. [Create a free Supabase account](https://supabase.com) ### 4. Pick or create the project Straight after approving, AppMint shows 'Pick a Supabase project': - Choose an existing project, or - Tap 'โž• Create new projectโ€ฆ' โ†’ pick the organisation โ†’ name it (e.g. appmint-prod) โ†’ pick a region close to your users (this cannot be changed later) โ†’ 'Create'. Provisioning takes 1-2 minutes. When AppMint creates the project it shows the database password ONCE, because Supabase only stores a hash of it. Copy it somewhere safe before closing that dialog - you do not need it for AppMint, but you will need it to connect any other tool. Hit the free-tier limit ('Free tier allows 2 projects per organization')? Pick an existing project instead, or upgrade in the Supabase dashboard. ### 5. Or connect manually (self-hosted / no OAuth) Settings โ†’ 'BACKEND - SUPABASE' โ†’ 'MANUAL / SELF-HOSTED': 1. Project URL - from your Supabase dashboard, Project Settings โ†’ API. It looks like https://YOUR-PROJECT-REF.supabase.co 2. anon key - the publishable key from the same page; it starts with 'eyJ'. This key is designed to be public: Row-Level Security is what protects your data, not hiding the key. 3. Turn on 'Enable Supabase backend for generated apps' and tap 'Save Supabase config'. Tick 'Self-hosted instance' if you run Supabase yourself. Manual mode cannot auto-deploy the schema on Cloud - add a Personal Access Token (next step) if you want that. ``` Project URL https://abcdefghijkl.supabase.co anon key eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... ``` ### 6. What AppMint does on every build Once connected, each generation does the backend work for you: - Applies the schema the AI wrote (tables, indexes, Row-Level-Security policies). - Runs the seed data, if the app needs any. - Creates Storage buckets for uploads and images. - Deploys edge functions (payments, email, webhooks) and skips ones that have not changed. - Sets the auth providers and Site URL. - Runs a security gate and a migration gate, then pulls TypeScript types back into the project. Your app's tables live in their own schema named appmint_. User accounts are shared across every app in the project, so one sign-up works everywhere. ### 7. Check that it worked Two quick checks: - The 'Supabase' card in AI Studio now reads 'Connected', and Settings shows the project ref. - Settings โ†’ 'DIAGNOSTICS' โ†’ 'โšก Test Supabase connection' - it reports success or the exact error. After you generate an app, open your Supabase dashboard โ†’ Table Editor and switch the schema selector from 'public' to appmint_ to see the tables AppMint created. ### 8. Which keys are safe where - anon key - safe inside generated apps. Row-Level Security decides what each signed-in user may read or write. - service_role key - NEVER goes into an app. AppMint stores it encrypted on this device only, and uses it for Storage buckets and self-hosted schema apply. - Personal Access Token (sbp_โ€ฆ) - also device-only; it lets AppMint deploy schema and edge functions on Supabase Cloud automatically. Create one at supabase.com/dashboard/account/tokens with read + write project scope. Both optional keys live in Settings โ†’ 'ADVANCED - DEPLOY CREDENTIALS'. [Create a Personal Access Token](https://supabase.com/dashboard/account/tokens) ### 9. Unlocks: Google Sign-In, Stripe, Remote Update Several features need Supabase connected first, and will send you here until it is: - 'Continue with Google' in generated apps. - Stripe payments (the checkout runs in an edge function in your project). - Remote Update - the updates you push to installed apps are stored in your own Supabase project. Connect once and all three become available; you do not repeat this per app. ### 10. Removing an app's backend Settings โ†’ 'DANGER ZONE' (visible when connected): - '๐Ÿ“‹ List & delete project tables' - browse every table AppMint created and delete the ones you no longer need. - '๐Ÿ—‘ Delete this whole app backend' - removes that app's schema, and optionally its Storage buckets and edge functions. You type DELETE to confirm. Choose 'Data only' if you are unsure: buckets and edge functions are shared across the whole project, not per app. Your user accounts are never touched. To disconnect entirely: Settings โ†’ 'ONE-TAP OAUTH' โ†’ '๐Ÿ”’ Disconnect Supabase OAuth'. ### 11. If something doesn't work - 'Not connected' after signing in โ†’ the OAuth finished but no project is bound yet. Settings โ†’ 'Choose project'. - A warning that the anon key was not fetched automatically โ†’ your Supabase OAuth approval is missing the Secrets:Read scope. Copy the anon key from Project Settings โ†’ API and paste it in Settings (step 5). - 'Could not list your Supabase organizations' when creating a project โ†’ create the project in the Supabase dashboard instead, then come back and pick it. - 'Connect Supabase first' in Remote Update or Stripe โ†’ this chapter, step 3. - Data reads/writes fail in a generated app โ†’ run 'โšก Test Supabase connection' first; if that passes, ask the AI to fix the Row-Level-Security policy for the table it names. --- # Tawk.to Live Chat > Put a staffed live-chat widget in the app without touching your website. - **Applies to:** AppMint - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/live-chat In-App Live Chat Setup (Premium Feature) ### 1. Create Tawk.to Account Sign up for a free Tawk.to account. Tawk.to provides live chat completely free with unlimited chats and agents. [Go to Tawk.to](https://www.tawk.to) ### 2. Create a Property After logging in: - Click 'Add Property' (top-right or from dashboard) - Enter your property/website name - Enter your website URL - Click 'Create Property' A property represents your website or app. ### 3. Get the Widget Embed Code In your Tawk.to dashboard: - Go to Administration (โš™๏ธ gear icon) - Select your property - Click on 'Chat Widget' - Scroll down to 'Direct Chat Link' or 'Widget Code' section - Click 'Get Widget Code' - Copy the full JavaScript snippet shown ``` Example embed code format: ``` ### 4. Setup in Web2APK Generator In the generator app (requires Premium): 1. Enable 'Live Chat' toggle in Step 3 2. Paste the FULL JavaScript embed snippet into the 'Tawk Widget Code' field 3. The chat button will appear in your app's side menu 4. Users tap it to open a live chat dialog โš ๏ธ Note: Live Chat is a Premium feature. Free accounts cannot enable it. ### 5. Test Live Chat After installing your generated app: - Open the app and look for the chat icon in the side menu - Tap it to open the chat dialog - In Tawk.to dashboard, go to 'Conversations' to see incoming chats - You can reply from the dashboard or the Tawk.to mobile agent app ๐Ÿ’ก Pro Tip: Install the Tawk.to app on your phone to respond to customers on the go. [Download Tawk.to Agent App](https://www.tawk.to/downloads/) ### 6. Customize the Widget In Tawk.to Administration > Chat Widget: - Change widget color to match your brand - Set online/offline hours - Add pre-chat form (collect name/email before chat) - Set up automated greetings - Configure canned responses for quick replies โœ… All free - no subscription needed for basic features --- # Local Notifications & Reminders > Schedule reminders on the device - no server, no internet, exact alarms included. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/local-notifications-and-reminders - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Show instant notifications, or schedule reminders that fire even when the app is closed - on-device, no server ### 1. What this adds Your app can show Android notifications from its own code - two levels: - Instant (Tier 1): a notification that appears right now, while the app is open. Good for 'download complete', 'timer finished', etc. - Scheduled reminders (Tier 2): a notification that fires at a future time even when the app is CLOSED, and survives a reboot. This is what reminder, habit, alarm and medication apps need. These are 100% on-device and offline - no server, no OneSignal account. (OneSignal push is a separate feature for messages YOU send from a server.) ### 2. Turn it on in the build wizard In Step 3 (Permissions), tick: - ๐Ÿ”” Notifications - lets the app post notifications (adds POST_NOTIFICATIONS). - โฐ Reminders - adds scheduled notifications that fire when the app is closed (adds the exact-alarm + reschedule-on-boot plumbing). Ticking Reminders auto-enables Notifications. Leave both off for apps that don't need notifications - nothing extra is added to your APK. ### 3. Call the bridge from your web code Appwright exposes a JavaScript bridge at window.WebToApk. Always feature-detect it first - it only exists inside the installed app, so your site still works in a browser. ``` const b = window.WebToApk || null; // Ask once (Android 13+): b?.requestNotificationPermission(); // Tier 1 โ€” instant (app open): b?.showNotification('Saved', 'Your note was saved', '', 'note'); // Tier 2 โ€” scheduled reminder in 1 hour: b?.scheduleNotification( 'water-1', 'Drink water', 'Time for a glass of water', String(Date.now() + 60*60*1000), 'daily' // none | minutely | hourly | daily | weekly ); b?.cancelNotification('water-1'); b?.cancelAllNotifications(); const pending = JSON.parse(b?.getScheduledNotifications() || '[]'); ``` ### 4. Let the AI wire it for you In AI App Studio, just describe what you want - e.g. 'a water reminder that pings me every 2 hours' or 'a medication tracker that reminds me at 8am daily'. When your request mentions reminders/alarms/notifications, Appwright automatically teaches the AI about this bridge so the generated app uses it correctly (with a graceful fallback when it isn't available). ### 5. Your own sound instead of the Android default Put an audio file in your web bundle (mp3, wav, ogg, m4a, flac or opus) and name its path as the sound. It works the same on an instant notification and on a reminder that fires days later with the app closed. The file is copied into place when you schedule, so a wrong file name comes back as false straight away instead of turning into the default sound at 7am. One Android rule worth knowing: a notification channel's sound is fixed when the channel is created, so Appwright makes a separate channel per sound. Changing a sound therefore resets that channel's own settings in Android's notification screen - unavoidable, and the honest behaviour. ``` const b = window.WebToApk || null; // Instant, with your sound: b?.notify(JSON.stringify({ title: 'Order confirmed', body: 'Arriving Tuesday', sound: '/audio/chime.mp3' // a file in YOUR bundle })); // Check a sound before you save it in a settings screen: const ok = b?.prepareNotificationSound('/audio/chime.mp3'); // 'silent' for no sound at all; leave it out for the Android default. ``` ### 6. Reminders with sound, pictures and buttons scheduleNotificationEx() takes everything notify() takes, plus when to fire it. Use it instead of the older five-argument scheduleNotification() when you want anything more than a title and a body. ``` const b = window.WebToApk || null; b?.scheduleNotificationEx('dose-1', JSON.stringify({ at: Date.now() + 60*60*1000, // epoch millis repeat: 'daily', // none|minutely|hourly|daily|weekly title: 'Time for your dose', body: 'Vitamin D โ€” 1 tablet', sound: '/audio/chime.mp3', actions: [{ id: 'taken', label: 'Taken' }] })); // Returns false if the sound file does not exist โ€” check it. ``` ### 7. Opening the app itself at a set time Add openApp: true and the reminder becomes a full-screen alert: on a locked phone the app opens over the lock screen, on an unlocked one it appears as a heads-up banner. This is the whole of what Android allows. An app cannot silently launch itself in the background - that has been blocked since Android 10 - so any page that promises 'the app just opens at 7am' has to go through this. On Android 14+ the user grants the alert per app; check canOpenAppAtTime() and send them to the settings screen if it says denied. When the app opens this way your page gets an appmint:reminder event, so it can run the work it could not do while closed. ``` const b = window.WebToApk || null; if (b?.canOpenAppAtTime() === 'denied') b?.requestOpenAppPermission(); b?.scheduleNotificationEx('standup', JSON.stringify({ at: tomorrowAt9am, repeat: 'daily', title: 'Stand-up', body: 'Daily sync starts now', sound: '/audio/alarm.mp3', openApp: true })); window.addEventListener('appmint:reminder', e => { // e.detail = { id: 'standup', overLockScreen: true } showReminderScreen(e.detail.id); }); ``` ### 8. Reliability on real devices Scheduled reminders are as reliable as Android allows: - On Android 12+, exact timing needs SCHEDULE_EXACT_ALARM. If it is off for your app, the reminder is still armed but Doze may batch it by minutes. Ask getAlarmPrecision() - it answers 'exact' or 'inexact' - and call requestExactAlarms() to send the user to the setting rather than letting them think your app is late. - Some aggressive phones (Xiaomi, Samsung, Oppo, etc.) kill background alarms to save battery. For critical reminders, ask users to disable battery optimization for your app. - Reminders are re-armed automatically after a reboot or an app update. --- # Appwright Push - Notify Your Users > Send a notification to every installed copy of your Appwright app from a web dashboard. - **Applies to:** Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/appwright-push Send notifications to everyone who installed your app, straight from Appwright on your own Firebase project - schedule, edit after sending, cancel, deep links ### 1. How It Works Appwright Push sends notifications to everyone who installed your app, from inside Appwright - send now, schedule, edit after sending, cancel, deep links. It runs on YOUR OWN Firebase project: your users, your project, no shared limits, and nothing of yours hosted by Appwright. Firebase Cloud Messaging is free with no message limit. You need two files from that project - google-services.json (for the build) and a service-account key (for sending). The next steps get both. (iOS is separate and unchanged: your APNs key via 'iOS push setup' in Notifications.) Appwright Push and OneSignal are alternatives: turning one on turns the other off. ### 2. Create a Firebase Project Go to Firebase Console and click 'Add project' (or open a project you already have). A free Firebase project is enough - no billing needed for notifications. One project can hold several of your apps. [Open Firebase Console](https://console.firebase.google.com) ### 3. Add Your App and Download google-services.json In your Firebase project: - Click 'Add app' and choose Android - Enter the package name EXACTLY as set in the Appwright wizard (e.g. com.yourname.yourapp) - a mismatch means notifications never arrive, and Appwright refuses the build for it - Click 'Register app' and download google-services.json - You can skip the remaining SDK steps - Appwright handles them ### 4. Turn It On and Upload the File In the build wizard's Push Notifications card: - Enable 'Appwright Push' - Tap 'Upload google-services.json' and pick the file from the previous step The wizard checks the file contains your package name. Then build your APK as normal - during the build your app is registered for push against your project. Users just install the app and open it once. On Android 13+ they are asked to allow notifications after the first screen loads. ### 5. Upload Your Service-Account Key (Once) Appwright sends through your project, so it needs a key from it - the same file OneSignal asks for. In Firebase Console: - โš™ Project settings โ†’ 'Service accounts' tab - Click 'Generate new private key' and confirm - a JSON file downloads In Appwright: Home > Tools > Notifications โ†’ pick your app โ†’ 'Upload key' โ†’ choose that JSON. Appwright verifies the key with a test send to your project before storing it (encrypted), so a green tick means the next send will really go out. One upload per app; rebuilding the app does not need it again unless you switch Firebase projects. โš ๏ธ Upload the SERVICE-ACCOUNT key here, not google-services.json - the screen tells you if you mix them up. To revoke access later, delete the key in Firebase Console. ### 6. Send Your First Notification In Home > Tools > Notifications: - Pick your app - Write a title (message, image and link are optional) - Tap 'Send notification' Every phone with your app installed receives it. Use 'Preview' first to see it exactly as users will - including a real notification on your own phone. ### 7. Schedule for Later In the compose form, tap the Delivery row and choose 'Schedule for later...', then pick a date and time. The notification goes out automatically at that moment. Until it fires, you can edit or cancel it from the History list below. ### 8. Fix a Mistake After Sending Tap any notification in History: - 'Edit & resend' - replaces it IN PLACE on every device with the corrected version - 'Cancel' - removes it from every phone where it has not been opened yet - 'Delete' - same as cancel, and clears it from your history โš ๏ธ A notification the user already opened or dismissed cannot be taken back. ### 9. Open a Specific Page The optional Link field controls what a tap opens: - A full https:// address - An in-app route like #/offers Your app's code can also read it any time: ``` const link = window.WebToApk?.getLaunchUrl?.(); window.addEventListener('appmint:deep-link', e => goTo(e.detail.url)); ``` ### 10. In-App Inbox (Optional) Every push is also stored inside the installed app (last 50), so your app can show its own notifications screen with unread badges. This works in EVERY build mode - including website (URL) apps, where you just add these few lines to your own site. Dismiss is a SWIPE, not a tiny โœ•, and it is reversible - pair it with a brief Undo: ``` const B = window.WebToApk; const inbox = JSON.parse(B?.getPushInbox?.() ?? "[]"); // [{id,title,body,imageUrl,link,receivedAt,read}] const unread = inbox.filter(m => !m.read).length; // live arrival while the app is open window.addEventListener('appmint:push', e => { if (e.detail.type === 'received') showBanner(e.detail.message); refreshInbox(); // fires for 'revoked' too }); B?.markPushRead?.(id); // "" = mark all read B?.dismissPushMessage?.(id); // swipe away B?.restorePushMessage?.(id); // ...Undo ``` ### 11. If Nothing Arrives on Your Test Phone Almost always a limit on the TEST PHONE, not your app or your users. Android allows roughly 100 push registrations per phone. Every FRESH install of an app that uses notifications takes one - so installing build after build while testing can use them all up, and that phone then stops receiving for newly installed apps. Free up slots (this is what actually works): - Uninstall test apps you no longer need - Android releases the slot each one was holding - Or clear a test app's data: Settings > Apps > [that app] > Storage > Clear data - that releases its slot without uninstalling - On an emulator, wiping it resets every slot at once Restarting the phone does NOT help: these registrations are stored permanently, which is why they survive a reboot. Clearing Google Play Services' cache does not free them either. Avoid it while testing: - Keep ONE package name while you iterate - every different package name takes its own slot - Install the new APK OVER the old one instead of uninstalling first - an update keeps the existing registration, a reinstall spends a new one - Test on a spare phone or an emulator; wiping an emulator resets its slots instantly - Delete test apps you have finished with Also check the basics: the app was opened at least once after installing, and notifications are allowed for it in Android settings. The app re-subscribes on every launch, so it recovers by itself once slots free up - no rebuild needed. ### 12. Limits and Good Practice - No limit on how many notifications you send - Firebase Cloud Messaging is free - Title up to 120 characters; image links must be https:// - On the free plan a short ad plays before each send - Test apps: remove them with the trash icon next to the app selector - this deletes the history and stored keys in Appwright only; your Firebase project is untouched, and a rebuild adds the app back - Rebuilding with a NEW google-services.json from the SAME project changes nothing: your key stays, existing installs keep receiving - โš ๏ธ Moving the app to a DIFFERENT Firebase project is a clean break. Rebuild with the new google-services.json, upload the new project's key (Appwright drops the old one, since it could never send for the new project) - and know that phones still running the OLD version stop receiving until their users update. Notifications reach a project, and those installs are still listening to the old one - Send sparingly: users uninstall apps that spam them --- # OneSignal Integration > Use OneSignal instead, for segments, scheduling and delivery reporting. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/onesignal-push Push Notifications Setup ### 1. Create OneSignal Account Sign up for a free OneSignal account to get started with push notifications. OneSignal provides unlimited push notifications for free. [Go to OneSignal.com](https://onesignal.com) ### 2. Create New App In OneSignal dashboard: - Click 'New App/Website' - Enter your app name - Select 'Google Android (FCM)' as platform - Click 'Next' ### 3. Create Firebase Project You need a Firebase project for OneSignal to work. Go to Firebase Console and create a new project or use an existing one. [Open Firebase Console](https://console.firebase.google.com) ### 4. Add Android App to Firebase _(AppMint only)_ In your Firebase project: - Click 'Add App' and select Android - Enter your app's package name (MUST match the package name you set in the generator) - Download 'google-services.json' file - โš ๏ธ IMPORTANT: This is the file you will upload to the Web2APK Generator later. - Click 'Continue' and finish setup ### 4. Add Android App to Firebase _(Appwright only)_ In your Firebase project: - Click 'Add App' and select Android - Enter your app's package name (MUST match the package name you set in the generator) - Download 'google-services.json' file - โš ๏ธ IMPORTANT: This is the file you will upload to Appwright later. - Click 'Continue' and finish setup ### 5. Create Firebase Service Account Key _(AppMint only)_ OneSignal requires a Firebase Service Account JSON to send notifications from their server. In Firebase Console: - Go to Project Settings > Service accounts tab - Click 'Generate new private key' - Confirm and download the JSON file โš ๏ธ WARNING: This file is ONLY for the OneSignal Dashboard. Do NOT upload this file to the Web2APK generator. [Open Firebase Console](https://console.firebase.google.com) ### 5. Create Firebase Service Account Key _(Appwright only)_ OneSignal requires a Firebase Service Account JSON to send notifications from their server. In Firebase Console: - Go to Project Settings > Service accounts tab - Click 'Generate new private key' - Confirm and download the JSON file โš ๏ธ WARNING: This file is ONLY for the OneSignal Dashboard. Do NOT upload this file to Appwright. [Open Firebase Console](https://console.firebase.google.com) ### 6. Configure OneSignal with Service Account Back in OneSignal dashboard setup: - When asked for Firebase credentials, choose 'Upload Service Account File' - Upload the Service Account JSON you downloaded from Firebase - Click 'Save & Continue' - Copy your OneSignal App ID (you'll need this in the generator) ### 7. Setup in Web2APK Generator _(AppMint only)_ In the generator app: 1. Enable 'OneSignal' toggle in Step 3 2. Click 'Upload google-services.json' button 3. Select the 'google-services.json' file (the one from Step 4) โš ๏ธ DO NOT upload the Service Account key from Step 5 here! 1. Paste your OneSignal App ID 2. Generate your APK ``` OneSignal App ID format: 12345678-1234-1234-1234-123456789012 ``` ### 7. Setup in Appwright _(Appwright only)_ In the generator app: 1. Enable 'OneSignal' toggle in Step 3 2. Click 'Upload google-services.json' button 3. Select the 'google-services.json' file (the one from Step 4) โš ๏ธ DO NOT upload the Service Account key from Step 5 here! 1. Paste your OneSignal App ID 2. Generate your APK ``` OneSignal App ID format: 12345678-1234-1234-1234-123456789012 ``` ### 8. Test Push Notifications After installing your generated APK: - Open the app on your device - Go to OneSignal dashboard - Navigate to 'Messages' > 'New Push' - Compose and send a test notification - You should receive it on your device within seconds ### 9. Important Notes โš ๏ธ Package Name: The package name in Firebase MUST exactly match your app's package name โš ๏ธ File Upload: Always upload google-services.json when using OneSignal, otherwise notifications won't work โš ๏ธ First Launch: Users must open the app at least once to receive notifications โœ… Free Forever: OneSignal is 100% free with unlimited notifications --- # AppMint Push - Notify Your Users > Send a notification to every installed copy of your AppMint app from a web dashboard. - **Applies to:** AppMint - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/appmint-push Send notifications to everyone who installed your app, straight from AppMint on your own Firebase project - schedule, edit after sending, cancel, deep links ### 1. How It Works AppMint Push sends notifications to everyone who installed your app, from inside AppMint - send now, schedule, edit after sending, cancel, deep links. It runs on YOUR OWN Firebase project: your users, your project, no shared limits, and nothing of yours hosted by AppMint. Firebase Cloud Messaging is free with no message limit. You need two files from that project - google-services.json (for the build) and a service-account key (for sending). The next steps get both. Works with every build mode: website, ZIP, HTML and AI. AppMint Push and OneSignal are alternatives: turning one on turns the other off. ### 2. Create a Firebase Project Go to Firebase Console and click 'Add project' (or open a project you already have). A free Firebase project is enough - no billing needed for notifications. One project can hold several of your apps. [Open Firebase Console](https://console.firebase.google.com) ### 3. Add Your App and Download google-services.json In your Firebase project: - Click 'Add app' and choose Android - Enter the package name EXACTLY as set in the AppMint wizard (e.g. com.yourname.yourapp) - a mismatch means notifications never arrive, and AppMint refuses the build for it - Click 'Register app' and download google-services.json - You can skip the remaining SDK steps - AppMint handles them ### 4. Turn It On and Upload the File In the build wizard's Push Notifications card: - Enable 'AppMint Push' - Tap 'Upload google-services.json' and pick the file from the previous step The wizard checks the file contains your package name. Then build your APK as normal - during the build your app is registered for push against your project. Users just install the app and open it once. On Android 13+ they are asked to allow notifications after the first screen loads. ### 5. Upload Your Service-Account Key (Once) AppMint sends through your project, so it needs a key from it - the same file OneSignal asks for. In Firebase Console: - โš™ Project settings โ†’ 'Service accounts' tab - Click 'Generate new private key' and confirm - a JSON file downloads In AppMint: Home > Notifications โ†’ pick your app โ†’ 'Upload key' โ†’ choose that JSON. AppMint verifies the key with a test send to your project before storing it (encrypted), so a green tick means the next send will really go out. One upload per app; rebuilding the app does not need it again unless you switch Firebase projects. โš ๏ธ Upload the SERVICE-ACCOUNT key here, not google-services.json - the screen tells you if you mix them up. To revoke access later, delete the key in Firebase Console. ### 6. Send Your First Notification In Home > Notifications: - Pick your app - Write a title (message, image and link are optional) - Tap 'Send Notification' Every phone with your app installed receives it. Use 'Preview' first to see it exactly as users will - including a real notification on your own phone. ### 7. Schedule for Later In the compose form, tap the Delivery row and choose 'Schedule for later...', then pick a date and time. The notification goes out automatically at that moment. Until it fires, you can edit or cancel it from the History list below. ### 8. Fix a Mistake After Sending Tap any notification in History: - 'Edit & resend' - replaces it IN PLACE on every device with the corrected version - 'Cancel' - removes it from every phone where it has not been opened yet - 'Delete' - same as cancel, and clears it from your history โš ๏ธ A notification the user already opened or dismissed cannot be taken back. ### 9. Open a Specific Page The optional Link field controls what a tap opens: - A full https:// address (great for website apps) - An in-app route like #/offers Your app's code can also read it any time: ``` const link = window.WebToApk?.getLaunchUrl?.(); window.addEventListener('appmint:deep-link', e => goTo(e.detail.url)); ``` ### 10. In-App Inbox (Optional) Every push is also stored inside the installed app (last 50), so your app can show its own notifications screen with unread badges. This works in EVERY build mode - including website (URL) apps, where you just add these few lines to your own site. Dismiss is a SWIPE, not a tiny โœ•, and it is reversible - pair it with a brief Undo: ``` const B = window.WebToApk; const inbox = JSON.parse(B?.getPushInbox?.() ?? "[]"); // [{id,title,body,imageUrl,link,receivedAt,read}] const unread = inbox.filter(m => !m.read).length; // live arrival while the app is open window.addEventListener('appmint:push', e => { if (e.detail.type === 'received') showBanner(e.detail.message); refreshInbox(); // fires for 'revoked' too }); B?.markPushRead?.(id); // "" = mark all read B?.dismissPushMessage?.(id); // swipe away B?.restorePushMessage?.(id); // ...Undo ``` ### 11. If Nothing Arrives on Your Test Phone Almost always a limit on the TEST PHONE, not your app or your users. Android allows roughly 100 push registrations per phone. Every FRESH install of an app that uses notifications takes one - so installing build after build while testing can use them all up, and that phone then stops receiving for newly installed apps. Free up slots (this is what actually works): - Uninstall test apps you no longer need - Android releases the slot each one was holding - Or clear a test app's data: Settings > Apps > [that app] > Storage > Clear data - that releases its slot without uninstalling - On an emulator, wiping it resets every slot at once Restarting the phone does NOT help: these registrations are stored permanently, which is why they survive a reboot. Clearing Google Play Services' cache does not free them either. Avoid it while testing: - Keep ONE package name while you iterate - every different package name takes its own slot - Install the new APK OVER the old one instead of uninstalling first - an update keeps the existing registration, a reinstall spends a new one - Test on a spare phone or an emulator; wiping an emulator resets its slots instantly - Delete test apps you have finished with Also check the basics: the app was opened at least once after installing, and notifications are allowed for it in Android settings. The app re-subscribes on every launch, so it recovers by itself once slots free up - no rebuild needed. ### 12. Limits and Good Practice - No limit on how many notifications you send - Firebase Cloud Messaging is free - Title up to 120 characters; image links must be https:// - On the free plan a short ad plays before each send - Test apps: remove them with the trash icon next to the app selector - this deletes the history and stored key in AppMint only; your Firebase project is untouched, and a rebuild adds the app back - Rebuilding with a NEW google-services.json from the SAME project changes nothing: your key stays, existing installs keep receiving - โš ๏ธ Moving the app to a DIFFERENT Firebase project is a clean break. Rebuild with the new google-services.json, upload the new project's key (AppMint drops the old one, since it could never send for the new project) - and know that phones still running the OLD version stop receiving until their users update. Notifications reach a project, and those installs are still listening to the old one - Send sparingly: users uninstall apps that spam them --- # Immersive Kiosk Mode > Lock the app to one screen for a shop counter, a museum or a demo unit. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/kiosk-mode True Fullscreen Without Nav Bars ### 1. What is Immersive Kiosk Mode? Immersive Kiosk Mode hides the Android navigation bar (back, home, recents) and the status bar permanently. The app takes over the entire screen. Perfect for: - Kiosk / POS terminals - Gaming apps - Digital signage displays - Exhibition / demo tablets - Restaurant ordering tablets ### 2. Enable in Generator In Step 2 (Display Settings): 1. Scroll to 'Display Options' 2. Toggle ON 'Immersive Kiosk Mode' 3. Generate your APK No code changes needed on your website - this is purely an Android-level feature. ### 3. How Users Exit Users can still access navigation by swiping from the screen edge. The bars will appear briefly (translucent) and then auto-hide again. โš ๏ธ Important: If you combine this with 'Exit Confirmation', the user must swipe to reveal the back button. Consider your use case carefully. ๐Ÿ’ก Tip: For true kiosk lockdown, also disable Pull-to-Refresh and set Link Mode to 'Internal'. ### 4. Best Practices โœ… Test thoroughly before deploying on kiosk devices โœ… Combine with Portrait or Landscape lock for single-orientation kiosks โœ… Disable Zoom for touch-screen kiosks โš ๏ธ Not recommended for general consumer apps - users may get confused โš ๏ธ Google Play policy: Kiosk apps should clearly state their purpose --- # Picture-in-Picture (PiP) > Float the video in a corner while the user does something else. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/picture-in-picture Floating Video Window Support ### 1. What is Picture-in-Picture? PiP allows your app to continue showing content in a small floating window when the user presses Home or switches apps. Perfect for: - Video streaming apps (YouTube-style) - Live sports / event websites - Video conferencing web apps - Music visualizer websites - Educational video platforms ### 2. Enable in Generator In Step 2 (Display Settings): 1. Scroll to 'Display Options' 2. Toggle ON 'Picture-in-Picture (PiP)' 3. Generate your APK Requires Android 8.0 (API 26) or higher. Older devices will ignore this setting gracefully. ### 3. How It Works When PiP is enabled: 1. User is watching video / using your app 2. User presses Home button 3. App shrinks to a small floating window 4. User can interact with other apps while watching 5. Tapping the PiP window returns to full app The PiP window shows whatever the WebView was displaying at the moment. ### 4. Website Optimization (Optional) Your website can detect PiP mode and optimize the view. When the window shrinks, you may want to hide menus and show only the video. ``` // Detect PiP mode in your website JS: document.addEventListener('resize', function() { if (window.innerWidth < 300) { // PiP mode - show only video document.querySelector('.navbar').style.display = 'none'; document.querySelector('video').style.width = '100%'; } else { // Full mode - restore UI document.querySelector('.navbar').style.display = 'block'; } }); ``` ### 5. Limitations โš ๏ธ Android 8.0+ only (API 26) โš ๏ธ Some devices may not support PiP (e.g., Android Go edition) โš ๏ธ PiP window size is controlled by Android, not your app โœ… Works with both video and non-video content โœ… No website changes required - works out of the box --- # Background Audio Playback > Keep playing when the user leaves the app or turns the screen off. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/background-audio Keep Audio Playing When Minimized ### 1. What is Background Audio? When enabled, audio/music playing in your WebView will continue playing even when the user switches to another app or turns off the screen. Perfect for: - Music streaming websites (SoundCloud, Bandcamp) - Podcast platforms - Radio stations - Audio meditation / prayer apps - YouTube music channels ### 2. Enable in Generator In Step 2 (Display Settings): 1. Scroll to 'Display Options' 2. Toggle ON 'Background Audio Playback' 3. Generate your APK No code changes needed on your website. ### 3. How It Works Normally, when an Android app goes to the background, its WebView pauses all media playback. With Background Audio enabled: - WebView is NOT paused when app goes to background - Audio continues playing via the device speaker or headphones - Video playback also continues (audio portion) ๐Ÿ’ก Tip: Combine with PiP for video sites so users can watch in a floating window AND keep audio when the window is dismissed. ### 4. Battery Considerations โš ๏ธ Background audio keeps the WebView process alive, which uses more battery. โœ… Recommended: Only enable for apps that genuinely need background audio โœ… Users can still stop playback by pausing on your website or force-stopping the app โœ… Android's battery optimization may eventually pause background apps - this is normal system behavior. ### 5. Website Best Practices For the best background audio experience, ensure your website: โœ… Uses the HTML5
link โœ… A JS redirect (window.location.href = 'upi://...') โœ… A popup - window.open('paytmmp://...') - which is how Razorpay Checkout hands off to UPI apps So Razorpay / Cashfree / PhonePe checkout pages work as-is: the customer taps a UPI option, the payment app opens, they approve, and your page's success handler fires when they return. If no app on the phone can handle the scheme, the user sees a clear toast instead of a blank error page. โš ๏ธ Selling DIGITAL goods in a Play Store app? Google requires Play Billing for those - use the IAP guide instead. UPI/card checkout is for physical goods, services, and apps distributed outside Play. ``` Pay โ‚น99 via UPI ``` ### 8. Complete Example - Razorpay Checkout with UPI, End to End A full, copy-pasteable flow using Razorpay Checkout. What happens on the phone: 1. Customer taps 'Pay โ‚น499' โ†’ Razorpay Checkout opens in the page 2. Customer picks UPI โ†’ their UPI app opens automatically (this is the paytmmp:// / phonepe:// hand-off the app handles for you) 3. Customer approves in the UPI app and returns - Android brings your app back on its own 4. The handler function fires with a payment ID โ†’ show your success screen 5. dismiss fires if they cancel โ†’ let them retry Replace rzp_test_XXXXXXXX with your key from dashboard.razorpay.com. Test with a key_test key first - Razorpay's test mode simulates UPI approval. โš ๏ธ For a real store, confirm the payment server-side (Razorpay webhook or Orders API + signature check) before shipping goods - the browser-side handler alone can be faked. ```

Premium Plan โ€” โ‚น499

``` --- # Block Screenshots on a Page > Turn off screenshots and screen recording for pages that show something private. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/block-screenshots - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Let one page in your app turn off screenshots, screen recording and the recents preview - and give it back when the user leaves ### 1. What this does One line from your page turns on Android's screen-capture block for the screen the user is looking at: - screenshots fail (the user gets Android's "can't take screenshot" message) - screen recording and casting show a black frame - the app's preview in the recents screen is hidden What it cannot do - and nothing on Android can - is stop someone photographing the screen with another phone. If you need that, you need a watermark, not a flag. ``` const b = window.WebToApk || null; // On the sensitive screen: b?.setSecureScreen(true); // Leaving it early (a modal closing, a tab switch inside one page): b?.setSecureScreen(false); // Current state: const locked = b?.isSecureScreen(); ``` ### 2. Protect one page, not the whole app Protection belongs to the page that asked for it. Appwright releases it the moment the app navigates anywhere else - a new page, a pushState route, a #hash route - so your statement screen can be protected while the rest of the app takes screenshots normally. That also means a page has to ask again after a reload: a fresh document has not asked for anything yet, and inheriting the last one's answer is how apps end up locked down on screens nobody meant to protect. ### 3. In a single-page app Route changes inside one page release protection automatically, so the usual pattern is to assert it when the protected view mounts and let navigation do the rest. ``` function showStatement() { render(statementView); window.WebToApk?.setSecureScreen(true); } // Nothing to undo on the way out โ€” moving to another route // releases it. Call setSecureScreen(false) yourself only when // you hide the sensitive content WITHOUT navigating. ``` ### 4. Do not use a timer for this A common idea is to re-assert protection on an interval, say every second, and let it lapse when the page stops asking. It does not work, and it is worth knowing why: Android does not consult your app when the user presses the screenshot buttons. The flag has to already be on the window. Any second where your page is busy, throttled in the background, or between timer ticks is a second in which the screenshot succeeds. Ask once when the sensitive content appears. Appwright takes protection away when the user leaves. --- # Remote Update > Push new app content to installed copies without a Play release. - **Applies to:** Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/remote-update Change your published apps without rebuilding ### 1. What it does Change ads, links, menu text and announcements in apps people already installed - no rebuild, no new APK, no Play review. Users see the change the next time they open the app. ### 2. Connect Supabase once Remote updates live in your own free Supabase project, so you stay in control of your data. Open the Remote Update screen (side menu) and tap Connect Supabase - one time, then every app you build can use it. ### 3. Edit โ†’ Publish โ†’ live on next open Pick the app, edit what you want, tap Publish. That's the whole loop. ### 4. Who gets it Remote Update publishing is included with every paid plan - Pro ($5/mo), Standard or Max - for unlimited apps. On Free you can explore the whole panel; publishing is the part that needs a plan. --- # Publish to Web > Put the same build online as a website, on a URL you can share. - **Applies to:** Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/publish-to-web Free subdomain instantly, custom domain when you're ready ### 1. One project - app AND website Every app you build can also be published to the web. Same code, same design: you get an installable APK for the Play Store and a live URL to share. ### 2. Free subdomain, instantly Publishing gives your app a free web address right away - nothing to configure, hosting included. ### 3. Custom domain Want yourapp.com? Connect a domain you own from the publish screen - the guided flow shows the exact DNS record to add and detects your provider. Custom domains are paid with Mints, no separate purchase. --- # Deep Linking > Open a specific page in the app from a link, a QR code or a notification. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/deep-linking - **Using AppMint:** wherever the text below says "Appwright", read "AppMint". The two builders share this feature and only the name changes. Steps where the instructions genuinely differ are given separately and labelled. Open Specific Pages from URLs & Notifications ### 1. What is Deep Linking? Deep Linking allows external sources (browsers, SMS, emails, social media, push notifications, QR codes) to open your app directly to a specific page instead of the homepage. Your app supports two kinds, and they are NOT equally easy: 1. โšก Custom Scheme (myapp://path) - works immediately, nothing to set up. Best for push notifications and QR codes. 1. ๐ŸŒ https:// links (https://mysite.com/page) - needs ONE file uploaded to your website. Without it Android keeps the link in the browser and your app never opens. This is the single most common reason deep links 'do not work'. Both are covered below. If you only need something working today, use the custom scheme. ### 2. How Custom Scheme URLs Work When a custom scheme link is tapped (e.g., myapp://products/shoes), the app: 1. Receives the custom scheme URL 2. Automatically maps it to your website URL 3. Opens the WebView to the correct page โœ… The scheme is derived from your App Name (lowercase, letters only). โœ… Example mapping: myapp://products/shoes โ†’ https://mysite.com/products/shoes The path segments after the scheme are preserved and appended to your website domain. ``` Custom scheme format: :// Examples: myshop://cart โ†’ https://myshop.com/cart puma://in/en/help โ†’ https://in.puma.com/in/en/help ``` ### 3. https:// links - one file must go on your website This is the part people miss, so read it before testing. Appwright puts your domain in the app automatically. But Android will NOT open your app from an https:// link until your WEBSITE proves the app belongs to you. On Android 12 and newer, an unproven link just opens the browser - no dialog, no choice. That is why a link can look like it 'does nothing'. The proof is one small file. Appwright already made it for you: after you build, look in Downloads for assetlinks.json. It contains your package name and your app's signing fingerprint. Upload it to your website at EXACTLY this address (the folder name starts with a dot): ``` https://yoursite.com/.well-known/assetlinks.json Rules โ€” all four must be true: 1. Served over https (not http) 2. Content-Type: application/json 3. NO redirect (a redirect to www breaks it) 4. Reachable publicly โ€” no login, no Cloudflare challenge Check it from any computer: curl -i https://yoursite.com/.well-known/assetlinks.json ``` ### 4. Publishing on Google Play? Use Play's fingerprint Google Play re-signs your app with ITS own key. So the fingerprint inside the assetlinks.json Appwright generated - which is your local key - will not match what users actually install from Play. If you publish on Play, replace the fingerprint: 1. Play Console โ†’ your app โ†’ Test and release โ†’ Setup โ†’ App integrity 2. Copy the SHA-256 from 'App signing key certificate' 3. Put that value in assetlinks.json and re-upload it You can list BOTH fingerprints in the same file - the local one for APKs you share directly, and Play's for Play installs. That way both work. ``` [{ "relation": ["delegate_permission/common.handle_all_urls"], "target": { "namespace": "android_app", "package_name": "com.yourapp.name", "sha256_cert_fingerprints": [ "AA:BB:...", // your local key (direct APK) "11:22:..." // Play App Signing key ] } }] ``` ### 5. Check whether Android accepted it After uploading the file, REINSTALL the app (Android only checks at install time, and it can take a minute). The command below tells you the truth. Look for your domain with 'verified'. If it says 'legacy_failure' or the domain is missing, the file is not being read correctly - go back and check the four rules. ``` # Does Android consider your domain verified? adb shell pm get-app-links com.yourapp.name # Force a re-check without reinstalling (Android 12+): adb shell pm verify-app-links --re-verify com.yourapp.name # Google's own checker (open in a browser): https://digitalassetlinks.googleapis.com/v1/statements:list? source.web.site=https://yoursite.com& relation=delegate_permission/common.handle_all_urls ``` ### 6. Why it usually fails In order of how often each one is the cause: 1. The file is not uploaded, or is at the wrong path. It must be /.well-known/assetlinks.json - with the dot. 1. www vs no-www. Android treats https://example.com and https://www.example.com as DIFFERENT domains. Appwright registers the exact host from your Website URL. If you use both, put the file on both, and tell us which host you entered. 1. A redirect. Many hosts redirect the apex domain to www. That breaks verification even though the file loads in a browser. 1. Published on Play but using the local fingerprint - see the previous step. 1. Not reinstalled after uploading. Android checks at install time. Custom scheme links (myapp://...) need NONE of this and always work - use them for push notifications and QR codes if you want something working today. ### 7. Testing Deep Links via ADB Test your deep links from a PC connected via USB debugging: 1. Enable USB Debugging on your phone 2. Connect via USB 3. Run the commands below. โš ๏ธ Important: 'am start' with an https link can open your app even when verification has FAILED, because you are handing the link straight to Android. It does not prove a real tap in Gmail or Chrome will work. Only 'pm get-app-links' (previous step) tells you that. Use am start to check your PAGE routing, not your domain setup. ``` # Test custom scheme: adb shell am start -a android.intent.action.VIEW \ -d "myapp://some/page" # Test website URL: adb shell am start -a android.intent.action.VIEW \ -d "https://mysite.com/some/page" # Verify which app handles it: adb shell cmd package resolve-activity --brief \ -a android.intent.action.VIEW \ -d "myapp://some/page" ``` ### 8. Deep Links in Push Notifications When sending a push notification via OneSignal, paste any deep link URL into the 'Launch URL' field. The app will open and navigate directly to that page. Both URL types work: โœ… https://mysite.com/flash-sale โœ… myapp://flash-sale ``` OneSignal Dashboard โ†’ New Push: Launch URL: https://mysite.com/flash-sale OR Launch URL: myapp://flash-sale Both will open the app on the sale page. ``` ### 9. Important: Warm Launch Behaviour If your app is already running in the background when a deep link is triggered, Android delivers the link directly without restarting the app - this is called a 'warm launch'. โœ… Your app handles this automatically. The WebView will navigate to the new page even when the app is already open. โš ๏ธ If deep links only open the homepage, check that: - Your Website URL in config matches the domain in the deep link - The path in the custom scheme URL is correct โš ๏ธ If https:// links do not open the app AT ALL, this is not a warm-launch problem - it is the assetlinks.json step above. Custom scheme links working while https links do nothing is the classic sign. ``` Warm launch works automatically. No extra setup needed. ``` --- # Offline Website Pro > Ship the site inside the APK so it opens with no connection at all. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/offline-website Convert ZIP to High-Performance Offline App ### 1. Prepare Your Website Files Collect all your HTML, CSS, JS, and image files into a single folder. Ensure your main entry file is named 'index.html'. Tips: - Use relative paths for all assets (e.g., ) - Avoid absolute paths (e.g., /C:/Users/...) - Keep filenames and folder names case-sensitive (Android is Linux-based) ### 2. Create a ZIP Archive Compress your website files into a standard .zip archive. Recommended structure: - index.html - style.css (optional) - script.js (optional) - images/... (optional) โœ… The generator accepts both: 1. ZIP with files directly at root 2. ZIP containing one parent folder (auto-detected) โš ๏ธ Avoid deep nesting like project/build/site/index.html unless that is your intended web root. ### 3. Quick Minimal Test (Exact) Use this exact test to verify your setup: 1. Create a folder named test_site 2. Add one file named index.html 3. Paste your sample HTML into index.html 4. Zip it as test_site.zip 5. In Step 1, choose Offline ZIP and select test_site.zip 6. Generate APK and install If this opens correctly, your pipeline is fine and larger-project blank screens are usually path or asset issues. ``` test_site.zip โ””โ”€โ”€ index.html ``` ### 4. Select Offline Mode _(AppMint only)_ In the Web2APK Generator (Step 1): 1. Select the 'Offline ZIP' radio button 2. Click the 'Choose Website ZIP' button 3. Select your prepared .zip file from your device storage ### 4. Select Offline Mode _(Appwright only)_ In Appwright (Step 1): 1. Select the 'Offline ZIP' radio button 2. Click the 'Choose Website ZIP' button 3. Select your prepared .zip file from your device storage ### 5. Generate Your APK Complete the rest of the steps (App Name, Icon, Splash, etc.) and click 'Generate APK'. Your website will now run entirely offline without needing an internet connection! ### 6. Troubleshooting โŒ Blank screen? Open index.html in a browser first and check for JS errors. โŒ Blank screen with simple ZIP? Rebuild with latest generator version (offline extraction fix included). โŒ Broken images/CSS/JS? Verify paths are relative (./assets/logo.png), not absolute (/assets/logo.png or C:\...). โŒ CSS not loading? Check exact filename case (style.css vs Style.css). โŒ JS modules failing? Ensure module file paths exist in the ZIP. --- # CSS / JS Quick Injection > Restyle or patch a website you are wrapping without editing the website. - **Applies to:** AppMint and Appwright - **Source:** the Integration Guide shipped inside the app; this page is generated from it. - **HTML:** https://freewebtoapk.com/docs/css-and-js-injection Global Tweaks - Same CSS & JS on Every Page ### 1. What Is CSS/JS Quick Injection? Quick Injection adds the SAME CSS and JavaScript to EVERY page your app loads - no conditions, no per-site rules. Think of it as a global style sheet and a global startup script that always runs, no matter which page the user is on. โœ… Perfect for: - Branding tweaks (colors, fonts, logo) - Hiding global elements (header, footer, cookie banner) - Small layout or font-size fixes for all pages - Adding a global floating button (WhatsApp, chat, back-to-top) โŒ NOT designed for: - Running code only on specific URLs โ†’ use Smart User Scripts instead - Complex automations with timing โ†’ use Smart User Scripts ### 2. Where to Find It in the Generator Step 4 (Permissions) โ†’ scroll down โ†’ ๐ŸŽจ CSS / JS Quick Injection card. Two text boxes: - ๐ŸŽจ Custom CSS - paste raw CSS rules - โšก Custom JavaScript - paste raw JS (no wrapping needed) Both are optional. Leave empty to skip. ### 3. Example 1: Hide Cookie Banner & Header Globally remove a sticky header and cookie consent popup on every page: ``` /* โ”€โ”€ Hide header & nav โ”€โ”€ */ header, nav, .navbar, #top-bar, .site-header { display: none !important; } /* โ”€โ”€ Remove cookie/GDPR banners โ”€โ”€ */ .cookie-banner, #cookie-notice, .gdpr-popup, [id*="cookie"], [class*="cookie"] { display: none !important; } ``` ### 4. Example 2: Full Dark Mode (CSS only) Turn any website dark with a single CSS filter - images stay natural: ``` /* โ”€โ”€ Dark Mode via CSS filter โ”€โ”€ */ html { filter: invert(1) hue-rotate(180deg) !important; } /* Keep images & media in original colors */ img, video, canvas, svg, picture { filter: invert(1) hue-rotate(180deg) !important; } ``` ### 5. Example 3: Scale / Zoom Fix Website looks tiny or too large on mobile? Fix the zoom level: ``` /* 85% scale โ€” adjust to your needs */ body { zoom: 0.85; } /* Force readable font size */ body, p, li, td { font-size: 15px !important; line-height: 1.65 !important; } ``` ### 6. Example 4: Floating WhatsApp Button (JS) Adds a floating WhatsApp contact button on every page (replace the phone number): ``` // Floating WhatsApp button (function() { if (document.getElementById('__wa_fab__')) return; var btn = document.createElement('a'); btn.id = '__wa_fab__'; btn.href = 'https://wa.me/1234567890?text=Hello'; btn.style.cssText = [ 'position:fixed','bottom:24px','right:20px', 'width:56px','height:56px','border-radius:50%', 'background:#25D366','color:#fff','font-size:26px', 'display:flex','align-items:center','justify-content:center', 'text-decoration:none','z-index:999999', 'box-shadow:0 4px 12px rgba(0,0,0,.35)' ].join(';'); btn.textContent = '๐Ÿ’ฌ'; document.body.appendChild(btn); })(); ``` ### 7. Example 5: Auto-Accept Cookies (JS) Automatically click 'Accept' on cookie consent dialogs: ``` // Auto-accept cookie banners (function() { var selectors = [ '.accept-cookies','#accept-btn','#cookie-accept', 'button[id*="accept"]','button[class*="accept"]', '[aria-label*="Accept"]' ]; selectors.forEach(function(sel) { var el = document.querySelector(sel); if (el) el.click(); }); })(); ``` ### 8. How It Works Internally - CSS is inserted as a