Skip to content
Gravity Tables
All documentation

Reference

Security best practices

A configuration checklist for security-conscious deployments. Permission gates, audit logging, capability hardening, CSP, the things to verify before you go live.

This doc is the operator’s checklist. The security page covers the trust posture; this doc covers what to actually configure. Walk through it before going live with sensitive data, customer records, financial pipelines, healthcare intake, school records.

The checklist is grouped into seven categories. Each item is concrete and verifiable.

1. View access (allowed_roles)#

The first line of defence. Without this, anyone who hits the page sees the table.

  • Every shortcode has allowed_roles unless the table is intentionally public (e.g. a published catalog)
  • Roles are narrow: allowed_roles="administrator" is broader than necessary in most cases. Prefer specific roles like support-agent or event-organiser
  • Logged-out visitors get a sensible fallback: configured via fallback="login_form" (default) or fallback="404" or fallback="hidden". Never silently render a table for an un-authorized visitor.
[gravity_table id="42" allowed_roles="finance,management" fallback="404"]

The role check happens server-side, before any data is fetched. Someone with browser dev tools cannot bypass it.

2. Editable columns (allow_edit)#

Most cells in most tables should be read-only. allow_edit is a positive allow-list, naming a column there opts it in.

  • Empty allow_edit="" is the default for any view that’s display-only (catalog, public directory, executive dashboard)
  • allow_edit lists only columns that genuinely change daily. A created timestamp shouldn’t be editable. A customer_id shouldn’t be editable. Status, notes, assigned-to, those are the editable surface.
  • Hidden form fields stay hidden: workflow primitives like status, priority, assigned_agent should be Hidden field types in Gravity Forms, never visible on the public submission form. The table can still edit them server-side.

3. Per-column edit gates (edit_permissions)#

The third permission layer: even within editable columns, different columns can require different roles.

  • High-impact columns gated to higher roles: value, priority, discount_percent, anything that affects money or workflow trajectory. Pattern: edit_permissions="value:manager,priority:lead"
  • Capability-based, not role-based internally, any custom-role plugin that adds capabilities (Members, User Role Editor, PublishPress Capabilities) works without rewriting the shortcode
  • Document your edit gates in plain English in your team’s wiki. The shortcode is the source of truth, but a parallel English description prevents accidental relaxation during a future edit.
[gravity_table id="leads"
  allow_edit="status,owner,follow_up,notes"
  edit_permissions="status:manager,owner:manager"]

In this configuration, regular reps edit follow_up and notes on their own; only managers reassign or change status.

4. Per-user filtering (filter_user_owns)#

For multi-tenant workspaces (customer portals, per-rep CRM views, per-counsellor school records), the table should auto-scope to rows the logged-in user owns.

  • filter_user_owns="<column>" is set on every customer-facing or per-staffer view
  • The owning column carries a real user identifier, typically a user_id (numeric) or username (text) field. Set on form submission via a hook, never trusted from form input.
  • Higher-role users override the filter, administrator, manager, etc. see everything. Configurable via filter_user_owns_override="administrator,manager" (defaults to administrator).

5. Audit log (audit_log)#

The audit log is forensic insurance. When something goes wrong, the audit log tells you what happened, when, and who.

  • audit_log="true" is set on every staff-side workspace where edits happen
  • Audit retention is reviewed annually, default is forever; tune via audit_log_retention_days="365" if you have a retention policy or storage constraints
  • Audit access is restricted, only manage_options capability can read the log. Tighten further via the gt_audit_access_capability filter if your compliance regime requires it.
  • Export-of-audit is part of your compliance review checklist, the audit data is queryable as CSV via the same export pipeline as entries

6. Bulk-action capability gates#

Bulk actions are powerful, they affect multiple rows at once. Their capability gates need extra attention.

  • Custom bulk actions check both the WP base capability AND any GF-specific capability (e.g. gravityforms_delete_entries for delete actions). The default edit_others_posts is often too permissive.
  • Destructive bulk actions (delete, mark-cancelled, refund) require manage_options or a custom high-trust capability. Pattern: bulk_permissions="delete:manage_options,refund:manage_options"
  • Custom bulk-action callbacks are reviewed for SQL injection / capability leakage. The plugin’s own actions are audited; yours are your responsibility.
add_filter('gt_register_bulk_actions', function ($actions) {
    $actions['custom_action'] = [
        'capability' => 'manage_options', // narrow
        'callback' => function ($entry_ids) {
            foreach ($entry_ids as $id) {
                // Validate every entry id before acting on it
                $entry = GFAPI::get_entry($id);
                if (is_wp_error($entry)) continue;
                // ... safe operation here
            }
        },
    ];
    return $actions;
});

7. AJAX + REST endpoint hardening#

The plugin’s own endpoints are CSRF-protected by nonces. Custom integrations need the same protection.

  • Custom AJAX endpoints check wp_verify_nonce() on every request. The plugin emits gt_rest_nonce on every page that renders a table; consume it from your custom JS.
  • Custom REST endpoints declared via register_rest_route() enforce permission_callback with a real capability check, not '__return_true'
  • Application Passwords, encourage admin users to use them for backend automations instead of cookie-auth from headless scripts. Audit access via the WP application-password log.

8. CSP (Content Security Policy)#

For sites with strict CSP headers (school districts, government, healthcare):

  • Plugin is on v4.1.51 or later, earlier versions emitted bare <script> blocks that violate CSP-strict. v4.1.51+ uses wp_add_inline_script() which nonce-tags inline scripts compatible with default-src 'self' policies.
  • Test with your CSP headers in place, different hosts apply different defaults; a working CSP-strict deployment requires browser DevTools console review
  • unsafe-eval is not required, the plugin uses no eval() or new Function() anywhere

9. Output sanitization#

Cells render values that may include HTML. The plugin sanitizes; custom integrations need to too.

  • Plugin is on v4.1.58 or later, earlier versions had two cell-render bugs around anchor escaping (double-escape on render, all-HTML strip on save). v4.1.58 introduced wp_kses allow-listing for <a>, <br>, <strong>, <em>.
  • Custom render filters via gt_render_cell echo through esc_html() or wp_kses(), never raw output. Pattern:
add_filter('gt_render_cell', function ($html, $value, $field, $entry) {
    if ($field['key'] === 'company_name') {
        return '<strong>' . esc_html($value) . '</strong>';
    }
    return $html;
}, 10, 4);
  • CSV export injection defence: cells starting with =, +, -, or @ get a ' prefix on CSV export so Excel renders them as text instead of evaluating as formulas. This is on by default; verify with a malicious test row.

10. Data residency (mostly inherited from your hosting)#

Gravity Tables doesn’t store entry data outside wp_gf_entry and wp_gf_entry_meta. Whatever country your WP host is in, that’s where your data is.

  • No third-party processors for entry data. License activation goes through Freemius (the only subprocessor) with only the licence key transmitted, never customer entry data.
  • Backups stay in your hosting layer, verify your hosting backup includes wp_gt_* tables (audit log, export log, table configs). Most managed hosts include the full DB; some specialty hosts whitelist specific tables.
  • EU-hosted sites stay in the EU, confirmed by the absence of any external entry-data transmission. The security page covers this in more detail.

11. Ongoing review#

  • Re-run this checklist on every major upgrade, new versions sometimes ship new shortcode parameters; verify your existing configurations are still complete.
  • Subscribe to the release notes RSS so you see security-relevant changes in version posts (badge: security highlights them)
  • Email security@fahdmurtaza.com for any concerns, same disclosure pipeline as the public security page documents
  • Security page, the trust posture (governance, disclosure, scope)
  • Permissions doc, the 3-layer permission model in depth
  • Hooks, gt_render_cell, gt_register_bulk_actions, audit-related filters
  • REST API, the API surface this checklist’s endpoint section refers to
  • Performance and scaling, the cache layer that holds the access checks