A browser may block a frontend from reading a PHP API response even though the endpoint works in a direct request. Configure the PHP response’s Access-Control-Allow-Origin header for the frontend’s exact origin, then handle any preflight request before sending the body.
PHP Access-Control-Allow-Origin Response for an Allowed Site
This example allows one frontend to send a JSON POST and read its response. Replace the example origin with your actual frontend origin. Put the code before any output, including whitespace or output from included files:
<?php
$allowed = 'https://app.example.com';
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
header('Vary: Origin', false);
if ($origin !== $allowed) {
http_response_code(403);
exit;
}
header('Access-Control-Allow-Origin: ' . $allowed);
header('Access-Control-Allow-Methods: POST, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(204);
exit;
}
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit;
}
header('Content-Type: application/json');
echo json_encode(['status' => 'ok']);
From a page served at https://app.example.com, the browser request can be:
const response = await fetch('https://api.example.com/endpoint.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'check' })
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
console.log(await response.json());
An Access-Control-Allow-Origin PHP response must be sent by the API server, not by the calling page. The example rejects requests without the allowed Origin as well; adapt that behavior if the endpoint also serves non-browser clients. The example does not authenticate users or process the submitted data. The PHP Access-Control-Allow-Origin header should be set consistently on each route that the frontend calls. In a PHP endpoint, configure Access-Control-Allow-Origin alongside the other CORS headers. Check your deployment configuration as well as application code.
Match the Origin and Plan for Credentials
An origin consists of scheme, host, and port. https://app.example.com differs from http://app.example.com and from the same host on a nondefault port. It has no path or trailing slash. Compare against a fixed allowlist; reflecting an arbitrary request origin would grant every site access to the response. For multiple trusted sites, select one matching origin and return that single value.
A public resource can use Access-Control-Allow-Origin: * when requests do not use credentials. For cross-origin cookies, configure credentials: 'include' in fetch, return the exact allowed origin, and add Access-Control-Allow-Credentials: true to both preflight and actual responses. Wildcard origin is invalid for credentialed access. Cookie SameSite and Secure settings and browser privacy policies also affect whether cookies are sent.
CORS controls whether browser code can read a response. It is not authentication or protection against every cross-site request. Keep authorization and any needed CSRF defenses on the endpoint. The CORS guide explains the browser rules.
Understand Preflight and Caching
The JSON content type causes the browser to send an OPTIONS preflight before the POST. The response must permit the requesting origin, the requested method, and the requested headers. Add only methods and headers the endpoint actually supports. If you add an authorization header to the request, list Authorization in the allowed headers and implement its validation separately.
Vary: Origin tells caches that responses can differ by request origin. The example sends it even when an origin is rejected; preserve other existing Vary values when integrating it. Apply the same CORS policy to relevant error responses, including errors generated by a proxy or web server, so the browser can expose them to the approved frontend.
Diagnose a Blocked Response
The most common failure is a missing or mismatched allow-origin header. Open the browser’s Network panel, select the OPTIONS request if present, and compare its Origin, requested method, and requested headers with the response. Then inspect the actual response too. If the header is missing, check whether routing, an error, or output before header() bypassed the PHP code. Return one matching origin on both responses and remove duplicate headers added by the server or proxy.
