Does Supabase Work With Clerk?
You can use Clerk and Supabase together, but they duplicate authentication responsibilities and require careful architecture to avoid conflicts.
Quick Facts
How Supabase Works With Clerk
Clerk and Supabase both handle authentication, which creates architectural overlap. The typical pattern is to use Clerk for user management and UI, then use Clerk's JWT tokens to authenticate requests to Supabase. You disable Supabase's built-in auth or use it only for row-level security (RLS) policies. Clerk provides the session management and pre-built components, while Supabase handles the database and realtime features. The developer experience is smooth once configured: Clerk handles signup/login, and you pass Clerk's JWT to Supabase client initialization. However, you're maintaining two authentication systems, which adds complexity around token refresh, user metadata sync, and logout flows. This combo works best when you want Clerk's superior UX and user management but need Supabase's PostgreSQL database and realtime capabilities. If you only need simple auth, using Supabase alone is simpler.
Best Use Cases
Quick Setup
npm install @clerk/nextjs @supabase/supabase-js// pages/api/auth.ts - Setup Supabase client with Clerk JWT
import { createClient } from '@supabase/supabase-js';
import { getAuth } = from '@clerk/nextjs/server';
export async function getSupabaseClient(req, res) {
const { getToken } = getAuth(req);
const token = await getToken({ template: 'supabase' });
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
global: {
headers: { Authorization: `Bearer ${token}` },
},
}
);
return supabase;
}
// pages/dashboard.tsx - Usage example
import { useAuth } from '@clerk/nextjs';
import { createClient } from '@supabase/supabase-js';
export default function Dashboard() {
const { getToken } = useAuth();
const [data, setData] = React.useState(null);
React.useEffect(() => {
(async () => {
const token = await getToken({ template: 'supabase' });
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{ global: { headers: { Authorization: `Bearer ${token}` } } }
);
const { data } = await supabase.from('posts').select('*');
setData(data);
})();
}, [getToken]);
return <div>{JSON.stringify(data)}</div>;
}Known Issues & Gotchas
Token mismatch: Supabase JWT validation fails if Clerk tokens aren't configured as valid JWTs for Supabase
Fix: Configure Supabase to accept Clerk's JWT_TEMPLATE. Set the JWKS URL to Clerk's public key endpoint in your Supabase project settings, or manually verify tokens in your API layer.
User metadata synchronization: Supabase auth_users table won't auto-sync with Clerk's user directory
Fix: Use Clerk webhooks to sync user data to a custom Supabase table (users_profiles). Clerk fires events on user creation/update; catch these and upsert into your Supabase table.
Session logout timing: Logging out from Clerk doesn't invalidate Supabase sessions if you cache tokens client-side
Fix: Always listen to Clerk's onClerkLoaded and session change events to clear Supabase client state and remove cached tokens.
RLS policies expect a user ID in auth.uid(), but Clerk's sub claim might not match your Supabase user table ID
Fix: Map Clerk user IDs to Supabase row IDs explicitly, or use custom claims in Clerk's JWT to include both identifiers.
Alternatives
- •Supabase Auth + Supabase UI: Simpler but less polished authentication UI; all features in one platform
- •Firebase + Clerk: Similar pattern but with Firestore instead of PostgreSQL; better for real-time document-based apps
- •Auth0 + Supabase: More enterprise-focused than Clerk, with deeper customization options
Resources
Related Compatibility Guides
Explore more compatibility guides