plans/local-supabase-support.md
Generated by swarm planning session on 2026-02-12
Add support for connecting Dyad apps to a local Supabase instance (via the Supabase CLI + Docker) as an alternative to cloud Supabase. Users can easily switch between cloud and local on a per-app basis, with cloud positioned as "Recommended for production" and local as "Great for development." Dyad detects whether local Supabase is running but does not manage the lifecycle — users run supabase start and supabase functions serve themselves.
Dyad users building apps with Supabase currently must connect to a cloud Supabase project for all development. This creates several pain points:
DROP TABLE hits a real cloud project. There is no safe local sandbox.Target user: Dyad users building apps with auth, database, or edge functions who want a faster, safer local development loop. Secondary: users exploring Supabase integration without committing to a cloud account.
supabase statussupabaseMode field on app record)supabase status)http://127.0.0.1:54321 and local anon key for local modepostgres npm package (pure-JS)supabase functions serve themselvessupabase start / supabase stop from within Dyad (v2)supabase functions serve process (v2)http://127.0.0.1:54323)Cloud Supabase remains the primary call-to-action. Local Supabase appears as a secondary option below:
[Connect to Supabase] <- branded button (existing)
Recommended for production
Use Local Supabase <- secondary text link (new)
Great for development
This preserves the current flow for the majority of users while making local discoverable for advanced users.
supabase start in your project terminal." with copy-to-clipboard and a "Check Again" buttonsupabase status --output json, store them, link app to local modehttp://127.0.0.1:54321 URLsNo Supabase connected (initial):
Local Supabase connected — running:
http://127.0.0.1:54321)http://127.0.0.1:54323)Local Supabase connected — not running:
supabase start in your project terminal"Local Supabase — prerequisites missing:
Mode switching confirmation:
supabase status when the local connected view is visible (reasonable interval, e.g., every 30 seconds)src/integrations/supabase/client.ts automatically and shows a toast: "Supabase client updated. Restart your dev server for changes to take effect."http://127.0.0.1:54323 in the system browser<button> element with ARIAIntroduce a local Supabase code path that coexists with the existing cloud integration. The key abstraction point is at the IPC handler layer — handlers detect supabaseMode from the app record and route to either cloud (Management API) or local (direct Postgres / CLI) implementations.
SupabaseConnector.tsx (UI)
│
▼
IPC Contracts (supabase.ts)
│
▼
IPC Handlers (supabase_handlers.ts)
│
┌───┴───┐
▼ ▼
Cloud Local
(Management API) (postgres npm + CLI)
For local mode:
postgres npm package (pure-JS, no native compilation) connecting to postgresql://postgres:[email protected]:54322/postgressupabase status --output json (requires CLI v1.50+ for JSON output)http://127.0.0.1:54321 with local anon keyhttp://127.0.0.1:54321/functions/v1/FUNCTION_NAME URLs; user serves locally via supabase functions serve| File | Change |
|---|---|
src/db/schema.ts | Add supabaseMode column to apps table |
src/lib/schemas.ts | Add localSupabase settings object to SupabaseSchema |
src/supabase_admin/supabase_local_client.ts | New file. CLI detection, supabase status parsing, direct Postgres queries via postgres npm |
src/supabase_admin/supabase_management_client.ts | Add local routing in executeSupabaseSql and key functions |
src/supabase_admin/supabase_context.ts | Update getSupabaseClientCode() and getPublishableKey() for local URLs/keys |
src/supabase_admin/supabase_schema_query.ts | No change (same SQL queries, different execution path) |
src/components/SupabaseConnector.tsx | Add local Supabase entry point, local connected state, prerequisite check UI, status display |
src/components/SupabaseIntegration.tsx | Conditionally render settings based on supabaseMode |
src/ipc/types/supabase.ts | Add new contracts: checkLocalPrerequisites, getLocalStatus, connectLocal, disconnectLocal |
src/ipc/handlers/supabase_handlers.ts | Add local handlers, route existing handlers by mode |
src/hooks/useSupabase.ts | Add hooks for local status, prerequisites |
src/prompts/supabase_prompt.ts | Add local-specific agent instructions (URLs, edge function serving, limitations) |
src/pro/main/ipc/handlers/local_agent/tools/get_supabase_project_info.ts | Return local connection details when in local mode |
src/pro/main/ipc/handlers/local_agent/tools/get_supabase_table_schema.ts | Route schema queries through local Postgres when in local mode |
Apps table (Drizzle migration):
supabaseMode: text("supabase_mode"), // "cloud" | "local" | null
When supabaseMode is "local":
supabaseProjectId is nullsupabaseOrganizationSlug is nullsupabase status output in settingsSettings schema (src/lib/schemas.ts):
localSupabase?: {
apiUrl: string; // e.g., "http://127.0.0.1:54321"
dbUrl: string; // e.g., "postgresql://postgres:[email protected]:54322/postgres"
studioUrl: string; // e.g., "http://127.0.0.1:54323"
anonKey: string; // from supabase status
serviceRoleKey: string; // from supabase status
cliVersion: string; // detected CLI version
}
These are auto-populated from supabase status --output json — never manually entered.
New IPC contracts:
checkLocalPrerequisites: defineContract({...})
// Returns: { docker: { installed, running }, cli: { installed, version, compatible } }
getLocalStatus: defineContract({...})
// Returns: { running: boolean, apiUrl?, dbUrl?, studioUrl?, anonKey?, serviceRoleKey? }
connectLocalSupabase: defineContract({ appId: string })
// Sets supabaseMode to "local", caches connection details
disconnectLocalSupabase: defineContract({ appId: string })
// Sets supabaseMode to null, clears cached details
Modified behavior (no contract schema changes):
executeSupabaseSql — routes to local Postgres when app is in local modesetAppProject / unsetAppProject — handles local mode transitionsget_supabase_project_info, get_supabase_table_schema) — return local details when in local modesupabaseMode column to apps table (Drizzle migration)localSupabase settings to SupabaseSchema in schemas.tssupabase_local_client.ts with:
docker command exists and daemon is running)supabase command exists, parse version)supabase status --output json parsing (require CLI v1.50+)checkLocalPrerequisites and getLocalStatus IPC contracts + handlersSupabaseConnector.tsxpostgres npm dependency (pure-JS Postgres client)supabase_local_client.ts via postgresexecuteSupabaseSql through local Postgres when supabaseMode === "local"supabase_schema_query.ts)getSupabaseClientCode() to emit http://127.0.0.1:54321 + local anon keygetPublishableKey() to return local anon key from cached statusconnectLocalSupabase / disconnectLocalSupabase IPC handlerssupabase status check while local view is visible)supabase_prompt.ts with local-specific instructions:
http://127.0.0.1:54321 for API, http://127.0.0.1:54321/functions/v1/ for edge functions)supabase functions serve (user-managed).env files, not Supabase dashboardget_supabase_project_info tool to return local connection detailsget_supabase_table_schema tool to query local Postgresclient.ts imports for local modeSupabaseIntegration.tsx based on mode:
http://127.0.0.1:54323supabase_local_client.ts — test CLI output parsing, version detection, status parsing, prerequisite checks (mock child_process)supabaseModesupabaseMode updates, client code regeneration, settings cache updateIS_TEST_BUILD pattern) — test the full connect flow, prerequisite error states, connected state displaysupabase start" message| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Docker not installed by most users | High | Medium | Gate behind prerequisite check, show clear guidance. Target audience (advanced devs) likely has Docker. |
supabase status --output json unavailable in older CLI versions | Medium | High | Require minimum CLI version (v1.50+). Show "Please update your Supabase CLI" if too old. |
| Port conflicts (54321, 54322, 54323 in use) | Medium | Medium | Surface clear error message when supabase status indicates issues. User resolves externally. |
| Schema divergence when switching local ↔ cloud | High | Medium | Strong confirmation dialog listing consequences. No automatic migration in v1. |
postgres npm package bundling issues in Electron | Low | High | Pure-JS package avoids native compilation. Lazy-import to avoid affecting startup for non-local users. |
| CLI output format changes across versions | Low | Medium | Use --output json for structured output. Pin minimum version. |
| Multiple apps sharing one local instance cause schema collisions | Medium | Low | Document limitation. Users can namespace tables. Consider per-app databases in v2. |
supabase status --output json. This should be validated in Phase 1 before building the parser.postgres npm package validation: Confirm that the pure-JS postgres package works correctly within Electron's Node.js environment for the complex metadata queries in supabase_schema_query.ts. Spike this early in Phase 2.| Decision | Reasoning |
|---|---|
| Detect-only lifecycle (no managed start/stop) | Dramatically reduces scope and complexity. Target audience is comfortable with terminals. Add managed lifecycle in v2 based on usage data. |
postgres npm package for SQL execution | Pure-JS avoids native compilation issues in Electron. Direct Postgres connection is more reliable than CLI shelling for SQL. |
| Per-app mode with shared local instance | Matches existing per-app Supabase project model. One Docker stack avoids resource overhead. Schema collisions are acceptable for v1. |
| Cloud as primary, local as secondary | Cloud is the recommended path for most users. Local has more prerequisites and fewer features. Contextual labels ("Recommended for production" / "Great for development") respect both positions. |
| Edge functions: agent-aware, user-served | Agent writes edge function code with correct localhost URLs. User runs supabase functions serve themselves. Consistent with detect-only lifecycle philosophy. |
| UI-first implementation order | Validates discoverability and flow early. Delivers a visible "steel thread" before deep backend investment. |
| No data migration between local and cloud | Massive scope trap. Users use supabase db push/pull externally if needed. Clear warning on mode switch. |
Generated by dyad:swarm-to-plan