django-cors-headers: CORS headers for Django, installed from pip
Django app for handling the server headers required for Cross-Origin Resource Sharing (CORS)
At a glance
- What is it?
- A middleware-only Django app that adds the server headers browsers need for cross-origin requests. It is small, MIT-licensed, and maintained by Adam Johnson, but it does not manage preflight logic for you and the README warns that misconfiguration exposes private data.
- Who is it for?
- Adopt django-cors-headers if you run a Django 5.2 to 6.1 app on Python 3.10 to 3.15 and need browsers on other origins to read your responses. Do not adopt it if you only need same-origin requests, or if you expect it to validate tokens or protect private endpoints; CORS is a browser rule, not authentication.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 4 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 2, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The browser rule django-cors-headers exists to satisfy
A browser refuses to hand a cross-origin response to JavaScript unless the server sends the right Access-Control-Allow-Origin header. Django does not send it. So a frontend on https://app.example.com calling an API on https://api.example.com gets a CORS error in the console even though curl works fine.
django-cors-headers is a Django app whose job is exactly that one header family. The README describes it as "a Django App that adds Cross-Origin Resource Sharing (CORS) headers to responses. This allows in-browser requests to your Django application from other origins." It is for Django developers who control the API side and need to declare which origins may read the responses.
The README is direct about the risk: "It's important you understand the implications before adding the headers, since you could be unintentionally opening up your site's private data to others." That is not boilerplate. CORS headers tell the browser which other sites may read your responses. If you allow every origin on an endpoint that returns user data, any page on the internet can read that data on behalf of a logged-in visitor. The project gives you the switch; it does not decide for you.
How CorsMiddleware adds headers, and why its position in MIDDLEWARE matters
The project is a middleware, not a view decorator or a URL-level hook. Installation adds corsheaders to INSTALLED_APPS and inserts corsheaders.middleware.CorsMiddleware into the MIDDLEWARE list. From then on, every response that passes through the middleware stack is inspected and, when the request's Origin matches your configured rules, the appropriate access-control-allow-origin header is attached.
The ordering constraint is the part people get wrong. The README says CorsMiddleware "should be placed as high as possible, especially before any middleware that can generate responses such as Django's CommonMiddleware or Whitenoise's WhiteNoiseMiddleware. If it is not before, it will not be able to add the CORS headers to these responses." Django's middleware runs in list order on the way in and reverse order on the way out, so a middleware that short-circuits and returns a response before CorsMiddleware sees it produces a response with no CORS headers. Static files served by WhiteNoise are the classic case: the file loads, the browser blocks the JavaScript from reading it, and the network tab shows a 200 with a missing header.
Configuration is settings-driven and there is no per-view API in the README. You must set at least one of CORS_ALLOWED_ORIGINS, CORS_ALLOWED_ORIGIN_REGEXES, or CORS_ALLOW_ALL_ORIGINS. CORS_ALLOWED_ORIGINS is an explicit list of scheme, host and optional port; the requesting origin is echoed back in the access-control-allow-origin header. CORS_ALLOWED_ORIGIN_REGEXES takes regexes instead, which the README suggests for cases where the explicit list is impractical, such as a large number of subdomains. CORS_ALLOW_ALL_ORIGINS is a boolean that, when true, ignores the other origin restrictions.
The README calls that last one "dangerous, as it allows any website to make cross-origin requests to yours." Treat it as a development-only setting. The older names CORS_ORIGIN_WHITELIST, CORS_ORIGIN_REGEX_WHITELIST and CORS_ORIGIN_ALLOW_ALL still work as aliases, with the new names taking precedence, so a settings file that mixes both will silently follow the new one.
Installing django-cors-headers from pip and making a first cross-origin request work
The README gives a pip install and two settings edits. Python 3.10 to 3.15 and Django 5.2 to 6.1 are supported, and the package depends on asgiref>=3.6 and django>=5.2.
Install the package:
python -m pip install django-cors-headersThen add the app. The README explicitly warns to keep the trailing comma, because removing it can produce a ModuleNotFoundError:
INSTALLED_APPS = [
...,
"corsheaders",
...,
]Add the middleware above CommonMiddleware. The README places it before django.middleware.common.CommonMiddleware in the example:
MIDDLEWARE = [
...,
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
...,
]Finally set at least one origin setting. The README's example lists four origins:
CORS_ALLOWED_ORIGINS = [
"https://example.com",
"https://sub.example.com",
"http://localhost:8080",
"http://127.0.0.1:9000",
]After restarting the server, a request from one of those origins should come back with the access-control-allow-origin header set to that origin. If the header is missing, check the middleware order first, then confirm the origin string matches exactly: the README defines an origin as scheme plus hostname plus optional port, so https://example.com and http://example.com are different entries, and default ports 443 and 80 are optional. The special values null and file:// are accepted as origins; the README notes that null is sent in privacy-sensitive contexts such as a client running from a file:// domain, and that file:// is sent by some versions of Chrome on Android due to a known bug.
Where django-cors-headers is the wrong tool
CORS is enforced by the browser. It is not a server-side access control mechanism. A script using requests, curl, or a server-side HTTP client ignores CORS headers entirely and will read your JSON whether or not you configured an allowlist. If the endpoint returns private data, the fix is authentication and authorization on the Django side, not a narrower CORS_ALLOWED_ORIGINS list. django-cors-headers does not authenticate anything.
The middleware also only handles responses that pass through it. Responses generated by middleware placed above CorsMiddleware, responses from a separate service behind a reverse proxy, and files served by a CDN that bypasses Django entirely will not get the headers. The README's own warning about middleware that can generate responses covers the Django-side cases; the proxy and CDN cases are outside the app's reach.
There is a subtler trap in the regex setting. CORS_ALLOWED_ORIGIN_REGEXES matches the origin string, and the README's example is r"^https://\w+\.example\.com$". A pattern that is too loose, for instance one that forgets to anchor the end, can match origins you did not intend, and the README does not document a dry-run or validation command for those patterns. You are trusting your own regex.
Finally, this is a Django-specific app. If you are not running Django, it has nothing to offer.
django-cors-headers compared with django-cors-middleware
The README's own history section is the clearest comparison available. django-cors-headers was created in January 2013 by Otto Yiu, went unmaintained from August 2015, and was forked in January 2016 to django-cors-middleware by Laville Augustin at Zeste de Savoir. In September 2016 Adam Johnson, Ed Morley and others took over maintenance of the original package.
The README states that "basically all of the changes in the forked django-cors-middleware were merged back, or re-implemented in a different way, so it should be possible to switch back." That is the actual difference in approach: django-cors-middleware is a fork that carried the project through a maintenance gap, and django-cors-headers is the upstream package that absorbed the fork's work afterward. The README invites anyone missing a feature to open an issue rather than pointing at the fork as a parallel option.
If you are choosing today, the deciding facts are the ones in this repository: django-cors-headers is at version 4.9.0, declares support for Django 5.2 to 6.1 and Python 3.10 to 3.15, and had its last push on 2026-09-07. The README does not publish a feature-by-feature comparison against django-cors-middleware, so if you depend on a specific behaviour from the fork, check that behaviour against this package's CHANGELOG.rst before migrating.
Licence, maintenance and upgrade cost
The project is MIT licensed, and the pyproject.toml declares license = "MIT" with license-files = ["LICENSE"]. MIT is permissive: you can use, modify and redistribute the code, including in closed-source products, provided the copyright notice and permission notice are kept. This is a description of the licence text, not legal advice; if your organisation has a policy on third-party licences, run it through that process.
Maintenance status is visible from the repository itself. The last push was on 2026-09-07, and the repository is not archived. The maintainer listed in pyproject.toml is Adam Johnson. The project carries a Development Status of Production/Stable and a Typing :: Typed classifier, and the README notes it has had 40+ contributors.
Upgrade cost is low by design. The dependency floor is django>=5.2, the supported range is stated as Django 5.2 to 6.1, and the configuration surface is a handful of settings names. The main upgrade hazard is the alias set: if your settings still use CORS_ORIGIN_WHITELIST, CORS_ORIGIN_REGEX_WHITELIST or CORS_ORIGIN_ALLOW_ALL, they keep working, but the new names take precedence, so a settings file with both old and new keys can behave differently than a reader expects. The repository ships CHANGELOG.rst and HISTORY.rst at the top level, which is where the concrete upgrade notes live.
Editorial conclusion
Adopt django-cors-headers if you run a Django 5.2 to 6.1 app on Python 3.10 to 3.15 and need browsers on other origins to read your responses. Do not adopt it if you only need same-origin requests, or if you expect it to validate tokens or protect private endpoints; CORS is a browser rule, not authentication. Before rollout, verify which of CORS_ALLOWED_ORIGINS, CORS_ALLOWED_ORIGIN_REGEXES, or CORS_ALLOW_ALL_ORIGINS your settings actually set, and confirm that CorsMiddleware sits above CommonMiddleware in MIDDLEWARE, because the README states that if it is not before, it will not be able to add the CORS headers to these responses.
Frequently asked questions
What is django-cors-headers?
It is a Django app that adds the CORS headers to responses so browsers on other origins can read them. The README describes it as a Django App that adds Cross-Origin Resource Sharing (CORS) headers to responses.
How do I install django-cors-headers?
Install it with python -m pip install django-cors-headers, then add "corsheaders" to INSTALLED_APPS and "corsheaders.middleware.CorsMiddleware" to MIDDLEWARE. The README warns to keep the trailing comma in the INSTALLED_APPS entry or you might get a ModuleNotFoundError.
How do I handle CORS headers in Django?
Add the corsheaders app and its CorsMiddleware, then set at least one of CORS_ALLOWED_ORIGINS, CORS_ALLOWED_ORIGIN_REGEXES, or CORS_ALLOW_ALL_ORIGINS. The README states that CorsMiddleware should be placed as high as possible, especially before any middleware that can generate responses such as Django's CommonMiddleware.
Should CORS be enabled?
Only if browsers on other origins need to read your responses. The README warns that adding CORS headers allows your resources to be accessed on other domains and that you could be unintentionally opening up your site's private data to others, so it advises understanding the implications first.
How to install CORS headers?
With pip: python -m pip install django-cors-headers. Then register the corsheaders app in INSTALLED_APPS and add corsheaders.middleware.CorsMiddleware to MIDDLEWARE, before any middleware that can generate responses.
What is a CORS header?
It is a response header that tells the browser which other origins may read the response. django-cors-headers adds these headers, and the README links to introductory resources such as Julia Evans' comic and the MDN article for background.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/adamchainz-django-cors-headers)