WordPress provides incredible support for you to work with form submissions in your application. Whether you add a form in the admin or public facing areas, the built-in mechanism with the admin-post and admin-ajax scripts will allow you to handle your form requests efficiently.
In this article, I’ll show you how to handle custom form submissions using the WordPress API. I’ll walk you through the process of adding a custom form in the admin area of a plugin, handle the form submission via an HTML as well as an AJAX request, and write the form handler in PHP to validate, sanitize and process the form input. I’ll stay within the admin realms of WordPress, the same concepts are applicable while working with forms in the public facing areas.
While I’ll stay within the admin realms of WordPress, the same concepts are applicable while working with forms in the public facing areas.
I’ll also be making use of object-oriented programming constructs for the plugin; however, you can achieve the same result using procedural code as well. The practice plugin can be downloaded from here to follow along with the article.
- WordPress Development for Beginners: Getting Started
- WordPress Development for Beginners: Building Plugins
- Advanced WordPress Development: Introduction to Object-Oriented Programming
Let’s get started by first understanding the built-in WordPress mechanism to handle a regular form post request.
Form Submissions with admin-post.php in WordPress
The gamut of hooks available in WordPress gives you great control over the flow of execution of your application. This is no different when it comes to processing forms. All you need is the correct hook to ‘hook into’ and add the custom form handler. The hooks for processing custom forms are dynamic in nature, meaning that the name of the hook partly depends on you.
To process submissions related to your form only, you need finer control as shown below:
This is done by pointing the form submission to the
admin-post.php file located in the
wp-admin directory of WordPress, and including a custom name for the
action in the form. On doing so, WordPress will trigger two action hooks based on the logged in status of the user:
admin_post_$actionfor logged in users
admin_post_nopriv_$actionfor non-logged in users
$action is the name of the action that was passed through the form.
You can then use
add_action to tie the PHP form handler to the triggered hooks, where you will have full control to process the form data with the
As you may have guessed already, despite its name, admin-post.php can handle POST and GET requests as well as requests for admin and non-admin areas of the application.
Let’s explore this with the help of a custom plugin.
The Object-Oriented Plugin Structure
My goal here is to help you understand everything that goes behind processing custom forms in WordPress with and without AJAX. For this article, I’ve prepared a custom plugin that you can download from here to follow along. I recommend that you have it open in a suitable editor and install it on a local WordPress setup only.
I built the plugin using object-oriented programming practices with the help of a plugin boilerplate. Boilerplate Starting Points are among the many best practices listed in the WordPress Plugin Handbook. They’re a great way to ensure consistency across your plugins, and save you a lot of time writing standard code. Over a period, you may even end up writing your own custom boilerplate based on your coding preferences. That’s what I did.
The plugin is based on my own plugin template which is a fork of the original WordPress Plugin Boilerplate project. It’s similar to the original project in many aspects but also has support for namespaces and autoloading. This way I don’t need to have unique prefixes for every class or function, and don’t end up with a lot of
require statements. However, the minimum required PHP version for my plugin is 5.6.0.
Note: If you don’t use namespaces or use procedural code you must prefix everything.
Here’s how the plugin is structured in the backend:
inc/core/*– core functionality of the plugin
inc/admin/*– functionality related with the admin area
inc/frontend/*– functionality related with the public facing areas
inc/common/*– functionality shared between the admin and the frontend
The plugin has a top-level admin menu with two menu items for the form pages.
To see how I added the admin menu pages, take a look at the
define_admin_hooks() method in
inc/core/class-init.php and the
add_plugin_admin_menu() method in the
inc/admin/class-admin.php of the plugin.
If you’d like to know more about adding admin pages to your plugin, have a look at our article about creating WordPress admin pages here.
Adding the Form to the Admin Page of the Plugin
When I added the “HTML Form Submit” menu page for the plugin, I had to also specify the callback to load the page content. This is where the form is added.
However, instead of directly writing the HTML in the
html_form_page_content method, I used another file
inc/admin/views for the form HTML and loaded it in the callback as shown below:
This is purely a coding preference. It allows me to keep my code readable by separating the HTML, and makes no difference to the output of the form on the plugin page.
Understanding Form Security, Structure, and Submission
The form that was added above has a select field with a drop-down list of existing WordPress users and two text fields for user input. However, this simple example has a lot going on behind the scenes. The form code below is self-explanatory, so let’s walk through the important elements:
The most important thing to keep in mind when dealing with forms in the admin area of WordPress is security. Secure your form using a combination of both WordPress Nonces and
current_user_can( $capability ). In my example, I’ve restricted entry to the form with
if( current_user_can( 'edit_users' ) ), i.e. the form will be loaded only if the logged in user has the
I also generated a custom nonce by using
wp_create_nonce() and then added it as a hidden form field. You can instead use
wp_nonce_field() to add it directly. Here’s a great article to understand Nonces in detail.
required attribute to leave form validation to the browser.
The form submission is made to the
admin-post.php using the
admin_url( 'admin-post.php' ) function rather than hardcoding the URL. When WordPress receives the form, it will look for the value of the
action field to trigger the form hooks. In my case, it will generate the
admin_post_nds_form_response hook. Had it been a page open to the public view, it would have triggered the
The Form Handler for the POST request
At this stage, if you submit the form, you’ll be redirected to an empty page with the page URL set to the admin-post.php. This is because there is no form handler to process the request yet. To process the request, I registered my custom handler
the_form_response in the
define_admin_hooks() method of
class-init.php like this:
$this->loader->add_action( 'admin_post_nds_form_response', $plugin_admin, 'the_form_response');
1.6 million WordPress Superheroes read and trust our blog. Join them and get daily posts delivered to your inbox – free!
If you were using procedural code you would simply do
add_action( 'admin_post_nds_form_response', 'the_form_response');
the_form_response() is where I’ll have full access to the form data via the
$_GET superglobals. As shown below, I added a breakpoint to the callback in my IDE to be certain that the hook would work as expected.
Form Validation and Input Sanitization
Before performing any operations, you must validate the nonce and sanitize the user input properly. I made use of the
wp_verify_nonce( $nonce_name, $nonce_action ) function to verify the nonce, and
sanitize_text_field() functions to sanitize the user input available in the $_POST variable. If the nonce verification fails, the user will get an error message as the server response, using the
wp_die() WordPress function.
Note: I accessed the form data using the
$_POST variable. Had I submitted the form using the
get method, I would instead make use of the
$_REQUEST global variable.
Only when I’m sure that everything is in order, would I perform a WordPress operation like adding the user-meta to the selected user.
To know more about input sanitization, I recommend that you read through the WordPress Codex: Validating Sanitizing and Escaping User Data here.
Submitting the Server Response
After performing the server operations, it’s important to send the server response back to the user. To do this, you will first need to redirect the user back to an admin page or one that provides some feedback. I redirected the user back to the plugin page and used WordPress
admin notices to display the server feedback. The server response in my example simply outputs the $_POST variable as a WordPress admin notice.
At this stage, I have a fully functional form in the admin area of my WordPress plugin. It’s secure and submits properly to my form handler, where the input data is sanitized and finally, the server response is visible. The form will work out of the box in all browsers that have support for HTML5. But there’s a lot I can do to improve the user experience such as adding AJAX support.
This approach of establishing a basic level of user experience that’s available in all browsers, and then adding advanced functionality for browsers that support it is called Progressive Enhancement.
Note: I’ve made the assumption that my users use modern browsers with HTML5 support. However, if the form had to be rendered on an older browser, the built-in HTML5 input validation for required fields would break. Can I Use is a great website that you can use to compare web features that are available across browsers and browser versions.
Form Submissions with AJAX (admin-ajax.php) in WordPress
AJAX in WordPress is handled via the
wp-admin/admin-ajax.php file. Here’s an overview of how custom forms can be processed via AJAX in WordPress:
You’ll notice that it’s quite similar to how forms are processed using
admin-post.php. When WordPress receives an AJAX request it will create two hooks based on the supplied action:
wp_ajax_$actionfor logged in users
wp_ajax_nopriv_$actionfor non-logged in users
$action is the name of the action that was passed.
Adding AJAX Support to the Plugin Form
The second menu page of the plugin “Ajax Form Submit” loads the form that’s submitted via an AJAX request. It’s added to the menu page in the same manner as discussed earlier, and uses the
partials-ajax-form-view.php file to load the form content. If you look at this file, you’ll notice that it’s nearly identical to the earlier form with the only differences being the value of the form
To add AJAX support, I performed the following steps:
- Used jQuery submit event handler to prevent the normal form submission
jQuery.ajax()to submit the form to
admin-post.php in the form HTML.
event.preventDefault(); is what actually prevents the normal form submission.
I gathered the form data using jQuery’s
serialize() function but there are many other ways to do this. One of them is using HTML5’s FormData interface. It’s beyond the scope of this article but it’s definitely worth looking at.
var ajax_form_data = $("#nds_add_user_meta_ajax_form").serialize();
I also added additional URL parameters to the serialized data, so I can distinguish between an AJAX and a regular request in the PHP form handler later.
ajax_form_data = ajax_form_data+'&ajaxrequest=true&submit=Submit+Form';
X-Requested-With HTTP header is automatically set to
XMLHttpRequest by the AJAX library. This can also be used to identify an AJAX request but it’s not always reliable.
The ajax() method of jQuery will submit the request to the server.
To get the form to submit to
admin-ajax.php, I used an array
params.ajaxurl that was passed in from PHP using
Note: The form data in my example includes the
action that WordPress will use to generate the hooks for the AJAX request. The following hooks will be triggered by WordPress:
wp_ajax_nds_form_responsefor logged in users
wp_ajax_nopriv_nds_form_responsefor non-logged in users
enqueue_scripts() method of
class-admin.php as below:
The ajaxurl Global Variable
ajaxurl instead of passing the URL for
admin-ajax.php from PHP. However, the variable is available only when dealing with the admin end and is unavailable when dealing with AJAX on the frontend.
Depending on the response from the server, the AJAX promise callbacks
.fail() will execute accordingly. In my example, for a successful request, I’ve added the response to the empty
#nds_form_feedback that was part of my form HTML. Finally, the fields are cleared by reseting the form.
The Form Handler for the AJAX Request
I’ve attached the same form handler
the_form_response to the AJAX request as well.
And in the form handler, I used
That’s it. With AJAX, the response is displayed without the page being reloaded or redirected.
$_POST['ajaxrequest'] would not be valid, and the form would submit normally by skipping the AJAX specific
if( isset( $_POST['ajaxrequest'] ) && $_POST['ajaxrequest'] === 'true' ) code block.
You can certainly do a lot more to improve the user experience, and I recommend you read through the jQuery API documentation for AJAX here.
We’ve covered a lot of ground here. AJAX is a fairly vast topic and is implemented in several ways. Here are some more examples of using AJAX in WordPress: