← Back to posts Cover image for Flutter Web's CORS Conundrum: Solving API Access Issues with Dart

Flutter Web's CORS Conundrum: Solving API Access Issues with Dart

· 5 min read
Weekly Digest

The Flutter news you actually need

No spam, ever. Unsubscribe in one click.

Chris
By Chris

Flutter Web’s CORS Conundrum: Solving API Access Issues with Dart

You’ve built a beautiful Flutter app that works perfectly on mobile. You compile it for the web, fire up Chrome, and suddenly your API calls fail with a cryptic CORS error in the console. Sound familiar? You’re not alone. CORS (Cross-Origin Resource Sharing) is one of the most common hurdles Flutter web developers face when connecting to external APIs. Let’s demystify this browser security feature and explore practical Dart-only solutions.

Why CORS Happens in Flutter Web (But Not Mobile)

First, understand this crucial distinction: CORS is a browser security policy, not a Flutter limitation. When you run your Flutter app on iOS or Android, you’re executing native code that can make network requests without these restrictions. But in a browser environment, JavaScript (and by extension, Dart compiled to JavaScript) must follow the same-origin policy.

The browser blocks requests to a different origin (domain, protocol, or port) unless the server responds with specific CORS headers allowing it. For example, if your Flutter web app runs at https://myapp.com and tries to fetch data from https://api.example.com, the browser will check if api.example.com includes Access-Control-Allow-Origin: https://myapp.com (or *) in its response headers.

The Core Problem: You Can’t Fix Server-Side Issues Client-Side

This is the most important point: If you don’t control the API server, you cannot truly solve CORS from your Flutter web app alone. Any workaround is exactly that—a workaround that might work in development but fail in production or break with browser updates.

That said, there are scenarios where you can implement practical solutions:

  1. You control the backend (ideal scenario)
  2. You’re in development mode and need a temporary solution
  3. The API supports CORS but needs proper configuration

Practical Dart Solutions and Workarounds

Solution 1: Using a Proxy in Development

During development, you can create a simple Dart proxy server that runs alongside your Flutter app. This server acts as an intermediary, making requests on behalf of your web app since server-to-server communication isn’t subject to CORS.

Here’s a basic proxy server using the shelf package:

// proxy_server.dart
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_proxy/shelf_proxy.dart';

void main() async {
  final handler = ProxyHandler(
    'https://api.target-service.com',
    client: HttpClient()..userAgent = 'MyFlutterApp',
  );

  final server = await io.serve(handler, 'localhost', 8080);
  print('Proxy server running on http://${server.address.host}:${server.port}');
}

Run this server, then modify your Flutter app to point to the proxy:

// In your Flutter web app
final response = await http.get(
  Uri.parse('http://localhost:8080/api/data'), // Proxy endpoint
  headers: {'Content-Type': 'application/json'},
);

Important: This is for development only. Never deploy a public-facing proxy without proper security measures.

Solution 2: Request Headers and Mode Configuration

Some APIs might work with specific request configurations. The http package allows you to set the request mode:

import 'package:http/http.dart' as http;

Future<void> fetchData() async {
  try {
    final response = await http.get(
      Uri.parse('https://api.example.com/data'),
      headers: {
        'Content-Type': 'application/json',
        'Origin': 'https://myapp.com', // Your web app's origin
      },
    );
    
    if (response.statusCode == 200) {
      // Process your data
    }
  } catch (e) {
    print('Request failed: $e');
  }
}

For more control, use the dart:html library directly (web-only):

import 'dart:html';
import 'dart:convert';

Future<Map<String, dynamic>> fetchWithCors() async {
  final request = HttpRequest();
  
  request
    ..open('GET', 'https://api.example.com/data')
    ..setRequestHeader('Content-Type', 'application/json')
    ..setRequestHeader('Accept', 'application/json');
  
  await request.send();
  
  if (request.status == 200) {
    return jsonDecode(request.responseText);
  } else {
    throw Exception('Failed to load data: ${request.status}');
  }
}

The Right Solution: Server-Side CORS Configuration

If you control the backend, this is the correct, permanent solution. Here are examples for common server frameworks:

Node.js/Express:

app.use((req, res, next) => {
  res.header('Access-Control-Allow-Origin', 'https://myapp.com');
  res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
  next();
});

Dart/shelf:

import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;

Middleware corsHeaders() {
  return (Handler innerHandler) {
    return (Request request) async {
      final response = await innerHandler(request);
      return response.change(
        headers: {
          'Access-Control-Allow-Origin': 'https://myapp.com',
          'Access-Control-Allow-Headers': 'content-type, authorization',
          'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        },
      );
    };
  };
}

void main() async {
  final handler = const Pipeline()
      .addMiddleware(corsHeaders())
      .addHandler((request) => Response.ok('CORS enabled!'));
  
  await io.serve(handler, 'localhost', 8080);
}

Common Mistakes to Avoid

  1. Assuming mobile solutions work on web: Always test API calls specifically in browser environments during development.
  2. Using no-cors mode as a fix: While you can set RequestMode.no-cors in browser APIs, this severely limits what you can do with the response (you can’t read the response body in JavaScript/Dart).
  3. Forgetting about credentials: If your API requires authentication cookies or tokens, you’ll need Access-Control-Allow-Credentials: true on the server and credentials: 'include' in your client requests.
  4. Not handling preflight requests: For non-simple requests (those with custom headers or non-standard content types), browsers send an OPTIONS preflight request first. Your server must handle these.

Best Practices for Flutter Web API Development

  1. Develop with CORS in mind from day one: If building full-stack, configure CORS headers early in development.
  2. Use environment-specific configuration: Maintain different API endpoints for development (with proxy) and production (direct to properly configured server).
class ApiConfig {
  static const bool isWeb = kIsWeb;
  
  static String get baseUrl {
    if (isWeb && const bool.hasEnvironment('USE_PROXY')) {
      return 'http://localhost:8080'; // Development proxy
    }
    return 'https://api.myapp.com'; // Production or mobile
  }
}
  1. Implement comprehensive error handling: CORS errors manifest as failed network requests. Provide clear user feedback and logging.
  2. Consider server-side rendering (SSR) or Flutter desktop: For complex applications hitting APIs without CORS support, these alternatives avoid browser restrictions entirely.

Wrapping Up

CORS in Flutter web isn’t a bug—it’s a browser security feature working as intended. While Dart-only workarounds exist for development scenarios, the only production-ready solution involves proper server configuration. By understanding the why behind CORS errors and implementing the appropriate solution for your situation, you can ensure your Flutter web apps communicate seamlessly with your APIs.

Remember: When you encounter a CORS error, first ask, “Do I control the server?” The answer determines your path forward. Happy coding!

This blog is produced with the assistance of AI by a human editor. Learn more

Related Posts

Cover image for Optimizing Flutter UI Performance: Best Practices for Date Formatting and Expensive Operations

Optimizing Flutter UI Performance: Best Practices for Date Formatting and Expensive Operations

Developers often face performance bottlenecks when performing expensive operations like date formatting directly within Flutter's `build` method, especially in fast-scrolling lists. This post will delve into common pitfalls, explain why these operations are costly, and provide practical strategies for optimizing UI performance by caching formatters, using `initState`, and leveraging `compute` for background processing without blocking the UI.

Cover image for Optimizing Your Flutter Dev Setup: IDEs, Simulators, and AI Tools for Peak Productivity

Optimizing Your Flutter Dev Setup: IDEs, Simulators, and AI Tools for Peak Productivity

Flutter developers frequently seek to refine their development environments. This post will dive into popular IDE choices like VS Code and Android Studio, discuss best practices for managing iOS and Android simulators (including in-IDE options), and explore the practical integration of AI tools for code generation and problem-solving to boost overall efficiency.

Cover image for Demystifying Flutter Performance: Practical Strategies for Large-Scale Apps

Demystifying Flutter Performance: Practical Strategies for Large-Scale Apps

Flutter's performance is often blamed for issues in complex applications, but the real culprits are usually architectural decisions, inefficient widget rebuilds, and unoptimized resource handling. This post will dive into common performance bottlenecks in large Flutter apps, providing actionable strategies for profiling, optimizing state management, handling images and network requests efficiently, and leveraging CI/CD for continuous performance monitoring.