Frontend CORS Errors Explained: A Debugging Workflow for Fetch, Maps, and Third-Party APIs
CORS errors are browser-enforced access failures, not usually failures in the API itself. This checklist helps you identify whether the problem is the request, the browser preflight, the API server, credentials, or your local development setup before changing code blindly.
Overview
Cross-Origin Resource Sharing, or CORS, controls whether browser JavaScript may read a response from a different origin. An origin is the combination of scheme, host, and port. For example, http://localhost:5173, http://localhost:3000, and https://app.example.com are different origins, even when they point to the same application or backend.
A typical CORS failure has three parts:
- Your frontend sends a request to another origin.
- The browser checks whether that origin is allowed to make or read the request.
- The server response does not satisfy the browser's CORS requirements, so the browser blocks frontend access to the response.
This distinction matters. Opening an API URL in a browser tab, using a server-side script, or testing with a command-line client may succeed because those actions are not subject to the same browser read restrictions. A successful request outside the browser does not prove that a frontend fetch() call is configured correctly.
Start every investigation in DevTools. Open the Network panel, reproduce the request, and inspect both the actual request and any request named OPTIONS. The Console may show a short CORS message, but the Network panel usually reveals the useful details: request origin, method, request headers, response status, redirect behavior, and response headers.
For mapping integrations, separate the map display from data requests. A tile layer, geocoding endpoint, routing service, search API, and your own application backend may all have different origins and requirements. A map that renders correctly can still have a blocked geocoding or route request. For a broader implementation pattern, see this mapping API tutorial covering search, geocoding, markers, and clustering.
Checklist by scenario
Scenario 1: A simple GET request fails
- Confirm the exact URL, including scheme, hostname, port, path, and query string.
- Check the frontend origin shown in the request headers. A different localhost port is still a different origin.
- Look for
Access-Control-Allow-Originin the response headers. - Check whether the server returned a redirect, authentication response, or error page without CORS headers.
- Test the endpoint with a minimal request before adding application logic.
fetch('https://api.example.test/items')
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
return response.json();
})
.then(data => console.log(data))
.catch(error => console.error('Request failed:', error));
If the request is visible in Network but your code cannot read the response, the issue is likely the server's CORS response rather than JSON parsing. If no request appears, inspect the URL construction, runtime errors, service worker behavior, and whether the function is called at all.
Scenario 2: The browser sends an OPTIONS preflight
Browsers may send a preflight request before the actual request when the method or headers require permission. The preflight commonly uses OPTIONS and asks whether the server accepts the origin, method, and headers.
Inspect the request headers, especially:
OriginAccess-Control-Request-MethodAccess-Control-Request-Headers
The server's preflight response needs to match the requested method and headers. A common failure is allowing GET while the frontend sends POST, or allowing basic requests but not a custom header such as Authorization. A preflight can also fail because the server does not handle OPTIONS on the relevant route.
Scenario 3: Adding JSON or authorization causes the failure
Compare the failing request with a plain request. Headers such as Content-Type: application/json and Authorization can change the browser's request path and trigger preflight handling. Do not remove security-relevant headers simply to make a test pass. Instead, configure the server or gateway to handle the intended request.
For debugging, reduce the request temporarily:
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(payload)
});
Then compare the browser's preflight headers with the server's allowed methods and headers. This is more reliable than repeatedly changing the frontend request without observing what the browser actually sends.
Scenario 4: A mapping API request is blocked
Identify which request failed: map assets, geocoding, reverse geocoding, routing, place search, or your own proxy endpoint. Check the provider's documented browser usage model and whether the request is intended to run directly in the browser. Some integrations require a server-side call to protect credentials, normalize responses, or avoid exposing a secret in client code.
Keep API keys and access restrictions separate from CORS diagnosis. An invalid key may produce an authorization error, while a missing CORS response prevents JavaScript from reading any response at all. Test with a permitted development origin and inspect the status and headers before changing map-library code. You can also use mocked responses while isolating frontend behavior; this workflow is described in How to Test Mapping Features Locally.
Scenario 5: Cookies or credentials are involved
If the request uses cookies, configure both sides deliberately. The frontend may use credentials: 'include', while the server must recognize the requesting origin and return the appropriate credentials-related response header. A wildcard origin is not a substitute for an explicit trusted origin when credentials are involved. Also verify cookie attributes, domain, path, and browser privacy behavior; a CORS fix cannot make an unavailable cookie appear.
What to double-check
- Origin matching: Compare the exact scheme, host, and port. Development and production origins often differ.
- Response coverage: Check success, error, redirect, and preflight responses. CORS headers only on successful responses can make failures look inconsistent.
- Allowed methods and headers: Match what the browser requests, not just what the application usually sends.
- Redirects: Follow the request chain. A redirect to a login page or another host can introduce a new CORS problem.
- Proxy behavior: If a development proxy is used, confirm its route prefix, target URL, rewrite rules, and whether the browser is calling the proxy or the remote API directly.
- Environment variables: Print a safe version of the resolved API base URL. A missing or incorrectly prefixed build variable can send requests to an unintended origin.
- Server logs: Confirm that the preflight and actual request reach the expected service, route, and deployment.
- Response parsing: Once CORS is fixed, separately handle non-2xx statuses, empty bodies, invalid JSON, and timeouts.
A useful fetch wrapper keeps transport failures distinct from HTTP failures:
async function getJson(url, options = {}) {
const response = await fetch(url, options);
if (!response.ok) {
throw new Error(`HTTP ${response.status} from ${url}`);
}
return response.json();
}
This does not solve CORS, but it prevents a resolved fetch promise from being mistaken for a successful API response. For map search, route previews, and geolocation features, that distinction makes the user interface easier to debug and recover.
Common mistakes
- Using
mode: 'no-cors'as a fix: This can produce an opaque response that frontend JavaScript cannot inspect. It does not provide normal access to API data. - Adding permissive headers in frontend code: A browser cannot grant itself permission by sending
Access-Control-Allow-Origin. That header belongs in the server response. - Disabling browser security during development: This hides the real integration behavior and can create unsafe habits. Prefer a configured local proxy, a test backend, or a controlled mock.
- Assuming every API supports direct browser calls: Some endpoints are designed for server-to-server access. Put sensitive credentials and provider-specific logic behind your backend when appropriate.
- Changing several variables at once: Record the original URL, method, headers, status, and preflight result before making changes. Otherwise, it becomes difficult to identify the actual fix.
- Blaming the map library: A library may expose the failed request, but the underlying issue can still be origin configuration, credentials, a proxy, or provider access restrictions.
When to revisit
Revisit this checklist whenever an application moves between local, preview, staging, and production environments; when a frontend build tool or proxy configuration changes; when an API route gains authentication or new headers; or when a mapping provider, domain, tile source, geocoder, or routing service is replaced.
Before releasing a change, run the same request from each supported origin and test both successful and failing responses. Record the intended origins, methods, headers, credential behavior, proxy path, and server-side owner in the project documentation. That small inventory turns a recurring CORS error into a repeatable check.
Action checklist: reproduce the failure, inspect Network, locate the preflight, compare the exact origin, verify server response headers, check redirects and credentials, test through the intended proxy or backend, and only then update frontend code. If the request belongs to a larger mapping feature, validate it alongside the map's data flow using the related searchable web map tutorial and keep provider-specific assumptions documented.