Flask-CORS is the standard way to handle Cross-Origin Resource Sharing (CORS) in Flask applications, and it’s surprisingly flexible once you understand how it maps to the underlying HTTP headers.

Let’s see it in action. Imagine you have a Flask app and you want to allow requests from http://localhost:3000 to your API running on http://localhost:5000.

from flask import Flask, jsonify
from flask_cors import CORS

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

@app.route("/api/data")
def get_data():
    return jsonify({"message": "Hello from Flask!"})

if __name__ == "__main__":
    app.run(debug=True)

Here, CORS(app, resources={r"/api/*": {"origins": "http://localhost:3000"}}) is the core. It tells Flask-CORS to apply CORS to any route starting with /api/ and to only allow requests originating from http://localhost:3000.

When a browser at http://localhost:3000 makes a request to /api/data on your Flask app, the browser first sends a preflight OPTIONS request. Flask-CORS intercepts this, checks if the origin (http://localhost:3000) is allowed for the requested path (/api/data), and if so, responds with headers like:

Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, OPTIONS
Access-Control-Allow-Headers: Content-Type

The browser then proceeds with the actual GET request. If the origin wasn’t allowed, the browser would block the request and show a CORS error.

The problem Flask-CORS solves is the browser’s Same-Origin Policy, a fundamental security feature that prevents a web page from making requests to a different domain than the one it originated from. Without CORS, your JavaScript running on http://localhost:3000 couldn’t fetch data from http://localhost:5000. CORS is a mechanism that allows servers to explicitly opt-in to allowing cross-origin requests.

Internally, Flask-CORS works by inspecting incoming requests. For preflight OPTIONS requests, it checks the Origin, Access-Control-Request-Method, and Access-Control-Request-Headers headers against its configuration. If they match, it adds the appropriate Access-Control-Allow-* headers to the OPTIONS response. For actual requests (like GET, POST, etc.), it checks the Origin header against its configuration and, if allowed, adds the Access-Control-Allow-Origin header to the response.

The resources argument in CORS() is a powerful dictionary where keys are route patterns (like r"/api/*") and values are dictionaries of CORS options for those routes. You can specify origins, methods, headers, expose_headers, allow_credentials, and max_age.

For example, to allow all origins for a specific route: CORS(app, resources={r"/public/*": {"origins": "*"}}). Using "*" is often convenient for development but generally discouraged for production APIs as it grants access to any domain.

To allow multiple specific origins: CORS(app, resources={r"/api/*": {"origins": ["http://localhost:3000", "https://your-frontend.com"]}}).

You can also apply CORS to the entire application by just passing the app instance: CORS(app). This defaults to allowing all origins and common methods for all routes.

The allow_credentials=True option is important if your frontend needs to send cookies or authentication headers with cross-origin requests. When allow_credentials is True, the Access-Control-Allow-Origin header cannot be *. You must explicitly list the allowed origins.

The expose_headers option is used to tell the browser that certain response headers are "safe to expose" to client-side JavaScript. By default, only a few headers are exposed. If your API returns custom headers like X-Total-Count, you’d need to list them here: CORS(app, resources={r"/api/*": {"origins": "*", "expose_headers": "X-Total-Count"}}).

When configuring CORS, it’s crucial to remember that the browser enforces the Same-Origin Policy. Flask-CORS merely instructs the browser what is allowed via HTTP headers. If the browser doesn’t receive the correct Access-Control-Allow-* headers, it will block the request before the response from your Flask app is even fully processed by the frontend code.

A common pitfall is forgetting to configure CORS for all the routes that your frontend needs to access, or not specifying all the necessary methods (like PUT, DELETE, OPTIONS) if your frontend uses them. If you only configure for GET and POST, but your frontend tries to PUT data, the preflight request might fail.

Another subtle point is how credentials work. If your frontend is making requests with fetch and includes credentials: 'include', the browser will add an Origin header. For the server to respond correctly, it must include Access-Control-Allow-Credentials: true and the Access-Control-Allow-Origin header must match the origin exactly (not *).

If you’re seeing No 'Access-Control-Allow-Origin' header is present on the requested resource. errors, it almost always means your Flask-CORS configuration isn’t broad enough for the origin, method, or headers being requested.

The next thing you’ll likely encounter is needing to handle more complex authentication scenarios, such as using JWT tokens with CORS, which often involves correctly setting allow_credentials and expose_headers for your token-related headers.

Want structured learning?

Take the full Flask course →