WordPress
Article

Handling POST Requests the WordPress Way

By Firdaus Zahari

An interactive website needs to be able to interact with user input, which is commonly in the form of form submissions. A WordPress site is no exception to this rule. There are various interactions happening on a website on a daily basis. For example, subscribing to a newsletter, sending a message to the site owner and filling in an order form. All of these usually happen from POST requests during a form submission.

WordPress Transients API

In this article, we’re going to take a simple example of handling a POST request from a user, which is submitted via a contact form. We are going to use an internal WordPress hook to properly get the data, process it accordingly and redirect the user back to a specific page.

This article assumes you have a basic knowledge of the WordPress Plugin API. If you’re not familiar, it’s highly recommended to become review the Codex page first.

The Background

WordPress is based on an event driven architecture. This means internally, WordPress core is filled up with various actions and filters to modify the program execution or to alter the content during runtime. Examples of actions that are running during program execution are init, wp, template_redirect and wp_head. Many plugins utilise these actions and filters to modify how WordPress works.

It is no different with what we are going to achieve. All we need to know is the proper hooks needed for the POST request and to modify our code accordingly. This is possible by pointing all our form submissions to a specific file in the wp-admin directory called admin-post.php. If you have experienced integrating your application with WordPress internal AJAX API, then you will notice that the basic structure of admin-post.php is not much different to the admin-ajax.php counterpart.

Anatomy of admin-post.php

At the very basic level, admin-post.php only contains 71 lines of code. It starts by defining WP_ADMIN constant and then loading WordPress by requiring wp-load.php. After that it sends an appropriate header and triggers the admin_init action.

The current action is determined by this line of code:

$action = empty( $_REQUEST['action'] ) ? '' : $_REQUEST['action'];

Despite the name, admin-post.php can actually handle both of POST and GET requests. However, what we’re interested to go through in the context of this article will be only related to POST request. Next,

if ( ! wp_validate_auth_cookie() ) {
    if ( empty( $action ) ) {
        /**
         * Fires on a non-authenticated admin post request where no action was supplied.
         *
         * @since 2.6.0
         */
        do_action( 'admin_post_nopriv' );
    } else {
        /**
         * Fires on a non-authenticated admin post request for the given action.
         *
         * The dynamic portion of the hook name, `$action`, refers to the given
         * request action.
         *
         * @since 2.6.0
         */
        do_action( "admin_post_nopriv_{$action}" );
    }
} else {
    if ( empty( $action ) ) {
        /**
         * Fires on an authenticated admin post request where no action was supplied.
         *
         * @since 2.6.0
         */
        do_action( 'admin_post' );
    } else {
        /**
         * Fires on an authenticated admin post request for the given action.
         *
         * The dynamic portion of the hook name, `$action`, refers to the given
         * request action.
         *
         * @since 2.6.0
         */
        do_action( "admin_post_{$action}" );
    }
}

We can see that two different action hooks will be triggered based on the user logged in status which is admin_post for logged in user and admin_post_nopriv for the non logged in user. If you need a more refined control to only processing the data related to your request, a more specific action will also be triggered, namely admin_post_nopriv_{$action} and admin_post_{$action}, again based on the logged in state of the user.

Let’s say our POST request has an action with a value of foobar. These two actions will be triggered if the current user is not logged in:

  • admin_post_nopriv
  • admin_post_nopriv_foobar

If you are currently logged in, two different actions will be triggered:

  • admin_post
  • admin_post_foobar

For GET request, when you are accessing the URL like this:

http://www.example.com/wp-admin/admin-post.php?action=foobar&data=test

All the above four hooks are still available to you to use.

With this knowledge, we can easily hook into the appropriate actions to handle the contact form submission without actually messing with our theme template.

Proof of Concept

Let’s take a contact form as an example. The simplest way to do this (not recommended) is perhaps to create a custom page template based on the basic page.php, hardcode a form and do the processing in that file itself. This is roughly how it looks:

<?php
/*
 * Template Name: Contact Page
 */

get_header();

if ( ! empty( $_POST ) ) {
    // Sanitize the POST field
    // Generate email content
    // Send to appropriate email
}

?>

<div id="content">
    <form action="" method="post">
        <label for="fullname">Full Name</label>
        <input type="text" name="fullname" id="fullname" required>
        <label for="email">Email Address</label>
        <input type="email" name="email" id="email" required>
        <label for="message">Your Message</label>
        <textarea name="message" id="message"></textarea>
        <input type="submit" value="Send My Message">
    </form>
</div>

<?php
get_footer();

While this implementation will work, there is no separation or concern whatsoever which will lead to trouble in the long run. Maintaining the form is a nightmare as the processing is only happening in one place, and if we are to duplicate the form somewhere else, we need to redo the processing again.

To fully utilize the event driven nature of WordPress, and also to provide a separation of concern, we can make use of the admin-post.php provided. Converting the existing form to make it compatible with admin-post.php is fairly straightforward too.

We first change this line from:

<form action="" method="post">

to:

<form action="<?php echo esc_url( admin_url('admin-post.php') ); ?>" method="post">

To generate a proper URL pointing to the admin-post.php, we use the built-in function admin_url. This will make sure that our URL is always correct in reference to the current site that is running.

We also need to add the action hidden input so that we can trigger the more specific hook related to our contact form submission. Let’s use an example of contact_form for the action value.

We add this line somewhere in between the <form> tag.

<input type="hidden" name="action" value="contact_form">

This is how the page template looks like after the modification:

<?php
/*
 * Template Name: Contact Page
 */

get_header();
?>

<div id="content">
    <form action="<?php echo esc_url( admin_url('admin-post.php') ); ?>" method="post">
        <label for="fullname">Full Name</label>
        <input type="text" name="fullname" id="fullname" required>
        <label for="email">Email Address</label>
        <input type="email" name="email" id="email" required>
        <label for="message">Your Message</label>
        <textarea name="message" id="message"></textarea>
        <input type="hidden" name="action" value="contact_form">
        <input type="submit" value="Send My Message">
    </form>
</div>

<?php
get_footer();

Notice that we are removing the POST processing function on top of the template since we are going to hook into the action later on.

Actual Handling of the POST Request

For this part, we have two options, and going with either one is fine. We can either hook into the admin_post_* action via the functions.php of our theme, or we can create a simple plugin to handle the contact form. Let’s just code this into the functions.php for simplicity sake.

Remember that we have our custom action contact_form in the form, so by rights, we will have these four hooks available to us:

  • admin_post_nopriv
  • admin_post_nopriv_contact_form
  • admin_post
  • admin_post_contact_form

Open up functions.php of your current theme and add these lines in:

function prefix_send_email_to_admin() {
    /**
     * At this point, $_GET/$_POST variable are available
     *
     * We can do our normal processing here
     */ 

    // Sanitize the POST field
    // Generate email content
    // Send to appropriate email
}
add_action( 'admin_post_nopriv_contact_form', 'prefix_send_email_to_admin` );
add_action( 'admin_post_contact_form', 'prefix_send_email_to_admin` );

Since we want to handle the form submission the same way no matter if the user is currently logged in or not, we point to the same callback function for both admin_post_nopriv_contact_form and admin_post_contact_form. The actual callback function, prefix_send_email_to_admin will be your main point to do the data processing.

Conclusion

admin-post.php is a useful, hidden gem contained in the WordPress core. By utilizing it, we can keep the separation of concern between the actual template, and unrelated code that is not needed for display separated by hooking into the appropriate hooks for processing.

  • http://SalaryNet30.com Jennie Brown

    like Russell replied I’m stunned that a mom can profit $8748 in a few weeks on the internet. pop over to this website on `my` `prof1Ie`

    ^&^GHGJHGJ

  • Ganesh Kumar

    If we want to validate the input field from server end(inside prefix_send_email_to_admin() function) and display error back to the template page then how we will do it. please guide.

    • http://infopay50.com Elsie Ventura

      ““I “am “ finally getting 95 Dollars“` an hr,….It’s time to take some action` and you can join it too.It is easy way to get rich`.Three weeks from now you will wish you have started today….

      +++++++++++++>>> Vis!t My Pr0f1le:)“`
      q9o

  • http://www.cygnet-infotech.com/ Hemang Rindani

    Nice article. Commenting and submitting forms are one of the better ways to interact with the website. WordPress comes with some great features that allows a developer to create powerful business sites. Themes, modules and plugins imparts flexibility to WP websites that can be modified to suit any business expectation. I feel the best way to handle post requests is to use specific built-in plugins that minimizes the efforts. User Submitted post is a nice plugin that enables a visitors to submit posts and images from any page of site. It adds a front end form via template tag or short-code that ensures that there is no additional load on server. This plugin possesses many other attractive features like allows a visitor to add title, category, tags etc. to the post and these permissions can be handles easily from the plugin management dashboard on admin panel. Using such tools make it lot easier for developer to manage website activities.

  • iknowsomething.com

    Thanks!
    One more thing is you have forgotten “redirect the user back to a specific page” as promised at the beginning. Would you correct this?

    • http://henriquesilverio.github.io Henrique Silvério

      You can redirect the user easily with the `wp_redirect` function.

  • upTree

    This is an awesome post. Thanks so much for sharing! When I click submit, the data is saved to the DB, but I’m taken to a blank white screen. How would we go about redirecting to another admin screen? Thanks!

  • Karolis Ramanauskas

    Works like a charm, thank you!

  • David Guerreiro

    Make sure the ‘admin_post_nopriv_contact_form’ is properly written and try to test it with other actions as that hooks is processing the non-logged in users actions

  • David Guerreiro

    From my point of view I think that you should have mentioned something about nonces. Every wordpress developer should get used and familiar with nonces despite of their level as a programmer.

  • Kishore Sahoo

    admin_post_foobar (do_action( “admin_post_{$action}” );) is not working for user roles like Subscriber … any idea?

  • Vector

    To be honest processing forms in wordpress is mostly terrible. This method, for example has no good way to provide feedback to the user after form validation or retain previously filled out form values in the event of an error unless those values are saved in some way that does not depend on the original POST data being available to the form at the time it is rendered. However, this method does allow you to redirect the user which requires the ability to output HTTP headers before any content has been provided to the browser. This would be the case if, for example, you have implemented your form as a shortcode inside of a plugin so that it can be used on any page.

    This is still possible if the form data is processed by submitting the form directly to the page on which it is located allowing you to process any POST data inside the function that renders the shortcode, but then any redirection that must take place now has to be done in the form of dynamically output javascript in-line on the page which is pretty ugly.

    I’m finding that even the most basic of web application development is an intricate series of hacks and work arounds that are temperamental, fragile, and brittle at best and just plain dysfunctional at worst.

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Instant Website Review

Use Woorank to analyze and optimize your website to improve your website to improve your ranking!

Run a review to see how your site can improve across 70+ metrics!

Get the latest in WordPress, once a week, for free.