@supabase/server - v0.2.0
    Preparing search index...

    Hono Adapter

    Install Hono as a peer dependency:

    pnpm add hono
    

    The adapter exports its own withSupabase that returns Hono middleware instead of a fetch handler.

    import { Hono } from 'hono'
    import { withSupabase } from '@supabase/server/adapters/hono'

    const app = new Hono()

    // Apply auth to all routes
    app.use('*', withSupabase({ allow: 'user' }))

    app.get('/todos', async (c) => {
    const { supabase } = c.var.supabaseContext
    const { data } = await supabase.from('todos').select()
    return c.json(data)
    })

    app.get('/profile', async (c) => {
    const { supabase, userClaims } = c.var.supabaseContext
    const { data } = await supabase
    .from('profiles')
    .select()
    .eq('id', userClaims!.id)
    return c.json(data)
    })

    export default { fetch: app.fetch }

    The context is stored in c.var.supabaseContext and contains the same SupabaseContext fields as the main withSupabase wrapper: supabase, supabaseAdmin, userClaims, claims, and authType.

    Apply different auth modes to different routes by using the middleware inline:

    import { Hono } from 'hono'
    import { withSupabase } from '@supabase/server/adapters/hono'

    const app = new Hono()

    // Public route — no auth
    app.get('/health', (c) => c.json({ status: 'ok' }))

    // User-authenticated route
    app.get('/todos', withSupabase({ allow: 'user' }), async (c) => {
    const { supabase } = c.var.supabaseContext
    const { data } = await supabase.from('todos').select()
    return c.json(data)
    })

    // Secret-key-protected admin route
    app.post('/admin/sync', withSupabase({ allow: 'secret' }), async (c) => {
    const { supabaseAdmin } = c.var.supabaseContext
    const { data } = await supabaseAdmin
    .from('audit_log')
    .insert({ action: 'sync' })
    return c.json(data)
    })

    // Dual auth — users or services
    app.get('/reports', withSupabase({ allow: ['user', 'secret'] }), async (c) => {
    const { supabase, authType } = c.var.supabaseContext
    return c.json({ authType })
    })

    export default { fetch: app.fetch }

    If a previous middleware already set c.var.supabaseContext, subsequent withSupabase calls skip auth. This matters when multiple app.use middlewares overlap on the same path — the first one to set the context wins.

    Important: Hono runs middleware in registration order (app.use before route-level middleware). An app.use('*', ...) middleware will always run before inline route middleware, so the skip-if-set pattern cannot be used to make a route stricter than the app-wide default.

    For routes that need different auth than the rest of the app, use per-route middleware without an app-wide middleware (see the "Per-route auth" section above).

    The Hono adapter does not handle CORS — the cors option is excluded from its config type. Use Hono's built-in CORS middleware:

    import { Hono } from 'hono'
    import { cors } from 'hono/cors'
    import { withSupabase } from '@supabase/server/adapters/hono'

    const app = new Hono()

    app.use('*', cors())
    app.use('*', withSupabase({ allow: 'user' }))

    app.get('/todos', async (c) => {
    const { supabase } = c.var.supabaseContext
    const { data } = await supabase.from('todos').select()
    return c.json(data)
    })

    export default { fetch: app.fetch }

    When auth fails, the adapter throws a Hono HTTPException. The original AuthError is available via cause:

    import { Hono } from 'hono'
    import { HTTPException } from 'hono/http-exception'
    import { withSupabase } from '@supabase/server/adapters/hono'
    import { AuthError } from '@supabase/server'

    const app = new Hono()

    app.use('*', withSupabase({ allow: 'user' }))

    // Custom error handler
    app.onError((err, c) => {
    if (err instanceof HTTPException && err.cause instanceof AuthError) {
    const authError = err.cause
    return c.json(
    { error: authError.message, code: authError.code },
    authError.status as 401 | 500,
    )
    }
    return c.json({ error: 'Internal server error' }, 500)
    })

    app.get('/todos', async (c) => {
    const { supabase } = c.var.supabaseContext
    const { data } = await supabase.from('todos').select()
    return c.json(data)
    })

    export default { fetch: app.fetch }

    Pass env to override auto-detected environment variables, same as the main wrapper:

    app.use(
    '*',
    withSupabase({
    allow: 'user',
    env: { url: 'http://localhost:54321' },
    }),
    )

    Forward options to the underlying createClient() calls:

    app.use(
    '*',
    withSupabase({
    allow: 'user',
    supabaseOptions: { db: { schema: 'api' } },
    }),
    )