supabase/supabase · warning

Method ${method} Not Allowed

Error message

Method ${method} Not Allowed

What it means

HTTP 405 from the MFA-factors admin route. Only DELETE is handled (it lists and removes all MFA factors for a user via selfHostedSupabaseAdmin.auth.admin.mfa); all other methods return `{ data: null, error: { message: 'Method ${method} Not Allowed' } }` with Allow: DELETE. A 405 here means the wrong verb reached an admin-only destructive endpoint.

Source

Thrown at apps/studio/pages/api/platform/auth/[ref]/users/[id]/factors.ts:16

import { NextApiRequest, NextApiResponse } from 'next'

import { apiWrapper } from '@/lib/api/apiWrapper'
import { selfHostedSupabaseAdmin as supabase } from '@/lib/api/self-hosted-admin'

export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)

async function handler(req: NextApiRequest, res: NextApiResponse) {
  const { method } = req

  switch (method) {
    case 'DELETE':
      return handleDelete(req, res)
    default:
      res.setHeader('Allow', ['DELETE'])
      res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
  }
}

const handleDelete = async (req: NextApiRequest, res: NextApiResponse) => {
  const { id } = req.query

  // Get all factors for the user
  const { data: factors, error } = await supabase.auth.admin.mfa.listFactors({
    userId: id as string,
  })
  if (error) {
    return res.status(400).json({ error: { message: error.message } })
  }

  factors?.factors.forEach(async (factor: any) => {
    const { error } = await supabase.auth.admin.mfa.deleteFactor({
      id: factor.id,
      userId: id as string,

View on GitHub (pinned to beee91b9c2)

Solutions

  1. Send DELETE: `fetch(`/api/platform/auth/${ref}/users/${id}/factors`, { method: 'DELETE' })`.
  2. If you needed to list factors, hit the pg/supabase admin list endpoint directly, not this route.
  3. Verify the calling code is not falling back to a default GET helper.
  4. Only add a non-DELETE case if the route is intended to support listing/creating.

Example fix

// before
await fetch(`/api/platform/auth/${ref}/users/${id}/factors`)

// after
await fetch(`/api/platform/auth/${ref}/users/${id}/factors`, { method: 'DELETE' })
Defensive patterns

Strategy: validation

Validate before calling

if (method.toUpperCase() !== 'DELETE') {
  throw new Error('factors route only supports DELETE')
}

Type guard

type FactorMethod = 'DELETE'
function isFactorMethod(m: string): m is FactorMethod {
  return m.toUpperCase() === 'DELETE'
}

Try / catch

const res = await fetch(url, { method: 'DELETE' })
if (res.status === 405) {
  throw new Error('Use DELETE to remove MFA factors; this route does not list/create')
}

Prevention

When it happens

Trigger: Calling /api/platform/auth/[ref]/users/[id]/factors with GET (expecting to list factors), POST (creating), or PUT/PATCH; or a fetch missing `method: 'DELETE'`.

Common situations: Assuming the endpoint lists factors (it does not — it only deletes); a UI 'Remove MFA factors' button that drops the method and sends GET; integration tests reusing a GET helper.

Related errors


AI-assisted analysis of supabase/supabase@beee91b9c2 (2026-08-12). Data as JSON: /api/errors/728b16d6347fcd2a. Report an issue: GitHub.