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

Useful Commands

The commands you will actually type while working on a Floot project, and the directory each one belongs in.

Almost every mistake on this page is a directory mistake: the Supabase CLI wants to be run from supabase/, Deno wants to be run from supabase/functions/, and Flutter wants the project root. So the page is grouped by where you stand rather than by tool.

From the project root

Regenerate the translations

Terminal
flutter gen-l10n

Reads l10n.yaml, so the .arb catalogs come from lib/core/presentation/l10n and the Dart lands in lib/core/presentation/l10n/generated. Those generated files are committed: edit an .arb, run this, and commit both halves together, or the app builds against the old strings.

Run the app

Terminal
flutter run

On the web, pick port 3000 explicitly:

Terminal
flutter run -d chrome --web-hostname 127.0.0.1 --web-port 3000

That port is not arbitrary. site_url in supabase/config.toml is http://127.0.0.1:3000, and it is where every confirmation and password-reset email points. On any other port those links land nowhere.

Run the app tests

Terminal
flutter test

Check your toolchain

Terminal
floot requirements check
floot requirements install

check reports what is missing or too old, scoped to the layers you generated; install fixes it. See the CLI reference for the full flag list.

From supabase/

Everything in this section is run from the supabase/ directory. The Supabase CLI resolves config.toml, .env.local and every relative path against it.

Start and stop the local stack

Terminal
supabase start
supabase stop

supabase start auto-loads supabase/.env.local; there is no --env-file flag on it.

There is one restart you have to know about:

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

The [db.vault] block in config.toml seeds five secrets into the Vault from .env.local, but only when the database volume is created. A plain supabase stop && supabase start restores the saved volume and skips seeding entirely, so an edited secret is silently ignored and local database webhooks keep failing signature checks. --no-backup destroys the volume, which is what makes the next start reseed. See Troubleshooting for the full symptom list.

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

Rebuild the database

Terminal
supabase db reset

Replays every migration, then the seeds: [db.seed] points at db_seeds/*.sql, and storage_seeds/ is loaded into the assets bucket. Use it after writing a migration, and before the database tests. Migration filenames are stamped at floot create time, so your project's history starts at its own creation moment.

Serve the Edge Functions

Terminal
supabase functions serve --env-file .env.local --import-map ./functions/deno.json --no-verify-jwt

Leave it running while you develop: transactional email, Stripe checkout and account deletion all go through it.

Regenerate the database types

Terminal
supabase gen types --local > functions/_shared/database.types.ts

Run it after any migration or db_seeds change, and after a supabase db reset; _shared/database.types.ts is generated output, never hand-edited.

Do not pass -s / --schema

Omitted, the CLI follows api.schemas in config.toml, which is app, graphql_public, pgmq_public and public. A hand-picked list silently drops schemas the Edge Functions type against, and there is no stripe schema to add: a pgTAP test asserts that no foreign table, foreign server or wrappers extension exists, so naming one only produces an error.

Run the database tests

Terminal
supabase db reset && supabase test db

supabase test db runs every .sql file under supabase/tests/database/ through pgTAP, installing the extension for the run and dropping it afterwards. Paths are sorted flat and lexicographically, which is the whole reason for the naming.

The db reset in front is not optional in practice: the suite asserts against the seeded plans and prices, and each file rolls its own work back but a half-migrated database fails everything.

From supabase/functions/

deno.json lives here, so Deno picks up the import map and the tasks automatically. Run these from supabase/functions/, not from supabase/.

Type-check every function

Terminal
deno task check

Shorthand for deno check .. This is also what proves the email templates are translated: every Record<Language, ...> block that is missing a language is reported with a file and a line.

Run the Edge Function tests

Terminal
deno task test

Shorthand for deno test -A --env-file=.env.test. .env.test ships with your project and holds deterministic, well-formed dummy credentials, so the suite passes offline and never touches a real Stripe or SMTP account. It is not .env.local, and the flag is --env-file=, not --env=.

Stripe

Forward webhook events to the local stack

Terminal
stripe listen --forward-to localhost:54321/functions/v1/stripe-webhook

Forwards live test-mode events to your local stack. Each run mints a new signing secret; copy it into STRIPE_WEBHOOK_SECRET in supabase/.env.local and restart supabase functions serve, or every event fails its signature check.

Provision the billing portal configurations

Two re-runnable scripts create the Billing Portal Configurations the app needs. Both are run from the project root, and read supabase/.env.local for your secret key.

Terminal
deno run --allow-net --allow-env --env-file=supabase/.env.local \
  supabase/scripts/create_restricted_portal_config.ts

It clones your account's default configuration with quantity removed from default_allowed_updates, so a customer cannot edit their seat count from Stripe's hosted UI. Paste the printed id back:

supabase/.env.local
STRIPE_PORTAL_CONFIGURATION_ID="bpc_..."

Seat-based billing only

This section applies only to projects generated with `--seat-based-billing`.

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.

Terminal
deno run --allow-net --allow-env --env-file=supabase/.env.local \
  supabase/scripts/create_confirm_portal_config.ts

The mirror image: it adds quantity, for the app's own plan-switch flow, which Stripe rejects with a 400 under the restricted configuration. See Seat-Based Billing for why the app needs its own configuration.

supabase/.env.local
STRIPE_PORTAL_CONFIRM_CONFIGURATION_ID="bpc_..."

Each runs once per Stripe account and per mode: test and live keys have separate configurations, so a project that goes live needs the pair again. Stripe explains why one configuration cannot serve both flows.

Android

Keep using localhost for the local Supabase API

The Android emulator treats localhost as itself, not your dev machine, so SUPABASE_URL=http://127.0.0.1:54321 in the app's .env.local fails from inside the emulator. Reverse port forwarding lets you keep that URL instead of switching to 10.0.2.2.

Terminal
adb devices

Lists the emulators and physical devices ADB can see, each with a device id.

Terminal
adb -s <device-id> reverse tcp:54321 tcp:54321

Any connection from inside the emulator to 127.0.0.1:54321 or localhost:54321 is now intercepted by ADB and forwarded to port 54321 on your machine, where the local Supabase API is listening. See Troubleshooting for the 10.0.2.2 alternative, which needs no forwarding but requires a separate .env.local value for Android.

Note

This forwarding rule is temporary. If the emulator or ADB restarts, run the adb reverse command again.

Simulators & desktop

All three of these open a deep link straight in a running simulator or emulator, or launch the app fresh on macOS if it is not already running, which beats retyping the link in a browser. The scheme is the one floot create derived from your --org: com.acme.myapp for --org com.acme on a project named my_app.

iOS Simulator
/usr/bin/xcrun simctl openurl booted "com.acme.myapp://oauth-callback"
Android Emulator
adb shell am start -a android.intent.action.VIEW -d "com.acme.myapp://oauth-callback"
macOS
open "com.acme.myapp://oauth-callback"

Requires the scheme to be registered in Info.plist; see Deep Links if it does not respond.

On this page