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.

  7. Simone

    Hello… just one question… what do you suggest if you want to add some meta information dependent both on user and a post type? e.g. if you want to store some specific user preferences on a specific post. Is there any built in functionality to achieve that goal or do I have to create it through custom plugin tables? Thank you in advance!

    • Pippin

      You could do that with ether user or post meta, or a custom table would also work.

      If you need to store a lot of data, use a custom table. It’s if just a few pieces of information, you could use user or post meta and include the user’s ID in the meta_key, allowing you to identify which user the metadata belongs too.

  8. Bootstrap Themes

    Very very helpfull post, thanks for sharing.

    Thanks Pippin your tutorials always rocks.

  9. WpHound

    Metadata API are really useful when it comes to store and retrieve data.anyway as usual love this article//

  10. Rick Martin

    Great Work…your post are really creative and too helpful for us…thanks for sharing..

  11. Alex

    helpful and it really i have used those codes for my website … thanks

  12. test

    TEST

  13. Gurpratap singh

    I am grateful to you for this great content.I am reading your article and its very nice, useful. Thanks for sharing such valuable information web designers sydney visit the site to know more.

  14. marle

    wordpress plugins_api() way to get the metadata

    hello and good day – many thanks pippin,

    I’m currently working on a parser to make a small preview on the newest plugins in wordpress.

    at the moment i think i work from a URL given

    I’d like to retrieve only the title of the page and https://wordpress.org/plugins/wp-job-manager
    https://wordpress.org/plugins/ninja-forms

    a little chunk of information (a bit of text)

    The project: for a list of meta-data of popular wordpress-plugins
    (cf. https://de.wordpress.org/plugins/browse/popular/ and gathering the first 50 URLs – that are
    50 plugins which are of interest! The challenge is: i want to fetch meta-data of all the existing plugins.
    What i subsequently want to filter out after the fetch is – those plugins that have the newest timestamp
    – that are updated (most) recently. It is all aobut acutality…

    https://wordpress.org/plugins/wp-job-manager
    https://wordpress.org/plugins/ninja-forms
    https://wordpress.org/plugins/participants-database ….and so on and so forth.

    see the source: https://wordpress.org/plugins/wp-job-manager/ we have the following set of meta-data for each wordpress-plugin:

    Version: 1.9.5.12
    installations: 10,000+
    WordPress Version: 5.0 or higher
    Tested up to: 5.4 PHP
    Version: 5.6 or higher
    Tags 3 Tags: database member sign-up form volunteer
    Last updated: 19 hours ago
    plugin-ratings

    but as i saw today: there we have a api that can be used for this purpose too:

    cf: https://developer.wordpress.org/reference/functions/plugins_api/

    The API is the proper way to get the metadata. However it is not very fast. For 50 calls it might take 1-2 min.
    I have the feeling that WP intentionally makes this call slow to discourage others from mining the plugin information.

    so – i am glad to see this reference here

    thank you

  15. steve

    I am looking for a way that helpscout tickets created with Gravity Form, carry the TAG that Restrict Content Pro membership has acquired.
    I’m confused!

Comments are closed.