# flask-cors: one extension to allow cross-origin requests across a whole Flask app

> flask-cors is a Flask extension that turns on Cross Origin Resource Sharing for every route by default, with per-resource or per-route configuration when you need it. Cookies stay off unless you ask for them, and the last push was on 2026-06-09.

**corydolphin/flask-cors** — Cross Origin Resource Sharing ( CORS ) support for Flask 

- Repository: https://github.com/corydolphin/flask-cors
- Website: https://flask-cors.readthedocs.io/en/latest/index.html
- Stars: 933 · Forks: 156
- Language: Python
- License: MIT
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/corydolphin-flask-cors

## A permissive default: CORS on every route, origin and method

flask-cors is a Flask extension for handling Cross Origin Resource Sharing so that cross-origin AJAX becomes possible. Its stated philosophy is blunt: when you want to enable CORS, you want it enabled for all use cases on a domain, with no mucking around with different allowed headers and methods.

That default is the defining design decision. The extension, by default, enables CORS support on all routes, for all origins and all methods. You can still parameterise every CORS header on a per-resource level, or drop to a decorator for a single route, but the starting point is wide open rather than locked down.

The consequence for reviewers is worth stating plainly. A blanket CORS policy on a public API is not itself a vulnerability, since CORS restricts browser script access rather than granting network reachability, but it does remove the browser as a boundary. Any origin can read responses from those routes, and if credentials are ever enabled the same policy lets any site make authenticated requests on a user's behalf.

The package handles the details for you: it writes the Access-Control headers onto responses and answers preflight requests so that the browser's own checks succeed before the real call is made. You do not hand write headers or manage OPTIONS routes.

## Three ways to scope it: app-wide, per resource, per route

Because the default is broad, the interesting part of the API is the narrowing. Three patterns exist, and they map onto how a real API grows.

The first is app-wide initialisation with no arguments, which is the simplest case and turns CORS on everywhere. The second is the resources option, which takes a dictionary mapping paths to a set of options, so CORS can be specified on a resource and origin level of granularity. The third is the decorator, added below Flask's own route decorator, which allows CORS on a given route alone.

```python
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
cors = CORS(app, resources={r"/api/*": {"origins": "*"}})

@app.route("/api/v1/users")
def list_users():
  return "user example"
```

The decorator route is spelled out separately in the documentation, with the cross_origin decorator placed under the route decorator rather than above it.

```python
@app.route("/")
@cross_origin()
def helloWorld():
  return "Hello, cross-origin-world!"
```

The repository also ships three example files under examples/: an app based example, a blueprints based example and a view based example. That trio is the closest thing to guidance on how the extension interacts with the different ways Flask applications get structured, since the extension hooks into the application and can also be attached to a blueprint or a view rather than the whole app.

## Installing it and seeing the first cross-origin response

Installation is a single pip command, and the project suggests upgrading in place so you move to the current release.

```bash
pip install -U flask-cors
```

The minimal first application wires the extension onto a Flask instance and defines one route.

```python
from flask import Flask
from flask_cors import CORS

app = Flask(__name__)
CORS(app)

@app.route("/")
def helloWorld():
  return "Hello, cross-origin-world!"
```

What you should see is a response that a browser on a different origin will accept, including the response headers that carry the allowed origin and the methods. If it does not, the project ships a troubleshooting path rather than leaving you guessing: enable debug logging on the flask_cors logger to see what happens under the hood and why.

```python
logging.getLogger('flask_cors').level = logging.DEBUG
```

For working on the extension itself, the development environment uses uv for both Python version management and dependency management, and the Makefile targets are the entry point. The target to start with is make test.

```bash
make test
```

That target runs pytest with coverage configured through pyproject.toml. Other targets cover the rest of the loop: make install creates the virtual environment with uv sync and installs the pre-commit hooks, make check runs the lock file check, mypy, and deptry, make docs serves the mkdocs documentation locally, and make build-and-publish builds the wheel and uploads it to PyPI.

## Credentials stay off, and turning them on pulls CSRF into scope

By default, submission of cookies across domains is disabled because of the security implications. The project is explicit about this and points to the documentation for how to enable credentialed requests, with a warning to add some form of CSRF protection before doing so.

This is the sharpest edge in the library. The combination of permissive origins and credentials is exactly the shape of a cross-site request forgery risk, because a browser will attach the user's cookies to a request the user never intended. The extension does not solve that problem; it hands you the option and asks you to bring the protection.

The practical read for an evaluator is that enabling credentials is a decision that needs its own design, not a configuration toggle to flip in passing. Any token or cookie based authentication scheme needs an anti-CSRF mechanism alongside it, and that mechanism is outside this package.

The extension also descends from an older source: it is based upon a decorator for HTTP Access Control written by Armin Ronacher. That lineage explains the design bias toward a small, permissive helper rather than a policy engine.

## A small dependency surface and a Python version floor of 3.9

The package metadata is deliberately modest. It requires Python 3.9 or newer and 4.0 is excluded, with classifiers covering Python 3.9 through 3.13. The runtime dependencies are flask at 0.9 or newer, Werkzeug at 0.7 or newer, and typing_extensions at 4.6.0 or newer on Python below 3.11, where the comment explains it provides typing.Unpack under PEP 692 for typing kwargs on the older interpreters.

That is the whole list. A library that touches every response on your app and pulls in almost nothing is easier to audit than one that arrives with a tree of transitive requirements.

The licence is MIT. It is a permissive licence, and the repository ships a LICENSE file along with a CHANGELOG.md and a CONTRIBUTING.md.

For contributors, the tooling is modern and strict. The Makefile check target runs uv lock --locked for lock file consistency, then mypy for static type checking, then deptry to flag obsolete dependencies. Linting is wired through pre-commit, ruff is in the dev dependency group, and pytest with pytest-cov handles the test suite. The project also carries tox.ini and a uv.lock file, so a contributor can reproduce the same environment in more than one way.

## Release rhythm: three patch releases in three days in June 2026

The version history shows a tight cluster of patch releases: 6.0.5 on 2026-06-08, 6.0.4 on 2026-06-07 and 6.0.3 on 2026-06-06. Three patch releases on three consecutive days points at a bug fixing push rather than a feature cadence, and the 6.x line suggests the API surface has been stable across a long stretch of patch numbering.

The last push was on 2026-06-09, one day after the 6.0.5 tag. So the source tree and the published package line are close together, and there is no sign of work in progress drifting away from what is on PyPI.

Version numbering comes from git tags rather than a hardcoded field: the pyproject file declares the version as dynamic, and the setuptools_scm configuration derives it from the latest git tag, with a fallback for source archives built outside a git checkout, where values are recovered from .git_archival.txt through export-subst in .gitattributes. In practice this means a clone of the repository reports whatever the latest tag was, which is useful for cutting a release and slightly surprising for anyone reading version metadata from a checkout.

The upstream project is not the only option here, and the difference is worth naming. A hand written after_request hook in Flask can add the same headers with no dependency at all, which is the right choice for a service with one or two endpoints or a strict allowlist of origins. flask-cors earns its place when an app has many routes, when you want preflight handling and header logic in one place, and when you would otherwise reimplement the same code badly across a codebase.

## Conclusion

flask-cors is the right pick for a Flask API that needs browser clients on another origin to read it, when you accept a permissive default and want one import instead of hand written after_request hooks. It is the wrong pick for an API that serves credentialed browser sessions, since enabling cookies across domains brings CSRF handling with it, and it is the wrong pick for a project that must allow only a named list of origins, because the package is built around allowing everything by default and narrowing requires deliberate per-resource configuration. Before adopting it, check that your Python version is 3.9 or newer, decide whether you want the blanket default or a resources mapping, and read the credential and CSRF section of the documentation before enabling credentialed requests.

## FAQ

### How do I install flask-cors and enable CORS on a Flask app?

Run pip install -U flask-cors, then import CORS from flask_cors and pass the app to it, for example CORS(app). That turns CORS on for all routes, all origins and all methods by default.

### How do I restrict flask-cors to specific routes or paths?

Pass a resources dictionary mapping path patterns to options, such as CORS(app, resources={r"/api/*": {"origins": "*"}}). For a single route, apply the cross_origin decorator below the Flask route decorator.

### Does flask-cors send cookies across domains by default?

No. By default, submission of cookies across domains is disabled because of the security implications. The documentation explains how to enable credentialed requests and warns that you should add CSRF protection before doing so.

### Which Python versions does flask-cors support?

The package requires Python 3.9 or newer, with classifiers for Python 3.9 through 3.13. It depends on flask 0.9 or newer, Werkzeug 0.7 or newer, and typing_extensions 4.6.0 or newer on Python below 3.11.

## Sources

- [corydolphin/flask-cors on GitHub](https://github.com/corydolphin/flask-cors)
- [License: MIT](https://github.com/corydolphin/flask-cors/blob/main/LICENSE)
- [Project website](https://flask-cors.readthedocs.io/en/latest/index.html)
- [README](https://github.com/corydolphin/flask-cors/blob/main/README.md)
- [Releases](https://github.com/corydolphin/flask-cors/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/corydolphin-flask-cors
