Skip to content
GPLWP Guides

How to save WordPress custom fields in a custom table

Developer Tools Time About 30–60 minutes for code, or about 15 minutes with a plugin 6 steps Updated 4 Sep 2026

DEVELOPER TOOLS
The short answer

WordPress custom fields normally use the post meta tables, so saving them in a custom table requires your own table, save routine and retrieval code, or a plugin that handles the mapping. For a small custom feature, code is fine; for field groups, MB Custom Table is the faster route.

The route
  1. 01Choose the table shape
  2. 02Create the custom table
  3. 03Add fields to the edit screen
  4. 04Save one row per object
  5. 05Read from the custom table
  6. 06Take the fast plugin route

What you need

  • WordPress administrator access
  • A backup or staging copy of the site
  • A custom post type or defined object to store fields against
  • Basic PHP and MySQL knowledge for the manual route

Choose the table shape

Decide whether one row should represent each post, user, term or another record. For post fields, include an object ID column and one column for each field you want to store. Use the WordPress table prefix rather than assuming it is wp_, because the prefix can be changed per site.

Choose column types deliberately. Text fields can use TEXT or VARCHAR, numeric values should use a numeric type, and fields that you will search or sort should have suitable indexes. Do not put every value into one serialized column unless the data cannot be queried separately.

Create the custom table

For a manual implementation, create the table from a small plugin, not from a theme. Run the creation routine on plugin activation with register_activation_hook(), load wp-admin/includes/upgrade.php, and pass a correctly formatted CREATE TABLE statement to dbDelta(). Use $wpdb->get_charset_collate() so the table follows the site's character set and collation.

Where this breaks: dbDelta() is particular about SQL formatting. Put each column on its own line, use KEY for indexes, use lowercase field types, specify lengths where required, and write PRIMARY KEY in the format WordPress expects. Keep a schema version and run the upgrade routine when the plugin version changes, because activation hooks do not run automatically on plugin updates.

Add fields to the edit screen

Add a meta box or another admin form for the fields. Include a nonce, check the current user's capability, ignore autosaves and revisions, and validate each submitted value according to its type. Use the appropriate sanitiser for the data rather than treating every field as plain text.

For example, a short label can use sanitize_text_field(), while a number should be validated as a number and a URL should use URL validation. WordPress describes sanitize_text_field() as removing tags, invalid UTF-8 and unwanted whitespace, but it is not suitable for every kind of input.

Save one row per object

Hook the save routine to save_post, or to the post-type-specific version when appropriate. The hook runs after a post is saved and also fires for imports and other save paths, so your callback must check the post type, permissions, nonce, autosave status and revision status before writing data.

Use $wpdb->insert() when the object has no row yet and $wpdb->update() when it does. Use the object ID as the primary key or unique lookup value, pass raw values with explicit formats such as %d and %s, and check the return value for database errors. Do not concatenate submitted values into SQL.

Read from the custom table

Replace calls to get_post_meta() with a small repository or helper function that reads the row by object ID. Use $wpdb->get_row() or $wpdb->get_results() with prepared queries, and escape values for their final output context when rendering them.

Also decide what happens when a post is deleted. Add a cleanup routine for deleted objects, or document why rows are retained. Test new posts, edits, revisions, autosaves, bulk edits, imports, empty values and deleted posts before using the table on production data.

Take the fast plugin route

Use MB Custom Table when your fields are managed by Meta Box and you do not need to write the storage layer yourself. Configure the field group to use custom-table storage, enter the table name, and either let the extension create the table or connect it to an existing table. The table needs an ID column, and its other column names must match the field IDs.

Save the post normally after configuring the field group. The values are then stored in one row for that object, with each field in its matching column. Use the plugin's helper with the custom-table arguments when reading values. This route is usually preferable when you have several fields, need a visual field builder, or want cleaner storage without maintaining insert, update and migration code yourself.

For existing post-meta data, do not assume changing the setting moves old values automatically. Back up the database and migrate the old values with a script or documented migration process, then verify that field IDs, column names and stored formats match.

The fast route

Let MB Custom Table do it

Stores Meta Box fields in one custom-table row, unlike default meta tables; choose it for cleaner, more scalable data storage.

Get MB Custom Table

Sources

  1. developer.wordpress.org /plugins/creating-tables-with-plugins/?utm_source=openai
  2. developer.wordpress.org /reference/functions/sanitize_text_field/?utm_source=openai
  3. developer.wordpress.org /reference/hooks/save_post/?utm_source=openai
  4. docs.metabox.io /extensions/mb-custom-table/?utm_source=openai
  5. support.metabox.io /topic/moving-to-mb-custom-tables/?utm_source=openai
  6. docs.metabox.io /database/?utm_source=openai

Questions

Does WordPress save custom fields in a custom table by default?
No. WordPress normally stores each custom field as metadata in a meta table, with the field key and value held in separate rows. A custom table needs its own schema and save logic, or a field-management extension that provides both. For a few fields on a small site, the default meta tables may be simpler and perfectly adequate. <cite></cite>
Can I use save_post to write custom fields to my table?
Yes. The save_post action runs after a post is saved, so it can collect validated field values and insert or update the matching custom-table row. Check permissions, nonces, autosaves and revisions first, and avoid triggering another post update inside the callback unless you prevent a loop. Use the post-type-specific hook when it suits the workflow. <cite></cite>
Will existing post meta move automatically to the custom table?
Usually not. Changing a field group to use custom-table storage does not by itself guarantee that older values in the standard meta tables will be copied. Back up the database, map each old field key to its new column, migrate the values with a script or suitable tool, and test the stored formats before removing the old metadata. <cite></cite>
Should every custom field have its own table column?
For a row-per-object custom-table design, each field normally maps to a column whose name matches the field ID. A group field is commonly stored as one serialized value in its top-level column rather than separate sub-field columns, which makes its contents harder to query. Create indexes only for values you regularly search, filter or sort. <cite></cite>
When is the default post meta table a better choice?
The default post meta table is often better when the site has only a small number of fields, relies heavily on WordPress-compatible metadata APIs, or needs broad compatibility with themes and page builders. A custom table adds schema, migration, querying and cleanup responsibilities. Choose it when the data volume or query pattern justifies that extra maintenance. <cite></cite>