Lovable Cloud to Supabase: Migration Path and Real Errors
Lovable Cloud to Supabase: Migration Path and Real Errors
Lovable Cloud to Supabase migration means copying your database schema, table data, auth users, and storage files from Lovable's managed shared backend to a standalone Supabase project you own. The process replays migration files from supabase/migrations/, gap-fills objects created in the SQL editor, transfers rows in foreign-key order, and preserves auth user UUIDs and bcrypt password hashes.
Most projects complete schema and data transfer in 5–10 minutes. Storage files add time proportional to file count and size. The source Lovable Cloud project remains read-only throughout; no downtime is required.
What is the difference between Lovable Cloud and standalone Supabase?
Lovable Cloud is a managed shared Supabase backend provisioned by Lovable when you create a project. Standalone Supabase is a project you create and own directly at supabase.com.
Lovable Cloud is read-only via the Supabase dashboard. All schema changes go through Lovable's editor. The database is shared infrastructure; you cannot install custom Postgres extensions or access connection pooling settings.
Standalone Supabase gives full control. You can use the SQL editor, run psql commands, install extensions like pg_cron or pg_trgm, and configure Supavisor pooler settings. You manage backups, point-in-time recovery, and compute scaling directly.
Billing differs. Lovable Cloud is bundled into your Lovable subscription. Standalone Supabase bills separately for compute (Pro plan $25/month minimum), storage ($0.125/GB), and bandwidth ($0.09/GB). If your project exceeds Lovable's shared resource limits, standalone Supabase may be cheaper and more predictable.
Migration to standalone Supabase is one-way. You cannot move a standalone project back to Lovable Cloud.
How does schema migration from Lovable Cloud work?
Schema migration runs in two phases: migration replay and gap-fill.
Step 1: Replay supabase/migrations/ files from your repository against the target database. These are SQL files created when you used Lovable's schema editor or ran supabase db commit. SupaMigrate reads them from your repo and executes them in order using the target project's service_role key.
Step 2: Gap-fill objects created in the SQL editor with no migration file. Developers often create tables, columns, functions, RLS policies, triggers, or views directly in the Supabase dashboard. These changes are not recorded as migration files. Gap-fill introspects the source database via the Supavisor pooler, queries pg_catalog and information_schema, and reconstructs these objects as CREATE statements.
Gap-fill uses pg_class, pg_attribute, pg_constraint, pg_policies, pg_trigger, and pg_proc to generate schema DDL. Objects are emitted in dependency order: base tables before foreign keys, tables before triggers, and functions before triggers that call them.
Migration replay and gap-fill combined produce a complete schema copy. The target database ends up with the same tables, columns, constraints, indexes, RLS policies, functions, triggers, and views as the source.
Detailed explanation of gap-fill introspection in Recreate a Postgres schema without pg_dump.
What order should I migrate tables with foreign keys?
Foreign key dependencies are resolved via pg_constraint.confrelid → pg_class.oid. This returns the parent table for every FK constraint. A topological sort produces an insert order: parent tables before child tables.
Example: orders has a foreign key user_id → users(id). The users table must be inserted before orders. If order_items references orders(id), the sequence is users, orders, order_items.
Circular FK references require special handling. If posts references users(id) and users references posts(last_post_id), both tables depend on each other. Solutions:
- Temporarily disable triggers:
ALTER TABLE posts DISABLE TRIGGER ALL, insert all rows,ALTER TABLE posts ENABLE TRIGGER ALL. - Split the FK: insert
userswithlast_post_id = NULL, insertposts, thenUPDATE users SET last_post_id = ... WHERE id = .... - Use
ALTER TABLE ADD CONSTRAINTafter both tables are populated.
SupaMigrate's data step batches inserts with ON CONFLICT DO NOTHING and respects FK order. If a row violates a foreign key (because the parent row was skipped or failed), it is logged but does not halt the migration.
After data transfer, sequences are resynced with:
SELECT setval('users_id_seq', MAX(id)) FROM users;
SELECT setval('orders_id_seq', MAX(id)) FROM orders;
This prevents duplicate key errors when new rows are inserted via the application.
How are auth.users migrated without forcing password resets?
UUIDs are preserved. The source auth.users.id column (a UUID primary key) is copied directly:
INSERT INTO auth.users (id, email, encrypted_password, email_confirmed_at, ...)
VALUES ('e7b2c3d4-...', 'user@example.com', '$2a$10$...', '2025-01-15 10:30:00', ...);
Bcrypt password hashes are copied from auth.users.encrypted_password. Supabase stores passwords as bcrypt strings like $2a$10$abcdefghijklmnopqrstuv.... The hash is transferable across Supabase instances. Users can log in immediately with existing passwords after migration.
Metadata columns are preserved:
email_confirmed_at: avoids re-sending confirmation emails.last_sign_in_at: retains activity history.created_at,updated_at: preserves user age.
The auth.identities table is also migrated. This links users to OAuth providers (Google, GitHub) via provider and provider_id. Without it, users who signed up via OAuth cannot log in.
Detailed explanation in Migrate Supabase auth users without forcing a password reset.
Why do migrations fail with 'relation does not exist'?
Migration files may reference tables created in earlier migrations that failed to apply. Out-of-order execution happens when migration 002 depends on 001 but 001 failed to run.
Example error from production logs:
Replay failed (20260414112656_df33940f-8789-4422-809b-209cebeeacfa.sql):
relation "public.event_tasks" does not exist
This means the migration file contains SQL like ALTER TABLE event_tasks ADD COLUMN ..., but event_tasks was never created. Possible causes:
- The migration that creates
event_taskswas deleted or never committed. - The migration ran in Lovable Cloud but the SQL file is missing from the repo.
- The table was created manually in the Supabase dashboard (so no migration file exists).
Gap-fill reconstructs source-only tables, but migration replay runs first. If the replay fails, gap-fill never runs.
Fix 1: Manually create missing tables in the target database before re-running replay:
CREATE TABLE event_tasks (id bigint primary key, ...);
Fix 2: Disable the failing migration file temporarily. Move it out of supabase/migrations/ or add a guard:
-- 20260414112656_df33940f-8789-4422-809b-209cebeeacfa.sql
DO $ BEGIN
IF EXISTS (SELECT FROM pg_tables WHERE tablename = 'event_tasks') THEN
ALTER TABLE event_tasks ADD COLUMN ...;
END IF;
END $;
Fix 3: Let gap-fill run first by completing the replay with --ignore-errors, then re-run migrations. This is not yet automated in SupaMigrate but can be done manually by applying migrations with psql.
What does 'duplicate key value violates unique constraint buckets_pkey' mean?
Storage buckets are created by Supabase automatically when you use the dashboard to upload files. Common bucket names: avatars, public, documents. These are rows in the storage.buckets table with id as the primary key.
Migration files that re-create these buckets will conflict with existing rows. Example error from production logs:
Replay failed (20260329211353_*.sql):
duplicate key value violates unique constraint "buckets_pkey"
This happens when a migration file contains:
INSERT INTO storage.buckets (id, name, public) VALUES ('avatars', 'avatars', true);
But the target Supabase project already has an avatars bucket created via the dashboard.
Fix 1: Exclude storage bucket creation statements from migration replay. Edit the migration file to remove INSERT INTO storage.buckets lines, or use ON CONFLICT DO NOTHING:
INSERT INTO storage.buckets (id, name, public)
VALUES ('avatars', 'avatars', true)
ON CONFLICT (id) DO NOTHING;
Fix 2: Delete the bucket in the target project before replaying migrations. This only works if no files have been uploaded yet.
Fix 3: Use SupaMigrate's Pro tier, which handles storage separately with per-file transfer. Buckets are created only if they don't exist, and migration files that reference buckets are automatically patched with ON CONFLICT DO NOTHING.
Additional error from production logs:
Create bucket "knowledge-docs" failed: HTTP 400
{"statusCode":"409","error":"Duplicate","message":"The resource already exists"}
This is the Supabase Storage API rejecting a bucket creation request. Same root cause, same fixes.
How long does a typical Lovable Cloud to Supabase migration take?
Real data: 136 tables migrated in approximately 5 minutes (schema + data, no storage files).
Schema replay + gap-fill: 30–90 seconds for most projects. Depends on the number of migration files, the complexity of gap-fill introspection, and network latency between the source and target databases.
Data transfer: depends on row count and network. Batched inserts run at approximately 2000 rows/sec. A database with 500,000 rows across 50 tables transfers in 4–8 minutes.
Storage transfer (Pro tier): per-file HTTP transfer from source to target. 1 GB of files takes approximately 3–5 minutes. Large files (>10 MB) transfer faster per byte than small files (<100 KB) due to HTTP overhead.
Migrations are resumable. If the browser tab closes or the network drops, re-run the migration. Already-transferred rows are skipped with ON CONFLICT DO NOTHING. Sequences are resynced at the end regardless of how many runs it took.
The Free tier ($0) runs analysis + schema migration only and completes in 30–60 seconds. Starter ($29) adds data transfer. Pro ($79) adds storage transfer.
Can I migrate only schema without data?
Yes. SupaMigrate Free ($0) runs analysis + schema migration only. It replays migration files and gap-fills schema without touching table rows.
Useful for testing the target schema before committing to a full data transfer. You can verify:
- All tables exist with correct columns and types.
- Foreign keys are in place.
- RLS policies are active.
- Functions, triggers, and views are present.
After verifying the schema, manually transfer data with pg_dump and pg_restore:
pg_dump 'postgresql://postgres.project-ref.supabase.co:5432/postgres?sslmode=require' \
--data-only --no-owner --no-privileges \
| psql 'postgresql://postgres.target-ref.supabase.co:5432/postgres?sslmode=require'
Or write custom scripts. This is common when you need to transform data during migration (e.g., anonymizing emails, resetting timestamps).
Starter ($29) adds automated data transfer with FK ordering and sequence resync. Pro ($79) adds storage file transfer.
Common errors
Error: Replay failed (20260414112656_df33940f-8789-4422-809b-209cebeeacfa.sql): policy "Members can view credit transactions" for table "credit_transactions" does not
Cause: The migration file references a table that does not exist yet. The table was created manually or in an earlier migration that failed.
Fix: Create the missing table before replaying migrations, or disable the failing migration file.
Error: Replay failed (20260329211353_*.sql): duplicate key value violates unique constraint "buckets_pkey"
Cause: The migration file tries to insert a storage bucket that already exists in the target database.
Fix: Add ON CONFLICT DO NOTHING to the INSERT INTO storage.buckets statement, or exclude it from replay.
Error: Create bucket "knowledge-docs" failed: HTTP 400 {"statusCode":"409","error":"Duplicate","message":"The resource already exists"}
Cause: The Supabase Storage API rejects bucket creation because a bucket with that name already exists.
Fix: Skip bucket creation if it already exists, or delete the bucket in the target project before migration.
Error: ENETUNREACH: Network is unreachable when connecting to Supabase from a local script.
Cause: Your network (especially IPv6-only environments) cannot reach Supabase's IPv4 endpoints. Supabase database connections require IPv4 or the Supavisor pooler.
Fix: Use the Supavisor pooler connection string, or configure IPv4 routing. Detailed explanation in Fixing Supabase IPv6 connection failures.
Error: permission denied for table auth.users
Cause: The connection string uses an anon or authenticated role key instead of the service_role key. Only the service_role key can write to auth.users.
Fix: Use the service_role key. Find it in Supabase dashboard → Settings → API → service_role (secret).
Error: value too long for type character varying(255) during data transfer.
Cause: A column has a VARCHAR(255) constraint in the target schema, but the source data contains longer strings.
Fix: Alter the target column to TEXT or increase the length limit before migration:
ALTER TABLE table_name ALTER COLUMN column_name TYPE TEXT;
FAQ
Does SupaMigrate store my service_role key?
No. Credentials are never stored. The service_role key is used only during the migration session and is discarded immediately after. SupaMigrate runs in the browser and in Supabase Edge Functions. Edge Functions receive the key as an HTTP header and do not log or persist it.
The source Lovable Cloud project is accessed read-only via the Supavisor pooler. No writes are made to the source database.
What happens if the migration fails halfway through?
The migration is resumable. Re-run the migration. Already-transferred rows are skipped with ON CONFLICT DO NOTHING. Sequences are resynced at the end regardless of how many runs it took.
If a specific table consistently fails, the error is logged. You can exclude it from migration and transfer it manually with pg_dump, or fix the schema issue (e.g., add a missing column) and re-run.
Can I migrate from Lovable Cloud to a self-hosted Supabase instance?
Yes. A self-hosted Supabase instance is a Postgres database with the Supabase auth schema. As long as you have a service_role key or equivalent superuser credentials, SupaMigrate can replay migrations, gap-fill schema, and transfer data.
Storage file transfer requires the target to support the Supabase Storage API. Self-hosted instances running supabase/storage-api are compatible. If you use MinIO or S3 directly, you must transfer files manually.
Do I need to pause my Lovable project during migration?
No. The source Lovable Cloud project remains read-only. SupaMigrate connects via the Supavisor pooler and runs SELECT queries only. Your Lovable app continues to serve users.
Data written to the source during migration is not captured. If you need a consistent snapshot, pause writes to the source, run the migration, then switch your application to the target database.
How do I verify the migration was successful?
Step 1: Count rows in critical tables:
SELECT 'users' AS table, COUNT(*) FROM users
UNION ALL
SELECT 'orders', COUNT(*) FROM orders
UNION ALL
SELECT 'posts', COUNT(*) FROM posts;
Compare counts between source and target.
Step 2: Verify auth users can log in. Test login with a known email and password. If successful, bcrypt hashes were copied correctly.
Step 3: Check foreign key constraints:
SELECT conname, conrelid::regclass, confrelid::regclass
FROM pg_constraint
WHERE contype = 'f';
Ensure all FKs are present.
Step 4: Verify RLS policies:
SELECT schemaname, tablename, policyname, permissive, roles, cmd
FROM pg_policies
ORDER BY tablename;
Compare output with the source database.
Step 5: Test your application against the target database. Update the SUPABASE_URL and SUPABASE_ANON_KEY in your frontend and run integration tests.
Comprehensive migration guide: Migrate a Lovable Cloud project to your own Supabase.
Additional Postgres documentation: pg_constraint, pg_policies.
Related reading
Recreate a Postgres schema without pg_dump
No shell, no Postgres binary, no pg_dump. You can still rebuild a schema by reading the system catalogs and emitting CREATE statements in dependency order.
Fixing Supabase IPv6 connection failures (ENETUNREACH)
The direct db.<ref>.supabase.co host resolves to IPv6. On an IPv4-only network you get connect ENETUNREACH. The pooler host answers on IPv4 — here is the switch.
Migrate Supabase auth users without forcing a password reset
Supabase's admin API cannot set a bcrypt hash, so recreating users there forces a password reset. Insert into auth.users directly and keep every login working.