supabase-upgrade-migrationClaude Skill
Execute analyze, plan, and execute Supabase SDK upgrades with breaking change detection.
| name | supabase-upgrade-migration |
| description | Upgrade Supabase SDK and CLI versions with breaking-change detection and automated code migration. Use when upgrading @supabase/supabase-js (v1→v2 or minor bumps), migrating auth/realtime/storage APIs, or updating the Supabase CLI. Trigger with phrases like "upgrade supabase", "supabase breaking changes", "migrate supabase v2", "update supabase SDK". |
| allowed-tools | Read, Write, Edit, Bash(npm:*), Bash(npx:*), Bash(pip:*), Bash(supabase:*), Bash(git:*), Grep, Glob |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| compatible-with | claude-code, codex, openclaw |
| tags | ["saas","supabase","migration","upgrade","sdk"] |
Supabase Upgrade Migration
Overview
Upgrade @supabase/supabase-js and the Supabase CLI with breaking-change detection, automated code migration, and rollback planning. Covers the v1-to-v2 migration path (auth method renames, data/error destructuring, realtime API overhaul), minor version bumps, @supabase/ssr adoption, and Python SDK upgrades via pip install --upgrade supabase.
Current State
!npm list @supabase/supabase-js 2>/dev/null | grep supabase || echo 'supabase-js not installed'
!supabase --version 2>/dev/null || echo 'CLI not installed'
!pip show supabase 2>/dev/null | grep Version || echo 'Python SDK not installed'
Prerequisites
@supabase/supabase-jsor the Pythonsupabasepackage installed in the project- Git with a clean working tree (no uncommitted changes)
- Test suite available for post-upgrade verification
- Node.js >= 18 (for supabase-js v2) or Python >= 3.8 (for Python SDK)
Instructions
Step 1: Audit Versions, Scan Usage, and Review Breaking Changes
Check every installed Supabase package and find all import sites in the codebase.
# Check current SDK version npm list @supabase/supabase-js # Check CLI version supabase --version # Check Python SDK version pip show supabase | grep Version # Find all JS/TS Supabase imports grep -rn "from '@supabase/supabase-js'" --include="*.ts" --include="*.tsx" --include="*.js" src/ lib/ app/ 2>/dev/null grep -rn "createClient" --include="*.ts" --include="*.tsx" --include="*.js" src/ lib/ app/ 2>/dev/null # Find all Python Supabase imports grep -rn "from supabase" --include="*.py" src/ app/ 2>/dev/null
supabase-js v1 → v2 breaking changes:
| v1 Pattern | v2 Replacement | Notes |
|---|---|---|
createClient(url, key) | createClient(url, key) | Signature unchanged, but return type differs |
supabase.auth.session() | supabase.auth.getSession() | Sync → async, returns { data: { session } } |
supabase.auth.user() | supabase.auth.getUser() | Sync → async, returns { data: { user } } |
supabase.auth.signIn({ email, password }) | supabase.auth.signInWithPassword({ email, password }) | Method split by auth type |
supabase.auth.signIn({ provider: 'google' }) | supabase.auth.signInWithOAuth({ provider: 'google' }) | OAuth separated |
supabase.auth.signIn({ email }) | supabase.auth.signInWithOtp({ email }) | Magic link separated |
supabase.auth.api.resetPasswordForEmail(e) | supabase.auth.resetPasswordForEmail(e) | .api namespace removed |
{ data: subscription } from onAuthStateChange | { data: { subscription } } | Extra destructuring level |
error.message string parsing | error.code enum (PGRST116, etc.) | Reliable error matching |
.single() returns error on 0 rows | .maybeSingle() for optional rows | New method for nullable results |
supabase.from('t').on('INSERT', cb).subscribe() | supabase.channel('c').on('postgres_changes', ...).subscribe() | Realtime v2 channel API |
supabase.storage.from('b').download('path') | Same, but returns { data: Blob, error } | Consistent error/data tuple |
Realtime v2 migration detail:
// v1 realtime supabase .from('messages') .on('INSERT', (payload) => console.log(payload.new)) .subscribe() // v2 realtime — channel-based API supabase .channel('messages-insert') .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (payload) => console.log(payload.new)) .subscribe()
Step 2: Run the Upgrade and Apply Code Migrations
Create a branch, install new packages, and transform code to match v2 APIs.
# Create upgrade branch git checkout -b upgrade-supabase-sdk # Upgrade JS/TS SDK npm install @supabase/supabase-js@latest # Upgrade SSR helper (if used with Next.js/SvelteKit/Nuxt) npm install @supabase/ssr@latest # Upgrade CLI npm install -g supabase@latest # Upgrade Python SDK pip install --upgrade supabase # Regenerate TypeScript types from linked project npx supabase gen types typescript --linked > lib/database.types.ts # Generate a database migration if schema drifted npx supabase db diff --use-migra -f upgrade_check
Apply auth code migrations:
// BEFORE (v1 auth patterns) const session = supabase.auth.session() const user = supabase.auth.user() const { error } = await supabase.auth.signIn({ email, password }) const { data: subscription } = supabase.auth.onAuthStateChange(callback) // AFTER (v2 auth patterns) const { data: { session } } = await supabase.auth.getSession() const { data: { user } } = await supabase.auth.getUser() const { error } = await supabase.auth.signInWithPassword({ email, password }) const { data: { subscription } } = supabase.auth.onAuthStateChange(callback)
Apply error handling migration:
// BEFORE (v1 — string matching) if (error.message.includes('not found')) { ... } // AFTER (v2 — structured error codes) if (error.code === 'PGRST116') { ... } // "not found" → PGRST116
Step 3: Verify, Test, and Prepare Rollback
# Type check (catches 90% of migration issues) npx tsc --noEmit # Run test suite npm test # Python tests python -m pytest tests/ -v # Manual smoke test critical auth flows: # 1. Sign up → confirm email → sign in with password # 2. OAuth sign in → callback handling # 3. Password reset → email → reset form # 4. Session refresh across page navigations # 5. Realtime subscription connect/disconnect # 6. Storage upload/download round-trip
Rollback procedure (if upgrade causes issues):
# Option A: Pin to previous version npm install @supabase/supabase-js@<previous-version> pip install supabase==<previous-version> # Option B: Revert the branch git stash && git checkout main
Output
@supabase/supabase-jsupgraded to latest version withnpm listconfirmation- All
supabase.auth.signIn()calls migrated tosignInWithPassword/signInWithOAuth/signInWithOtp - Sync auth methods (
session(),user()) replaced with asyncgetSession()/getUser() - Realtime subscriptions migrated from
.on()to channel-based API data/errordestructuring updated where return shapes changed- TypeScript types regenerated from current schema
- Test suite passing, type checking clean
- Rollback branch or version pin documented
Error Handling
| Error | Cause | Solution |
|---|---|---|
Property 'session' does not exist | v1 sync .session() removed in v2 | Replace with await supabase.auth.getSession() |
Property 'signIn' does not exist | signIn split into multiple methods in v2 | Use signInWithPassword, signInWithOAuth, or signInWithOtp |
supabase.auth.api is undefined | .api namespace removed in v2 | Call methods directly on supabase.auth.* |
TypeError: supabase.from(...).on is not a function | Realtime API replaced in v2 | Use supabase.channel().on('postgres_changes', ...) |
Type errors after gen types | Database schema changed between versions | Update application code to match new generated types |
PGRST116 error on .single() | Zero rows returned (v2 throws) | Use .maybeSingle() for optional lookups |
ERR_REQUIRE_ESM after upgrade | v2 is ESM-only in some bundlers | Update tsconfig.json to "module": "esnext" or use dynamic import() |
AuthSessionMissingError | getSession() called before auth initialized | Wrap in onAuthStateChange listener or check session !== null |
Examples
Full v1 → v2 auth migration (Next.js):
// lib/supabase.ts — client initialization (unchanged API) import { createClient } from '@supabase/supabase-js' import type { Database } from './database.types' export const supabase = createClient<Database>( process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY! )
// app/login/page.tsx — v2 auth flow export async function login(email: string, password: string) { const { data, error } = await supabase.auth.signInWithPassword({ email, password, }) if (error) { // v2: use error.code instead of parsing error.message if (error.code === 'invalid_credentials') { return { success: false, message: 'Invalid email or password' } } throw error } return { success: true, session: data.session } }
// hooks/useAuth.ts — v2 session listener import { useEffect, useState } from 'react' import { supabase } from '@/lib/supabase' import type { Session } from '@supabase/supabase-js' export function useAuth() { const [session, setSession] = useState<Session | null>(null) useEffect(() => { // v2: getSession is async, returns nested { data: { session } } supabase.auth.getSession().then(({ data: { session } }) => { setSession(session) }) // v2: subscription nested one level deeper const { data: { subscription } } = supabase.auth.onAuthStateChange( (_event, session) => setSession(session) ) return () => subscription.unsubscribe() }, []) return session }
Python SDK upgrade:
# Before (supabase-py < 2.0) from supabase import create_client supabase = create_client(url, key) data = supabase.table("users").select("*").execute() users = data["data"] # After (supabase-py >= 2.0) from supabase import create_client, Client supabase: Client = create_client(url, key) response = supabase.table("users").select("*").execute() users = response.data # attribute access, not dict
Resources
- supabase-js v2 Migration Guide — official step-by-step
- supabase-js Releases — changelog for every version
- Supabase CLI Releases — CLI changelog
- Auth Helpers Migration —
@supabase/auth-helpers→@supabase/ssr - Realtime v2 Guide — channel-based API reference
- supabase-py Releases — Python SDK changelog
Next Steps
For CI integration with the upgraded SDK, see supabase-ci-integration. For database migration workflows after schema changes, see supabase-migration-deep-dive.
Similar Claude Skills & Agent Workflows
trello-automation
Automate Trello boards, cards, and workflows via Rube MCP (Composio).
supabase-automation
Automate Supabase database queries, table management, project administration, storage, edge functions, and SQL execution via Rube MCP (Composio).
stripe-automation
Automate Stripe tasks via Rube MCP (Composio): customers, charges, subscriptions, invoices, products, refunds.
shopify-automation
Automate Shopify tasks via Rube MCP (Composio): products, orders, customers, inventory, collections.
miro-automation
Automate Miro tasks via Rube MCP (Composio): boards, items, sticky notes, frames, sharing, connectors.
macos-design
Design and build native-feeling macOS application UIs.