GuidesPYTHON
Fix CORS errors in Django
The browser withholds the response because Django sent no matching Access-Control headers to authorize the calling web page. When you maintain the Django project, installing and configuring the django-cors-headers package resolves the issue by attaching the expected response headers. A correct django cors setup spans middleware order, origin lists, and authentication settings.
Confirm the fix belongs in Django
When your front-end code queries your own Django API and the browser console reports a missing Access-Control-Allow-Origin header, the fix belongs inside Django settings. The browser refuses to expose the payload because the server omitted the access-control response headers, which is a backend configuration task rather than a front-end fetch bug. Review the diagnosis guide to inspect the headers from the browser network panel.
If the failing API belongs to a third party, its configuration is out of reach. A public proxy like cors.dev can re-serve public GET and HEAD responses from public HTTPS hosts with the headers your origin needs. The proxy operator can observe everything routed through it, so route public data only.
Install and register the app
Install django-cors-headers into your Python environment, then register the application in your settings.py file.
$ python -m pip install django-cors-headers
# settings.py
INSTALLED_APPS = [
# ...
"corsheaders", # keep the trailing comma
# ...
]Add 'corsheaders' to INSTALLED_APPS. Pay attention to the trailing comma on the entry: omitting it can concatenate adjacent strings or raise a ModuleNotFoundError during startup.
Place the middleware
Django processes middleware in the order declared in MIDDLEWARE. Add CorsMiddleware near the top of the stack.
# settings.py
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware", # as high as possible
"django.middleware.common.CommonMiddleware",
# ...
]
CORS_ALLOWED_ORIGINS = [
"https://app.example.com",
"http://localhost:5173",
]
# Optional: only API URLs receive CORS headers (regex search on the path)
CORS_URLS_REGEX = r"^/api/"corsheaders.middleware.CorsMiddleware must sit as high as possible in the MIDDLEWARE list, before django.middleware.common.CommonMiddleware and before any middleware that can return an early response, such as whitenoise.middleware.WhiteNoiseMiddleware. If another middleware generates a response before CorsMiddleware runs, that response exits without the CORS headers.
Allow only your front-end origins
You must define at least one of CORS_ALLOWED_ORIGINS, CORS_ALLOWED_ORIGIN_REGEXES, or CORS_ALLOW_ALL_ORIGINS. An origin consists of scheme, host, and an optional non-default port. When an incoming request matches an allowed entry, the middleware echoes that origin back in the Access-Control-Allow-Origin header.
CORS_ALLOWED_ORIGINSdefaults to an empty list. Supply explicit origins such ashttps://example.com,https://sub.example.com,http://localhost:8080, orhttp://127.0.0.1:9000.CORS_ALLOWED_ORIGIN_REGEXEStakes regular expressions, such as^https://\w+\.example\.com$, to match dynamic host patterns.CORS_ALLOW_ALL_ORIGINS = Truepermits requests from every website on the internet. The package documentation calls this dangerous, because any site can make cross-origin requests to your API.CORS_URLS_REGEXlimits which request paths receive CORS headers. It defaults to^.*$, covering every URL, and you can narrow it to paths like^/api/.
How the middleware answers preflights
When a browser sends an OPTIONS request carrying Origin and Access-Control-Request-Method headers, CorsMiddleware constructs the preflight response itself and returns it immediately. The probe never touches your view functions or your URL configuration.
CORS_ALLOW_METHODSdetermines which HTTP methods are permitted during preflight. The default list includesDELETE,GET,OPTIONS,PATCH,POST, andPUT. Import and extend the default viacorsheaders.defaults.default_methods.CORS_ALLOW_HEADERScontrols the accepted non-standard request headers. The default list includesaccept,authorization,content-type,user-agent,x-csrftoken, andx-requested-with. Import and extend this list viacorsheaders.defaults.default_headers.CORS_PREFLIGHT_MAX_AGEsets theAccess-Control-Max-Ageheader in seconds and defaults to86400, one day. Setting it to0or another falsey value omits the header.CORS_EXPOSE_HEADERSlists extra response headers that front-end JavaScript can read. It defaults to[].
Allow cookies and session authentication
If your client relies on session cookies or other credentials, you must update both Django and your front-end request configuration.
# settings.py
CORS_ALLOW_CREDENTIALS = True
# Browser POSTs under session auth are still CSRF-checked
CSRF_TRUSTED_ORIGINS = [
"https://app.example.com",
]
# The session cookie defaults to SameSite=Lax, which blocks cross-site
# sending. Relax only when the frontend truly lives on another site.
SESSION_COOKIE_SAMESITE = "None"
SESSION_COOKIE_SECURE = True # required when SameSite is "None"
# Browser side: fetch(url, { credentials: "include" })- Setting
CORS_ALLOW_CREDENTIALS = Trueappends theAccess-Control-Allow-Credentials: trueheader to both preflight and standard responses. The default value isFalse. - Browsers reject responses to credentialed requests when
Access-Control-Allow-Originis*. UsingCORS_ALLOWED_ORIGINSavoids this failure because the middleware echoes the explicit requesting origin instead of a wildcard. - The client code must opt into sending cookies by setting
credentials: 'include'on thefetchcall. - Django's
SESSION_COOKIE_SAMESITEdefaults to'Lax', which prevents browsers from attaching session cookies on cross-site requests. SettingSESSION_COOKIE_SAMESITE = 'None'allows cross-site transmission, but requires the cookie to be markedSecure. - Django CSRF validation operates independently from CORS. Any state-changing cross-origin request made under session authentication, such as a
POST, also requires that origin inCSRF_TRUSTED_ORIGINS.
Verify the headers with curl
Inspect the responses using curl to confirm that your Django middleware emits the expected headers on standard and preflight requests.
$ curl -i -H "Origin: https://app.example.com" \
http://127.0.0.1:8000/api/users/
HTTP/1.1 200 OK
access-control-allow-origin: https://app.example.com
# The preflight the browser sends before a JSON POST
$ curl -i -X OPTIONS \
-H "Origin: https://app.example.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type" \
http://127.0.0.1:8000/api/users/
HTTP/1.1 200 OK
access-control-allow-origin: https://app.example.com
access-control-allow-methods: DELETE, GET, OPTIONS, PATCH, POST, PUT
access-control-max-age: 86400A passing configuration returns an Access-Control-Allow-Origin header echoing the exact value provided in the Origin request header on GET responses. The preflight OPTIONS probe returns a 200 status with Access-Control-Allow-Methods and Access-Control-Allow-Headers.
Good questions.
Does CorsMiddleware handle OPTIONS preflight requests without changes to urls.py?
Yes. When an incoming OPTIONS request contains both the Origin and Access-Control-Request-Method headers, CorsMiddleware generates the preflight response itself. The request returns before reaching your view functions or URL routing.
Why must CorsMiddleware be placed above CommonMiddleware?
Middleware that can generate a response on its own, such as CommonMiddleware issuing a redirect or WhiteNoiseMiddleware serving a static file, short-circuits everything below it. If CorsMiddleware sits below such middleware, those responses never receive CORS headers, so the package documentation requires it as high as possible.
Why does the browser still block session cookies after setting CORS_ALLOW_CREDENTIALS to True?
Django sets SESSION_COOKIE_SAMESITE to 'Lax' by default, which instructs browsers not to send session cookies on cross-site requests. To allow cross-site cookies, set SESSION_COOKIE_SAMESITE to 'None', which also requires the cookie to carry the Secure flag.
Why does a session-authenticated POST request still fail after CORS is configured?
Django's CSRF Referer verification runs separately from CORS headers. For state-changing requests using session authentication, you must also add the requesting origin, scheme included, to CSRF_TRUSTED_ORIGINS.