Skip to content

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:

bash
flutter run -d chrome --web-browser-flag "--disable-web-security"

For integration tests:

bash
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:

bash
#!/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:

bash
export CHROME_EXECUTABLE=~/chrome_launcher

Using flutter_cors Package ​

For a more streamlined development experience:

bash
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 ​

javascript
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
<?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 ​

csharp
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) ​

java
@CrossOrigin
@PostMapping(path="/upload")
public @ResponseBody ResponseEntity<Void> upload(@RequestBody Object object) {
    // Your controller logic
}

NGINX Configuration ​

nginx
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:

dart
// ❌ 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:

dart
// 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:

dart
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 ​

  1. Never disable CORS in production - It's a critical security feature
  2. Be specific with origins - Avoid using * in production; specify allowed domains
  3. Handle OPTIONS requests - Ensure your server properly responds to preflight requests
  4. 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.