Install H3 as a peer dependency:
pnpm add h3The adapter exports its own withSupabase that returns H3 middleware instead of a fetch handler. Works with standalone H3 servers and Nuxt server routes (which run on H3 under the hood).
The middleware stores the context on event.context.supabaseContext. Add this declaration once in your project (e.g. in types/h3.d.ts) for typed access:
import type { SupabaseContext } from '@supabase/server'
declare module 'h3' {
interface H3EventContext {
supabaseContext: SupabaseContext
}
}import { H3 } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'
const app = new H3()
// Apply auth to all routes
app.use(withSupabase({ auth: 'user' }))
app.get('/todos', async (event) => {
const { supabase } = event.context.supabaseContext
const { data } = await supabase.from('todos').select()
return data
})
app.get('/profile', async (event) => {
const { supabase, userClaims } = event.context.supabaseContext
const { data } = await supabase
.from('profiles')
.select()
.eq('id', userClaims!.id)
return data
})
export default { fetch: app.fetch }The context is stored in event.context.supabaseContext and contains the same SupabaseContext fields as the main withSupabase wrapper: supabase, supabaseAdmin, userClaims, jwtClaims, and authMode.
Apply different auth modes to different routes by attaching the middleware to specific handlers:
import { H3 } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'
const app = new H3()
// Public route — no auth
app.get('/health', () => ({ status: 'ok' }))
// User-authenticated route
app.get('/todos', withSupabase({ auth: 'user' }), async (event) => {
const { supabase } = event.context.supabaseContext
const { data } = await supabase.from('todos').select()
return data
})
// Secret-key-protected admin route
app.post('/admin/sync', withSupabase({ auth: 'secret' }), async (event) => {
const { supabaseAdmin } = event.context.supabaseContext
const { data } = await supabaseAdmin
.from('audit_log')
.insert({ action: 'sync' })
return data
})
// Dual auth — users or services
app.get(
'/reports',
withSupabase({ auth: ['user', 'secret'] }),
async (event) => {
const { authMode } = event.context.supabaseContext
return { authMode }
},
)
export default { fetch: app.fetch }Use defineHandler to attach auth to a single Nuxt server route:
// server/api/games.get.ts
import { defineHandler } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'
export default defineHandler({
middleware: [withSupabase({ auth: 'user' })],
handler: async (event) => {
const { supabase } = event.context.supabaseContext
return supabase.from('favorite_games').select()
},
})Register as a server middleware to apply auth to every route:
// server/middleware/supabase.ts
import { withSupabase } from '@supabase/server/adapters/h3'
export default withSupabase({ auth: 'user' })If a previous middleware already set event.context.supabaseContext, subsequent withSupabase calls skip auth. This enables a pattern where route-level middleware overrides the app-wide default:
const app = new H3()
// App-wide: require user auth
app.use(withSupabase({ auth: 'user' }))
// This route needs secret auth instead.
// The route-level middleware runs first, sets the context,
// and the app-wide middleware skips.
app.post('/webhook', withSupabase({ auth: 'secret' }), async (event) => {
const { supabaseAdmin } = event.context.supabaseContext
// ...
})The H3 adapter does not handle CORS — the cors option is excluded from its config type. Use H3's built-in CORS utilities:
import { H3, handleCors } from 'h3'
import { withSupabase } from '@supabase/server/adapters/h3'
const app = new H3()
app.use((event) => handleCors(event, { origin: '*' }))
app.use(withSupabase({ auth: 'user' }))
app.get('/todos', async (event) => {
const { supabase } = event.context.supabaseContext
const { data } = await supabase.from('todos').select()
return data
})
export default { fetch: app.fetch }Pass env to override auto-detected environment variables, same as the main wrapper:
app.use(
withSupabase({
auth: 'user',
env: { url: 'http://localhost:54321' },
}),
)Forward options to the underlying createClient() calls:
app.use(
withSupabase({
auth: 'user',
supabaseOptions: { db: { schema: 'api' } },
}),
)