POST Body not Included with JavaScript Fetch

I just ran into a problem and I wanted to document it for myself and for anyone else who might have issues. First I describe the problem, then I give the solution. Scroll down if you’re looking for the solution.

The Problem

After posting with JavaScript fetch I did not see the “body” arguments come through on the server. The method of the fetch was set to POST and the body included was an instance of FormData. According to the documentation on MDN, everything should’ve worked. So why wasn’t it working?

The Basic Client Side Code

const body = new FormData(myForm)
// assume myForm.action = "https://example.com/ajax/post"
const response = await fetch(myForm.action, {
	method: "post",
	body,
})

The Basic Server Side Code

<?php
// file: index.php within the ajax/post directory

// don't bother processing the post if there is none
if(empty($_POST)){
	exit;
}

// ... processing code below

I spent some time debugging and without a doubt, every POST request to the index.php file did not have the $_POST array filled out. The POST array was empty as well as the REQUEST array, even the oft-touted file_get_contents('php://input') came up empty.

The Solution

You aren’t going to like it. I don’t like it. The solution to this problem is so annoying that you’ll just facepalm like Picard.

Add a slash to the end of the url you are posting to.

The problem url is: https://example.com/ajax/post
The working url is: https://example.com/ajax/post/

Currently, when this url is posted to, the server responds with a 301 Redirect before the index.php file is hit. But why? The problem is that you do not have a trailing slash in your url. That’s it. You are posting to an index.php file within a directory, but your url does not have a trailing slash. So your server helpfully redirects you to a url with a trailing slash, and you lose your posted information along the way.

Yep, that’s it. Add a trailing slash and you’ll see your body come through when debugging.