Fix: Flask CORS Not Working
Quick Answer
How to fix CORS errors in Flask — installing flask-cors correctly, handling preflight OPTIONS requests, configuring origins with credentials, route-specific CORS, and debugging missing headers.
The Error
A browser request to your Flask API fails with a CORS error:
Access to fetch at 'http://localhost:5000/api/data' from origin 'http://localhost:3000'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on
the requested resource.Or after installing flask-cors, CORS still doesn’t work for some routes:
Access to XMLHttpRequest at 'http://localhost:5000/api/users' from origin 'http://localhost:3000'
has been blocked by CORS policy: Response to preflight request doesn't pass access control
check: It does not have HTTP ok status.Or with credentials:
The value of the 'Access-Control-Allow-Origin' header in the response must not be
the wildcard '*' when the request's credentials mode is 'include'.Why This Happens
Flask doesn’t add CORS headers by default. The flask-cors extension handles this, but misconfiguration is common:
flask-corsnot installed or not initialized —CORS(app)was not called, so no headers are added.CORS(app)called after route definitions — Flask extension registration order matters for some setups.- Preflight OPTIONS not handled — for requests with custom headers or methods like PUT/DELETE, browsers send an OPTIONS request first. If Flask returns 405 for OPTIONS, the actual request is never sent.
- Wildcard origin with credentials —
origins='*'andsupports_credentials=Truetogether are invalid per the CORS spec. When sending cookies, you must specify exact origins. - Blueprint-level CORS misconfigured —
CORS(app)doesn’t always cover blueprints added after initialization without explicit configuration. - Route returns an error (4xx/5xx) — if the route handler throws before returning, Flask might return a response without CORS headers, so the browser sees both the status error and the CORS error.
Fix 1: Install and Initialize flask-cors Correctly
pip install flask-corsfrom flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# Initialize CORS before registering routes
CORS(app)
@app.route('/api/data')
def get_data():
return {'data': 'hello'}
if __name__ == '__main__':
app.run(debug=True)CORS(app) with no arguments allows all origins (*) for all routes. Test this first, then restrict to specific origins in production.
Verify the headers are present:
curl -I -X OPTIONS http://localhost:5000/api/data \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: GET"The response should include Access-Control-Allow-Origin: *.
Fix 2: Restrict to Specific Origins
In production, never use *. Specify the exact allowed origins:
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# Single origin
CORS(app, origins=['https://app.example.com'])
# Multiple origins
CORS(app, origins=[
'https://app.example.com',
'https://staging.example.com',
'http://localhost:3000', # Dev only
])Or use environment-based configuration:
import os
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
allowed_origins = os.environ.get(
'CORS_ORIGINS',
'http://localhost:3000'
).split(',')
CORS(app, origins=allowed_origins)Set CORS_ORIGINS=https://app.example.com in production and CORS_ORIGINS=http://localhost:3000 in development.
Fix 3: Handle Credentials (Cookies and Authorization Headers)
When the browser sends credentials: 'include' (for cookies) or an Authorization header, you must:
- Set
supports_credentials=True - Specify exact origins (not
*)
CORS(app,
origins=['https://app.example.com', 'http://localhost:3000'],
supports_credentials=True)This sets both Access-Control-Allow-Origin: https://app.example.com (the actual request origin) and Access-Control-Allow-Credentials: true.
Client-side (JavaScript):
// With cookies
fetch('http://localhost:5000/api/profile', {
credentials: 'include',
});
// With Authorization header
fetch('http://localhost:5000/api/profile', {
headers: { Authorization: `Bearer ${token}` },
});Warning: Do not use
origins='*'withsupports_credentials=True— this combination violates the CORS spec and browsers will reject the response. Always specify exact origins when using credentials.
Fix 4: Configure Route-Specific CORS
Apply different CORS settings to different routes:
from flask import Flask
from flask_cors import CORS, cross_origin
app = Flask(__name__)
# Global CORS (no credentials, all origins)
CORS(app)
# Route-level override using decorator
@app.route('/api/public')
@cross_origin() # Allows all origins for this route
def public_endpoint():
return {'data': 'public'}
@app.route('/api/private')
@cross_origin(origins=['https://app.example.com'], supports_credentials=True)
def private_endpoint():
return {'data': 'private'}
# Disable CORS for a specific route (internal use only)
@app.route('/internal/health')
@cross_origin(origins=[])
def health_check():
return {'status': 'ok'}Or configure per-path with resources:
CORS(app, resources={
r'/api/public/*': {'origins': '*'},
r'/api/private/*': {
'origins': ['https://app.example.com'],
'supports_credentials': True,
},
r'/internal/*': {'origins': []},
})Fix 5: Fix Blueprint CORS
When using Flask Blueprints, apply CORS directly to the blueprint or ensure the app-level CORS is initialized before blueprints are registered:
# blueprints/users.py
from flask import Blueprint
from flask_cors import CORS
users_bp = Blueprint('users', __name__)
# Apply CORS to the blueprint directly
CORS(users_bp, origins=['https://app.example.com'])
@users_bp.route('/users')
def get_users():
return {'users': []}# app.py
from flask import Flask
from flask_cors import CORS
from blueprints.users import users_bp
app = Flask(__name__)
CORS(app) # App-level CORS
app.register_blueprint(users_bp, url_prefix='/api')If both app-level and blueprint-level CORS are configured, the blueprint’s config takes precedence for its routes.
Fix 6: Handle CORS Headers on Error Responses
When your Flask route raises an exception, Flask’s default error handlers return responses without CORS headers — the browser blocks them, hiding the real error from the frontend:
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
CORS(app)
# Add CORS headers to error responses
@app.after_request
def after_request(response):
response.headers['Access-Control-Allow-Origin'] = '*'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
return response
@app.errorhandler(404)
def not_found(e):
return jsonify({'error': 'Not found'}), 404
@app.errorhandler(500)
def server_error(e):
return jsonify({'error': 'Internal server error'}), 500Using after_request ensures CORS headers are always added, even to error responses. This lets the browser actually display the error status code rather than showing only “CORS blocked.”
Pro Tip: When debugging CORS issues, check two things in the Network tab: (1) the OPTIONS preflight request — does it return 200 with the right headers? (2) the actual GET/POST — does it include
Access-Control-Allow-Origin? If the preflight passes but the main request is blocked, the issue is with the actual response headers.
Fix 7: Configure Allowed Headers and Methods
If your request includes custom headers (e.g., Authorization, X-API-Key, X-Custom-Header), they must be listed in allow_headers:
CORS(app,
origins=['https://app.example.com'],
allow_headers=['Content-Type', 'Authorization', 'X-API-Key'],
methods=['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
max_age=3600) # Cache preflight for 1 hourFor manual header handling without flask-cors:
from flask import Flask, request, jsonify
app = Flask(__name__)
def add_cors_headers(response):
response.headers['Access-Control-Allow-Origin'] = 'https://app.example.com'
response.headers['Access-Control-Allow-Headers'] = 'Content-Type, Authorization'
response.headers['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
return response
app.after_request(add_cors_headers)
# Handle preflight manually
@app.before_request
def handle_preflight():
if request.method == 'OPTIONS':
response = app.make_default_options_response()
add_cors_headers(response)
return responseStill Not Working?
Verify flask-cors is actually installed in your virtual environment:
pip show flask-cors
# If not found: pip install flask-corsCheck import — common mistake is importing from the wrong package:
# Wrong
from flask.cors import CORS
# Correct
from flask_cors import CORSTest without the browser to isolate whether it’s a CORS issue or an application error:
# Direct test — no browser CORS check
curl http://localhost:5000/api/data
# Simulated CORS request
curl -H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization" \
-X OPTIONS \
-v http://localhost:5000/api/dataIf curl works but the browser doesn’t, it’s definitely CORS. If curl also fails, it’s an application error unrelated to CORS.
Check for middleware stripping headers — if Flask runs behind nginx or a reverse proxy, the proxy might strip Access-Control-* headers or not forward the Origin header. Check nginx configuration for proxy_pass_header or add_header directives.
For related Flask issues, see Fix: Flask 404 Not Found and Fix: Express CORS Not Working.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Kafka Consumer Not Receiving Messages, Connection Refused, and Rebalancing Errors
How to fix Apache Kafka issues — consumer not receiving messages, auto.offset.reset, Docker advertised.listeners, max.poll.interval.ms rebalancing, MessageSizeTooLargeException, and KafkaJS errors.
Fix: Flask Route Returns 404 Not Found
How to fix Flask routes returning 404 — trailing slash redirect, Blueprint prefix issues, route not registered, debug mode, and common URL rule mistakes.
Fix: Fastify Not Working — 404, Plugin Encapsulation, and Schema Validation Errors
How to fix Fastify issues — route 404 from plugin encapsulation, reply already sent, FST_ERR_VALIDATION, request.body undefined, @fastify/cors, hooks not running, and TypeScript type inference.
Fix: OpenAI API Not Working — RateLimitError, 401, 429, and Connection Issues
How to fix OpenAI API errors — RateLimitError (429), AuthenticationError (401), APIConnectionError, context length exceeded, model not found, and SDK v0-to-v1 migration mistakes.