Connecting a modern React frontend (running on http://localhost:3000 or Vite on http://localhost:5173) to a custom PHP REST API backend is one of the most common web architectures. However, almost every developer hits this notorious browser blocking error:
In this comprehensive guide, you will learn why CORS errors occur, how to properly handle browser preflight OPTIONS requests in PHP, and the exact production-ready code to fix it in both PHP and React.
Quick Solution (TL;DR): Universal PHP CORS Header
If you need an immediate fix, place this robust CORS snippet at the very top of your PHP API endpoint or bootstrap script (before any output or echo statements):
<?php
// 1. Allow Specific Origin (or dynamically echo allowed domains)
$allowed_origins = ['http://localhost:3000', 'http://localhost:5173', 'https://yourfrontenddomain.com'];
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (in_array($origin, $allowed_origins)) {
header("Access-Control-Allow-Origin: $origin");
} else {
// For open development APIs:
header("Access-Control-Allow-Origin: *");
}
// 2. Allow Requested HTTP Methods
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS");
// 3. Allow Custom Headers sent by React / Axios
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With");
// 4. Handle Browser Preflight OPTIONS Request Immediately
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
// Your actual API logic continues below...
header("Content-Type: application/json; charset=UTF-8");
echo json_encode(["status" => "success", "message" => "CORS configured successfully!"]);
?>
Why Does the CORS Error Happen?
CORS (Cross-Origin Resource Sharing) is a browser-enforced security mechanism. It prevents a malicious website loaded in a user's browser from making unauthorized requests to your private backend API.
A request is considered Cross-Origin when any of these three elements differ between the frontend and backend:
- Protocol / Scheme:
http://vshttps:// - Domain / Hostname:
frontend.comvsapi.backend.com - Port:
localhost:3000vslocalhost:80(or Apache port 8080)
💡 Why does it work in Postman but fail in React?
Postman is a standalone developer tool without a browser sandbox, so it ignores CORS policies. Web browsers (Chrome, Edge, Firefox, Safari) strictly enforce CORS compliance on all JavaScript fetch() and axios requests.
The Secret Problem: Browser Preflight OPTIONS Requests
When your React app sends a simple GET request with standard headers, the browser sends it directly. However, whenever you send a POST, PUT, or DELETE with Content-Type: application/json or an Authorization: Bearer <token> header, the browser initiates a Preflight Request.
The preflight request uses the HTTP OPTIONS method to ask your PHP server:
"Hey PHP server! Is origin http://localhost:3000 allowed to send a POST request with JSON headers to this endpoint?"
If your PHP script doesn't explicitly respond to the OPTIONS request with a 200 OK status code, the browser halts execution and throws the CORS error before your actual POST request is ever sent!
How to Fix the Preflight in PHP:
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
http_response_code(200);
exit();
}
Production-Ready Solutions
Method 1: Reusable PHP CORS Middleware (Recommended)
Create a dedicated helper file named cors.php in your backend and include it across all API endpoints:
<?php
// cors.php - Place in your includes/ or middleware folder
function enable_cors() {
// Specify allowed domains
$allowed_origins = [
'http://localhost:3000',
'http://localhost:5173',
'https://app.aoneinfo.com'
];
if (isset($_SERVER['HTTP_ORIGIN']) && in_array($_SERVER['HTTP_ORIGIN'], $allowed_origins)) {
header("Access-Control-Allow-Origin: " . $_SERVER['HTTP_ORIGIN']);
header("Access-Control-Allow-Credentials: true");
} else {
header("Access-Control-Allow-Origin: *");
}
header("Access-Control-Max-Age: 86400"); // Cache preflight response for 24 hours
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization, X-Requested-With, Accept");
// Intercept preflight OPTIONS request
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'])) {
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, PATCH, OPTIONS");
}
if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'])) {
header("Access-Control-Allow-Headers: " . $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']);
}
http_response_code(200);
exit(0);
}
}
// Call on startup
enable_cors();
?>
Method 2: Configuring CORS in Apache .htaccess
If you want Apache to handle CORS headers automatically before PHP is even invoked, add this to your .htaccess file in your API directory:
<IfModule mod_headers.c>
Header always set Access-Control-Allow-Origin "*"
Header always set Access-Control-Allow-Methods "POST, GET, OPTIONS, DELETE, PUT"
Header always set Access-Control-Allow-Headers "Content-Type, Authorization, X-Requested-With"
</IfModule>
# Respond with 200 OK to preflight OPTIONS requests immediately
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=200,L]
How to Read JSON Post Data in PHP from React
When React sends data via axios.post(url, { name: 'John' }) or fetch(), PHP will not populate $_POST because the body is raw JSON, not URL-encoded form data.
Always parse incoming JSON in your PHP script like this:
<?php
require_once 'cors.php';
// Read raw body stream
$raw_input = file_get_contents('php://input');
$data = json_decode($raw_input, true);
if (!$data) {
http_response_code(400);
echo json_encode(["error" => "Invalid JSON payload received"]);
exit();
}
$user_name = htmlspecialchars($data['name'] ?? '');
echo json_encode([
"success" => true,
"message" => "Hello, " . $user_name
]);
?>
React Frontend Axios / Fetch Implementation
On your React frontend, ensure your Axios client is configured properly:
import React, { useState } from 'react';
import axios from 'axios';
const API_URL = 'http://localhost/api/users.php';
export default function UserRegistration() {
const [name, setName] = useState('');
const [response, setResponse] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
try {
const res = await axios.post(API_URL, { name }, {
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json'
}
});
setResponse(res.data.message);
} catch (error) {
console.error('API Error:', error.response?.data || error.message);
}
};
return (
<div className="p-6 max-w-md mx-auto bg-white rounded-xl shadow-md">
<h2 className="text-xl font-bold mb-4">React & PHP API Test</h2>
<form onSubmit={handleSubmit} className="space-y-4">
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter name"
className="w-full border p-2 rounded"
/>
<button type="submit" className="bg-blue-600 text-white px-4 py-2 rounded">
Send to PHP Backend
</button>
</form>
{response && <p className="mt-4 text-green-600 font-semibold">{response}</p>}
</div>
);
}
For more related troubleshooting, check out our guide on How to Fix Axios Network Error in React and Node.js.
Top 3 Common Mistakes to Avoid
- Sending Whitespace Before
header(): In PHP, if any HTML, spaces, or BOM characters are output before callingheader(), PHP will throw "Headers already sent" and fail to send CORS headers. Ensure no blank lines exist before<?php. - Using Wildcard
*With Cookies/Credentials: If your React app setswithCredentials: true, the browser will rejectAccess-Control-Allow-Origin: *. You must echo the specific requesting origin. - Forgetting
exit()in the OPTIONS Block: Always callexit();right after returning200 OKfor preflight OPTIONS requests, otherwise PHP will continue executing subsequent code and might throw validation errors.
Frequently Asked Questions (FAQ)
Why does my API work in Postman but fail with CORS error in React?
Postman is a server-to-server client and does not enforce browser security sandboxes. CORS is strictly enforced by web browsers on client-side JavaScript requests.
How do I allow multiple frontend domains in PHP?
Maintain an array of allowed domain strings in PHP. Check if $_SERVER['HTTP_ORIGIN'] is in the array, and if so, echo that origin in header("Access-Control-Allow-Origin: $origin").
Can I fix CORS completely from the React frontend?
During local development, you can use a Vite proxy or Webpack proxy in React to route requests through the same origin. However, for production deployment, the PHP backend must send the appropriate CORS headers.