This article describes how to work with WordPress custom fields, also called post meta. In it, we describe how to use WordPress’s post meta PHP functions, especially get_post_meta() and update_post_meta() , and provide in-depth code demos as well as practical advice for working with custom fields tools like Pods and Advanced Custom Fields.

This discussion of WordPress custom fields is a chapter from our outstanding WordPress course, Up and Running. If you want to become a knowledgeable WordPress developer, Up and Running is your best, clearest route to getting there.

The Best Way to Learn WordPress Development Get Up and Running Today! Up and Running is our complete "learn WordPress development" course. Now in its updated and expanded Third Edition, it's helped hundreds of happy buyers learn WordPress development the fast, smart, and thorough way. Here's what one of them had to say: "I think anyone interested in learning WordPress development NEEDS this course. Watching the videos was like a bunch of lights being turned on." -Jason, WordPress developer Get Up and Running Now

This article is also part of a larger free series on WordPress custom fields (post meta) and custom taxonomies. If you’d like to know what custom fields are, and whether you should be adding a piece of post meta or creating a custom taxonomy, have a look in that article series.

Now, read on. 🙂

Key Takeaways: Custom field data can be added to a post in the “Custom Fields” section of the Post Editor, or programmatically using update_post_meta() . update_post_meta() is also the function to change a custom field’s value for a specific post.

. is also the function to change a custom field’s value for a specific post. Once stored, custom field data can be accessed using get_post_meta() . This function always requires a post ID; in The Loop, you can find this ID with get_the_ID() , but outside it you’ll need to use other methods.

. This function always requires a post ID; in The Loop, you can find this ID with , but outside it you’ll need to use other methods. The final noteworthy function for working with custom field data is delete_post_meta() , which destroys a given custom field’s data for a given post.

, which destroys a given custom field’s data for a given post. Creating attractive user interfaces for users to input and change custom fields is difficult and labor-intensive in WordPress. Several good plugins and projects exist to solve the problem, and it’s worth using those before attempting the process by hand.

In this chapter, we’ll discuss how to add custom field data—both from the WordPress admin interface and programmatically—and how to use, modify, and delete that data.

How Custom Fields Work

Custom fields are stored in the WordPress database’s wp_postmeta table (note that wp_ can be changed to another database prefix), and look like this:

As you can see, custom fields are simple key/value pairings: they map a custom field name ( meta_key ) to a custom field value ( meta_value ) for a particular post ( post_id ). The only data a custom field has beyond these three elements is a unique ID ( meta_id ).

Once stored, custom field data can be retrieved and used, modified, and deleted with several simple WordPress functions.

Adding Custom Field Data to a Post

Below is the default interface for manually adding custom field data to a post:

Relative to most interfaces, this one is pretty unattractive and difficult to use; we’ll give some advice for creating new interfaces below.

Using Custom Field Data

WordPress’s most useful function for accessing a post’s custom field data is called get_post_meta() . (Others exist, including the_meta() and get_post_custom() , but both behave strangely enough to be worth omitting.) To see how get_post_meta() works, let’s examine it in a couple of environments.

In the Loop

Let’s first try get_post_meta() inside The Loop:

<?php /* Plugin Name: WPShout Add Favorite Flavor to Content */ function wpshout_favorite_flavor_subtitle( $content ) { $fave_flave = get_post_meta( get_the_ID(), 'wpshout_current_favorite_flavor', true ); if( empty( $fave_flave ) ) { return $content; } $fave_flave_string = '<em>My current favorite flavor is: ' . $fave_flave . '</em><hr>'; return $fave_flave_string . $content; } add_filter( 'the_content', 'wpshout_favorite_flavor_subtitle' );

On the Post whose custom field we set in the screenshot above, we get this result:

In the Loop in a Plugin?

First off: How can we be in The Loop if we’re writing a plugin? This is an important point.

The answer is that we’ve hooked into the_content , a filter hook that takes place inside The Loop. The the_content hook is triggered just before the the_content() or the_excerpt() filter tags execute.

So going back to our factory analogy: By hooking into the_content , we got pulled into the section of the factory that’s running The Loop. If we’d hooked in somewhere else—say, body_class , which is our next example—the code above wouldn’t have worked.

get_post_meta() Arguments

get_post_meta() accepts three arguments:

A post ID. This is required for all calls of get_post_meta() . In the example above, since we’re in The Loop, we can use the get_the_ID() template tag, which gets the current post’s data automatically. A custom field key. We set this key ourselves— wpshout_current_favorite_flavor —in our post’s “Custom Fields” box. This argument is optional; if you don’t fill it in, you’ll get an associative array of all the post’s custom fields. Whether we’d like our custom field value as a string. If we set to true , as we did here, we get a string; if we leave it off or set it to false , we’ll get back an array. To keep things simple here (and most of the time), it’s set to true .

if( empty( $fave_flave ) ) { }

This section of code simply “exits early” if the post meta we’re trying to fetch doesn’t have a value, so that we don’t end up changing posts that don’t have this custom field.

We’re checking if $fave_flave is empty, because if the custom field is missing, get_post_meta() return s an empty string. So we’re checking whether we indeed got back a value, or just an empty string. (Note that if get_post_meta() ‘s third argument is omitted or set to false , it return s an empty array instead.)

Because we’re hooking onto the_content —using a filter—we have to give back what we’ve been given, which is why we return $content unaltered.

$fave_flave_string =

Since we know our $fave_flave variable exists, we’re building a string using that variable.

return $fave_flave_string . $content;

This adds our created string before the main post content string, and return s the modified content back for WordPress to work on.

add_filter( 'the_content', 'wpshout_favorite_flavor_subtitle' );

We hooked into the_content , which runs right before a post’s main content is printed to the page. We’re hooking in our own function, wpshout_favorite_flavor_subtitle() , so that it runs, executing the code inside it, when WordPress fires the_content . As all filters pass an argument to their hooked functions, note that wpshout_favorite_flavor_subtitle() has an argument, $content , which is returned back whether or not it’s modified.

Outside the Loop

Here’s a use of a custom field outside The Loop. It’s based on this custom field in a post:

Based on the code below, we’re going to add the custom class we’ve defined, .kitties-class , to our post’s HTML body element. (We’ve styled .kitties-class for effect!) The end result will look like:

And here’s the code:

<?php /* Plugin Name: WPShout Add Custom Body Class */ function wpshout_add_custom_body_class( $classes ) { if( ! is_singular() ) { return $classes; } global $post; $custom_body_class = get_post_meta( $post->ID, 'wpshout_custom_body_class', true ); if( empty( $custom_body_class ) ) { return $classes; } $classes[] = $custom_body_class; return $classes; } add_filter( 'body_class', 'wpshout_add_custom_body_class' );

Let’s walk through the bits of the code that are new—we’ll skip the few bits that are very similar to the last example.

if( ! is_singular ) {}

Because this code changes the <body> class of the entire webpage, we don’t want multiple posts running it at once. In other words, it only makes sense as an approach on singular webpages. This code simply “exits early” if the post bundle has more than one post in it. For more on conditionals like is_singular() , see WordPress’s Conditional Tags.

global $post

This line is very important. global means “floating around in WordPress’s global state,” which is the set of variables and objects that exist in the background of WordPress’s processes and tell WordPress “what’s going on.” global $post; lets us access the $post global variable locally, in the current function, by the name $post .

What is $post ? It’s a big PHP object, sitting in WordPress’s global state, with lots of information about the current post. We need to talk to $post because we need to know the current post’s ID to be able to run get_post_meta() .

This is a tricky subject: it gets into PHP object syntax and quite a bit about global state. Think of $post as a direct source of information about the current post that WordPress is using to create the webpage.

$post->ID

This comes right out of the previous line. We want the post’s ID—without it, we can’t run get_post_meta() —and we don’t have access to our convenient get_the_ID() template tag since we’re outside The Loop.

The way to ask an object like $post for one of its properties is with PHP object syntax: in this case, $post->ID . What this code gives us back is the ID of the current post (which is also the only post that got fetched for this page load, since we know this is a singular page).

$classes[] = $custom_body_class;

The event we’ve hooked onto, body_class , passes us a variable, $classes , to work with. $classes is an array of classes that WordPress is already planning to add to the webpage’s <body> tag. It looks something like this: array ( 'single', 'single-post', 'postid-2196', 'single-format-standard', 'logged-in', 'admin-bar' ) .

Talking to arrays is different than talking to other data types, like strings. In this case want to add an element to the $classes array, and the PHP syntax for adding a single element to an array is: $array_name[] = $thing_to_add; .

With our new array element added to the $classes array, all that’s left is to pass the modified $classes back for further processing. We do this with return $classes; .

add_filter( ‘body_class’, ‘wpshout_add_custom_body_class’ );

This time, we’re hooking into body_class , an event that fires long before The Loop ever runs. This is why we had to use global $post above. When we hook into body_class , it gives us our $classes array to modify and give back, just as the the_content filter gives us a $content string. (By the way, $classes and $content are variable names we decide ourselves—but they’re very smart names, since they describe what’s in them.)

Programmatically Adding, Changing, and Deleting Custom Fields So far, we’ve only covered how to retrieve data from custom fields. Another common task for developers is to programmatically change or remove this data. The functions for this are quite simple:

update_post_meta() This is the function for either adding or updating a particular custom field value to a post. It looks as follows:

update_post_meta( $post_id, $meta_key, $meta_value, $prev_value );

Its arguments, in order, are:

$post_id : The ID of the post to be affected $meta_key : The name of the custom field to be affected (for the most recent example, this would be 'wpshout_custom_body_class' ) $meta_value : The value that the custom field should now take—this can be a string, integer, array, or any other data type depending on your needs $prev_value : This optional parameter handles duplicate meta keys; you can almost always omit it

As a note, WordPress also carries a similar function called add_post_meta() , but update_post_meta() is better in most situations, since it will either add the custom field if it’s new or update it if it isn’t.

delete_post_meta()