How to Display Form Entries

Introduction

Do you want to display form entries on a page within your site to show your users completed form submissions publicly? This tutorial will walk you through how to create this functionality and how use it.

By default, only pro users can view form entries from within the WordPress admin based on the Access Controls assigned to their user, but you can easily create functionality that will allow you to display form entries on any front-facing page or post of your site.

Creating the shortcode

First, we’re going to give you a code snippet that will need to be added to your site so that you can easily re-use this same shortcode with the exception of just updating the form ID as you need it.

Just add the following code snippet to your site. If you’re not sure how to do this, please review this tutorial.

/**
 * Custom shortcode to display WPForms form entries in table view.
 *
 * Basic usage: [wpforms_entries_table id="FORMID"].
 * 
 * Possible shortcode attributes:
 * id (required)  Form ID of which to show entries.
 * user           User ID, or "current" to default to current logged in user.
 * fields         Comma separated list of form field IDs.
 * number         Number of entries to show, defaults to 30.
 * 
 * @link https://wpforms.com/developers/how-to-display-form-entries/
 *
 * Realtime counts could be delayed due to any caching setup on the site
 *
 * @param array $atts Shortcode attributes.
 * 
 * @return string
 */
function wpf_entries_table( $atts ) {

	// Pull ID shortcode attributes.
	$atts = shortcode_atts(
		[
			'id'     => '',
			'user'   => '',
			'fields' => '',
			'number' => '',
		],
		$atts
	);

	// Check for an ID attribute (required) and that WPForms is in fact
	// installed and activated.
	if ( empty( $atts['id'] ) || ! function_exists( 'wpforms' ) ) {
		return;
	}

	// Get the form, from the ID provided in the shortcode.
	$form = wpforms()->form->get( absint( $atts['id'] ) );

	// If the form doesn't exists, abort.
	if ( empty( $form ) ) {
		return;
	}

	// Pull and format the form data out of the form object.
	$form_data = ! empty( $form->post_content ) ? wpforms_decode( $form->post_content ) : '';

	// Check to see if we are showing all allowed fields, or only specific ones.
	$form_field_ids = isset( $atts['fields'] ) && $atts['fields'] !== '' ? explode( ',', str_replace( ' ', '', $atts['fields'] ) ) : [];

	// Setup the form fields.
	if ( empty( $form_field_ids ) ) {
		$form_fields = $form_data['fields'];
	} else {
		$form_fields = [];
		foreach ( $form_field_ids as $field_id ) {
			if ( isset( $form_data['fields'][ $field_id ] ) ) {
				$form_fields[ $field_id ] = $form_data['fields'][ $field_id ];
			}
		}
	}

	if ( empty( $form_fields ) ) {
		return;
	}

	// Here we define what the types of form fields we do NOT want to include,
	// instead they should be ignored entirely.
	$form_fields_disallow = apply_filters( 'wpforms_frontend_entries_table_disallow', [ 'divider', 'html', 'pagebreak', 'captcha' ] );

	// Loop through all form fields and remove any field types not allowed.
	foreach ( $form_fields as $field_id => $form_field ) {
		if ( in_array( $form_field['type'], $form_fields_disallow, true ) ) {
			unset( $form_fields[ $field_id ] );
		}
	}

	$entries_args = [
		'form_id' => absint( $atts['id'] ),
	];

	// Narrow entries by user if user_id shortcode attribute was used.
	if ( ! empty( $atts['user'] ) ) {
		if ( $atts['user'] === 'current' && is_user_logged_in() ) {
			$entries_args['user_id'] = get_current_user_id();
		} else {
			$entries_args['user_id'] = absint( $atts['user'] );
		}
	}

	// Number of entries to show. If empty, defaults to 30.
	if ( ! empty( $atts['number'] ) ) {
		$entries_args['number'] = absint( $atts['number'] );
	}

	// Get all entries for the form, according to arguments defined.
	// There are many options available to query entries. To see more, check out
	// the get_entries() function inside class-entry.php (https://a.cl.ly/bLuGnkGx).
	$entries = wpforms()->entry->get_entries( $entries_args );

	if ( empty( $entries ) ) {
		return '<p>No entries found.</p>';
	}

	ob_start();

	echo '<table class="wpforms-frontend-entries">';

		echo '<thead><tr>';

			// Loop through the form data so we can output form field names in
			// the table header.
			foreach ( $form_fields as $form_field ) {

				// Output the form field name/label.
				echo '<th>';
					echo esc_html( sanitize_text_field( $form_field['label'] ) );
				echo '</th>';
			}

		echo '</tr></thead>';

		echo '<tbody>';

			// Now, loop through all the form entries.
			foreach ( $entries as $entry ) {

				echo '<tr>';

				// Entry field values are in JSON, so we need to decode.
				$entry_fields = json_decode( $entry->fields, true );

				foreach ( $form_fields as $form_field ) {

					echo '<td>';

						foreach ( $entry_fields as $entry_field ) {
							if ( absint( $entry_field['id'] ) === absint( $form_field['id'] ) ) {
								echo apply_filters( 'wpforms_html_field_value', wp_strip_all_tags( $entry_field['value'] ), $entry_field, $form_data, 'entry-frontend-table' );
								break;
							}
						}

					echo '</td>';
				}

				echo '</tr>';
			}

		echo '</tbody>';

	echo '</table>';

	$output = ob_get_clean();

	return $output;
}
add_shortcode( 'wpforms_entries_table', 'wpf_entries_table' );

What this code snippet is going to do is allow you to use this shortcode anywhere on your site that accepts shortcodes such as pages, posts and widget areas.

Just by using this [wpforms_entries_table id="FORMID"], your visitors will see entries submitted for the form you specify in id=”FORMID”.

A form’s ID number can be found by going to WPForms » All Forms and looking to the number in the Shortcode column for each form.

How to locate your form ID

Using the shortcode

Next, we’re going to copy and paste our shortcode to a new page in WordPress.

First you’ll need to determine what form ID you want to display these entries on. In our example, we’re going to display entries for form ID 211 so our shortcode looks like this.

[wpforms_entries_table id="211"]

The only required attribute to use the shortcode is the id, without defining this in your shortcode, nothing will display so the form ID must be listed inside the shortcode.

However, you can filter down your entries even further by defining some of the attributes of the shortcode and we’re going to review each available attribute.

Utilizing the the user attribute

If a user is currently logged into your site and you only want them to see the form entries they’ve submitted use this shortcode.

[wpforms_entries_table id="FORMID" user="current"]

If the user isn’t logged in, all the entries for this form would display.

If the user is logged in but hasn’t completed the form (while logged in) then No results found. would display on the page.

Defining the fields attribute

To only display certain fields, you’ll first need to find out the field IDs for the fields you want to display. In order to find those field IDs, please review this tutorial.

Once you know what the field IDs are, you would use this as your shortcode. In this example we only want the Name field which is field ID 0 and the Comments field which is field ID 2.

[wpforms_entries_table id="211" fields="0,2"]

By separating the field IDs with a comma, the above example will only show two fields in the entries.

Using the number attribute

By default, the shortcode will only display the first 30 entries, however if you’d like to display a smaller amount, for example, if you only want to show the first 20 entries, add your shortcode like this.

[wpforms_entries_table id="211" number="20"]

The number=”20″ variable passed inside the shortcode will determine how many entries the table will display. If you want to show all entries, change the number to 9999.

displaying form entries in a default table view

Styling the table

Table styling is something that is completely optional as most themes have default table styling built in as do browsers as well, however, we wanted to get you started with an example, so if you want change the default table styling, copy and paste this CSS to your site using one of these implementation options.

The styling of your entry table will vary depending on the default styling your theme may or may not have for tables.

table {
    border-collapse: collapse;
}

thead tr {
    height: 60px;
}

table, th, td {
    border: 1px solid #000000;
}

td {
    white-space: normal;
    max-width: 33%;
    width: 33%;
    word-break: break-all;
    height: 60px;
    padding: 10px;
}
tr:nth-child(even) {
    background: #ccc
}

tr:nth-child(odd) {
    background: #fff
}

Add stying to your form entries table

Adding a non-table view

If you would prefer to not use an HTML table view to display your entries, we’ve got an alternative solution for you as well. Instead of creating your shortcode with the code snippet above, use this code snippet instead.

/**
 * Custom shortcode to display WPForms form entries in non-table view.
 *
 * Basic usage: [wpforms_entries_table id="FORMID"].
 * 
 * Possible shortcode attributes:
 * id (required)  Form ID of which to show entries.
 * user           User ID, or "current" to default to current logged in user.
 * fields         Comma separated list of form field IDs.
 * number         Number of entries to show, defaults to 30.
 * 
 * @link https://wpforms.com/developers/how-to-display-form-entries/
 *
 * Realtime counts could be delayed due to any caching setup on the site
 *
 * @param array $atts Shortcode attributes.
 * 
 * @return string
*/
function wpf_entries_table( $atts ) {
 
    // Pull ID shortcode attributes.
    $atts = shortcode_atts(
        [
            'id'     => '',
            'user'   => '',
            'fields' => '',
            'number' => '',
        ],
        $atts
    );
 
    // Check for an ID attribute (required) and that WPForms is in fact
    // installed and activated.
    if ( empty( $atts['id'] ) || ! function_exists( 'wpforms' ) ) {
        return;
    }
 
    // Get the form, from the ID provided in the shortcode.
    $form = wpforms()->form->get( absint( $atts['id'] ) );
 
    // If the form doesn't exists, abort.
    if ( empty( $form ) ) {
        return;
    }
 
    // Pull and format the form data out of the form object.
    $form_data = ! empty( $form->post_content ) ? wpforms_decode( $form->post_content ) : '';
 
    // Check to see if we are showing all allowed fields, or only specific ones.
    $form_field_ids = ! empty( $atts['fields'] ) ? explode( ',', str_replace( ' ', '', $atts['fields'] ) ) : [];
 
    // Setup the form fields.
    if ( empty( $form_field_ids ) ) {
        $form_fields = $form_data['fields'];
    } else {
        $form_fields = [];
        foreach ( $form_field_ids as $field_id ) {
            if ( isset( $form_data['fields'][ $field_id ] ) ) {
                $form_fields[ $field_id ] = $form_data['fields'][ $field_id ];
            }
        }
    }
 
    if ( empty( $form_fields ) ) {
        return;
    }
 
    // Here we define what the types of form fields we do NOT want to include,
    // instead they should be ignored entirely.
    $form_fields_disallow = apply_filters( 'wpforms_frontend_entries_table_disallow', [ 'divider', 'html', 'pagebreak', 'captcha' ] );
 
    // Loop through all form fields and remove any field types not allowed.
    foreach ( $form_fields as $field_id => $form_field ) {
        if ( in_array( $form_field['type'], $form_fields_disallow, true ) ) {
            unset( $form_fields[ $field_id ] );
        }
    }
 
    $entries_args = [
        'form_id' => absint( $atts['id'] ),
    ];
 
    // Narrow entries by user if user_id shortcode attribute was used.
    if ( ! empty( $atts['user'] ) ) {
        if ( $atts['user'] === 'current' && is_user_logged_in() ) {
            $entries_args['user_id'] = get_current_user_id();
        } else {
            $entries_args['user_id'] = absint( $atts['user'] );
        }
    }
 
    // Number of entries to show. If empty, defaults to 30.
    if ( ! empty( $atts['number'] ) ) {
        $entries_args['number'] = absint( $atts['number'] );
    }
 
    // Get all entries for the form, according to arguments defined.
    // There are many options available to query entries. To see more, check out
    // the get_entries() function inside class-entry.php (https://a.cl.ly/bLuGnkGx).
    $entries = wpforms()->entry->get_entries( $entries_args );
 
    if ( empty( $entries ) ) {
        return '<p>No entries found.</p>';
    }
 
    ob_start();
 
    echo '<div class="wrapper">';
 
        echo '<div class="header-row">';
 
            // Loop through the form data so we can output form field names in
            // the table header.
            foreach ( $form_fields as $form_field ) {
 
                // Output the form field name/label.
                echo '<span class="column-label">';
                    echo esc_html( sanitize_text_field( $form_field['label'] ) );
                echo '</span>';
            }
 
        echo '</div>';
 
        echo '<div class="entries">';
 
            // Now, loop through all the form entries.
            foreach ( $entries as $entry ) {
 
                echo '<div class="entry-details">';
 
                // Entry field values are in JSON, so we need to decode.
                $entry_fields = json_decode( $entry->fields, true );
 
                foreach ( $form_fields as $form_field ) {
 
                    echo '<span class="details">';
 
                        foreach ( $entry_fields as $entry_field ) {
                            if ( absint( $entry_field['id'] ) === absint( $form_field['id'] ) ) {
                                echo apply_filters( 'wpforms_html_field_value', wp_strip_all_tags( $entry_field['value'] ), $entry_field, $form_data, 'entry-frontend-table' );
                                break;
                            }
                        }
 
                    echo '</span>';
                }
 
                echo '</div>';
            }
 
        echo '</div>';
 
    echo '</div>';
 
    $output = ob_get_clean();
 
    return $output;
}
add_shortcode( 'wpforms_entries_table', 'wpf_entries_table' );

However, it’s important to note here that since we’re not using a standard table, there will be no default styling applied when using this option, so the CSS in the next step is vital but we’ll list out the CSS classes used so that you can apply your own styling to this option.

Styling the non-table

Below is a detailed list of the CSS classes used and what their purpose can be (if needed).

  • .wrapper – this is an overall wrapper around the entire block of entries and can be used if you would like to reduce the width of the block so it doesn’t stretch 100% of your page.
  • .header-row – this is another overall wrapper around the labels of each column (only the labels) if you would wish to change the background color of label section.>
  • .column-label – this wrapper will wrap around each individual label title. For example, Name, Email Address and Suggestion/Comment each has one of these wrappers surrounding them and could be used to target the size of this text.
  • .entries – this is an overall wrapper around the entries themselves if you would wish to change the background color of this section to appear differently than the labels.
  • .entry-details – this is a overall wrapper around each individual entry and could be useful if you would like to provide a different color background for each entry row.
  • .details – each column of information is wrapped in this class so that you can add any additional styling you wish such as font-size, color etc.

The CSS we used in our example can be copied and pasted to your site.

.wrapper {
    width: 100%;
    clear: both;
    display: block;
}
 
.header-row {
    float: left;
    width: 100%;
    margin-bottom: 20px;
    border-top: 1px solid #eee;
    padding: 10px 0;
    border-bottom: 1px solid #eee;
}
 
span.details, span.column-label {
    display: inline-block;
    float: left;
    width: 33%;
    margin-right: 2px;
    text-align: center;
    margin: 5px 0;
}
 
.entries {
    display: block;
    clear: both;
    width: 100%;
}
 
.header-row span.column-label {
    text-transform: uppercase;
    letter-spacing: 2px;
}

.entry-details {
    border-bottom: 1px solid #ccc;
    width: 100%;
    display: block;
    float: left;
    margin: 20px 0;
    padding-bottom: 20px;
}

The CSS provided above is based on only display 3 fields from your entry, the Name, Email and Comments field. If you would like to display more fields, please remember to update your CSS accordingly.

This is an example of the non-table form after the CSS was added

And that’s it, you’ve now created a shortcode that can be added in various places on your site so that you can display your form entries to non-admin users. Would you also like to display how many entries are left on your form when using the Form Locker addon? Try out our code snippet for How to Display Remaining Entry Limit Number.

Filter Reference: wpforms_html_field_value

FAQ

Q: How can I display just a count of unread entries?

A: You would add this snippet to your site.

<?php
/**
 * Custom shortcode to display WPForms form entries count for a form.
 *
 * Realtime counts could be delayed due to any caching setup on the site
 *
 * @link https://wpforms.com/developers/how-to-display-form-entries/
 *
 */
function wpf_dev_entries_count( $atts ) {

	// Pull ID shortcode attributes.
	$atts = shortcode_atts(
		[
			'id'     => '',
			'type' => 'all', // all, unread, read, or starred.
		],
		$atts
	);

	if ( empty( $atts['id'] ) ) {
		return;
	}

	$args = [
		'form_id' => absint( $atts['id'] ),
	];

	if ( $atts['type'] === 'unread' ) {
		$args['viewed'] = '0';
	} elseif( $atts['type'] === 'read' ) {
		$args['viewed'] = '1';
	} elseif ( $atts['type'] === 'starred' ) {
		$args['starred'] = '1';
	}

	return wpforms()->entry->get_entries( $args, true );
	
}
add_shortcode( 'wpf_entries_count', 'wpf_dev_entries_count' );

Then you are free to use this shortcode anywhere on your WordPress site where shortcodes are accepted.

[wpf_entries_count id="13" type="unread"]

You can just change the type to be unread, read or starred.

Q: Why aren’t my newer entries showing?

A: This is likely due to caching either on the server level, site level or browser level. Please check that your cache is completely clear.