🚀 Join the waitlist now! waitlist.floot.dev
LogoFlootdocs

Troubleshooting

A guide to resolving common development problems.

Start here

Most "this used to work" problems are a missing tool or one that is too old, so before you read any further, ask the CLI:

Terminal
floot requirements check

It probes Git, the Dart SDK, FVM, Flutter, Docker, the Supabase CLI, Deno, jq, OpenSSL, the Stripe CLI and your platform toolchains, and reports which of them are absent or below the version this project needs. It exits non-zero when anything is wrong, which makes the repair a one-liner:

Terminal
floot requirements check || floot requirements install

floot requirements install installs the missing ones through your host's package manager, asking before it touches anything. See the CLI reference for the full flag list.

Environment

Every Edge Function fails to boot

ENV VALIDATION FAILED: {
  formErrors: [],
  fieldErrors: {
    SB_SECRET_KEY: [ "Invalid input: expected string, received undefined" ]
  }
}

The whole environment is validated the moment any Edge Function starts, and throws if the shape is wrong. Because every function shares that validation, one missing variable takes them all down at once, which is the point: a half-configured backend should fail at functions serve rather than at the first request that needed the variable.

A blank value counts as unset

A value of the empty string is treated as unset before validation runs, so SMTP_USER= and a completely absent SMTP_USER are the same thing. That is deliberate, it is what lets the conditionally-required variables sit in the file as empty placeholders, but it does mean the cure is always to fill a value in, never just to add the line.

Read the fieldErrors keys: each one names a variable in supabase/.env.local. These are required in every project, whatever it was generated with:

DATABASE_WEBHOOK_SECRET, DENO_ENV, ENABLE_MULTI_TENANCY, SMTP_FROM, SMTP_HOST, SMTP_PASSWORD, SMTP_PORT, SMTP_USER, SB_URL, SB_PUBLISHABLE_KEY, SB_SECRET_KEY, SB_AUTH_EXTERNAL_REDIRECT_URI, SB_SEND_EMAIL_HOOK_SECRET, SB_STORAGE_URL, WEBSITE_URL.

The SMTP pair is prefilled, not blank

floot create seeds SMTP_USER and SMTP_PASSWORD with a placeholder, because the local mail catcher that supabase start runs ignores credentials anyway. If you blank them while wiring up a real provider, the functions stop booting until you fill both back in; the validator requires them unconditionally, whatever the host.

The rest are optional until a feature flag turns them mandatory. The message says so in as many words: STRIPE_SECRET_KEY is required when ENABLE_STRIPE is "true".

FlagVariables it makes mandatory
Requires ENABLE_STRIPESTRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, APP_URL_SCHEME
Requires ENABLE_REVENUECATREVENUECAT_SECRET_API_KEY, REVENUECAT_WEBHOOK_SECRET
Requires ENABLE_SEAT_BASED_BILLINGSTRIPE_PORTAL_CONFIRM_CONFIGURATION_ID

Requires ENABLE_SEAT_BASED_BILLING

Seat-based billing is off in a new project. Set `ENABLE_SEAT_BASED_BILLING=true` in both `.env.local` and `supabase/.env.local` to use this section.

Seat-based billing also validates its own prerequisites, and reports them against itself rather than against the variable that is missing:

ENABLE_MULTI_TENANCY is required when ENABLE_SEAT_BASED_BILLING is "true".

ENABLE_STRIPE is required when ENABLE_SEAT_BASED_BILLING is "true".

Both of those mean the same thing: you turned seats on without turning on what they are built from. Seat billing's moving parts are on Seat-Based Billing.

A release build throws on startup

Exception: Error loading .env: <the underlying failure>
Release builds read `.env`, which is git-ignored and is not created by
`floot create`. Copy `.env.local` to `.env`, replace the local values with your
production ones, then add `- .env` under `flutter: assets:` in pubspec.yaml:
an asset that is not listed there is not bundled.

The message names the remedy, but here is the reasoning behind it.

Debug and profile builds load .env.local; release builds load .env. Every .env* file is git-ignored, and floot create writes only .env.local, so until you create .env yourself, the first release build you run throws here, before a single frame is drawn.

There is a second way to hit this with the file sitting right there on disk: Flutter reads it as a bundled asset, so .env must also be listed under flutter: assets: in pubspec.yaml. floot create lists .env.local and not .env, for the same reason it writes one and not the other.

Fix

Both halves are covered step by step in Building for release: copy .env.local to .env, swap in your production values, and add .env to the asset list.

Database webhooks fail after you change a secret

401 INVALID_SIGNATURE. Failed to verify the request.

Five values in supabase/.env.local are also read from the Supabase Vault by SQL, under lowercase names, because Postgres cannot see your environment file:

supabase/.env.localVault secret
ACCOUNT_DELETION_EMAIL_PEPPERaccount_deletion_email_pepper
DATABASE_WEBHOOK_SECRETdatabase_webhook_secret
MFA_RECOVERY_CODE_PEPPERmfa_recovery_code_pepper
SB_STORAGE_URLstorage_url
SB_URLsupabase_url

The [db.vault] block in supabase/config.toml seeds them locally, but only when the database volume is created. A plain supabase stop && supabase start restores the saved volume and skips seeding entirely, so the Vault keeps whatever it was seeded with the first time.

That is what the 401 above is: the database signs the webhook with the stale database_webhook_secret while the Edge Function verifies it against the fresh DATABASE_WEBHOOK_SECRET from supabase/.env.local. Every database-driven function is affected at once.

Fix

Destroy the volume so the next start reseeds the Vault.

Terminal
supabase stop --no-backup && supabase start

--no-backup throws away your local database. Migrations and seeds rebuild the schema, but any data you typed in by hand is gone.

Hosted projects seed nothing

Nothing creates these five for you on a hosted project. Create them yourself under those exact lowercase names, Studio → Vault, or vault.create_secret(), or the same webhooks fail there too.

Supabase

The initial migration refuses to run

ERROR: realtime.messages does not have RLS pre-enabled. This project requires the realtime image shipped with Supabase CLI >= 2.116.0 (older images leave RLS disabled, which would expose private broadcast topics). Upgrade the Supabase CLI and rerun.

This is a deliberate guard in supabase/migrations, not a bug. Since 2.116.0 the realtime image owns realtime.messages and ships it with row-level security already enabled; postgres may add policies to it but can no longer ALTER it. On an older image RLS stays off, the subscription-topic policies protect nothing, and any signed-in user can subscribe to any topic, so the migration stops rather than build that.

Confirm the version you are on.

Terminal
floot requirements check supabase

Upgrade the Supabase CLI to 2.116.0 or later.

Start from a clean volume, so the new realtime image is pulled and the migration re-runs from the beginning.

Terminal
supabase stop --no-backup && supabase start

Android

Emulator is not reaching the backend

Connection refused - errno 101

SocketException: Connection failed (OS Error: Network is unreachable, errno = 101)…

Fix

Turn the Wi-Fi off on emulator.

Connection refused - errno 111

SocketException: Connection refused (OS Error: Connection refused, errno = 111)…

This occurs because the Android emulator does not recognize localhost as the host machine. Instead, you should use the IP address 10.0.2.2. The port stays 54321, that is the local Supabase API, which is what the app talks to.

.env.local
SUPABASE_URL=http://10.0.2.2:54321

Note

This is the app's .env.local at the project root, not supabase/.env.local. Change .env the same way if you are pointing a release build at a local backend. On a physical device neither 127.0.0.1 nor 10.0.2.2 works; use your machine's LAN address, from ipconfig getifaddr en0 on macOS or ipconfig on Windows.

If you would rather keep 127.0.0.1 and avoid a separate Android-only SUPABASE_URL, forward the port instead; see Useful Commands.

Flutter

Widget Testing

Pending timers

When running widget tests, you might encounter the following error:

The following assertion was thrown running a test: A Timer is still pending even after the widget tree was disposed.

…'!timersPending’

This typically occurs when an animation or asynchronous operation hasn't completed. Below are two approaches to resolve this using tester.runAsync.

Run the pump and settle in that function.

await tester.runAsync(() async => tester.pumpAndSettle());

Apple build failures (Swift Package Manager)

Floot builds all iOS/macOS plugins with Swift Package Manager; there is no CocoaPods integration, and your project has no Podfile. If you see an error mentioning pod install or CocoaPods's specs repository, it is coming from a stale build directory rather than from Floot.

Note

If you are encountering this issue for your Mac app, replace every occurrence of the ios path with macos in the steps below.

Fix

Run flutter clean to remove build artifacts.

Delete ios/Flutter/ephemeral so the generated Swift package is rebuilt from scratch.

Run flutter pub get to fetch Dart dependencies.

Run flutter run to build and launch the app.

On this page