Skip to content

Instantly share code, notes, and snippets.

@jesperorb
Last active April 30, 2024 22:27
Show Gist options
  • Save jesperorb/a6c12f7d4418a167ea4b3454d4f8fb61 to your computer and use it in GitHub Desktop.
Save jesperorb/a6c12f7d4418a167ea4b3454d4f8fb61 to your computer and use it in GitHub Desktop.
PHP form submitting with fetch + async/await

PHP Form submitting

If we have the following structure in our application:

  • 📁 application_folder_name
    • 📄 index.php
    • 📄 handle_form.php
    • 📄 main.js

And we fill our index.php with the following content just to get a basic website with a form working. You should be able to run this through a php-server of your choice.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>PHP</title>
</head>
<body>

    <form action="handle_form.php" method="POST" id="example_form">
        <label for="username">Username</label>
        <input type="text" name="username" id="username">
        <label for="favorite_number">Favorite number</label>
        <input type="number" name="favorite_number" id="favorite_number">
        <input type="submit" value="Skicka">
    </form>

    <script src="main.js"></script>
</body>
</html>

Submitting a form with PHP

Lets focus on the form. To successfully submit a form you need to have your inputs inside of the form-element, this indicates that the inputs shall be sent along with the form if we submit the form "like normal". To send the form without JavaScript all inputs need to have a name, in fact they should have a name regardless if you are sending the data via JavaScript or PHP. The actual form needs 3 things:

  • action - telling the form to which file should this information be sent, in this case handle_form.php which is located in the same folder as index.php
  • method - how should this information be sent: POST or GET. Default is GET
  • <input type="submit"> - To be able to send the form via click, even if you handle this via JavaScript, you should always ha a "Send"-button. This button can also be <button type="submit"> Send </button> to allow HTML inside of the button (for example to add an icon inside a button)
<form action="handle_form.php" method="POST" id="example_form">
    <label for="username">Username</label>
    <input type="text" name="username" id="username">
    <label for="favorite_number">Favorite number</label>
    <input type="number" name="favorite_number" id="favorite_number">
    <input type="submit" value="Skicka">
</form>

All this information needs to exists in HTML to successfully send a request. If this markup is correct we should be able to submit the form to the file handle_form.php:

<?php
echo $_POST["username"];
echo $_POST["favorite_number"];

<input name="username"> will result in the value of the input field being stored in $_POST["username"]. So the name of the input field will decide what the variable is called in PHP.

  • If we use method="POST" on the form the values will be stored in $_POST
  • If we use method="GET" or do not supply a method to the form the values will be stored in $_GET
  • PHP also has a $_REQUEST-variable that can contain these values.

$_POST and $_GET will always be an Associative Array, this is how we would write the variable if it wouldn't get created for us:

<?php
$_POST = [
    "username"          => "zero_cool",
    "favorite_number"   => "10"
];

The JavaScript equivalent would be:

var $_POST = {
    username: "zero_cool",
    favorite_number: "10"
};

Using GET instead of $_POST

We can send the same information with GET instead:

<form action="handle_form.php" method="GET" id="example_form">
    <label for="username">Username</label>
    <input type="text" name="username" id="username">
    <label for="favorite_number">Favorite number</label>
    <input type="number" name="favorite_number" id="favorite_number">
    <input type="submit" value="Skicka">
</form>

The only difference in this case is that we store the data inside of $_GET instead of $_POST.

<?php
// Inside of `handle_form.php`
echo $_GET["username"];
echo $_GET["favorite_number"];

example

<?php
$_GET = [
    "username"          => "zero_cool",
    "favorite_number"   => "10"
];

The JavaScript equivalent would be:

var $_GET = {
    username: "zero_cool",
    favorite_number: "10"
};

The other difference is that the information will be available inside of the URL:

http://localhost:8888/handle_form.php?username=zero_cool&favorite_number=10

This also means that we can basically call PHP-files like the one handling our form with a link instead. So if we were to write the code below, we would get the same result. This is only possible with GET-requests because the information is always sent via the URL and not inside of body like with POST.

<a href="http://localhost:8888/handle_form.php?username=zero_cool&favorite_number=10">
Click me to submit
</a>

Submitting a form with JavaScript

We can submit the same form with the use of JavaScript without reloading the page. The same logic will be applied inside of the PHP-file and most of the work must be done in JavaScript. If we have the same form as above we must first make sure the form isn't being submitted. A forms default behavior is to redirect you to a new site, which is fine if we don't have JavaScript. But in this case we can use JavaScript to our advantage.

// Get the whole form, not the individual input-fields
const form = document.getElementById('example_form');

/**
 * Add an onclick-listener to the whole form, the callback-function
 * will always know what you have clicked and supply your function with
 * an event-object as first parameter, `addEventListener` creates this for us
 */
form.addEventListener('click', function(event){
    //Prevent the event from submitting the form, no redirect or page reload
    event.preventDefault();
});

No we have stopped the form from behaving like it normally would. That also means that we need to specify what it should do instead. So we know that we need to communicate with the backend. Even though the client side and the server side are on the same domain, JavaScript can't communicate back to PHP without going through AJAX. AJAX must always be used. Let's use the native built in way of using AJAX: fetch. Notice that the first code below will not work, it is just a first step

// Get the whole form, not the individual input-fields
const form = document.getElementById('example_form');

/**
 * Add an onclick-listener to the whole form, the callback-function
 * will always know what you have clicked and supply your function with
 * an event-object as first parameter, `addEventListener` creates this for us
 */
form.addEventListener('click', function(event){
    //Prevent the event from submitting the form, no redirect or page reload
    event.preventDefault();
    postData();
});

async function postData(){
    /*
     * We are still using the same file as before and we are still not touching
     * either the backend code or the actual form. We could grab 
     * the action-attribute from the form but it's easier to just put 
     * in the 'URL' here. We don't need to supply PORT or 'localhost'
     */
    const response = await fetch('handle_form.php');
    /*
     * Because we are using `echo` inside of `handle_form.php` the response
     * will be a string and not JSON-data. Because of this we need to use
     * `response.text()` instead of `response.json()` to convert it to someting
     * that JavaScript understands
     */
    const data = await response.text();
    //This should later print out the values submitted through the form
    console.log(data);
}

But we are still missing the information to be sent. In raw PHP/HTML the form handles converting the data and sending it to handle_form.php. If we write our own logic for sending the data we must also make sure that the data is properly formatted.

The data needs to be formatted to x-www-form-urlencoded or multipart/form-data which is what PHP is expecting to recieve. The easiest way to handle this is via the FormData-object. We can also make it accept JSON but it's easier to use the FormData-object.

// Get the whole form, not the individual input-fields
const form = document.getElementById('example_form');

/**
 * Add an onclick-listener to the whole form, the callback-function
 * will always know what you have clicked and supply your function with
 * an event-object as first parameter, `addEventListener` creates this for us
 */
form.addEventListener('click', function(event){
    //Prevent the event from submitting the form, no redirect or page reload
    event.preventDefault();
    /**
     * If we want to use every input-value inside of the form we can call
     * `new FormData()` with the form we are submitting as an argument
     * This will create a body-object that PHP can read properly
     */
    const formattedFormData = new FormData(form);
    postData(formattedFormData);
});

async function postData(formattedFormData){
    /**
     * If we want to 'POST' something we need to change the `method` to 'POST'
     * 'POST' also expectes the request to send along values inside of `body`
     * so we must specify that property too. We use the earlier created 
     * FormData()-object and just pass it along.
     */
    const response = await fetch('handle_form.php',{
        method: 'POST',
        body: formattedFormData
    });
    /*
     * Because we are using `echo` inside of `handle_form.php` the response
     * will be a string and not JSON-data. Because of this we need to use
     * `response.text()` instead of `response.json()` to convert it to someting
     * that JavaScript understands
     */
    const data = await response.text();
    //This should now print out the values that we sent to the backend-side
    console.log(data);
}

Adding more values to FormData in JavaScript

If we want to add stuff that isn't inside of the form to this object we can use the .append()-method on the FormData-object:

form.addEventListener('click', function(event){
    event.preventDefault();
    const formattedFormData = new FormData(form);
    formattedFormData.append('property', 'value');
    postData(formattedFormData);
});

Sending both GET AND POST

The form and the fetch-call will only either use GET or POST. A request can only be one of these requests. We can however send along values in the URL and these will be stored inside of the $_GET-variable even though our request is a POST.

const form = document.getElementById('example_form');
form.addEventListener('click', function(event){
    event.preventDefault();
    const formattedFormData = new FormData(form);
    postData(formattedFormData);
});

async function postData(formattedFormData){
    /**
     * The request is still 'POST' but the $_GET variable
     * will get values too: 'name' and 'favorite_color'
     */
    const response = await fetch(
        'handle_form.php?name=Jesper&favorite_color=pink',
        {
            method: 'POST',
            body: formattedFormData
        }
    );
    const data = await response.text();
    console.log(data);
}

This would result in the following variables being set inside of handle_form.php:

<?php
// Inside of `handle_form.php`
echo $_POST["username"];
echo $_POST["favorite_number"];
echo $_GET["name"];
echo $_GET["favorite_color"];

Using JSON

FormData is used when the server expects to recieve data in the form of x-www-form-urlencoded. This isn't always the case, some servers prefer that you instead send the data as a JSON-object. This means that we have to make some changes to both the frontend and the backend. If we use the same form as earlier:

const form = document.getElementById('example_form');
form.addEventListener('click', function(event){
    event.preventDefault();
    /*
     * There is no shortcut like with 'new FormData(this)', we need
     * to construct the form-object ourselves. We are creating a regular
     * object instead of a FormData-object. `this` refers to the form,
     * we could also write: form.username.value and form.favorite_number.value
     */
    const formattedFormData = {
        username: this.username.value,
        favorite_number: this.favorite_number.value
    }
    postData(formattedFormData);
});

async function postData(formattedFormData){
    /**
     * The request is still 'POST' but the $_GET variable
     * will get values too: 'name' and 'favorite_color'
     */
    const response = await fetch(
        'handle_form.php',
        {
            method: 'POST',
            /*
             * We also need to stringify the values, turn the
             * JavaScript object to a single string that is accepted
             * as JSON. So we are sending one string that contains
             * all our values
             */
            body: JSON.stringify(formattedFormData)
        }
    );
    const data = await response.text();
    console.log(data);
}

If we choose to send the data like this we also need to tell PHP to expect the data to be accepted like this:

<?php
//inside of 'handle_form.php'

/* We are parsing all the data that is being sent as JSON. `json_decode`
 * turn our JSON-object into a PHP Associative array and stores it inside of
 * `$data`
 */
$data = json_decode(file_get_contents('php://input'), true);
echo $data["username"];
echo $data["favorite_number"];

If we choose to accept data as JSON we should probably also send back JSON. So if we want to 'echo' back some value in the form of json, we should use json_encode before sending it. This is a bit dumb example tho because we are not doing anything with the data, but it's more to get a hang of the syntax and how it works.

<?php
//inside of 'handle_form.php'
$data = json_decode(file_get_contents('php://input'), true);

echo json_encode($data);

If we return JSON we must also use .json() instead of .text() in JavaScript:

const data = await response.text();
const data = await response.json();

PHP API Proxy

This is useful when we are restricted by CORS which means that some APIs do not allow us to make requests to their API via a browser. I have written a small guide to getting around that problem here: Handle CORS Client-side

We can make a small proxy-file that handles redirecting the data via our own server. Let's use Pokémon API as an example because the do not allow CORS. So instead of calling the API directly we call our own file that calls the API:

async function fetchPokemon(){
  const response = await fetch('fetch_pokemon.php?pokemon=25');
  const pokemon = await response.json();
  console.log(pokemon);
}

Inside fetch_pokemon.php we use file_get_contents that can usually be used to fetch data from an API:

<?php
$response = file_get_contents('http://pokeapi.co/api/v2/pokemon/' . $_GET["pokemon"]);
echo $response;

Notice that we are sending along which Pokémon to be fetched by adding a query parameter after ?. The key will be $_GET["pokemon"] and the value will be 25 in this case. No we will not get any CORS errors.

Be cautious and do not trust data from the user. Sending data from the user with a request like this can be harmful. Always validate your input before sending it

@rd-frutuoso
Copy link

very thanks! this is amazing!

@accorinti
Copy link

Thanks for the amazing tutorial. Could you please also write 2 lines on error handling?

@Sananthu-47
Copy link

Where is fetchData?

@jesperorb
Copy link
Author

jesperorb commented Oct 31, 2020

Where is fetchData?

It was a typo, was supposed to be postData, changed it now.

@jesperorb
Copy link
Author

Hello, I'm new to web programming and javascript. I've read the article and tried to use it to send validated form data to mailhandle.php - file managing the emails sending. In same website on 2 of the pages it is working with no problems. On the third one I faced issues I don't understand.
The difference between the pages is:
the working 2 have url type: domain.tld/pagename-lvl1, The response gives me the result message of mailhandle.php correctly.
the third: domain.tld/pagename-lvl1/pagename-lvl2/pagename-lvl3. The resposne gives me the home page (it's url is type domain.tld/pagename-lvl1) instead the result message of mailhandle.php.
My search to understand the problem and fix it lead to nothing at this point.

I'll be very happy to receive some advice and knowledge about his problem.
You can reach me by e-mail.
Thank you!

Not sure I understand fully. It would help if you could supply the code.

@jakarezinho
Copy link

Thank you!

@SabrinaMarkon
Copy link

Fantastically useful and clearly explained. My thanks!

@joaquimnetocel
Copy link

FANTASTIC!!!!!

@ouzkagan
Copy link

really amazing thing. Thxx

@TroAlexis
Copy link

Thank you, that helped me a lot!

@ouzkagan
Copy link

ouzkagan commented Oct 3, 2021

thanks for this but I keep getting this issue - any suggestion on how to resolve? script.js:4 Fetch API cannot load file:///project/fetch_pokemon.php?pokemon=25. URL scheme "file" is not supported.

You should only put something like "fetch_pokemon.php" depending on your directory. You can check where are you are making request from network tab in developer tools.

@indefinitelee
Copy link

@jesperorb
sent you a tip with BAT

@104cubes
Copy link

104cubes commented Aug 19, 2022

You are the best.
Only one... tip?
What about cross origin?
Thank you very very much!! The only valid tutorial about.
On the headers receipt on php file:

PHP headers:

header("Access-Control-Allow-Origin: *");
header("Content-Type: application/json; charset=UTF-8");
header("Access-Control-Allow-Methods: GET, POST, PUT, DELETE, FETCH, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");

And don't forget to do not set in send when fetch:

                    // mode: 'no-cors', Don't do this when headers above (php file) like this. 
                     body: JSON.stringify(toSend),
                     headers: {
                        'Content-Type': 'application/json'
                        // 'Content-Type': 'application/x-www-form-urlencoded',
                    }

This is the way to let the call to be correct both from local and remote.

This is my question about that in Stack Overflow. I used part of your code.
https://stackoverflow.com/questions/73407380/fetch-call-to-php-from-remote-client-no-error-and-no-data.

Thanks again!

@eversionsystems
Copy link

Wow, I was struggling for so long with a COR's issue. Was using a proxy to get around it (https://www.npmjs.com/package/local-cors-proxy) but this solution is way better!! Thanks very much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment