Skip to content

/commands/cors/enable-cors-flask

Enable CORS in Flask with flask-cors

verified onenvironmentlocal · windows 10.0.26200 arm64 · GNU bash 5.3.15 (Git Bash)updated
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:

bash
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.py

Output

== 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

OptionDefaultEffect
origins'*'Allowed origins: a string, a list, or regular expressions
resourcesr'/*'Map of path patterns to their own options, for example only /api/*
methodsall common methodsAccess-Control-Allow-Methods in preflight responses
allow_headers'*'Request headers allowed; with the default, the requested headers are echoed
expose_headersnoneResponse headers JavaScript may read
supports_credentialsFalseAdds Access-Control-Allow-Credentials: true
max_agenoneAccess-Control-Max-Age for caching the preflight

Restrict CORS to the API

python
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_headers explicitly, every header the front end sends (for example Authorization) must be in it, or the browser reports Request header field is not allowed.
  • An origin includes scheme and port: during development, http://localhost:5173 must be on the list, not only localhost.
  • Do not also add Access-Control-Allow-Origin in 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().

Sources