Free checklist
The pre-launch checklist for AI-built apps.
The same twenty things our paid audit checks, written so you can test your own app — most take a few minutes each. Tick each one that passes. Whatever you can’t tick, or can’t check, is where to look first.
- Free, no sign-up
- Twenty checks
- About an hour
01The checklist
Twenty checks, in the order the audit runs them.
Tick a check once your app passes it. The mobile four only apply if you ship to the App Store or Google Play.
Security
-
- How to check
- In the Supabase dashboard, open Advisors → Security Advisor and look for tables flagged with row-level security disabled. Then sign out of your app, copy one of its own database requests from the browser’s Network tab (a URL containing /rest/v1/) and run it again.
- Passes if
- No table holding user data is flagged, and the signed-out request returns nothing private.
Why it matters
Row-level security was never switched on, so the browser key that ships in your JavaScript bundle can read every row in every table — including other customers’ records. From the outside the app looks completely normal.
-
- How to check
- Open your live site, go to the Sources tab in the browser’s developer tools, and search the JavaScript for “service_role”, “sb_secret_”, “sk_live_” and “secret”.
- Passes if
- Only publishable keys reach the browser — the Supabase anon or publishable key, a Stripe pk_ key — and nothing that can bypass your access rules.
Why it matters
An admin-level key was used in client code to work around access rules that were never configured. It bypasses every policy you add later, so fixing the policies alone changes nothing.
-
- How to check
- As an admin, copy the request an admin-only action sends (Network tab → Copy as cURL). Sign in as an ordinary test user, swap that user’s session token into the copied request, and send it.
- Passes if
- The request is refused by the server or the database, not merely hidden in the interface.
Why it matters
The role check lives in the React component. The endpoints behind it have none, so anyone who calls them directly can perform administrative actions — including changing their own role.
-
- How to check
- Create two test accounts in two separate organizations. Signed in as the first, copy a request that loads one of its records, change the ID to one of the second account’s records, and send it.
- Passes if
- Nothing belonging to the second account comes back.
Why it matters
The tenant identifier used to filter queries is read from a value the client sends. Change it in the request and another organization’s records come back, because the server never checks it against the session.
-
- How to check
- Upload a file as a test user, copy the file’s URL, and open it in a private window while signed out. In Supabase, Storage shows whether each bucket is public.
- Passes if
- Private files only open through a signed URL that expires; the bare URL shows nothing to a signed-out visitor.
Why it matters
The storage bucket was left open and filenames are predictable. Invoices, identity documents and private images are readable without logging in, and enumerable by anyone who guesses the pattern.
Payments
-
- How to check
- In test mode, buy with the test card 4242 4242 4242 4242. Check the webhook deliveries to your endpoint in the payment provider’s dashboard, then the buyer’s plan in your database. In your webhook code, look for a signature check (in Stripe, constructEvent with your webhook signing secret).
- Passes if
- Every delivery gets a 2xx response, the account upgrades within seconds, and the handler rejects events without a valid signature.
Why it matters
The card is charged, the customer sees a success screen, and the account is never upgraded. Almost always the webhook is unverified, unhandled, or never arrives at a handler that does anything.
-
- How to check
- In the payment provider’s dashboard, resend a webhook event that has already been delivered successfully, then look at the buyer’s rows in your database.
- Passes if
- Still one subscription and one payment record: the handler recognises events it has already processed.
Why it matters
Payment providers retry webhooks by design and occasionally deliver duplicates in normal operation. The handler inserts a row each time, so your database quietly disagrees with the provider about what was bought.
-
- How to check
- In test mode, cancel a test subscription and refund a test payment from the provider’s dashboard, then sign in as that test user.
- Passes if
- Access ends when it should and the refund shows up in your own data — not only in the provider’s.
Why it matters
One event is handled — the successful checkout — and nothing else. Canceled customers keep their access, refunded customers still count as revenue, and failed renewals are invisible.
-
- How to check
- Find the server code that creates the checkout session and see where the amount or price ID comes from.
- Passes if
- The server looks the price up from your own product list. The browser only says which product, never how much.
Why it matters
The amount to charge is sent from the client to the checkout session. Anyone can change it in the request before it is sent, and pay whatever they like.
Reliability
-
- How to check
- Run a production build on your own machine (your build command, then start or preview) and compare the environment variable names your code reads with the ones set on your host.
- Passes if
- The production build runs locally, and every variable the code reads is set on the host.
Why it matters
Different environment variables, a missing build step, a server-only library imported into client code, a database connection that works from your laptop and not from the host. The error is a 500 with nothing behind it.
-
- How to check
- Ask yourself: if the app broke at 3am, who would be told, and how? Look for an error tracker receiving events from production and an uptime monitor checking the site.
- Passes if
- An alert reaches you within minutes, and production errors arrive in a tracker with enough detail to diagnose.
Why it matters
No error tracking, no uptime check, no alerting. A broken payment path or a failing migration is reported by whoever happens to hit it, and there is no trace left to diagnose from afterwards.
-
- How to check
- Load your busiest page with the Network tab open and note the slowest requests. In Supabase, Advisors → Performance Advisor flags missing indexes.
- Passes if
- Key pages load in about a second, and no query on a growing table runs without an index.
Why it matters
Missing indexes, queries issued inside a loop, whole tables fetched and then filtered in memory. All of it is fine with fifty rows and none of it is fine with fifty thousand.
-
- How to check
- Open your host’s list of previous deployments and check that each one maps to a commit, and that you could put the previous one back live.
- Passes if
- Deploys come from git, not a laptop, and you know how to roll back in under five minutes.
Why it matters
Deploys go out from a laptop, there is no record of what shipped, and the only way to undo a release is to write the old behavior again by hand under pressure.
Data
-
- How to check
- Find where your database backups live and whether your plan includes them, then ask when one was last restored — even into a scratch project.
- Passes if
- Automatic backups exist, and one has been restored at least once.
Why it matters
Running on defaults with no export schedule, or with backups nobody has ever restored from. A backup that has not been restored is an assumption, not a recovery plan.
-
- How to check
- Compare the database URL set for preview or staging deployments with the one set for production.
- Passes if
- They are different databases; previews never touch real customer data.
Why it matters
One connection string across every environment. Preview deployments mutate real customer data, and a migration tested on a branch runs against production. There is also nowhere safe to rehearse a restore.
-
- How to check
- Look for a migrations folder in your repository (for Supabase, supabase/migrations) and check that its newest file matches the last change you made to the database.
- Passes if
- Every schema change is a migration file in git, and a fresh database can be built from them alone.
Why it matters
Columns added in a dashboard, no migration files, no history. Environments drift apart and nobody can recreate the database from scratch — including whoever has to recover it.
Mobile
-
- How to check
- List what the app does that your website cannot: push notifications, offline use, the camera, native navigation. Read it against Apple’s App Review Guideline 4.2.
- Passes if
- The app does real things a browser cannot. It is not a web view of your site.
Why it matters
A web view with nothing a browser could not do. Resubmitting the same build with a better description does not change the outcome, and each attempt costs a review cycle.
-
- How to check
- Find the Android target SDK in your project (targetSdkVersion in android/app/build.gradle, or your Expo config) and compare it with the minimum Google Play currently requires.
- Passes if
- The target SDK meets Google Play’s current minimum.
Why it matters
Google raises the target API floor every year and refuses new builds below it before review even begins. The upgrade then surfaces layout and orientation changes the app never handled.
-
- How to check
- List every SDK in the app that collects data — analytics, crash reporting, ads, sign-in — and compare the list with your App Store privacy answers and your Play data safety form.
- Passes if
- Every data type the app sends is declared, and the iOS build includes a privacy manifest.
Why it matters
The data safety form declares no collection while the app ships analytics and account data. That mismatch is itself a policy violation, independent of the collection it fails to declare.
-
- How to check
- Install a release build on a real phone (TestFlight, or a Play internal testing track), send yourself a push notification, and tap a link to your domain from another app.
- Passes if
- The notification arrives and the link opens the app, not the browser.
Why it matters
Certificates, entitlements and associated domains were never configured for a release build, so notifications silently fail and links open the browser instead of the app.
Ticked of 20 Send us your results
02What next
What your ticks mean.
-
All twenty ticked
Launch. Keep the error tracker and the backups running, and run the list again after any large change.
-
A few unticked
Fix the security and payments ones before you take money. The rest can follow in your first weeks.
-
Several you couldn’t check
That is normal — some need you to read code you did not write. It is exactly what the audit is for.
Rather have it checked for you? How the audit works
The reasoning behind the checks, at length: From Lovable demo to production: a checklist
Free review
Send us your results.
We’ll reply within one business day with the two or three things we would fix first. Free, written, and no call required.
- Your ticks are sent with the form
- Read by the engineer who does the audits
- No mailing list — we only reply to this
Rather we checked it for you?
The audit runs all twenty against your code with read-only access, plus the problems specific to your app, and ranks every finding with a fix estimate. Two business days, $349.
Prefer to write? Send your requirements