Solving CORS Errors in Flutter Web
Problem Statement
When developing Flutter web applications, you may encounter Cross-Origin Resource Sharing (CORS) errors when making HTTP requests to external APIs. The error typically appears as:
Access to XMLHttpRequest at 'https://api.example.com' from origin 'http://localhost:port' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
This occurs because browsers enforce CORS policies that restrict cross-origin requests for security reasons. Unlike mobile apps where these restrictions don't apply, web browsers require explicit permission from the server to allow cross-origin requests.
Important Note
CORS is a browser security feature, not a bug. It cannot be bypassed in production code running in users' browsers. The solutions below are categorized by development vs. production scenarios.
Development Solutions
Using Browser Flags (Flutter 3.3.0+)
For development and testing purposes, you can disable web security in Chrome:
flutter run -d chrome --web-browser-flag "--disable-web-security"
For integration tests:
flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -d web-server --web-browser-flag="--disable-web-security"
Alternative Development Approach
If the above doesn't work with your setup, you can create a custom Chrome launcher:
#!/bin/zsh
trap "trap - SIGTERM && kill -- -$$" SIGINT SIGTERM EXIT
set -e
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --test-type --disable-web-security "$@"
set +e &
PID=$!
wait $PID
trap - SIGINT SIGTERM EXIT
wait $PID
Save this as ~/chrome_launcher
, make it executable (chmod 755 ~/chrome_launcher
), and add to your shell configuration:
export CHROME_EXECUTABLE=~/chrome_launcher
Using flutter_cors Package
For a more streamlined development experience:
dart pub global activate flutter_cors
fluttercors --disable
Development Only
These solutions disable important browser security features and should never be used in production environments.
Production Solutions
For production applications, you must configure your server to properly handle CORS requests. Here are examples for various server technologies:
Node.js/Express Server
const express = require('express');
const cors = require('cors');
const app = express();
// Enable CORS for all routes
app.use(cors());
// Or configure specific options
app.use(function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET,PUT,PATCH,POST,DELETE");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
next();
});
PHP Backend
<?php
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST');
header("Access-Control-Allow-Headers: X-Requested-With, Content-Type, Authorization");
// Your PHP code here
?>
ASP.NET MVC Web API
protected void Application_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "https://yourdomain.com");
if (HttpContext.Current.Request.HttpMethod == "OPTIONS")
{
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE");
HttpContext.Current.Response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
HttpContext.Current.Response.End();
}
}
Spring Boot (Java)
@CrossOrigin
@PostMapping(path="/upload")
public @ResponseBody ResponseEntity<Void> upload(@RequestBody Object object) {
// Your controller logic
}
NGINX Configuration
location / {
# Handle preflight OPTIONS requests
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
add_header 'Access-Control-Max-Age' 1728000;
return 204;
}
# Add CORS headers
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'Origin, Content-Type, Accept, Authorization';
try_files $uri $uri/ =404;
}
Common Pitfalls and Solutions
Protocol Mismatch
Ensure your API URL includes the protocol:
// ❌ Incorrect
baseUrl: "localhost:3000/"
// ✅ Correct
baseUrl: "http://localhost:3000/"
Simple Requests vs. Preflight Requests
Some requests trigger preflight OPTIONS requests. To avoid this, keep requests simple by using standard content types:
// This may avoid preflight requests
headers: {
'Content-Type': 'text/plain', // Simple content type
},
CORS Proxy for Development
For testing, you can use a CORS proxy service:
http.post('https://cors-anywhere.herokuapp.com/https://api.example.com/data', ...)
WARNING
Proxy services are for development only and should not be used in production due to security and performance concerns.
Best Practices
- Never disable CORS in production - It's a critical security feature
- Be specific with origins - Avoid using
*
in production; specify allowed domains - Handle OPTIONS requests - Ensure your server properly responds to preflight requests
- Use environment variables - Configure different CORS settings for development vs production
Conclusion
CORS errors in Flutter web require different approaches for development and production:
- Development: Use browser flags or local proxies to bypass restrictions
- Production: Properly configure your server to handle CORS requests
Remember that CORS is a browser security feature designed to protect users, and finding the right server-side solution will ensure your Flutter web application works securely and reliably for all users.