The WordPress metadata API is a simple way to store and retrieve information related to various objects in WordPress, such as posts, users, comments, and taxonomy terms. Out of the box, WordPress includes post meta, user meta, comment meta, and term meta, but what if you want metadata on other objects, such as custom objects provided by a plugin? Thankfully, the metadata API is actually quite simple to extend, allowing developers to easily register their own kind of metadata that is attached to their own, custom objects.

Before diving into how we can extend the metadata API, let’s have a quick refresher on the existing metadata functions available in WordPress.

For each object type, there are four primary functions used by developers:

  • get
  • add
  • update
  • delete

Posts

For post objects, we have the following metadata functions:

  • get_post_meta()
  • add_post_meta()
  • update_post_meta()
  • delete_post_meta()

Comments

For comment objects, we have the following metadata functions:

  • get_comment_meta()
  • add_comment_meta()
  • update_comment_meta()
  • delete_comment_meta()

Users

For user objects, we have the following metadata functions:

  • get_user_meta()
  • add_user_meta()
  • update_user_meta()
  • delete_user_meta()

Terms

For term objects, we have the following metadata functions:

  • get_term_meta()
  • add_term_meta()
  • update_term_meta()
  • delete_term_meta()

All of these functions work exactly the same way. For example, to get metadata, it would look like this:

$value = get_post_meta( $post_id, 'meta_key', true );

To update metadata:

$updated = update_user_meta( $user_id, 'meta_key', 'New Value' );

To delete metadata:

$deleted = delete_term_meta( $term_id, 'meta_key' );

To add a new row of metadata:

$added = add_post_meta( $post_id, 'meta_key', 'The new value', $unique = true );

Notice that they are all identical in everything but name. Each of these functions is actually just a wrapper for the general metadata functions:

  • get_metadata()
  • add_metadata()
  • update_metadata()
  • delete_metadata()

For example, get_post_meta() simple calls get_metadata() like this:

$value = get_metadata( 'post', $object_id, 'meta_key', $single = true );

And, update_term_meta() simple calls update_metadata() like this:

$updated = update_metadata( 'term', $object_id, 'meta_key', $single = true );

Extending the metadata API

We have refreshed our memory on what the standard metadata API functions look like, so now let’s see how we can extend these to interact with our own metadata.

The first thing you need to do is have an object type that metadata will be registered for. This could really be anything but let me provide you with a few examples.

In AffiliateWP we use a custom table for affiliate accounts. An affiliate is similar to a user account and we often want to store metadata for affiliates, much like is often done for user accounts. We extended the metadata API to provide support for affiliate meta.

In Easy Digital Downloads we use a custom table to keep track of customer records. We recently added a new customer meta API that extends the one in WordPress. This allows us to store metadata for customer records.

In Restrict Content Pro we use a custom table for subscription levels and payment records. Both of these needed to support custom metadata, so we added metadata tables that extend the WordPress API.

Other examples of object types that may need metadata could include invoices, sale receipts, photos, and so many more. Basically, if you register a custom table and do not rely on the core WordPress tables, it may behoove you to add a metadata layer as well.

There are several components involved with registering your own metadata layer:

  1. You need to create a custom table in which to store the metadata
  2. You need to make WordPress aware of the meta table
  3. You can optionally define wrapper  functions or class methods for the core metadata functions that make it easier to interact with your metadata layer

Let’s look at each piece.

Creating the metadata table

The metadata has to be stored somewhere. In the vast majority of cases, the best option will likely be to register a custom table that closely mimics the default metadata tables for posts, users, comments, and terms.

If you’re unfamiliar with creating custom database tables, I’d recommend you read through my series on building a database abstraction layer, especially part 3, which covers creating the tables.

The MySQL syntax that WordPress core uses to create the postmeta table looks like this:

CREATE TABLE `wp_postmeta` (
  `meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `post_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `meta_key` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `meta_value` longtext COLLATE utf8mb4_unicode_ci,
  PRIMARY KEY (`meta_id`),
  KEY `post_id` (`post_id`),
  KEY `meta_key` (`meta_key`(191))
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

Unless you have a specific reason for deviating from this structure, I’d recommend using the same table structure.

Let’s use AffiliateWP’s affiliate meta as an example.

The MySQL for our table looks like this:

CREATE TABLE `wp_affiliate_wp_affiliatemeta` (
  `meta_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `affiliate_id` bigint(20) NOT NULL DEFAULT '0',
  `meta_key` varchar(255) DEFAULT NULL,
  `meta_value` longtext,
  PRIMARY KEY (`meta_id`),
  KEY `affiliate_id` (`affiliate_id`),
  KEY `meta_key` (`meta_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

We create this table when AffiliateWP is first installed, along with our other custom tables.

The structure is simple and mimics core’s metadata structure:

  • meta_id – This is an auto incrementing row that holds the ID of the row
  • affiliate_id – This is an integer that is set to the ID of the affiliate the metadata belongs to
  • meta_key – This is a string identifier for the value stored
  • meta_value – This is the actual value of the metadata stored

Registering the metadata table with WordPress

Once the table has been created, we need to make WordPress aware of it. This is what will permit us to utilize the core metadata API functions, such as update_metadata(), to interact with our data.

I would recommend registering the table on the plugins_loaded hook but it could likely be done during other actions as well.

function pw_register_metadata_table() {
	global $wpdb;
	$wpdb->affiliatemeta = $wpdb->prefix . 'affiliate_wp_affiliate_meta';
}
add_action( 'plugins_loaded', 'pw_register_metadata_table' );

That’s really all there is to it. You can see how we actually do it in AffiliateWP here.

There is one important notes about registering the table. The value passed to $wpdb most follow an exact naming scheme as it defines the value that needs to be passed to the $object_type parameter of the metadata functions.

The type value is determined by everything before “meta”, so in our example above we used “affiliatemeta”, which makes the value we need to pass to the object type “affiliate”.

If you register the table as $wpdb->customermeta, you would pass “customer” as the object type.

Interacting with the metadata

Now that we have created the table and registered it with WordPress, we can use the metadata API functions in WordPress to read, write, and delete to our metadata table.

Add metadata:

$added = add_metadata( 'affiliate', $affiliate_id, 'some_key', 'The value' );

Update metadata:

$updated = update_metadata( 'affiliate', $affiliate_id, 'some_key', 'The value' );

Get metadata:

$value = get_metadata( 'affiliate', $affiliate_id, 'some_key', $single = true );

Delete meta:

$deleted = delete_metadata( 'affiliate', $affiliate_id, 'some_key' );

If desired, this could be formalized a bit more for your specific use case by defining wrapper functions to the core functions. For example, in AffiliateWP, we register methods in our database class like this:

/**
 * Retrieve affiliate meta field for a affiliate.
 *
 * @param   int    $affiliate_id  Affiliate ID.
 * @param   string $meta_key      The meta key to retrieve.
 * @param   bool   $single        Whether to return a single value.
 * @return  mixed                 Will be an array if $single is false. Will be value of meta data field if $single is true.
 *
 * @access  public
 * @since   1.6
 */
function get_meta( $affiliate_id = 0, $meta_key = '', $single = false ) {
	return get_metadata( 'affiliate', $affiliate_id, $meta_key, $single );
}

We also define global functions to make the class methods more accessible:

/**
 * Retrieve affiliate meta field for a affiliate.
 *
 * @param   int    $affiliate_id  Affiliate ID.
 * @param   string $meta_key      The meta key to retrieve.
 * @param   bool   $single        Whether to return a single value.
 * @return  mixed                 Will be an array if $single is false. Will be value of meta data field if $single is true.
 *
 * @access  public
 * @since   1.6
 */
function affwp_get_affiliate_meta( $affiliate_id = 0, $meta_key = '', $single = false ) {
	return affiliate_wp()->affiliate_meta->get_meta( $affiliate_id, $meta_key, $single );
}

The metadata API in WordPress is really quite nice and does a lot of the heavy lifting for us. There’s no need to write any MySQL for CRUD actions since it’s all handled for us in the metadata API.

  1. WPServer.com

    Thanks for writing about the metadata API. It’s useful and I don’t think that many developers knows about it

  2. Deborah L Oxford

    I love the way that you break it down for us beginners. Makes it easy to understand. My experience is not just old-school, but dinosaur-school, and it helps a lot when I can translate it to Assembler or COBOL. 🙂

  3. Yudhistira Mauris

    Thanks a lot Pippin for the tutorial! I have a question. If we have a custom table in database, is it better to create additional columns to store data or create a custom metadata table and store data there?

    • Pippin

      It depends on the data you’re storing. Do you have an example?

  4. Daniel Iser

    Great article, I wonder if you would find any use in a library I wrote to piggy back the meta system but for custom table structures.

    IE You can do something like

    update_post_meta( 1, ‘pt_location’, array(
    ‘city’ => ‘Jacksonville’,
    ‘state’ => ‘Florida’,
    ‘zipcode’ => 32204,
    ‘lat’ => 30.315243,
    ‘long’ => -81.685681,
    ) );

    Which fills a table with columns city, state, zipcode etc.

    Have been meaning to circle around and clean it up a bit but if anybody is interested: https://github.com/danieliser/WP-Post-Partner-Tables

    • gurmeet

      i have also make a plugin blood user registeration form using user meta. with short code .also send mail .task working

  5. gurmeet singh

    sir i have ,name ,email.location ,mobile etc have fields ,
    and i want all record store in default wordpress table
    my email is ;gurmeet1330@gmail.com
    please give me answer on my mail

    for example i am no use
    this code , i want all record store in default wordpress table
    CREATE TABLE `wp_affiliate_wp_affiliatemeta` (
    `meta_id` bigint(20) NOT NULL AUTO_INCREMENT,
    `affiliate_id` bigint(20) NOT NULL DEFAULT ‘0’,
    `meta_key` varchar(255) DEFAULT NULL,
    `meta_value` longtext,
    PRIMARY KEY (`meta_id`),
    KEY `affiliate_id` (`affiliate_id`),
    KEY `meta_key` (`meta_key`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  6. H71

    I can’t understand register table section. How this works? `$wpdb->affiliatemeta = …` .

    • Pippin

      That line simply registers affiliatemeta as a property on the $wpdb object. The value of the property is the name of the affiliate meta database table.

Leave a Reply

Error: Please enter a valid email address

Error: Invalid email

Error: Please enter your first name

Error: Please enter your last name

Error: Please enter a username

Error: Please enter a password

Error: Please confirm your password

Error: Password and password confirmation do not match