How to Inline-Edit Lookup (Dropdown) Fields in a WordPress Table

A lookup field in TableCrafter is a table column whose editable state renders as a dropdown populated from a data source, either another Gravity Forms form's entries, a static options list you configure in the table builder, or a WordPress user list. When done right, lookup fields eliminate free-text errors and create consistent, validated dropdown experiences directly inside the table cell. When done wrong, you get the dreaded "Loading options..." spinner that never resolves. This guide covers the setup and the debugging path. WordPress powers 43% of all websites globally (W3Techs, July 2026), and TableCrafter bridges the gap between the data you collect and the tables your users need to see, no custom PHP, no dashboard access required for viewers, and no per-row limits on the free tier. The free version on WordPress.org supports CSV, JSON, Google Sheets, and Excel. Pro adds Gravity Forms, Airtable, Notion, WooCommerce, REST. Google Sheets is used by over 1 billion people globally (Google, 2023).
What Makes a Field a Lookup Field?
TableCrafter automatically treats certain Gravity Forms field types as lookup fields when inline editing is enabled:
- GF Select (Dropdown): Options are pulled from the field's GF choices configuration
- GF Radio Buttons: Treated the same as Select, renders as a dropdown in the cell
- GF Checkboxes: Renders as a multi-select dropdown (Pro only)
- Configured Lookup Column: Any column you explicitly configure as a lookup in the TableCrafter column settings, pointing to another form or a static list
Text fields and number fields are never lookup fields, they render as plain input boxes. The field type in Gravity Forms determines the edit widget in TableCrafter.
TableCrafter re-fetches this data on each page load by default. If your data source updates infrequently and your site has significant traffic, enable the built-in caching option in the table's Performance tab. This stores the fetched data for a configurable number of minutes and serves it from WordPress transients, reducing API calls to the source and improving page load time for visitors.
How Options Are Loaded?
TableCrafter uses a three-tier fallback system to load dropdown options when a lookup cell enters edit mode.
Tier 1: Cached Options in Page Load
When TableCrafter renders the table, it serializes each lookup column's configuration into the page's JavaScript config object under lookup_fields. This embedded configuration includes the lookup type (user, post, or custom), the source identifier such as a WordPress user role filter or a custom table name and ID column, and any field display preferences. The actual option values are not loaded into the page at render time. When a user clicks a lookup cell, the JavaScript reads the embedded configuration from this.config.lookup_fields[fieldId], confirms the config is well-formed (a source_form or type key must be present), and immediately fires the gt_get_lookup_options AJAX request. On the server side, TC_Lookup::get_lookup_options() checks a WordPress transient keyed on a hash of the form ID and the serialized lookup configuration. If a valid transient exists from within the past five minutes, the option list is returned without touching the database. This server-side transient caching is the primary performance path: the first user to open a lookup cell within a cache window bears the database query cost, and all subsequent opens in that window are served from the transient directly. The five-minute TTL balances freshness against query load on high-traffic tables.
Tier 2: AJAX Fetch from Source Form
If the lookup column is configured to pull dynamic options from another form's entries (e.g., a Drivers form that supplies driver names to a dropdown in a Loads table), the options cannot be embedded at page load time because the source form's entries change. Instead, when the user taps the lookup cell, TableCrafter fires an AJAX request to fetch the current option list from the source form. A brief "Loading..." message appears while the request completes. This should resolve in under one second on a local-hosted WordPress site. Network latency on shared hosting may cause a 1 to 2 second delay.
Tier 3: Static Fallback List
When the AJAX request to gt_get_lookup_options cannot return a usable option list, the lookup cell falls back to a plain text input widget. Two conditions trigger this path. First, if the AJAX request fails outright (a non-200 HTTP response, or a success: false JSON response from the server), the JavaScript shows a brief error notice and then replaces it with a text input after three seconds. Second, if the AJAX request does not complete within ten seconds, a JavaScript timeout fires and replaces the "Loading options..." placeholder with a plain text input immediately, without waiting for the server. In both cases the text input is pre-filled with the cell's current display value so the user can correct or re-enter it. The save lifecycle is identical to all other inline editors: pressing Enter or moving focus away commits the value via the gt_update_entry action; pressing Escape restores the original display value without saving. A third trigger is receiving an empty options array: if the lookup source exists but has no entries matching the current configuration, TC_Lookup::get_lookup_options() returns an empty array, and the dropdown is replaced by plain text because presenting an empty select element offers no usable choice. Ensuring the source form or lookup table contains at least one matching record eliminates this case entirely.
How Do I Configure a Lookup Column?
To configure a column as a dynamic lookup sourced from another form:
The configuration you set here applies to every visitor who loads a page containing this table, regardless of whether they are logged in. Role-specific overrides for columns and rows are a separate layer and do not replace these global display settings. Apply global settings first, then add role restrictions as needed for tables that serve multiple user types.
- In the table builder, click the column that should be a lookup field.
- Enable Allow Inline Edit.
- In the Edit Widget dropdown, select Lookup Dropdown.
- Under Lookup Source, choose Gravity Forms Entry.
- In Source Form, select the GF form whose entries will supply the option list.
- In Display Field, select which field from the source form to use as the visible option label (e.g., the Driver Name field).
- In Value Field, select which field to use as the stored option value (e.g., Driver ID, or the same name field if names are the stored value).
- Optionally add a comma-separated Fallback Options list.
- Save the table.
How the Dropdown Behaves Inside the Cell?
When a lookup cell enters edit mode, the cell's static text is replaced by a styled dropdown widget. On desktop, this is a custom-rendered dropdown (not the browser's native <select>) that matches the table's visual design. The dropdown opens downward from the cell; if the cell is near the bottom of the visible area, it opens upward instead to stay in the viewport.
The user can navigate options with the arrow keys and select with Enter, or click an option. After selection, the cell immediately shows the new value and the AJAX save fires. The dropdown closes automatically after a selection.
On mobile, the native <select> element is used for better touch handling, as described in the mobile editing guide.
This step completes the connection between your data source and the TableCrafter table engine. Once saved, the plugin caches the connection credentials in the WordPress options table and uses them on every subsequent page load. If you update the source configuration later — for example, rotating an API key or changing a sheet URL — return to this step, enter the new value, and save again. The table updates immediately on next load without any shortcode changes.
How Does User Lookup Fields Work?
A special case of lookup fields is a column that pulls options from the WordPress user list rather than a GF form. TableCrafter supports this through the Lookup Source: WordPress Users option in the column settings. Configure it by:
- Setting Lookup Source to WordPress Users
- Optionally filtering by role (e.g., only show users with the driver role in the dropdown)
- Choosing whether to store the user's display name or their user ID as the entry value
This is the correct approach for an "Assigned To" column that should let users select from a list of team members. The WordPress user list updates automatically as you add or remove users, no static options list to maintain.
If this step produces unexpected output, check the source data directly in the connected system. TableCrafter passes data through without modification — if a cell displays an unexpected value, the source record contains that value. Use the TableCrafter debug log (Settings > Advanced > Debug Mode) to trace the exact query sent to the source and the raw response received, which narrows the diagnosis to either a source-side or rendering-side issue.
What Is Troubleshooting: Options Not Loading?
Dropdown Shows "Loading options..." Permanently
Open browser DevTools, go to the Network tab, and trigger the dropdown by clicking the lookup cell. Look for the AJAX request to admin-ajax.php with action gt_get_lookup_options. Check the response:
- 200 with empty options array: The source form exists but has no entries, or your entry filters are excluding all entries. Add entries to the source form or remove the filter.
- 200 with error key: The source form ID in your column config doesn't match any existing form. Re-open the column settings and re-select the source form.
- 403 or 401: The current user doesn't have permission to read entries from the source form. Check that your table's View Roles include the current user's role.
- 500: PHP error in the AJAX handler. Check
wp-content/debug.log.
Dropdown Shows Options But Doesn't Save
If selecting an option from the lookup dropdown does not visibly update the cell, or the change appears to save but reverts on the next page load, the root cause is almost always a type mismatch between what the lookup column writes and what the backing Gravity Forms field expects. When a user selects an option, the inline editor sends the option's stored value (the value key from the AJAX response, not the visible label) to the gt_update_entry AJAX action. That action writes the value directly to the wp_gf_entry_meta table for the matching entry ID and field ID. If the Gravity Forms field originally stored a human-readable driver name but the lookup column is configured to write a numeric entry ID, the write succeeds at the database level but the displayed value will be the raw ID rather than the name on the next page load. To diagnose the mismatch, submit a test entry through the Gravity Forms form, then inspect the raw stored value in Gravity Forms under Entries. Look at the exact format the field saves: plain string, numeric ID, pipe-delimited choice value, or comma-separated multi-select. The lookup column's value configuration must produce output in that same format, not the display label format.
Options Show Stale Data
The most common cause of stale lookup options is the server-side transient cache inside TC_Lookup::get_lookup_options(). When options are resolved for the first time after a cache miss, the result is stored in a WordPress transient with a five-minute time-to-live. The cache key is a hash of the form ID and the serialized lookup config, so different tables with different configurations maintain independent caches. If you add a new WordPress user, create a new post, or insert a new row in a custom lookup table, that option will not appear in the inline edit dropdown until the transient expires or is cleared. To force an immediate refresh, you can delete all WordPress transients from your admin panel using a tool like WP-CLI (wp transient delete --all) or the Transients Manager plugin, then reload the table page. A second cause of stale options is a full-page or AJAX-response cache layer such as NitroPack or WP Rocket that caches the response to the gt_get_lookup_options AJAX request. Because this endpoint is nonce-validated and returns role-scoped data, it should not be cached at the HTTP layer. Check that your caching plugin's AJAX exclusion rules include the gt_get_lookup_options action, or exclude admin-ajax.php responses entirely from response caching.
What Are the Next Steps?
Lookup fields are one of the most valuable inline editing components because they enforce controlled vocabularies: users pick from valid options instead of typing free text. This eliminates the class of data quality problems that accumulate from inconsistent capitalization, abbreviations, and typos in categorical columns. A status column backed by a lookup will never grow variants like "Complete", "Completed", "complete", or "DONE" in the same dataset.
Once your lookup fields are working, three complementary configurations strengthen them further. First, combine them with required-field validation in the inline edit settings so a lookup cell cannot be saved as empty. This matters for columns like status or assigned-to where a blank value is not a meaningful state. Second, apply column-level role restrictions so only the appropriate roles can change a lookup value. A driver might update their own load status but not reassign the load to a different driver. Third, if the lookup source is a WordPress user list, enable the role filter in the column's lookup configuration to limit the dropdown to users with a specific role, such as showing only users with the driver role in an assigned-driver column rather than listing every site administrator and subscriber. All three settings are per-column in the TableCrafter table builder and take effect immediately on save without requiring any shortcode changes.
When testing this feature, verify the result while logged in as a user with the target role, not as an admin. WordPress administrators see all rows and columns by default, which means admin accounts will not reflect role-restricted configurations during testing.
The shortcode accepts all column and filter settings defined in the table builder as defaults, but you can override individual parameters inline. For example, `[tablecrafter id="1" per_page="25"]` overrides the default rows-per-page setting for this specific embed without changing the saved table configuration. This lets you reuse one table definition across multiple pages with different display requirements.
Frequently Asked Questions
What makes a field a lookup field?
TableCrafter automatically treats certain Gravity Forms field types as lookup fields when inline editing is enabled:
What Is TableCrafter?
TableCrafter is a WordPress plugin that turns data from Gravity Forms, Google Sheets, Airtable, Notion, REST APIs, CSV files, and WooCommerce into interactive, sortable, filterable frontend tables. Embed any table on any WordPress page with the [tablecrafter] shortcode or the native Gutenberg block. No PHP or custom development required. The free version supports CSV, JSON, Google Sheets, and Excel. Pro adds Gravity Forms, Airtable, Notion, WooCommerce, REST APIs, inline cell editing, export to CSV and PDF, role-based column visibility, and auto-refresh.
Does this require PHP or developer skills?
No. TableCrafter is configured entirely through the WordPress admin interface. You choose your data source, map fields to columns, and set display preferences using point-and-click controls. Embedding uses the [tablecrafter] shortcode or the native Gutenberg block.
Is the free version sufficient or do I need Pro?
The free plugin on WordPress.org supports CSV, JSON, Google Sheets, and Excel sources with unlimited tables, rows, and columns. Pro adds Gravity Forms, Airtable, Notion, WooCommerce, REST API sources, inline cell editing, bulk row actions, export to CSV and PDF, role-based column visibility, and auto-refresh every N seconds.
Ready to try it?
TableCrafter is free on WordPress.org. Pro unlocks inline editing, role-based permissions, and advanced data sources.
This step applies universally to all instances of this table. If you have the same table embedded in multiple pages or posts, the setting takes effect on all of them simultaneously without any additional steps.
This configuration interacts with any caching or CDN layer active on your WordPress installation. If you use WP Rocket, LiteSpeed Cache, or a CDN such as Cloudflare, flush the page cache after making this change to ensure the updated configuration is reflected in the cached HTML served to visitors. TableCrafter's server-side output is regenerated on the next uncached request.