GuidesDIAGNOSE
Fix an Angular CORS error
An Angular CORS error means the API did not return an Access-Control-Allow-Origin header for your app's origin, which is http://localhost:4200 under ng serve. HttpClient then gets an HttpErrorResponse with status 0. In development, forward /api through ng serve with a proxy.conf.json file, or in production allow your origin on the API or serve both from one origin.
Recognize the error
The console shows Access to fetch at 'http://localhost:3000/api/items' from origin 'http://localhost:4200' has been blocked by CORS policy. Angular 22 uses the fetch backend by default; apps on the XHR backend show Access to XMLHttpRequest at instead. HttpClient receives no response, only an HttpErrorResponse with status 0 and statusText Unknown Error.
The error message depends on the backend: with the default fetch backend it reads Http failure response for http://localhost:3000/api/items: 0 undefined, because a failed fetch carries no status text. On the XHR backend it reads Http failure response for http://localhost:3000/api/items: 0 Unknown Error. Status 0 also appears when the API is not running or the host name does not resolve; the console message tells you which one.
Set up proxy.conf.json for ng serve
The Angular CLI dev server can forward requests to your API. The browser then only talks to http://localhost:4200, so there is no cross-origin request. You configure it with a proxy.conf.json file.
{
"/api/**": {
"target": "http://localhost:3000",
"secure": false
}
}- Create src/proxy.conf.json
Map
/api/**tohttp://localhost:3000. Settingsecure: falseskips certificate checks when the target useshttps, for example a local API with a self-signed certificate. - Reference it in angular.json
Set
proxyConfigtosrc/proxy.conf.jsonin theoptionsof theservetarget, or runng serve --proxy-config src/proxy.conf.json. - Restart ng serve
The dev server reads the proxy file when it starts, so restart
ng serveafter every change. - Call relative URLs
Request
/api/items, nothttp://localhost:3000/api/items. An absolute URL skips the proxy and runs into CORS again.
{
"projects": {
"my-app": {
"architect": {
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"proxyConfig": "src/proxy.conf.json"
}
}
}
}
}
}import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
export interface Item {
id: number;
name: string;
}
@Injectable({ providedIn: 'root' })
export class ItemsService {
private readonly http = inject(HttpClient);
// ng serve forwards /api/items to http://localhost:3000/api/items
list() {
return this.http.get<Item[]>('/api/items');
}
}| Pattern | Matches | Does not match |
|---|---|---|
| /api | Only /api itself | /api/items |
| /api/* | /api/items | /api/items/42 |
| /api/** | /api/items and /api/items/42 | Paths outside /api |
The older webpack-based builder, @angular-devkit/build-angular:dev-server, treats /api as /api plus every sub-path. Check the builder of the serve target in angular.json to see which one you use.
Fix CORS in production
ng build outputs static files, and the dev-server proxy is not part of them. In production, either the API allows your app's origin with Access-Control-Allow-Origin, or a reverse proxy on your host serves the app and /api from the same origin, so no CORS check happens. An HttpInterceptor cannot fix CORS: it changes the request, and CORS is decided by the response headers. Server guides: Express, Spring Boot, FastAPI, Django, Laravel.
| Where | Fix |
|---|---|
| ng serve | proxy.conf.json forwards /api to your API |
| Production, API you run | Access-Control-Allow-Origin for your app's origin |
| Production, one host | A reverse proxy serves the app and /api from one origin |
| Public third-party API | https://proxy.cors.dev/ prefix for GET and HEAD |
| Third-party API with a key | Your own backend |
withCredentials and cookies
withCredentials: true makes HttpClient send cookies cross-origin. The response then needs Access-Control-Allow-Credentials: true and your exact origin; * is rejected. Details: Access-Control-Allow-Credentials.
Third-party APIs without CORS headers
For a public third-party API that sends no CORS headers, put https://proxy.cors.dev/ in front of the URL. For example, the Steam news API on api.steampowered.com sends no CORS headers. A plain HttpClient GET adds only an Accept header, which the proxy passes through. The free tier covers GET and HEAD with no signup, no key, responses up to 1 MiB, and fair-use rate limits on a shared pool. Authorization and custom headers are rejected, so APIs that need a key go through your own backend.
import { HttpClient } from '@angular/common/http';
import { Injectable, inject } from '@angular/core';
interface SteamNews {
appnews: { newsitems: { gid: string; title: string; url: string }[] };
}
@Injectable({ providedIn: 'root' })
export class SteamNewsService {
private readonly http = inject(HttpClient);
latest(appId: number) {
const target = `https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=${appId}&count=5`;
return this.http.get<SteamNews>(`https://proxy.cors.dev/${target}`);
}
}Good questions.
What does Http failure response 0 Unknown Error mean in Angular?
It means no HTTP response reached HttpClient: a CORS block, an API that is not running, or a network or DNS failure. The status is 0, and the browser console shows which cause it was.
Where does proxy.conf.json go?
It goes in src/proxy.conf.json. The angular.json file references it through proxyConfig in the serve target.
Why is my Angular proxy config not working?
Restart ng serve after changing the file, and use relative URLs like /api/items. Check the file path in angular.json. On @angular/build:dev-server, use /api/** to match nested paths.
Does the Angular proxy work in production?
No: ng build output does not include it. The API must allow your origin, or a reverse proxy must serve the app and the API from one origin.
Can an HttpInterceptor fix a CORS error?
No: an interceptor changes the request, and CORS depends on the response headers from the API.