Skip to content
🛠 How to add one custom field to many posts in WordPress

🛠 How to add one custom field to many posts in WordPress

A site on WordPress has grown to hundreds of posts, and each one needs a new custom field added. Opening every post by hand and clicking "Update" is a half-day job, at least.

Especially if you are refining a theme or connecting an SEO plugin that needs the same meta_key across the entire feed of posts. The WordPress admin panel does not offer an "apply to all" button, but it does give you two ways to solve this with code.

Below, both methods with ready-to-use snippets: fast (one SQL query for the whole batch) and simple (a loop over IDs using the native add_post_meta function). Choose based on the task.

Two monitors display various blocks of source code and development files on a developer's workspace

💡 Quick overview:

  • You can bulk-assign the same custom field to all posts using add_post_meta in a loop, 4 lines of code.
  • If there are hundreds of posts and speed matters, one direct INSERT into the wp_postmeta table via a custom function.
  • Both methods work through the theme's functions.php or the Code Snippets plugin. Back up the database before running any SQL.

Why add one field to many posts at once

Custom fields (also known as post meta) store additional information about a record: view count, tags for internal filtering, a "favorite" flag, a related product ID. Anything that does not fit into the standard post_title and post_content fields.

Usually a field is added on a case-by-case basis: open a post, find the "Custom Fields" block in the editor, enter a key-value pair. But when the field must appear on all 200 posts at once, the manual method is out.

Typical scenarios where bulk action is needed:

  • You activated a child theme that uses get_post_meta($post_id, 'featured_post', true) to mark popular posts. Without bulk population, old posts will not appear in the output.
  • An SEO audit required setting noindex for a specific content type via a meta field, rather than through robots.txt.
  • Migration from another platform: data from the old CMS landed in a CSV, and it needs to be loaded into wp_postmeta for thousands of records.

WordPress has no native add_multiple_posts_meta function. But there are two proven paths, choose based on the situation.

Method 1. One SQL query for all posts

The fastest method, a direct INSERT into the wp_postmeta table in a single transaction. Instead of 200 individual database queries, you make one, and it executes in milliseconds.

Below, the add_multiple_posts_meta function, which accepts an array of post IDs and mirrors the signature of the native add_post_meta: same parameters, same order.

Add the code to the child theme's functions.php or via the Code Snippets plugin. The function itself checks whether such a field already exists for the post (if $unique = true is passed) and filters out invalid IDs.

1if( !function_exists( 'add_multiple_posts_meta' ) ){
2 function add_multiple_posts_meta( $post_ids, $meta_key, $meta_value, $unique = false ){
3
4 // If key or ID is not set — exit
5 if( !$meta_key || empty( $post_ids ) ){
6 return false;
7 }
8
9 // One post — pass to native add_post_meta
10 if( is_numeric( $post_ids ) || ( is_array( $post_ids ) && count( $post_ids ) === 1 ) ){
11 return add_post_meta(
12 is_array( $post_ids ) ? $post_ids[0] : $post_ids,
13 $meta_key,
14 $meta_value,
15 $unique
16 );
17 }
18
19 global $wpdb;
20
21 // Filter out invalid IDs and duplicates
22 $valid_post_ids = array_unique(
23 array_filter( $post_ids, function( $post_id ) use ( $wpdb, $meta_key, $unique ){
24 return $post_id
25 && is_numeric( $post_id )
26 && get_post_status( $post_id )
27 && (
28 ! $unique
29 || ! $wpdb->get_var( $wpdb->prepare(
30 "SELECT COUNT(*) FROM $wpdb->postmeta WHERE post_id = %d AND meta_key = %s",
31 $post_id,
32 $meta_key
33 ) )
34 );
35 } )
36 );
37
38 if( ! count( $valid_post_ids ) ){
39 return false;
40 }
41
42 // Sanitization
43 $meta_key = wp_unslash( $meta_key );
44 $meta_value = sanitize_meta( $meta_key, wp_unslash( $meta_value ), 'post' );
45
46 // Collect data and format strings for a single INSERT
47 $insert_data = [];
48 $insert_formats = [];
49
50 foreach( $valid_post_ids as $post_id ){
51 array_push( $insert_data, $post_id, $meta_key, $meta_value );
52 $insert_formats[] = '(%d, %s, %s)';
53 }
54
55 $sql_string = "INSERT INTO $wpdb->postmeta (`post_id`, `meta_key`, `meta_value`) VALUES "
56 . implode( ', ', $insert_formats );
57
58 // ... unpacks the array into separate arguments for prepare
59 $sql_query = $wpdb->prepare( $sql_string, ...$insert_data );
60
61 return $wpdb->query( $sql_query );
62 }
63}

What happens here line by line. The !function_exists check is a safeguard against conflicts: if another plugin has already declared a function with that name, there will be no fatal error. The is_numeric($post_ids) block passes a single ID to the native add_post_meta, so the same function works with both a single post and an array. The array_filter filter discards IDs of drafts, deleted records, and those for which the unique field already exists. Sanitization via wp_unslash and sanitize_meta is mandatory: without it, serialized data and special characters will break the query.

*Important note about ...$insert_data.* The ... (splat) operator unpacks the array into a flat list of arguments. Without it, $wpdb->prepare would receive one array argument instead of a set of (%d, %s, %s), and the query would fail with a placeholder count error. PHP 5.6+ (WordPress requires a minimum of 7.0, so you are safe).

How to use

Pass an array of post IDs, the key, and the value as a regular call. The fourth parameter, the uniqueness flag: true will not add the field if it already exists for the post.

1$post_ids = [ 1, 2, 3, 42, 108 ];
2$meta_key = 'featured_priority';
3$meta_value = 'high';
4$unique = false; // will set for all, even if the field already existed
5
6add_multiple_posts_meta( $post_ids, $meta_key, $meta_value, $unique );

It is convenient to run such a call via the init hook (once, then remove it) or via WP-CLI with the wp eval-file command. After execution, be sure to remove the code from functions.php, otherwise the function will fire on every page load.

Method 2. Loop over IDs with add_post_meta

If you do not want to touch SQL directly, use the native add_post_meta in a loop. The code is four times shorter and relies entirely on the WordPress core.

1$post_ids = [ 1, 2, 3, 42, 108 ];
2$meta_key = 'featured_priority';
3$meta_value = 'high';
4$unique = false;
5
6foreach( $post_ids as $post_id ){
7 add_post_meta( $post_id, $meta_key, $meta_value, $unique );
8}

The loop iterates over the array and calls add_post_meta for each ID. The function itself checks the record's existence, key uniqueness (if $unique = true is set), and data correctness; no additional validation is needed.

Performance. With a hundred posts, the difference from the SQL query is imperceptible. But if you have 5,000 records, each add_post_meta call generates a separate INSERT, plus internal hooks (added_post_meta, update_post_meta_cache). At such volumes, the first method with a single query will run 10-20 times faster.

For one-off operations with a couple of dozen posts, use the loop, do not overcomplicate things.

Comparison of approaches

Criterion

Method 1 (single SQL)

Method 2 (add_post_meta loop)

Speed on 5,000 posts

~0.2 sec (1 query)

~3-6 sec (5,000 queries)

Code complexity

~50 lines with validation

4 lines

Safety

Manual sanitization needed

add_post_meta handles everything

WordPress hooks

Not called

added_post_meta, update_post_meta_cache

When to choose

Bulk migration, CSV import

One-off edits, up to a hundred posts

A universal rule: if there are fewer than a hundred posts and the operation is one-off, use the loop. If it involves regular synchronization or migration of thousands of records, only the direct query with the prepared function.

⁉️🤔 Frequent questions

Can I assign a field selectively, only to posts from a specific category?

Yes. Get the post IDs via get_posts with the 'category' => ID_рубрики parameter, then pass the array to either of the two methods above. For thousands of posts, add 'numberposts' => -1 and 'fields' => 'ids'; the query will return only an array of IDs, without loading objects.

Here is a ready-made example: $post_ids = get_posts( [ 'category' => 5, 'numberposts' => -1, 'fields' => 'ids', 'post_type' => 'post' ] );. Then, call add_multiple_posts_meta($post_ids, ...) or a loop with add_post_meta.

What if I need a different field value for different posts?

Bulk insertion will not work here. Build an associative array [ ID_поста => значение ] and iterate over it in a loop with add_post_meta or update_post_meta. One value for all, SQL; different values for different posts, only a loop.

For 50+ posts with unique values, use update_post_meta instead of add_post_meta: it does not create duplicates if the field already exists.

Is it safe to write SQL queries directly to wp_postmeta?

It is safe under two conditions. First: always run data through $wpdb->prepare; it escapes special characters and blocks SQL injections. Second: make a full database backup before running. One incorrect UPDATE without WHERE, and wp_postmeta is cleared for all records.

Backup command via WP-CLI: wp db export backup-before-meta.sql. Restore: wp db import backup-before-meta.sql.

How does add_post_meta differ from update_post_meta?

add_post_meta adds a new row to wp_postmeta, even if the key already exists for the post, resulting in a duplicate. update_post_meta checks for the key's existence: if it exists, it updates the value; if not, it creates it. For bulk population where there is one key per post, add_post_meta with $unique = true produces the same effect.

In practice: add_post_meta($id, 'views', 0, true) will create the field once and never touch it again. update_post_meta($id, 'views', $count) will overwrite the value on every call.

Where should I place the code, in the theme or a plugin?

For one-off operations, in the child theme's functions.php, with immediate removal after execution. For recurring tasks, package it as a Must-Use plugin (wp-content/mu-plugins/ folder) or add it to Code Snippets with the "Run once" flag. Code in an active theme without removal will hit the database on every page load; that is unnecessary load.

What to pick for your task

Practice shows: 9 out of 10 cases are covered by the loop with add_post_meta. That is four lines of code, a native function, and no surprises.

A direct SQL query is justified in two situations: bulk migration (thousands of posts, time is valuable) and periodic synchronization where you control sanitization on input. Otherwise, do not overcomplicate things.

What to do right now:

  • If you need to assign a field to ≤100 posts, copy the loop from the second method, plug in your IDs, and run it.
  • If there are several thousand posts, take the function from the first method, but test it on a site copy first.
  • In any scenario, back up the database before running. This is not a precaution; it is a rule.

And which method do you usually use to add custom fields: via the admin panel, code, or a plugin like ACF? Write in the comments, it is interesting to compare approaches.