Free tools Windows power users keep installed
One-click scans. No signup required.
Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.
When JavaScript sends JSON.stringify(data), PHP does not place that JSON in $_POST. Read the raw request with php://input, decode it, and validate the result. If php://input is empty, the problem is not JSON decoding alone: verify the browser request URL, payload, redirects, and the PHP/Apache route receiving it.
Minimal working JSON example
Use an explicit request content type and return a consistent JSON response:
async function sendData() {
const response = await fetch("/test.php", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({
cows: "When the cows come home",
dogs: "Who let the dogs out?"
})
});
const text = await response.text(); // useful while diagnosing
if (!response.ok) throw new Error(`HTTP ${response.status}: ${text}`);
console.log(JSON.parse(text));
}
<?php
header('Content-Type: application/json; charset=utf-8');
$raw = file_get_contents('php://input');
if ($raw === false || $raw === '') {
http_response_code(400);
echo json_encode(['error' => 'Request body is empty']);
exit;
}
try {
$data = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
http_response_code(400);
echo json_encode(['error' => 'Request body is not valid JSON']);
exit;
}
if (!is_array($data)) {
http_response_code(400);
echo json_encode(['error' => 'Expected a JSON object']);
exit;
}
$cows = $data['cows'] ?? null;
$dogs = $data['dogs'] ?? null;
if (!is_string($cows) || !is_string($dogs)) {
http_response_code(422);
echo json_encode(['error' => 'cows and dogs must be strings']);
exit;
}
echo json_encode([
'cows' => str_replace('cows', 'alpacas', $cows),
'dogs' => str_replace('dogs', 'cats', $dogs)
]);
PHP’s php://input stream contains the raw request body. JSON_THROW_ON_ERROR makes malformed input explicit instead of leaving you to interpret a silent null.
Recommended Free Tools
Why $_POST is empty for JSON
PHP automatically populates $_POST for application/x-www-form-urlencoded and multipart/form-data. A raw application/json body is not one of those form encodings, so use:
#1 Best Overall
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
That does not mean PHP cannot read JSON; it means JSON and form data use different APIs. The PHP documentation explains this distinction in its $_POST reference.
First determine what actually arrived
During development, temporarily inspect the request before decoding:
Rank #2
<?php
header('Content-Type: text/plain; charset=utf-8');
$raw = file_get_contents('php://input');
var_dump([
'method' => $_SERVER['REQUEST_METHOD'] ?? null,
'content_type' => $_SERVER['CONTENT_TYPE'] ?? null,
'content_length' => $_SERVER['CONTENT_LENGTH'] ?? null,
'post' => $_POST,
'raw_body' => $raw
]);
Do not return raw bodies or enable verbose errors in production; log redacted diagnostics on the server instead.
| Observation | Likely explanation |
|---|---|
$_POST is empty but raw body contains JSON |
Normal for JSON; decode php://input. |
| Raw body is empty | No body was sent, the wrong endpoint ran, a redirect/proxy intervened, or the request was rejected before this PHP process. |
| Raw body is nonempty but decoding fails | Malformed JSON or an encoding problem. |
| Response is HTML | Often a 404, redirect, PHP warning/fatal error, rewrite, or server error—not a JSON response. |
| Request is absent from Network tools | JavaScript failed before fetch(), or the browser blocked the request. |
Inspect the browser Network request
- Open developer tools and select Network.
- Reload the page or trigger the action.
- Open the PHP request and check its final URL, method, status, redirects, request headers, payload, response headers, and response body.
You should see a POST request with Content-Type: application/json and a payload similar to {"cows":"When the cows come home","dogs":"Who let the dogs out?"}. Logging the Response object in the console cannot prove that this request body was sent.
Check the URL, not just the filename
fetch("./test.php") is resolved relative to the document URL, not the JavaScript file’s directory. A page in another directory, Apache Alias, virtual host, rewrite rule, HTTP-to-HTTPS redirect, or different hostname can send the request to a different script. A successful status code does not prove that the expected PHP file executed.
Confirm the final URL in Network tools and check web-server and PHP logs. Also verify that the response is generated by PHP rather than being a static file or an HTML error page. The SitePoint discussion that inspired this problem was closed without a verified root cause; its reported Debian update timing should not be treated as proof that Debian, TLS, or Apache caused the failure.
Rank #4
Separate browser issues from server issues with curl
curl -i
-X POST
-H 'Content-Type: application/json'
-H 'Accept: application/json'
--data '{"cows":"When the cows come home","dogs":"Who let the dogs out?"}'
https://example.test/test.php
If curl works but fetch() does not, investigate URL resolution, redirects, CORS, credentials, and JavaScript execution. If both produce an empty body, investigate the endpoint, PHP handler, Apache configuration, proxy, and logs.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsUse a form format when you want $_POST
For simple flat fields, URL encoding is often easier:
const body = new URLSearchParams({
cows: "When the cows come home",
dogs: "Who let the dogs out?"
});
fetch("/test.php", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8" },
body
});
<?php
$cows = $_POST['cows'] ?? '';
$dogs = $_POST['dogs'] ?? '';
For files or multipart form fields, use FormData and read $_POST and $_FILES:
const form = new FormData();
form.append("cows", "When the cows come home");
form.append("dogs", "Who let the dogs out?");
fetch("/test.php", { method: "POST", body: form });
Do not manually set Content-Type: multipart/form-data with FormData. The browser must add the boundary. PHP’s file-upload documentation covers this format. Also note that php://input has special limitations for multipart requests; use the parsed form variables instead.
Headers, redirects, CORS, and sessions
Content-Typedescribes the request body.Acceptdescribes the response format preferred by the client; it does not make PHP parse JSON. See MDN’sContent-Typereference.- Inspect redirect chains. Host canonicalization or HTTPS redirects can expose an unexpected endpoint.
- For cross-origin requests,
application/jsoncommonly triggers anOPTIONSpreflight. The server must answer it with appropriate CORS headers; otherwise the POST may never reach PHP. - Use
credentials: "include"only when cross-origin cookies or sessions are required, together with compatible cookie and CORS settings.
Read and validate the body once
The request body is a stream. Store the result of one file_get_contents('php://input') call rather than assuming repeated reads will independently provide the same data. Validate required keys and types after decoding; JSON syntax alone does not validate business rules.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Quick Recap
Production checklist
- Confirm the fetch call runs and appears in Network tools.
- Verify the final URL, method, payload, status, redirects, and request content type.
- Use
php://inputfor JSON; use$_POST/$_FILESfor form encodings. - Distinguish an empty body from invalid JSON with explicit checks or
JSON_THROW_ON_ERROR. - Return
Content-Type: application/jsonand appropriate 4xx/5xx status codes. - Use
response.text()while diagnosing HTML errors, then switch toresponse.json()once responses are reliable. - Limit request sizes, authenticate sensitive endpoints, apply CSRF protection where applicable, and avoid exposing raw input or PHP errors.
- Escape returned values when inserting them into HTML.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

