Custom Table Storage for WordPress Custom Fields | Field Forge
Download Log in

Custom Table Storage — Faster Than wp_postmeta

Every other WordPress custom fields plugin — Advanced Custom Fields (ACF), Secure Custom Fields (SCF), Meta Box, Pods, CMB2, Carbon Fields — stores field values in wp_postmeta. For small sites this works fine. For sites with 1,000+ posts, complex repeaters, or frequent frontend queries, it becomes the performance bottleneck that makes WordPress slow.

Field Forge uses a dedicated indexed table for field values. Combined with batch loading and object cache integration, this delivers 3–10x faster query times on real-world sites.


The wp_postmeta problem

WordPress’s wp_postmeta table is a generic key-value store attached to every post. It’s how post_title vs post_content vs custom_field_x all get stored. The schema looks like this:

CREATE TABLE wp_postmeta (
  meta_id   BIGINT(20) PRIMARY KEY,
  post_id   BIGINT(20),
  meta_key  VARCHAR(255),
  meta_value LONGTEXT,
  INDEX (post_id),
  INDEX (meta_key(191))
);

Simple. Flexible. Works for any key-value pair. And on a typical plugin-heavy WordPress site, it’s enormous — easily millions of rows on a 5-year-old e-commerce site with lots of orders and product meta.

The N+1 query problem

Here’s what happens when you load an archive page with 20 posts, each with 10 custom fields:

-- Query 1: Fetch posts
SELECT * FROM wp_posts WHERE post_type = 'post' LIMIT 20;

-- Queries 2-201: For each post, fetch each field
SELECT meta_value FROM wp_postmeta WHERE post_id = 1 AND meta_key = 'field_a';
SELECT meta_value FROM wp_postmeta WHERE post_id = 1 AND meta_key = 'field_b';
-- ... 198 more queries

200 separate queries. Each query is fast individually, but the round-trip overhead adds up. On sites hitting the database over a network (common with managed hosting), this can add 500ms+ to every pageview. Cumulative Layout Shift goes up. Core Web Vitals drop. SEO suffers.

The repeater problem (worse)

Repeater fields in ACF are stored with one meta row per sub-field per row. A repeater with 5 sub-fields and 10 rows creates 50 meta entries per post. Add 20 posts to an archive and you’re looking at 1,000+ meta queries on a single pageview. This is why ACF-heavy sites notoriously slow down past a certain scale.


Field Forge’s custom table architecture

Field Forge stores field values in a dedicated wp_fieldforge_values table:

CREATE TABLE wp_fieldforge_values (
  id              BIGINT(20) PRIMARY KEY,
  post_id         BIGINT(20) NOT NULL,
  field_group_id  BIGINT(20) NOT NULL,
  field_name      VARCHAR(255) NOT NULL,
  parent_id       BIGINT(20) NULL,     -- for nested values (repeater/group/flex)
  row_index       INT NULL,            -- for repeater row position
  value           LONGTEXT,
  INDEX (post_id, field_name),
  INDEX (post_id, field_group_id),
  INDEX (parent_id, row_index)
);

Key differences from wp_postmeta:

  • Composite index on (post_id, field_name) — single-query lookup for any specific field on any specific post
  • Dedicated hierarchy columns (parent_id, row_index) — nested values (repeater, group, flex) are structured, not string-concatenated
  • Isolated from core meta — WordPress plugins writing to wp_postmeta don’t pollute field data
  • Purpose-built indexes — queries Field Forge actually makes are optimized; generic wp_postmeta isn’t

Benchmarks

We ran controlled benchmarks on a test site with 10,000 posts, each with a field group containing 15 fields (3 text, 2 WYSIWYG, 1 image, 1 repeater with 5 sub-fields, 1 flexible content with 3 layout types).

Archive page load (20 posts, all fields rendered)

wp_postmeta (ACF / SCF) Field Forge custom table
SQL queries 302 1 (batch_load)
Query time (local DB) 840ms 95ms
Query time (network DB, +20ms RTT) 6,880ms 115ms
First Contentful Paint 2,100ms 340ms

Single post page load (1 post, all fields rendered)

wp_postmeta Field Forge
SQL queries 16 1
Query time (local DB) 45ms 12ms
Query time (network DB, +20ms RTT) 365ms 32ms

Bulk field update (100 posts, 10 fields each)

wp_postmeta Field Forge
SQL queries 1,000 INSERTs 1 multi-row INSERT
Time (local DB) 1,240ms 48ms
Time (network DB) 21,240ms 68ms

On network-attached databases (which is how nearly all managed WordPress hosting works — Kinsta, WP Engine, Cloudways, SiteGround Cloud, etc.), the difference is dramatic. Round-trip latency is the dominant cost, and Field Forge makes fewer round trips.


Batch loading API

Developer-facing API for explicit batch loading when Field Forge’s automatic preloading isn’t enough:

// Load fields for a specific set of posts in one query
$post_ids = [1, 2, 3, 4, 5];
FieldForge::batch_load($post_ids);

// Now any get_field() call on these posts hits the cache
foreach ($post_ids as $id) {
  $hero = get_field('hero_title', $id);  // No DB query
}

The batch_load() call issues a single WHERE post_id IN (...) query and populates Field Forge’s in-memory cache for all requested posts. Subsequent get_field() calls are cache hits.

Automatic preloading

Field Forge hooks into WordPress’s the_posts filter to auto-preload fields for the main query. Archive pages, search results, and category listings get batch-loaded field data automatically — no code changes required.


Object cache integration

Field Forge respects WordPress’s wp_cache_* API:

  • With Redis or Memcached: field values are cached in the object cache with a long TTL. Subsequent page loads hit the cache, not the database.
  • With default transient cache: still uses WordPress’s in-memory cache for the request lifecycle.
  • Separate cache group: Field Forge uses its own cache group (fieldforge) so it doesn’t conflict with other plugins or core cache operations.

Cache invalidation happens automatically on field value update or delete via action hooks.


What this means in practice

For agency client sites

Faster archive pages, faster search results, faster dynamic templates. Core Web Vitals improve. Client retention improves because the site “feels snappy.”

For e-commerce on WooCommerce

Product listing pages with 30+ products and complex custom fields render in the same time as simpler sites. Shopping cart interactions are faster because product meta loads are batched.

For headless WordPress

REST API and WPGraphQL responses are faster. Static site generators (Next.js ISR, Astro, Nuxt) hit fewer database queries per build. Build times improve.

For migration from ACF / SCF

Field Forge’s ACF compatibility layer means get_field() calls in your theme return the same values — but they come from the custom table, not wp_postmeta. The performance improvement happens transparently after migration.


When wp_postmeta is fine

To be fair: if your site has under 500 posts and simple custom fields (no deep repeaters, no flexible content), the wp_postmeta approach works. You won’t notice a speed difference. Field Forge’s performance advantage becomes meaningful at scale — 1,000+ posts, complex repeaters, or sites on network-attached databases.


Ready for faster WordPress custom fields?

Get Field Forge — from $35/year →

Custom table storage is included in every version of Field Forge, including the free one.

Forge AI Assistant Online

Hi! I'm the Field Forge AI assistant. Ask me anything about the plugin — setup, features, troubleshooting, or development.

Just now
Powered by Forge AI · Browse docs