Laravel automatically parses a JSON request body when the client sends the correct Content-Type: application/json header — $request->input() and the dedicated $request->json() method both read from this already-parsed data, and understanding when each is actually needed avoids confusion.
Reading JSON POST data the normal way
public function store(Request $request)
{
$name = $request->input('name');
$email = $request->input('email');
}
This works transparently for a JSON request exactly the same way it works for a normal form submission — input() doesn't care whether the underlying request was JSON or form-encoded, as long as the client sent the correct content type header for Laravel to parse it correctly in the first place.
Using the dedicated json() method
$name = $request->json('name');
$allData = $request->json()->all();
json() specifically reads from the parsed JSON payload — functionally overlapping with input() for a genuinely JSON request, but useful when the code needs to be explicit that it's expecting JSON specifically, or when working with a nested structure via dot notation.
Reading a nested JSON value with dot notation
// { "customer": { "name": "Alex", "address": { "city": "Austin" } } }
$city = $request->input('customer.address.city');
Getting the entire decoded JSON body as an array
$data = $request->all();
// or, explicitly for JSON:
$data = json_decode($request->getContent(), true);
getContent() returns the raw, unparsed request body as a string — falling back to manually calling json_decode() on it is rarely necessary given Laravel's automatic parsing, but it's the right tool for the rare case of needing the truly raw body text before any parsing happens at all.
Why this fails silently without the correct Content-Type header
// Client sends JSON but with the WRONG content type:
fetch('/api/orders', {
method: 'POST',
body: JSON.stringify({ name: 'Alex' }),
// missing: headers: { 'Content-Type': 'application/json' }
});
Without the correct Content-Type: application/json header, Laravel doesn't know to parse the body as JSON at all — $request->input('name') then returns null, not because the data wasn't sent, but because Laravel never parsed the raw body in the first place; this is one of the most common sources of "the field is always null" confusion when integrating with a JSON API client.
Checking whether a request actually is JSON
if ($request->isJson()) {
// the request's Content-Type indicates JSON
}
if ($request->wantsJson()) {
// the client's Accept header indicates it wants a JSON response back
}
isJson() and wantsJson() check two genuinely different things — the format of what the client *sent* versus what format the client *wants back* — a request can be form-encoded but still want a JSON response, or vice versa, so these shouldn't be assumed to always agree with each other.
Validating JSON input exactly like any other request data
$request->validate([
'customer.name' => 'required|string',
'customer.address.city' => 'required|string',
]);
Laravel's validation rules work identically on JSON input as on form data, including dot notation for validating nested fields — there's no separate JSON-specific validation syntax needed, since by the time validation runs, the JSON has already been parsed into the same underlying request data structure as any other input source.
A practical webhook use case: reading a raw, unparsed body for signature verification
public function handleWebhook(Request $request)
{
$payload = $request->getContent(); // the raw string, exactly as received
$signature = $request->header('X-Webhook-Signature');
$expected = hash_hmac('sha256', $payload, config('services.webhook.secret'));
if (! hash_equals($expected, $signature)) {
abort(403);
}
$data = json_decode($payload, true);
}
This is exactly why getContent() matters despite Laravel's automatic parsing — following the webhook-signature pattern covered elsewhere on this site, the signature must be verified against the exact raw bytes the client sent, not a re-serialized version of the already-parsed data, since even a semantically identical re-encoding can produce a different byte sequence and an incorrect signature check.