DEV Community


CORS, preflighted requests & OPTIONS method

Software Developer.
Updated on ・3 min read

Working on my side project, I came across a problem familiar to many - CORS. However, it wasn't the usual message I get when I forget to enable cross-origin on my backend. This one said:

Failed to load resource: Preflight response is not successful.
Fetch API cannot load <url>. Preflight response is not successful
Enter fullscreen mode Exit fullscreen mode

This happened on the very first GET request I made to the server. I was fetching data from an API that was hosted on a different domain from my front-end. It was a simple request and the error left me very confused. After little troubleshooting, I found few solutions to my problem and learned quite a lot.

CORS - Quick intro

CORS or Cross-Origin Resource Sharing is a way for server to check if requests coming in are allowed if they're coming from a different origin. Meaning, if web application makes a request to, using either XMLHttpRequest or fetch API, CORS will use HTTP headers to tell the application if has the right permission to access The permissions need to be configured at, where you specify what methods and from what origins can come through.

exchange of headers between client and server

By default, for security, both XMLHttpRequest and fetch follow same-origin policy so if you haven't specifically configured CORS and the HTTP headers on your back-end, the requests from different domain will fail.

What is a preflight request?

When it comes to preflight, we can divide requests into two categories: simple requests and preflighted requests.

Simple requests

Some requests - called simple - don't trigger a preflight check. There is a simple exchange of CORS headers between client and server to check the permissions. For request to be simple, it needs to meet several criteria:

  • The allowed methods are
    • GET
    • HEAD
    • POST
  • The only headers which are allowed:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Type (but note the additional requirements below)
    • Last-Event-ID
    • DPR
    • Save-Data
    • Viewport-Width
    • Width
  • There are only three values allowed for Content-Type header for simple requests
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • No event listeners are registered on any XMLHttpRequestUpload object used in the request; these are accessed using the XMLHttpRequest.upload property.
  • No ReadableStream object is used in the request.

Preflighted requests

Now, if the request doesn't meet the criteria above, the browser automatically sends a HTTP request before the original one by OPTIONS method to check whether it is safe to send the original request. Most common cases are if requests have DELETE, PUT or any other method that can amend data, any headers that are not CORS-safelisted (listed above) or Content-Type of, let's say application/json.

So, let's say we send a DELETE request to the server. The browser sends OPTIONS request with headers containing info about the DELETE request we made.

OPTIONS /users/:id 
Access-Control-Request-Method: DELETE 
Access-Control-Request-Headers: origin, x-requested-with
Enter fullscreen mode Exit fullscreen mode

The server then makes a check and if it allows the request, it responds with status code 200 OK.

HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Enter fullscreen mode Exit fullscreen mode

After this check, the browser will send the original DELETE request we made in the first place.

The error and how to fix it

You have several options, really. The easiest option would be to avoid the preflight request altogether by making sure your request falls into the simple category. Go through your settings and headers and remove or change any complex headers that aren't needed.

However, this only solves small percentage of cases. My app is authenticated and I'm sending Authorization header with tokens by PUT and DELETE methods. I am using cors npm package that enables CORS in my Express app so I checked their documentation and they also provide OPTIONS handling. All's fixed just with one line.

Final words

If you want to learn more, I'd recommend the MDN's articles on CORS and OPTIONS. This article was very useful when I was trying to eliminate preflight, despite some advices like Moving Authorization to the query string seemed little extreme and for some reason very uncomfortable.

I hope you enjoyed this article, if you have any questions or notes, just comment bellow ;)

Discussion (10)

antogarand profile image
Antony Garand

The easiest option would be to avoid the preflight request altogether by making sure your request falls into the simple category. Go through your settings and headers and remove or change any complex headers that aren't needed. Is application/json really necessary in your case? Change it to text/plain.

This is a bad solution and does not solve the inner problem.

Preflights requests exist as an extra layer of security when manipulating an API, and sending an invalid mime type only adds to the confusion.

Adding OPTIONS support to your backend is the best solution, as long as you don't do something silly like always returning 200 for options query.

effingkay profile image
Klaudia Author

That's actually a good point, I didn't mean to say that you should be sending invalid or wrong types just so you avoid preflight (although I see how that came across and I changed it slightly) but I think having preflight requests for every single request you make can be an overkill.

I was thinking about the case when I added content-type: application/json to every request on my front end even when it was not necessary and that resulted in unnecessary preflight requests for simple GET request which should have been allowed by already enabled CORS.

And thank you for pointing this out :)

effingkay profile image
Klaudia Author

You know, this was the first time I've encountered this problem. Until a few weeks ago, I haven't even heard of OPTIONS method to be completely honest and I did authenticated APIs before. I guess it depends how you handle CORS in the first place, maybe some packages manage preflight automatically or maybe you don't even need it because you don't make any OPTIONS requests. It all depends on your user case.

patricknelson profile image
Patrick Nelson

Moving Authorization to the query string seemed little extreme and for some reason very uncomfortable.

This is because query strings are typically logged. It's a very bad idea to be logging credentials in plain text like that (for obvious reasons).

imsingh profile image
mohinder singh • Edited

Can anybody suggest me anything, for my below question on stackOverflow. Thanks

pchandoria profile image
Pramod Chandoria

Thanks for providing the reason for the problem.
But I think there is no valid solution except implement the OPTIONS handler.
Preflight call does not make sense as it kills performance. Not sure why the spec council did not thing about performance while ensuring security.

I was thinking if origin server can send some CORS headers e.g. Access-Control-Allow-Origin: to make browser happy and avoid preflight call.
I am not very clear here for whom behalf browser is security concerned here. or If it is then spec should allow to send CORS headers for trust. If concern is then can take care anyhow by looking at the referer. Finger crossed

maxsierra16 profile image
Maximo Sierra

Really helpful!

hannes_solo profile image
Hans Solo

This was a great explanation! :)

rickgg profile image

Thank you!! I was having trouble with a project and this simple guide explained everything that MDN, Google Docs and StackOverflow couldn't say simply.