Skip to content
Gravity Tables
tutorial

How to build a customer portal with Gravity Forms and Gravity Tables

A complete walkthrough for building a self-serve customer portal on WordPress. Per-user filtering, inline editing, role-aware exports, audit trail, all from one shortcode.

· 7 min read · By Fahd Murtaza

A customer portal used to be a six-week WordPress project: custom user dashboard plugin, custom REST endpoints, custom shortcode for the form, custom JavaScript for editing, and a sprawling permissions layer. This guide covers the same job in about an hour.

The result: customers log in, see their own submissions, edit specific fields, export their data, and never email support to ask “where’s my account info”. One shortcode does all of it.

What you will build#

A page at /my-account that, for any logged-in customer, shows:

  • A table of all of their orders (or any other Gravity Form submissions)
  • The status of each row at a glance (active, trial, cancelled)
  • Inline editing for fields they can change (shipping address, phone, preferences)
  • An export button so they can download their own data as CSV or PDF
  • Zero data leak: customers see only their own rows, server-enforced

For staff, the same Gravity Form data powers a /staff view with full bulk operations, status edits, and audit trail. Two pages, one form, one plugin.

Prerequisites#

  • WordPress 5.0+
  • PHP 8.0+
  • Gravity Forms (any tier)
  • Gravity Tables (free tier works for under 500 entries; Pro for production)
  • A Gravity Form that customers already submit to (orders, applications, intake, anything)

If your customers don’t have WordPress accounts yet, install any membership plugin (WooCommerce, MemberPress, Paid Memberships Pro). Gravity Tables works with all of them, the only thing it cares about is whether the user is logged in and what role they have.

Step 1: identify the form#

Pick the form your customers already submit. Common shapes:

  • A WooCommerce checkout form (orders → entries)
  • A contact request form (each request is an entry)
  • A subscription sign-up form (each signup is an entry, one per customer)
  • An application form (each application is an entry)

Note the form ID, you’ll need it for the shortcode.

Step 2: build the customer-facing table#

Create a new WordPress page called “My Account”. Add this shortcode:

[gravity_table id="42"
  filter_by_user="true"
  allowed_roles="customer,subscriber"
  allow_edit="shipping_address,phone,preferences"
  export="csv,pdf"
  audit_log="true"]

Replace id="42" with your form’s ID. Save the page. That’s the customer side, done.

What this configuration does:

  1. filter_by_user="true" , each customer sees only entries where created_by matches their user ID. Enforced at the database query layer.
  2. allowed_roles="customer,subscriber" , only logged-in users with one of these roles see the table at all. Guests get a polite log-in prompt. Other roles (admin, editor) get an access-denied message unless explicitly added.
  3. allow_edit="shipping_address,phone,preferences" , customers can click any of these three columns to edit. Other columns render read-only.
  4. export="csv,pdf" , a download toolbar with two formats. Both respect the per-user filter, so each customer downloads only their own data.
  5. audit_log="true" , every edit logs who, what, when, with old and new values. Useful for compliance and dispute resolution.

Step 3: the staff view#

Create another page called “Staff Dashboard” (or wherever you want it to live, internal pages, admin sub-page, etc):

[gravity_table id="42"
  allowed_roles="staff,manager,administrator"
  allow_edit="status,priority,notes,shipping_address,phone,preferences"
  edit_permissions="status:manager,priority:manager"
  bulk="approve,export,delete"
  bulk_permissions="delete:manage_options"
  export="csv,excel,pdf"
  audit_log="true"]

What changes for staff:

  • They see all entries (no filter_by_user)
  • They can edit more fields: status and priority, plus everything customers can edit
  • edit_permissions gates status and priority to managers (capability, not just role). Other staff see those columns read-only.
  • They get bulk operations with delete restricted to admins
  • They get all three export formats including Excel
  • The audit log captures every staff edit with full attribution

Step 4: the audit log#

The audit log lives in a wp_gt_audit table created on first audit-enabled save. View it from:

Tables → System → Audit log

Filter by user, table, date range, or action type. Export to CSV for compliance reviews. Set retention via the gt_audit_retention_days filter (defaults to forever, set to a number for daily cleanup).

For PHI / HIPAA-adjacent use cases, also enable export auditing:

... export="csv,pdf" audit_exports="true" ...

That logs every export with format, row count, filter state, and a SHA-256 hash of the generated file. Useful when “who exported this” matters more than “who has access”.

Step 5: handling logged-out users#

Customers landing on /my-account while logged out should see a login prompt, not an empty table. The default behaviour:

  • Empty table with “Log in to see your submissions” message

To customize:

[gravity_table id="42"
  filter_by_user="true"
  allowed_roles="customer,subscriber"
  guest_message="Sign in to see your orders. <a href='/login'>Log in here</a>."
  ...]

Or to hide the table entirely from guests (useful when the table’s existence is sensitive):

[gravity_table id="42"
  filter_by_user="true"
  allowed_roles="customer,subscriber"
  require_login="true"
  ...]

With require_login="true", guests see nothing on the page where the shortcode renders, no error, no message, just absence.

Step 6: customer-service “view as customer”#

For staff to help customers, you can add allow_user_switch to the staff dashboard:

[gravity_table id="42"
  allowed_roles="staff,manager,administrator"
  allow_user_switch="manage_options"
  ...]

A small “Viewing as: customer@example.com” dropdown appears in the toolbar. Admins can switch context to any customer, see exactly what that customer sees, and trigger edits as if they were that customer. The audit log captures both the staff actor and the customer they were acting as.

This is the production pattern most support teams settle on, eliminates the “can you screenshot your dashboard?” support reply.

Step 7: making it look like a portal#

The shortcode renders a table. To make /my-account feel like a destination, wrap it in your theme’s standard page template with extra context above the table:

<!-- Above the shortcode -->
<header class="page-header">
  <h1>Welcome back, [user_first_name]</h1>
  <p class="subtitle">Your orders, settings, and downloads, all in one place.</p>
</header>

<!-- The shortcode -->
[gravity_table id="42" filter_by_user="true" ...]

<!-- Below the shortcode -->
<aside class="help-block">
  Need help? <a href="mailto:support@yoursite.com">Email support</a>.
</aside>

If you’re using Elementor, Bricks, or a page builder, the same pattern applies, drop the shortcode into a Shortcode element with header/footer rows above and below.

What this replaces#

Without this pattern, the typical “customer portal” project looks like:

  1. Build a custom /my-account page template with manual GFAPI::get_entries() calls
  2. Add a custom REST endpoint for inline editing
  3. Add manual nonce verification, capability checks, and audit logging
  4. Build a custom JavaScript front-end that hits the REST endpoint
  5. Style everything from scratch
  6. Add CSV export logic in PHP
  7. Maintain all of the above through theme updates and plugin updates

A six-week project, easily. The shortcode pattern collapses this to a single line of WordPress page content with the same security guarantees, the same audit trail, and a UI that’s been tested across hundreds of production deployments.

Recipes for variations#

Subscription dashboard (with cancellation)#

[gravity_table id="subscriptions" filter_by_user="true"
  allow_edit="payment_method,billing_address"
  custom_actions="cancel:confirm" export="pdf"]

Adds a “Cancel” button per row that triggers a confirmation dialog and then a webhook to your billing system.

Healthcare patient intake#

[gravity_table id="intake" filter_by_user="true"
  allowed_roles="patient,clinician"
  allow_edit="phone,emergency_contact,preferred_pharmacy"
  audit_log="true" audit_exports="true"
  export="pdf" export_ttl="3600"]

Tighter export TTL (1 hour vs default 24h) for PHI-sensitive data. Audit on both edits and exports.

Real-estate agent listings#

[gravity_table id="listings" filter_by_user="true"
  allow_edit="status,price,description,photos"
  mobile_layout="cards" export="csv,pdf"]

Mobile cards because agents update from the field. Editable photos column triggers an inline media uploader.

Testing your portal#

Before shipping:

  1. Log in as a customer. Visit /my-account. Verify you see only your own rows.
  2. Try to edit a non-editable column (status, priority). It should not show as editable.
  3. Open dev tools, modify a data-entry-id attribute to point at another user’s entry, attempt an inline edit. The AJAX handler must return 403.
  4. Export your data. Verify the CSV contains only your rows.
  5. Log out. Revisit /my-account. Verify you see the login prompt, not the table.
  6. Log in as a staff member. Verify staff dashboard shows all rows.
  7. Check the audit log. Verify every edit you made is logged with timestamp, user, old value, new value.

If all seven pass, you have a production-grade customer portal. Ship it.