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.
```
๐ Pro content here โ visible after buying.
Upgrade to remove the ads.
Levels 11-20 unlocked!
```
### 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 ]