supabase-upgrade-migration
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
Provided by Plugin
supabase-pack
Claude Code skill pack for Supabase (30 skills)
Installation
This skill is included in the supabase-pack plugin:
/plugin install supabase-pack@claude-code-plugins-plus
Click to copy
Instructions
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 |
ERRREQUIREESM 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.