/commands/cors/enable-cors-flask
Enable CORS in Flask with flask-cors
- tool
- CORS
- versions
- Flask-Cors 6.x, Flask 3.x
- os
- linux, macos, windows
Install the extension (pip install flask-cors) and wrap the app: CORS(app) adds Access-Control-Allow-Origin: * to every response and answers preflight OPTIONS requests. For a real API, restrict it with CORS(app, origins=[...]) or per path with resources={r"/api/*": {...}}, and add supports_credentials=True only when the browser must send cookies.
Setup and check with the Flask test client
The test client sends the same Origin and Access-Control-Request-* headers a browser sends, without starting a server:
cat > app.py <<'EOF'
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
CORS(app, origins=["https://app.example.com", "https://admin.example.com"],
supports_credentials=True)
@app.get("/items")
def items():
return {"ok": True}
def show(title, resp):
print("==", title, resp.status_code)
for k in sorted(resp.headers.keys()):
if k.startswith("Access-Control") or k == "Vary":
print(f"{k}: {resp.headers[k]}")
c = app.test_client()
show("GET from an allowed origin", c.get("/items", headers={"Origin": "https://app.example.com"}))
show("preflight", c.options("/items", headers={
"Origin": "https://admin.example.com",
"Access-Control-Request-Method": "PUT",
"Access-Control-Request-Headers": "content-type"}))
show("GET from another origin", c.get("/items", headers={"Origin": "https://evil.example"}))
EOF
uv run -q --no-project --python 3.13 --with "flask>=3,<4" --with "flask-cors>=6,<7" python app.pyOutput
== GET from an allowed origin 200 Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: https://app.example.com Vary: Origin == preflight 200 Access-Control-Allow-Credentials: true Access-Control-Allow-Headers: content-type Access-Control-Allow-Methods: DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT Access-Control-Allow-Origin: https://admin.example.com Vary: Origin == GET from another origin 200
The allowed origins are reflected with Vary: Origin and Access-Control-Allow-Credentials: true. The preflight echoes the requested header and lists the default methods. The third request still runs the view and returns 200, but without CORS headers, so a browser on that origin cannot read it.
Common options
| Option | Default | Effect |
|---|---|---|
| origins | '*' | Allowed origins: a string, a list, or regular expressions |
| resources | r'/*' | Map of path patterns to their own options, for example only /api/* |
| methods | all common methods | Access-Control-Allow-Methods in preflight responses |
| allow_headers | '*' | Request headers allowed; with the default, the requested headers are echoed |
| expose_headers | none | Response headers JavaScript may read |
| supports_credentials | False | Adds Access-Control-Allow-Credentials: true |
| max_age | none | Access-Control-Max-Age for caching the preflight |
Restrict CORS to the API
CORS(app, resources={r"/api/*": {"origins": ["https://app.example.com"]}})Routes outside /api/ then get no CORS headers at all, which is what you want for HTML pages and admin views.
Pitfalls
- If you set
allow_headersexplicitly, every header the front end sends (for exampleAuthorization) must be in it, or the browser reports Request header field is not allowed. - An origin includes scheme and port: during development,
http://localhost:5173must be on the list, not onlylocalhost. - Do not also add
Access-Control-Allow-Originin nginx or a gateway: two values make the browser reject the response. - Test from the command line with curl and an Origin header; curl shows the headers but never blocks anything.
FAQ
Why does CORS(app) not work for my blueprint or error responses?
CORS(app) covers every route registered on the app, including blueprints, but only responses that pass through Flask's after_request hooks. A crash in a proxy or WSGI server in front of Flask returns a response without CORS headers, which the browser reports as a CORS error.
How do I allow cookies with flask-cors?
Pass supports_credentials=True and an explicit list of origins. The extension then adds Access-Control-Allow-Credentials: true and reflects the matching origin instead of sending *.
Can I enable CORS on a single route?
Yes, decorate the view with @cross_origin() from flask_cors, which takes the same options as CORS().