Post meta bridge
7 min read
Your fields live in their own database tables, but a lot of code still talks to WordPress post meta: themes, importers, sync tools, other plugins. The post meta bridge lets that code keep working. When something calls get_post_meta(), update_post_meta(), add_post_meta() or delete_post_meta() with the key of one of your fields, the plugin answers from the field group table, or writes to it, instead of wp_postmeta.
// Both lines use the field "color" of a group that is located on posts.
update_post_meta( $post_id, 'color', 'red' ); // stored in the group table, not in wp_postmeta
echo get_post_meta( $post_id, 'color', true ); // "red"The bridge is on by default for every group. It is a per-group setting, see Turning it off.
What is bridged
A call is bridged when all of these are true:
Condition | Detail |
|---|---|
The meta key is a field key | The top-level field key, exactly as in the builder (for example |
The group is active and has Bridge get_post_meta() and update_post_meta() on | Default on. |
The field has a column | Not layout fields (tab, accordion...), and never Password fields. |
The object is a post the group is located on | Every location rule counts (post type, status, template, category...): a write lands only where the edit screen would show the group (verified: a group limited to drafts did not take a write for a published post, which stayed in post meta). |
The key does not start with | Core's own keys ( |
The post is not a revision | |
The key is not owned by ACF | ACF keeps a |
Every other call, for every other key, goes straight to WordPress without any query.
Users, terms and comments are not bridged, only posts.
Reading
get_post_meta( $id, 'color', true ); // the value, typed
get_post_meta( $id, 'color', false ); // [ value ]
get_post_meta( $id ); // NOT bridged: only the real post meta- A bridged read returns the typed PHP value, not a string: a Number field gives
42or12.5, a True / False field givestrueorfalse, checkbox, repeater and group fields give arrays (the same valueswpmcf_get_field()returns without formatting). Core code that expects strings must cast them. get_post_meta( $id, $key, false )returns a list with exactly one entry.- Password fields never appear in the values of groups and repeaters that are read this way.
- When the field has no value for that post, WordPress answers as usual from real post meta. So values that were written before the plugin existed, or by code that wrote post meta directly, are still found.
- Calling
get_post_meta( $id )with no key returns only the real post meta.
Writing
The write goes through the same code as wpmcf_update_field(): the field type validates and sanitizes the value, and the box on the edit screen shows it afterwards.
Call | What happens |
|---|---|
| Sets the field. Returns |
| With a previous value, the field is replaced only when its current value equals it (loose comparison, as in core). Otherwise |
| Sets the field when it has no value yet. Returns |
| Unique add: returns |
| When the field already has a value, a field cannot hold a second one: the value is stored in real post meta like core would. See the warning below. |
| Clears the field (the value becomes empty) and removes the real post meta rows of that key. Returns |
| Clears the field only when its current value equals |
| Not bridged. It deletes real post meta rows only and leaves the field values alone. |
Things to know:
- Return values differ from core.
update_post_meta()andadd_post_meta()returntrue(not a meta ID) on success, andupdate_post_meta()also returnstruewhen nothing changed. - A value the field type cannot use is refused. The call returns
falseand the stored value is left alone. Verified:update_post_meta( $id, 'qty', 'abc' )on a Number field returnedfalseand the stored42stayed; an invalid email, an array for a Text field, and a string for a Checkbox field were refused too. (An empty value is not refused:''clears the field and returnstrue.)falseis also returned for the previous-value mismatch above and for a field that cannot be written. - Multi-value fields need arrays.
update_post_meta( $id, 'tags', [ 'a', 'b' ] )works for a Checkbox field. The strings'a','a,b'and'["a","b"]'are refused, so an importer that sends strings for these types stores nothing in the field. - Writes can fall through to real post meta: for a post the group is not located on (any rule that does not match), a group without a table, a second non-unique
add, and adeletewith a value that does not match. Those stay inwp_postmetalike in core. - A second value added with
add_post_meta()is stored but cannot be read back. After the field has a value,add_post_meta( $id, 'color', 'second' )putssecondinwp_postmeta, butget_post_meta( $id, 'color', false )still returns only the field's value. If you need several values per key, a field type that holds a list (Checkbox, Repeater, Relationship) is the right place. - The post must exist. A write for a post that does not exist goes to post meta as in core.
- Revisions: core redirects
update_post_meta()on a revision ID to the parent post, so the bridge writes the parent's field. Reads on a revision are not bridged.
ACF's own keys
ACF stores a reference _{name} = field_xxxx next to every value it saves. When that reference exists for the key, the post meta is owned by ACF: the bridge leaves the key alone for reads and writes. get_post_meta() returns ACF's own post meta even if the group table also has a value for that key, and writes go to post meta (verified). Migrated sites can therefore run ACF next to the plugin without the two stepping on each other; see Migrating from ACF.
Turning it off
Open the field group, Settings, Group settings tab: Bridge get_post_meta() and update_post_meta(). Off means: no reads from and no writes to the table for that group's keys; post meta behaves as if the field did not exist. In a definition it is settings.meta_bridge (true by default); see Group settings.
When to turn it off:
- Another plugin stores unrelated data under the same key as one of your fields.
- A field key equals a meta key that core, a theme or a plugin uses for a different purpose.
- ACF is still active and owns the same keys.
An inactive group (Active off) is not bridged either.
Use with importers (WP All Import and similar)
Importers that fill custom fields with update_post_meta() / add_post_meta() write straight into your tables when the custom field name equals a field key. That makes the bridge the easiest way to import values:
- Create the field group with the field keys you want (for example
price,sku) and place it on the post type you import. - In the importer, map the column to a custom field whose name is the field key (
price). - Run the import, then check a post: the value is in the field, not in post meta.
Checklist:
- The post must match the group's location rules (post type and any other rule); otherwise the value goes to post meta (see the table above).
- Send values in the shape the field type accepts: numbers as numbers, dates in the field's storage format, arrays for multi-value fields. A refused value is simply not stored, so check the importer's log for failed custom fields.
- An importer that writes
wp_postmetawith its own SQL does not call these functions and is not bridged. Use the importer's custom field option, or import from a CSV with the plugin's own tools (data tables are a Pro feature, see Import a CSV file). - Importers that repeat
add_post_meta()for several values of one key only keep the first one in the field. - WP All Import and other importers were not installed when this page was verified; the behaviour above was tested with the core meta functions they use.
Performance
A bridged read costs one row load for a post that has a value; for any other key the bridge returns before doing anything, so it does not slow down core or other plugins. Field keys are read once per request.
Hooks
The bridge is wired on get_post_metadata, update_post_metadata, add_post_metadata and delete_post_metadata (priority 10, module fields.meta-bridge). A filter of yours that answers earlier (returns a non-null value) wins and the bridge does nothing.
Still stuck? Contact support