How to Create and Use WordPress Custom Fields

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

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.
  • 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.
  • 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.
  • 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:

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:

  1. 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.
  2. 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.
  3. 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() returns 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 returns 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 returns 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:

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.


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:


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:

  1. $post_id: The ID of the post to be affected
  2. $meta_key: The name of the custom field to be affected (for the most recent example, this would be 'wpshout_custom_body_class')
  3. $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
  4. $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() removes a custom field entirely for a post. It looks as follows:

delete_post_meta( $post_id, $meta_key, $meta_value );

Its arguments are:

  1. $post_id: The ID of the post to be affected
  2. $meta_key: The name of the custom field to be deleted from the post’s metadata
  3. $meta_value: This optional parameter handles duplicate meta keys; you can almost always omit it

Updating and Deleting a Custom Field: An Example

The following plugin is quirky behavior-wise, but is good for demonstrating update_post_meta() and delete_post_meta(). Its functionality is to:

  1. Count every time a post’s content is loaded, and display the count before the post content.
  2. Any time the homepage is visited, reset the count to 0 for each post that’s loaded on the homepage.

The result looks as follows:

This counter goes up by 1 every time you reload the page, and resets whenever you visit the homepage.

And here’s the code:

Plugin Name: WPShout Count Times Content Loaded

function wpshout_count_times_content_loaded( $content ) {
	// Reset the count if on the site's homepage
	if ( is_front_page() ) {
		delete_post_meta( get_the_ID(), 'wpshout_times_content_loaded' );
		return $content;

	// Get the count; count is 0 if custom field not found
	$post_loads_count = get_post_meta( get_the_ID(), 'wpshout_times_content_loaded', true );
	if( empty( $post_loads_count ) ) {
		$post_loads_count = 0;

	// Add 1 to the count and save
	update_post_meta( get_the_ID(), 'wpshout_times_content_loaded', $post_loads_count );	
	// Return the updated count and the main post content
	return '<p><em>Content loaded ' . $post_loads_count . ' times</em></p>' . $content;
add_filter( 'the_content', 'wpshout_count_times_content_loaded' );

As odd as this plugin is, there shouldn’t be anything in it that surprises you if you’ve followed the rest of this chapter.

Creating Attractive Custom Fields User Interfaces

WordPress’s default custom fields user interface is unattractive and difficult to use. Unfortunately, WordPress’s core software does not expose an API for creating an attractive interface, so doing it by hand is a fair amount of work.

We recommend you examine several options for creating custom field interfaces:

Third-Party Plugins

Several plugins exist for creating attractive custom field interfaces. Two we recommend are Pods ( and Advanced Custom Fields ( These can be a good choice for many needs.

The Custom Metaboxes and Fields for WordPress Project

For more control than a custom fields plugin, we recommend CMB2, at Once you’ve loaded the project’s files, you pass a single large configuration array into a custom event it registers. This greatly simplifies creating attractive custom field interfaces.

By Hand

If, for some reason, you need to create a from-scratch user interface for one or more custom fields, our favorite tutorial on the subject is by Justin Tadlock, at:

Understanding the Power of WordPress Custom Fields

Custom fields are a crucial way to extend WordPress past its default role as an article-publishing engine. You should now understand custom fields’ nature as a key/value store for post-level data, and how to store, modify, and access custom field data.

Summary Limerick

To use custom fields can be sweet,
If you know get_(), update_(), and delete_().
Now, to build a UI?
That’s a job you can try,
Or use third-party code to complete.

Quiz Time!

  1. In most situations, the best function to add custom field data to a post is:
    1. update_post_meta()
    2. add_post_meta()
    3. get_post_meta()
  2. get_post_meta() always requires:
    1. A post ID to target
    2. A key to target
    3. A value to search for and retrieve
  3. Omitting get_post_meta()‘s third argument, or setting it to false, results in:
    1. The custom field data not being sanitized for output
    2. An error if the targeted custom field does not exist for the targeted post
    3. The custom field data being passed in as an array rather than a string

Answers and Explanations

  1. A. add_post_meta() behaves strangely by default if the custom field already exists for the post. update_post_meta() behaves sensibly, adding the custom field if it doesn’t exist and updating it if it does.
  2. A. This ID is the function’s first argument. If B (the second argument) is omitted, the function will return an associative array of all custom fields associated with the post. C is unrelated to how get_post_meta() works.
  3. C. Setting this argument to true makes working with single custom field values slightly more convenient.

Image credit: johnrobertshepherd