Allow Users To Submit To Your WordPress Site: A Quotes Plugin


In this tutorial, you'll learn how to create a plugin that allows users to submit form data. You'll also learn about security by using nonces.

What You'll Learn

  • How to display and process a form using a shortcode
  • Use nonces to secure user submissions

It's simpler than you think

Shortcodes are often used to display simple data, but because they are actually a way to branch out of a page or post and execute code, they can be used for quite complex tasks, such as displaying and processing forms.

We'll construct a plugin that will allow logged in users to:

  • submit quotes for moderation and publication
  • view their unpublished quotes
  • delete their unpublished quotes

Here's what we're aiming for:

All code is available in the plugin source at the top of this tutorial.

Step 1 Set-Up the Plugin

The WordPress plugin folder is located in your WordPress installation folder at wp-content/plugins. Create a folder inside the plugins folder. Let's call it submit-user-quotes. Now, create the plugin file itself. Let's call it submit_user_quotes.php. The path to your plugin file should now be: wp-content/plugins/submit-user-quotes/submit_user_quotes.php

Every Wordpress plugin needs some header information so WordPress can identify it and make it available on your dashboard plugin page.

Plugin Name: Submit User Quotes
Plugin URI:
Description: Allows registered users to submit quotes.
Version: 1.0
License: GPLv2
Author: Ross Elliot
Author URI:

You can edit this info as per your own requirements.

You'll see the plugin listed like this:

Step 2 Plugin Initialization Function

We're going to create a custom post type named Quotes to hold our quotes and a custom taxonomy named quote_category. This will enable cleaner administration of the quotes than simply assigning them to normal posts and categories.

The Init Hook and Function

We'll use the following initialization code to create our custom post type and custom taxonomy:

add_action('init', 'suq_plugin_init');

function suq_plugin_init(){

  $quote_type_labels = array(
    'name' => _x('Quotes', 'post type general name'),
    'singular_name' => _x('Quote', 'post type singular name'),
    'add_new' => _x('Add New Quote', 'quote'),
    'add_new_item' => __('Add New Quote'),
    'edit_item' => __('Edit Quote'),
    'new_item' => __('Add New Quote'),
    'all_items' => __('View Quotes'),
    'view_item' => __('View Quote'),
    'search_items' => __('Search Quotes'),
    'not_found' =>  __('No Quotes found'),
    'not_found_in_trash' => __('No Quotes found in Trash'), 
    'parent_item_colon' => '',
    'menu_name' => 'Quotes'
  $quote_type_args = array(
    'labels' => $quote_type_labels,
    'public' => true,
    'query_var' => true,
    'rewrite' => true,
    'capability_type' => 'post',
    'has_archive' => true, 
    'hierarchical' => false,
    'menu_position' => null,
    'supports' => array('title', 'editor', 'author')
  register_post_type('quotes', $quote_type_args);

  $quote_category_labels = array(
    'name' => _x( 'Quote Categories', 'taxonomy general name' ),
    'singular_name' => _x( 'Quote', 'taxonomy singular name' ),
    'search_items' =>  __( 'Search Quote Categories' ),
    'all_items' => __( 'All Quote Categories' ),
    'parent_item' => __( 'Parent Quote Category' ),
    'parent_item_colon' => __( 'Parent Quote Category:' ),
    'edit_item' => __( 'Edit Quote Category' ), 
    'update_item' => __( 'Update Quote Category' ),
    'add_new_item' => __( 'Add New Quote Category' ),
    'new_item_name' => __( 'New Quote Name' ),
    'menu_name' => __( 'Quote Categories' ),

  $quote_category_args = array(
    'hierarchical' => true,
    'labels' => $quote_category_labels,
    'show_ui' => true,
    'query_var' => true,
    'rewrite' => array( 'slug' => 'quote_category' ),
  register_taxonomy('quote_category', array('quotes'), $quote_category_args);
  $default_quote_cats = array('humor', 'politics', 'sport', 'philosophy');
  foreach($default_quote_cats as $cat){
    if(!term_exists($cat, 'quote_category')) wp_insert_term($cat, 'quote_category');

What this code does:

We will now have a Quotes menu in our admin dashboard and a way to administer quotes and their categories.

Step 3 Define A Shortcode

Next, we'll define a shortcode that will allow us to display (and process) the user quotes submission form in a post or page:

add_shortcode('suq_form', 'suq_form_shortcode');

Here we use the WordPress add_shortcode function to define a shortcode named suq_form and a function named suq_form_shortcode that will be called whenever WordPress encounters the shortcode [suq_form] in a post or a page.

Before we look at the form display and processing functions, let's talk a little about...


Because our plugin accepts data from the user, we implement the following security mechanisms:

  • only logged-in users have access to the post submission form
  • we use nonces to verify that the forms were generated by our plugin
  • quotes are submitted using wp_insert_post which sanitizes the data before saving it to the database
  • users can only view their own quotes, and nonces prevent them from deleting any other user's quotes


A nonce is a number used once. We use them to verify that the data coming back to us is actually from the forms we've created.

Here we generate a nonce field using wp_nonce_field that will be included in our form as a hidden field:

wp_nonce_field('suq_form_create_quote', 'suq_form_create_quote_submitted');

Because it's now a hidden field in our form, it'll come back to us when the form is submitted. We can then check that the nonce is valid using wp_verify_nonce:

wp_verify_nonce($_POST['suq_form_create_quote_submitted'], 'suq_form_create_quote') )

That will return true if the nonce verifies.

Step 4 The Main Function

This is the function called by our shortcode. It displays and processes the quote submission form and the quote listing/deletion form. We'll take it in bite-sized pieces and in Step 5 we'll look at the helper functions.

function suq_form_shortcode(){

    return '<p>You need to be logged in to post a quote.</p>';


  global $current_user;
  • check to see if the user is logged in
  • grab the WordPress $current_user variable which we'll need to get our user ID
  if (isset( $_POST['suq_form_create_quote_submitted'] ) && wp_verify_nonce($_POST['suq_form_create_quote_submitted'], 'suq_form_create_quote') ){
    $suq_quote_author = trim($_POST['suq_quote_author']);
    $suq_quote_text = trim($_POST['suq_quote_text']);  
    if($suq_quote_author != '' && $suq_quote_text != ''){
      $quote_data = array(
      	'post_title' => $suq_quote_author,
      	'post_content' => $suq_quote_text,
        'post_status' => 'pending',
        'post_author' => $current_user->ID,
        'post_type' => 'quotes'     
      if($quote_id = wp_insert_post($quote_data)){
        wp_set_object_terms( $quote_id, (int)$_POST['suq_quote_category'], 'quote_category');
        echo '<p>Quote created and awaiting moderation!</p>';
    }else{//author or text field is empty
      echo '<p>Quote NOT saved! Who said it? and Quote must not be empty.</p>';    
  • if the quote creation form has been submitted, there'll be a suq_form_create_quote_submitted field which was generated by our wp_nonce_field function. We can then verify the nonce and proceed to process the submitted quote
  • do some basic validation by making sure the quote author and quote text fields both have something in them, if not, display error message
  • construct an array setting the post status to pending (the admin will now have to approve it for publication), setting the post type to quotes (our custom post type), and setting the author of the quote to the currently logged-in user
  • if the quote was successfully inserted, set the category for the quote and display a success message
  if (isset( $_POST['suq_form_delete_submitted'] ) && wp_verify_nonce($_POST['suq_form_delete_submitted'], 'suq_form_delete')){

      if($quotes_deleted = suq_delete_quotes($_POST['suq_delete_id'])){        
        echo '<p>' . $quotes_deleted . ' quote(s) deleted!</p>';
  • if the quote delete form has been submitted, there'll be a suq_form_delete_submitted field which was generated by our wp_nonce_field function. We can then verify the nonce and proceed to process the array of quotes checked for deletion
  • we check that we actually have some quotes checked for deletion by testing $_POST['suq_delete_id']. If so, we hand them off to the suq_delete_quotes function (see Step 5)
  • if quotes were deleted, we display a success message
  echo suq_get_create_quote_form($suq_quote_author, $suq_quote_text, $suq_quote_category);

  if($quotes_table = suq_get_user_quotes($current_user->ID)){
    echo $quotes_table;
  • we output the quote creation form
  • finally, we output the quote listing/deletion form by passing the user ID to the suq_get_user_quotes function (see Step 5)

Step 5 Helper Functions

Here we'll look at the functions that generate the forms and the function that deletes the selected quotes.

function suq_get_create_quote_form($suq_quote_author = '', $suq_quote_text = '', $suq_quote_category = 0){

  $out .= '<form id="create_quote_form" method="post" action="">';

  $out .= wp_nonce_field('suq_form_create_quote', 'suq_form_create_quote_submitted');

  $out .= '<label for="suq_quote_author">Who said it?</label><br/>';
  $out .= '<input type="text" id="suq_quote_author" name="suq_quote_author" value="' . $suq_quote_author . '"/><br/>';
  $out .= '<label for="suq_quote_category">Category</label><br/>';  
  $out .= suq_get_quote_categories_dropdown('quote_category', $suq_quote_category) . '<br/>';  
  $out .= '<label for="suq_quote_text">Quote</label><br/>';          
  $out .= '<textarea id="suq_quote_text" name="suq_quote_text" />' . $suq_quote_text . '</textarea><br/><br/>';          
  $out .= '<input type="submit" id="suq_submit" name="suq_submit" value="Submit Quote For Publication">';

  $out .= '</form>';

  return $out;
  • the function accepts 3 optional arguments for repopulating the form fields. This is a convenience for the user.
  • a nonce field is output which we check when the form is submitted
  • we output a dropdown for the quote categories by calling suq_get_quote_categories_dropdown (see next function)
function suq_get_quote_categories_dropdown($taxonomy, $selected){

  return wp_dropdown_categories(array('taxonomy' => $taxonomy, 'name' => 'suq_quote_category', 'selected' => $selected, 'hide_empty' => 0, 'echo' => 0));

  • the function accepts 2 arguments including the element ID of the currently selected category
  • we use the WordPress wp_dropdown_categories function to create a dropdown that lists the quote categories from the quote_category taxonomy (our custom taxonomy)
function suq_get_user_quotes($user_id){

  $args = array(
    'author' => $user_id,
    'post_type' => 'quotes',
    'post_status' => 'pending'    
  $posts = new WP_Query($args);

  if(!$posts->post_count) return 0;
  $out .= '<p>Your Unpublished Quotes</p>';
  $out .= '<form method="post" action="">';
  $out .= wp_nonce_field('suq_form_delete', 'suq_form_delete_submitted');  
  $out .= '<table id="quotes">';
  $out .= '<thead><th>Said By</th><th>Quote</th><th>Category</th><th>Delete</th></thead>';
  foreach($posts->posts as $post){

    $quote_cats = get_the_terms($post->ID, 'quote_category');
    foreach($quote_cats as $cat){
      $quote_cat = $cat->name;

    $out .= wp_nonce_field('suq_post_delete_' . $post->ID, 'suq_post_delete_id_' . $post->ID, false); 
    $out .= '<tr>';
    $out .= '<td>' . $post->post_title . '</td>';
    $out .= '<td>' . $post->post_content . '</td>';
    $out .= '<td>' . $quote_cat . '</td>';    
    $out .= '<td><input type="checkbox" name="suq_delete_id[]" value="' . $post->ID . '" /></td>';          
    $out .= '</tr>';

  $out .= '</table>';
  $out .= '<input type="submit" name="suq_delete" value="Delete Selected Quotes!">';

  $out .= '</form>';  
  return $out;

  • accept the user ID because we need to get a listing of quotes for the current user only
  • create $args to specify our user, the post type of quotes and quotes that are pending (not yet published by the admin)
  • execute a custom query using WP_Query
  • return false if our query returns no quotes
  • start a form and generate a nonce for the form
  • loop through the quotes making sure we also grab the category of the quote
  • generate a nonce for the quote delete checkbox, assigning a unique name for the nonce by concatenating the post ID
  • output a table row containing the quote info as well as a delete checkbox

Why add a nonce for each quote?

Forms can be manipulated in the browser to post back unexpected data. In our case, each delete checkbox is assigned the value of a post. But what if a malicious user altered that value and caused our delete function to remove a post that was not actually listed?

One way to avoid this, is to use nonces for each row of post data, ensuring that the nonces are uniquely named with the post value to be deleted. We then verify the nonce upon form submission to make sure it's a genuine return value.

function suq_delete_quotes($quotes_to_delete){

  $quotes_deleted = 0;

  foreach($quotes_to_delete as $quote){

    if (isset($_POST['suq_post_delete_id_' . $quote]) && wp_verify_nonce($_POST['suq_post_delete_id_' . $quote], 'suq_post_delete_' . $quote)){  

      $quotes_deleted ++;


  return $quotes_deleted;
  • the function accepts an array of quote IDs to delete
  • each quote ID is checked to see if a nonce was generated for it
  • if the nonce verifies, we trash the quote using the Wordpress function wp_trash_post

Step 6 Some Styling

Just drop this style info into the style.css file in your theme folder:

#quotes th{

Step 7 Try It Out

Activate the plugin, pop the shortcode onto a page, log into your site, and test it out.

The full plugin code source and a demo site link is listed at the top of this tutorial.

The source folder also contains a Wordpress page template with a custom loop that displays published quotes for all users.

Final Thoughts

  • the quotes plugin could be improved by offering an edit option. As it is, users can only delete their quotes
  • you could also include an image upload option to brighten things up
  • perhaps add some custom fields to the quotes post type for quote meta info

Useful Links

Related Posts
  • Code
    Creative Coding
    Advanced Use of Attachments in WordPress: Assigning Categories and Taxonomy Terms to AttachmentsAdvanced use of attachments in wordpress 400
    This tutorial is the first in a four part series in which you'll learn some techniques for working with images in attachments in WordPress which give you advanced options. Read More…
  • Code
    Using HighCharts in WP-AdminHighcharts 400
    Charts are a great way to present data. They make data more digestible by making it visually appealing. In WordPress, there is no built-in method for getting posts and pages data in a graphical form. Although, there are certain plugins available which integrate Google Analytics with WordPress, but they are overkill if you want to get only a portion of that data. Also, nothing should keep you from learning new techniques and to dive straight into the subject is the best way to learn.Read More…
  • Code
    Create a Shortcode to List Posts With Multiple ParametersPost listing shortcode main image400
    On many of the client sites I build, I find there are times when I need to include a post listing on a page. I'm not talking about archive pages here, but adding a custom listing to an existing static page. For example, I might want to list some posts on the 'About' page, or the site may require an in-depth page for a topic, with a list of posts and custom post types related to that topic. One way to do this is by creating a custom page template to include the current page content plus the results of a second custom query, but if you want more flexibility over how you list posts, or just want to do it a few times, a shortcode will be a simpler solution. In this tutorial, I'll show you how to create a simple shortcode to list all posts of a custom post type, and then I'll expand on that to create a shortcode with various parameters that users can specify to list posts however they want.Read More…
  • Code
    Integrating Multiple Choice Quizzes in WordPress – Creating the FrontendIntegrating multiple choice quizzes in wordpress
    This is the second part of the series on developing a multiple choice quiz plugin for WordPress. In the first part we created the backend of our plugin to capture the necessary data to store in the database. In this final part, we will be creating the frontend of the plugin where the users can take quizzes and evaluate their knowledge.Read More…
  • Code
    Integrating Multiple Choice Quizzes in WordPress - Creating the BackendIntegrating multiple choice quizzes in wordpress
    Multiple choice questions are something that most of us have faced at least once in our life. We love them because we can provide correct answers by logically thinking about provided possibilities, even if we don't exactly know the correct answer. Also answering takes less time which makes it so popular. Creating a multiple choice quiz in WordPress can be a very exciting and profitable task. You can use it in your personal blog to attract more visitors, or you can create a premium section with advanced quizzes, or you can create quizzes focusing on popular certification exams. There are numerous possibilities for making it profitable.Read More…
  • Code
    Creating Responsive Pricing Table Plugin for WordPressPricing table plugin
    Pricing tables are a key component of your business that promotes your products and helps users choose between different services you have. Most modern commercial WordPress themes provide built in Pricing Tables. There are also plenty of free and commercial pricing table plugins for WordPress. This tutorial is intended to provide knowledge to WordPress developers on creating a plugin from scratch which enables customization in different projects. Every web design is trying to accomplish responsive features which enable better look and feel on any kind of device. The pricing tables created with this plugin will work on all kinds of devices such as mobiles and tablets as well. So let's get started.Read More…