Back to Dyad

Local Supabase Support

plans/local-supabase-support.md

0.44.021.8 KB
Original Source

Local Supabase Support

Generated by swarm planning session on 2026-02-12

Summary

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.

Problem Statement

Dyad users building apps with Supabase currently must connect to a cloud Supabase project for all development. This creates several pain points:

  1. Dev/production parity gap: Every schema change, test row, or accidental DROP TABLE hits a real cloud project. There is no safe local sandbox.
  2. Onboarding friction: Users must create a cloud Supabase account and project before they can start experimenting with Supabase features in Dyad.
  3. Offline development impossible: No internet = no Supabase-backed app development.
  4. Cost concerns: Supabase free tier is limited (2 projects). Users iterating on multiple apps hit limits quickly.

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.

Scope

In Scope (MVP)

  • Detect local Supabase prerequisites (Docker installed, Docker running, Supabase CLI installed, CLI version compatible)
  • Detect local Supabase running status via supabase status
  • Per-app toggle between cloud and local Supabase (supabaseMode field on app record)
  • Connect an app to the local Supabase instance (auto-discover connection details from supabase status)
  • Update client codegen to emit http://127.0.0.1:54321 and local anon key for local mode
  • Schema discovery against local Postgres using postgres npm package (pure-JS)
  • Update agent system prompts with local-specific instructions (correct URLs, local limitations)
  • Edge function support: agent generates edge function code with correct localhost URLs; user runs supabase functions serve themselves
  • Mode switching with confirmation dialog and client code regeneration
  • Contextual labeling: cloud = "Recommended for production", local = "Great for development"
  • Conditional settings: hide/adapt cloud-only settings (edge function pruning) for local mode

Out of Scope (Follow-up)

  • Auto-installing Docker or Supabase CLI
  • Managing supabase start / supabase stop from within Dyad (v2)
  • Managed supabase functions serve process (v2)
  • Data/schema migration between local and cloud environments
  • Multi-local-instance support (one shared instance per machine for v1)
  • Docker Desktop auto-launch (consider for v2, macOS-only initially)
  • Local Supabase Studio embedding (just link to http://127.0.0.1:54323)

User Stories

  • As a Dyad user, I want to connect my app to a local Supabase instance so that I can develop against a local database without risking my cloud data.
  • As a Dyad user, I want to switch between local and cloud Supabase per-app so that I can develop locally and point to cloud when ready.
  • As a new Dyad user, I want to try Supabase features locally without creating a cloud account so that I can evaluate the integration before committing.
  • As a Dyad user building with AI, I want the AI agent to know it's working against local Supabase so that it generates correct URLs, connection strings, and edge function invocations.
  • As a Dyad user, I want to see whether my local Supabase is running so that I know if my local backend is available.
  • As a Dyad user with edge functions, I want the AI agent to write edge function code that works locally so that I can test functions before deploying to cloud.

UX Design

Entry Point

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.

User Flow (Local)

  1. User opens app details → sees SupabaseConnector
  2. Clicks "Use Local Supabase" secondary link
  3. Dyad runs prerequisite checks (Docker installed? Running? CLI installed? Version compatible?)
  4. If prerequisites fail: Show specific, actionable guidance (e.g., "Supabase CLI not found. Install it from https://supabase.com/docs/guides/cli")
  5. If prerequisites pass but Supabase not running: Show instructional state: "Local Supabase is not running. Run supabase start in your project terminal." with copy-to-clipboard and a "Check Again" button
  6. If running: Auto-discover connection details from supabase status --output json, store them, link app to local mode
  7. App is now connected to local Supabase. Agent generates code with http://127.0.0.1:54321 URLs

Key States

No Supabase connected (initial):

  • Cloud branded button (primary CTA) with "Recommended for production" label
  • "Use Local Supabase" text link with "Great for development" label

Local Supabase connected — running:

  • Clear "LOCAL" badge distinguishing from cloud
  • Connection URL displayed (http://127.0.0.1:54321)
  • Green status indicator + "Running" text label
  • "Open Studio" button (links to http://127.0.0.1:54323)
  • "Disconnect" button
  • Settings adapted for local mode (hide cloud-only options)

Local Supabase connected — not running:

  • Yellow/red status indicator + "Not Running" text label
  • Instructional message: "Run supabase start in your project terminal"
  • Copy-to-clipboard for the command
  • "Check Again" / refresh button

Local Supabase — prerequisites missing:

  • Specific error per missing prerequisite (Docker not installed, Docker not running, CLI not found, CLI version too old)
  • Actionable guidance with links to installation docs
  • "Check Again" button after user resolves

Mode switching confirmation:

  • Dialog: "Switching to [cloud/local] will update your app's database connection. The target environment may not have matching tables or data. You may need to apply migrations. Your [current] data will not be affected."
  • Confirm / Cancel buttons

Interaction Details

  • Prerequisite check runs automatically when user clicks "Use Local Supabase" — no manual trigger needed
  • Status detection polls supabase status when the local connected view is visible (reasonable interval, e.g., every 30 seconds)
  • "Check Again" button triggers an immediate re-check
  • Mode switching regenerates src/integrations/supabase/client.ts automatically and shows a toast: "Supabase client updated. Restart your dev server for changes to take effect."
  • "Open Studio" opens http://127.0.0.1:54323 in the system browser

Accessibility

  • Fix existing `` connect button (SupabaseConnector.tsx ~line 454) — wrap in proper <button> element with ARIA
  • All new controls must be keyboard navigable (Tab to reach, Enter/Space to activate)
  • Status indicators use text labels alongside colored indicators (not color-only)
  • Copy-to-clipboard button has visible focus indicator
  • Screen readers announce connection type and status

Technical Design

Architecture

Introduce 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:

  • SQL execution: postgres npm package (pure-JS, no native compilation) connecting to postgresql://postgres:[email protected]:54322/postgres
  • Status/detection: Shell out to supabase status --output json (requires CLI v1.50+ for JSON output)
  • Client codegen: Emit http://127.0.0.1:54321 with local anon key
  • Edge functions: Agent writes code with http://127.0.0.1:54321/functions/v1/FUNCTION_NAME URLs; user serves locally via supabase functions serve

Components Affected

FileChange
src/db/schema.tsAdd supabaseMode column to apps table
src/lib/schemas.tsAdd localSupabase settings object to SupabaseSchema
src/supabase_admin/supabase_local_client.tsNew file. CLI detection, supabase status parsing, direct Postgres queries via postgres npm
src/supabase_admin/supabase_management_client.tsAdd local routing in executeSupabaseSql and key functions
src/supabase_admin/supabase_context.tsUpdate getSupabaseClientCode() and getPublishableKey() for local URLs/keys
src/supabase_admin/supabase_schema_query.tsNo change (same SQL queries, different execution path)
src/components/SupabaseConnector.tsxAdd local Supabase entry point, local connected state, prerequisite check UI, status display
src/components/SupabaseIntegration.tsxConditionally render settings based on supabaseMode
src/ipc/types/supabase.tsAdd new contracts: checkLocalPrerequisites, getLocalStatus, connectLocal, disconnectLocal
src/ipc/handlers/supabase_handlers.tsAdd local handlers, route existing handlers by mode
src/hooks/useSupabase.tsAdd hooks for local status, prerequisites
src/prompts/supabase_prompt.tsAdd local-specific agent instructions (URLs, edge function serving, limitations)
src/pro/main/ipc/handlers/local_agent/tools/get_supabase_project_info.tsReturn local connection details when in local mode
src/pro/main/ipc/handlers/local_agent/tools/get_supabase_table_schema.tsRoute schema queries through local Postgres when in local mode

Data Model Changes

Apps table (Drizzle migration):

typescript
supabaseMode: text("supabase_mode"), // "cloud" | "local" | null

When supabaseMode is "local":

  • supabaseProjectId is null
  • supabaseOrganizationSlug is null
  • Connection details come from cached supabase status output in settings

Settings schema (src/lib/schemas.ts):

typescript
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.

API Changes

New IPC contracts:

typescript
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 mode
  • setAppProject / unsetAppProject — handles local mode transitions
  • Agent tools (get_supabase_project_info, get_supabase_table_schema) — return local details when in local mode

Implementation Plan

Phase 1: Foundation + UI Shell

  • Add supabaseMode column to apps table (Drizzle migration)
  • Add localSupabase settings to SupabaseSchema in schemas.ts
  • Create supabase_local_client.ts with:
    • Docker detection (check if docker command exists and daemon is running)
    • Supabase CLI detection (check if supabase command exists, parse version)
    • supabase status --output json parsing (require CLI v1.50+)
  • Add checkLocalPrerequisites and getLocalStatus IPC contracts + handlers
  • Add "Use Local Supabase" secondary link in SupabaseConnector.tsx
  • Build prerequisite check UI (Docker/CLI not found states with guidance)
  • Build "not running" instructional state with copy-to-clipboard
  • Build local connected state (LOCAL badge, status indicator, Open Studio button)
  • Fix `` accessibility issue on existing connect button

Phase 2: Core Local Operations

  • Add postgres npm dependency (pure-JS Postgres client)
  • Implement local SQL execution in supabase_local_client.ts via postgres
  • Route executeSupabaseSql through local Postgres when supabaseMode === "local"
  • Implement local schema discovery (reuse existing queries from supabase_schema_query.ts)
  • Update getSupabaseClientCode() to emit http://127.0.0.1:54321 + local anon key
  • Update getPublishableKey() to return local anon key from cached status
  • Implement connectLocalSupabase / disconnectLocalSupabase IPC handlers
  • Add mode switching with confirmation dialog + client code regeneration
  • Add status polling (periodic supabase status check while local view is visible)

Phase 3: Agent Integration

  • Update supabase_prompt.ts with local-specific instructions:
    • Correct URLs (http://127.0.0.1:54321 for API, http://127.0.0.1:54321/functions/v1/ for edge functions)
    • Note that edge functions are served locally via supabase functions serve (user-managed)
    • Note that branches and cloud-specific features are not available
    • Note that secrets are managed via .env files, not Supabase dashboard
  • Update get_supabase_project_info tool to return local connection details
  • Update get_supabase_table_schema tool to query local Postgres
  • Ensure agent generates correct client.ts imports for local mode
  • Update edge function invocation URLs in agent prompts to use localhost

Phase 4: Polish + Settings

  • Conditionally render settings in SupabaseIntegration.tsx based on mode:
    • Hide "Skip pruning edge functions" for local mode
    • Adapt "Write SQL migration files" if needed
  • Add "Open Studio" button linking to http://127.0.0.1:54323
  • Handle edge case: user has cloud connected, clicks "Use Local" → show switching confirmation
  • Handle edge case: local Supabase stops while app is connected → show "not running" state gracefully
  • Toast notification after mode switch: "Supabase client updated. Restart your dev server."

Testing Strategy

  • Unit tests: supabase_local_client.ts — test CLI output parsing, version detection, status parsing, prerequisite checks (mock child_process)
  • Unit tests: Conditional routing in handlers — verify local vs cloud dispatch based on supabaseMode
  • Unit tests: Client codegen — verify correct URLs and keys for local vs cloud mode
  • Integration tests: Mode switching — verify supabaseMode updates, client code regeneration, settings cache update
  • E2E tests: Mock local Supabase CLI responses (similar to existing IS_TEST_BUILD pattern) — test the full connect flow, prerequisite error states, connected state display
  • Manual testing matrix:
    • Fresh install, no Docker → helpful error with install link
    • Docker installed but not running → "Start Docker and try again"
    • Docker + CLI installed, Supabase not started → "Run supabase start" message
    • Docker + CLI + Supabase running → full connect flow
    • Switch cloud → local on same app → confirmation dialog, client regenerated
    • Switch local → cloud on same app → confirmation dialog, client regenerated
    • Agent generates correct code for local mode (URLs, edge function paths)
    • Agent generates correct code for cloud mode (unchanged from current behavior)

Risks & Mitigations

RiskLikelihoodImpactMitigation
Docker not installed by most usersHighMediumGate behind prerequisite check, show clear guidance. Target audience (advanced devs) likely has Docker.
supabase status --output json unavailable in older CLI versionsMediumHighRequire minimum CLI version (v1.50+). Show "Please update your Supabase CLI" if too old.
Port conflicts (54321, 54322, 54323 in use)MediumMediumSurface clear error message when supabase status indicates issues. User resolves externally.
Schema divergence when switching local ↔ cloudHighMediumStrong confirmation dialog listing consequences. No automatic migration in v1.
postgres npm package bundling issues in ElectronLowHighPure-JS package avoids native compilation. Lazy-import to avoid affecting startup for non-local users.
CLI output format changes across versionsLowMediumUse --output json for structured output. Pin minimum version.
Multiple apps sharing one local instance cause schema collisionsMediumLowDocument limitation. Users can namespace tables. Consider per-app databases in v2.

Open Questions

  • Minimum Supabase CLI version: Need to verify which version first supported 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.
  • Per-app database isolation: v1 uses a shared local instance. If users request per-app isolation, we could create separate Postgres databases within the single instance (one per app). Defer unless needed.

Decision Log

DecisionReasoning
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 executionPure-JS avoids native compilation issues in Electron. Direct Postgres connection is more reliable than CLI shelling for SQL.
Per-app mode with shared local instanceMatches existing per-app Supabase project model. One Docker stack avoids resource overhead. Schema collisions are acceptable for v1.
Cloud as primary, local as secondaryCloud 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-servedAgent writes edge function code with correct localhost URLs. User runs supabase functions serve themselves. Consistent with detect-only lifecycle philosophy.
UI-first implementation orderValidates discoverability and flow early. Delivers a visible "steel thread" before deep backend investment.
No data migration between local and cloudMassive scope trap. Users use supabase db push/pull externally if needed. Clear warning on mode switch.

Generated by dyad:swarm-to-plan