Unexpected CORS errors encountered in HTTP DELETE Edge Function invocations
Bug report
- [x] I confirm this is a bug with Supabase, not with my own application.
- [x] I confirm I have searched the Docs, GitHub Discussions, and Discord.
Describe the bug
The edge function runtime throws CORS errors in the browser when attempting to invoke a function for HTTP DELETE methods. I haven't tested beyond HTTP DELETE and POST, but I suspect that this issue may apply to other methods as well.
To Reproduce
- Create an HTTP DELETE edge function, follow the guides to develop locally and configure CORS as instructed in the docs here
- In a web application, make a call to
supabase.functions.invokeand attempt to invoke your function using the 'DELETE' method. - Observe the network tab in your browser's dev tools window. Note that an unexpected CORS error is thrown, even if your CORS headers are configured and applied to responses correctly according to the documentation.
Here is the code that causes the request to fail.
const { error } = await supabase.functions.invoke('delete-self', {
method: 'DELETE',
})
Expected behavior
I should be able to call this endpoint without encountering CORS errors like I can with POST requests. Additionally, the docs for supabase.functions.invoke should be updated to allow the body option to be undefined for DELETE requests.
Screenshots
Workaround
You can still successfully invoke these functions, but you must use the HTTP POST method to do so. Here's an example of a working invocation:
const { error } = await supabase.functions.invoke('delete-self', {
body: {},
method: 'POST',
})
You'll also need to ensure that your edge function is adjusted to allow POST requests instead of DELETE requests.
System information
- OS: macOS Sequoia 15.5
- Browser: Brave Browser 1.79.123
- Version of supabase-js: 2.49.1
- Version of Node.js: 22.14.0
Additional context
Here's the full content of my edge function and the contents of _shared/cors.ts
// _shared/cors.ts
export const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
}
// delete-self/index.ts
import 'jsr:@supabase/functions-js/edge-runtime.d.ts'
import { createClient } from 'jsr:@supabase/supabase-js@2'
import { corsHeaders } from '../_shared/cors.ts'
Deno.serve(async (req) => {
if (req.method === 'OPTIONS') {
return new Response('ok', {
headers: corsHeaders
})
}
if (req.method !== 'DELETE') {
return new Response('Method not allowed', {
headers: {
...corsHeaders,
'Allow': 'DELETE'
},
status: 405
})
}
try {
// Create a Supabase client with the Auth context of the logged-in user
const supabaseClient = createClient(Deno.env.get('SUPABASE_URL') as string, Deno.env.get('SUPABASE_ANON_KEY') as string, {
global: {
headers: {
Authorization: req.headers.get('Authorization') as string,
},
},
})
const token = req.headers.get('Authorization')?.replace('Bearer ', '')
const { data: { user }, error: userError } = await supabaseClient.auth.getUser(token)
if (userError) throw userError
if (!user) throw new Error('Failed to get user')
// Sign out the user globally
await supabaseClient.auth.signOut()
// Create a separate service role client to handle the actual deletion action, since the user is logged out, and
// the delete action is a service role action
const serviceRoleClient = createClient(Deno.env.get('SUPABASE_URL') as string, Deno.env.get('SUPABASE_SERVICE_ROLE_KEY') as string)
const { error: deleteUserError } = await serviceRoleClient.auth.admin.deleteUser(user.id)
if (deleteUserError) throw deleteUserError
return new Response(JSON.stringify({
message: 'User deleted successfully.',
}), {
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
status: 200,
})
} catch (error: any) {
return new Response(JSON.stringify({
error: error?.message,
}), {
headers: {
...corsHeaders,
'Content-Type': 'application/json',
},
status: 400,
})
}
})
Have you checked the function log on the dashboard?
If there is a problem with the function, CORS header may be removed when the gateway returns the response instead. Instead of calling the function via the API in supabase-js, sending the request via curl is also a good way to find out what the problem is.
I can invoke the functions via cURL/Postman without a problem. It's when I try invoking it from the supabase JS client in the browser that I run into problems.
I can invoke the functions via cURL/Postman without a problem. It's when I try invoking it from the supabase JS client in the browser that I run into problems.
If so, we should probably move this issue to supabase-js.
I don't believe Curl/Postman do options requests at least by default.
@AverageCakeSlice I was able to reproduce your issue and confirm its a CORS-related problem specific to DELETE method (and other non simple methods)
its from the lack of proper CORS headers in the preflight (OPTIONS) response
how to resolve the CORS errors:
Deno.serve(async (req) => {
const origin = req.headers.get("origin") || "*";
const corsHeaders = {
'Access-Control-Allow-Origin': origin, // * is not valid when credentials like authorization are used
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
'Access-Control-Allow-Methods': 'DELETE', // needed to allow DELETE requests
};
if (req.method === 'OPTIONS') {
return new Response('ok', {
headers: corsHeaders
});
}
if (req.method !== 'DELETE') {
return new Response('invalid method');
}
return new Response('deleted', {
headers: {
...corsHeaders,
'Content-Type': 'text/plain'
}
});
});
its not a bug but i think this should be mentioned in the docs for clarity
this issue can be closed since this is not a bug in supabase-js
This issue has been automatically marked as stale because it has not had any activity for 6 months. It will be closed in 30 days if no further activity occurs.
If this is still relevant, please comment to keep it open.
I have created this project here as a playground: https://github.com/mandarini/test-edge
I can invoke all CRUD functions without an issue.
- Here is my
cors.tsfile: https://github.com/mandarini/test-edge/blob/main/supabase/functions/_shared/cors.ts
As @GAURAV-DEEP01 very correctly pointed out, you needed to allow the method in the cors config. You are right, we should enhance the docs there. I am closing this issue, and pinging the docs team.