diff --git src/wp-admin/includes/schema.php src/wp-admin/includes/schema.php
index dd36834..d809ec5 100644
--- src/wp-admin/includes/schema.php
+++ src/wp-admin/includes/schema.php
@@ -57,7 +57,16 @@ function wp_get_db_schema( $scope = 'all', $blog_id = null ) {
 	$max_index_length = 191;
 
 	// Blog specific tables.
-	$blog_tables = "CREATE TABLE $wpdb->terms (
+	$blog_tables = "CREATE TABLE $wpdb->termmeta (
+  meta_id bigint(20) unsigned NOT NULL auto_increment,
+  term_id bigint(20) unsigned NOT NULL default '0',
+  meta_key varchar(255) default NULL,
+  meta_value longtext,
+  PRIMARY KEY  (meta_id),
+  KEY term_id (term_id),
+  KEY meta_key (meta_key($max_index_length))
+) $charset_collate;
+CREATE TABLE $wpdb->terms (
  term_id bigint(20) unsigned NOT NULL auto_increment,
  name varchar(200) NOT NULL default '',
  slug varchar(200) NOT NULL default '',
diff --git src/wp-admin/includes/upgrade.php src/wp-admin/includes/upgrade.php
index 967cfe3..0cbca06 100644
--- src/wp-admin/includes/upgrade.php
+++ src/wp-admin/includes/upgrade.php
@@ -2628,6 +2628,12 @@ function pre_schema_upgrade() {
 		$wpdb->query( "ALTER TABLE $wpdb->postmeta DROP INDEX meta_key, ADD INDEX meta_key(meta_key(191))" );
 		$wpdb->query( "ALTER TABLE $wpdb->posts DROP INDEX post_name, ADD INDEX post_name(post_name(191))" );
 	}
+
+	if ( $wp_current_db_version < 34370 ) {
+		if ( $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->termmeta}'" ) ) {
+			$wpdb->query( "ALTER TABLE $wpdb->termmeta DROP INDEX meta_key, ADD INDEX meta_key(meta_key(191))" );
+		}
+	}
 }
 
 /**
diff --git src/wp-includes/default-filters.php src/wp-includes/default-filters.php
index e9ddc47..20684e2 100644
--- src/wp-includes/default-filters.php
+++ src/wp-includes/default-filters.php
@@ -201,6 +201,7 @@ add_filter( 'pingback_ping_source_uri', 'pingback_ping_source_uri'            );
 add_filter( 'xmlrpc_pingback_error',    'xmlrpc_pingback_error'               );
 add_filter( 'title_save_pre',           'trim'                                );
 add_filter( 'get_comment_metadata',     'wp_lazyload_comment_meta',     10, 2 );
+add_filter( 'get_term_metadata',        'wp_lazyload_term_meta',        10, 2 );
 
 add_filter( 'http_request_host_is_external', 'allowed_http_request_hosts', 10, 2 );
 
diff --git src/wp-includes/taxonomy-functions.php src/wp-includes/taxonomy-functions.php
index 1ab178e..9d40f17 100644
--- src/wp-includes/taxonomy-functions.php
+++ src/wp-includes/taxonomy-functions.php
@@ -948,6 +948,7 @@ function get_term_to_edit( $id, $taxonomy ) {
  * @since 2.3.0
  * @since 4.2.0 Introduced 'name' and 'childless' parameters.
  * @since 4.4.0 Introduced the ability to pass 'term_id' as an alias of 'id' for the `orderby` parameter.
+ *              Introduced the 'meta_query' and 'update_term_meta_cache' parameters.
  *
  * @global wpdb  $wpdb WordPress database abstraction object.
  * @global array $wp_filter
@@ -1004,6 +1005,9 @@ function get_term_to_edit( $id, $taxonomy ) {
  *                                           no effect on non-hierarchical taxonomies. Default false.
  *     @type string       $cache_domain      Unique cache key to be produced when this query is stored in an
  *                                           object cache. Default is 'core'.
+ *     @type bool         $update_term_meta_cache Whether to prime meta caches for matched terms. Default true.
+ *     @type array        $meta_query             Meta query clauses to limit retrieved comments by.
+ *                                                See `WP_Meta_Query`. Default empty.
  * }
  * @return array|int|WP_Error List of Term Objects and their children. Will return WP_Error, if any of $taxonomies
  *                        do not exist.
@@ -1027,7 +1031,8 @@ function get_terms( $taxonomies, $args = '' ) {
 		'hide_empty' => true, 'exclude' => array(), 'exclude_tree' => array(), 'include' => array(),
 		'number' => '', 'fields' => 'all', 'name' => '', 'slug' => '', 'parent' => '', 'childless' => false,
 		'hierarchical' => true, 'child_of' => 0, 'get' => '', 'name__like' => '', 'description__like' => '',
-		'pad_counts' => false, 'offset' => '', 'search' => '', 'cache_domain' => 'core' );
+		'pad_counts' => false, 'offset' => '', 'search' => '', 'cache_domain' => 'core',
+		'update_term_meta_cache' => true, 'meta_query' => '' );
 	$args = wp_parse_args( $args, $defaults );
 	$args['number'] = absint( $args['number'] );
 	$args['offset'] = absint( $args['offset'] );
@@ -1287,6 +1292,16 @@ function get_terms( $taxonomies, $args = '' ) {
 		$where .= $wpdb->prepare( ' AND ((t.name LIKE %s) OR (t.slug LIKE %s))', $like, $like );
 	}
 
+	// Meta query support.
+	$join = '';
+	if ( ! empty( $args['meta_query'] ) ) {
+		$mquery = new WP_Meta_Query( $args['meta_query'] );
+		$mq_sql = $mquery->get_sql( 'term', 't', 'term_id' );
+
+		$join  .= $mq_sql['join'];
+		$where .= $mq_sql['where'];
+	}
+
 	$selects = array();
 	switch ( $args['fields'] ) {
 		case 'all':
@@ -1332,7 +1347,7 @@ function get_terms( $taxonomies, $args = '' ) {
 	 */
 	$fields = implode( ', ', apply_filters( 'get_terms_fields', $selects, $args, $taxonomies ) );
 
-	$join = "INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
+	$join .= " INNER JOIN $wpdb->term_taxonomy AS tt ON t.term_id = tt.term_id";
 
 	$pieces = array( 'fields', 'join', 'where', 'orderby', 'order', 'limits' );
 
@@ -1365,6 +1380,12 @@ function get_terms( $taxonomies, $args = '' ) {
 		update_term_cache( $terms );
 	}
 
+	// Prime termmeta cache.
+	if ( $args['update_term_meta_cache'] ) {
+		$term_ids = wp_list_pluck( $terms, 'term_id' );
+		update_termmeta_cache( $term_ids );
+	}
+
 	if ( empty($terms) ) {
 		wp_cache_add( $cache_key, array(), 'terms', DAY_IN_SECONDS );
 
@@ -1446,6 +1467,139 @@ function get_terms( $taxonomies, $args = '' ) {
 }
 
 /**
+ * Add meta data field to a comment.
+ *
+ * @since 4.4.0
+ *
+ * @param int    $term_id    Term ID.
+ * @param string $meta_key   Metadata name.
+ * @param mixed  $meta_value Metadata value.
+ * @param bool   $unique     Optional, default is false. Whether the same key should not be added.
+ * @return int|bool Meta ID on success, false on failure.
+ */
+function add_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) {
+	return add_metadata( 'term', $term_id, $meta_key, $meta_value, $unique );
+}
+
+/**
+ * Remove metadata matching criteria from a term.
+ *
+ * You can match based on the key, or key and value. Removing based on key and
+ * value, will keep from removing duplicate metadata with the same key. It also
+ * allows removing all metadata matching key, if needed.
+ *
+ * @since 4.4.0
+ *
+ * @param int    $term_id    Term ID.
+ * @param string $meta_key   Metadata name.
+ * @param mixed  $meta_value Optional. Metadata value.
+ * @return bool True on success, false on failure.
+ */
+function delete_term_meta( $term_id, $meta_key, $meta_value = '' ) {
+	return delete_metadata( 'term', $term_id, $meta_key, $meta_value );
+}
+
+/**
+ * Retrieve meta field for a term.
+ *
+ * @since 4.4.0
+ *
+ * @param int    $term_id Term ID.
+ * @param string $key     Optional. The meta key to retrieve. By default, returns data for all keys.
+ * @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.
+ */
+function get_term_meta( $term_id, $key = '', $single = false ) {
+	return get_metadata( 'term', $term_id, $key, $single );
+}
+
+/**
+ * Update term meta field based on term ID.
+ *
+ * Use the $prev_value parameter to differentiate between meta fields with the
+ * same key and term ID.
+ *
+ * If the meta field for the term does not exist, it will be added.
+ *
+ * @since 4.4.0
+ *
+ * @param int    $term_id    Term ID.
+ * @param string $meta_key   Metadata key.
+ * @param mixed  $meta_value Metadata value.
+ * @param mixed  $prev_value Optional. Previous value to check before removing.
+ * @return int|bool Meta ID if the key didn't exist, true on successful update, false on failure.
+ */
+function update_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) {
+	return update_metadata( 'term', $term_id, $meta_key, $meta_value, $prev_value );
+}
+
+/**
+ * Updates metadata cache for list of term IDs.
+ *
+ * Performs SQL query to retrieve the metadata for the term IDs and updates the
+ * metadata cache for the terms. Therefore, the functions, which call this
+ * function, do not need to perform SQL queries on their own.
+ *
+ * @since 4.4.0
+ *
+ * @param array $term_ids List of post IDs.
+ * @return array|false Returns false if there is nothing to update. Returns an array of metadata on success.
+ */
+function update_termmeta_cache( $term_ids ) {
+	return update_meta_cache( 'term', $term_ids );
+}
+
+/**
+ * Lazy load termmeta when inside of a `WP_Query` loop.
+ *
+ * @since 4.4.0
+ *
+ * @param null $check   The `$check` param passed from the 'pre_term_metadata' hook.
+ * @param int  $term_id ID of the term whose metadata is being cached.
+ * @return null In order not to short-circuit `get_metadata()`.
+ */
+function wp_lazyload_term_meta( $check, $term_id ) {
+	global $wp_query;
+
+	if ( $wp_query instanceof WP_Query && ! empty( $wp_query->posts ) && ! empty( $wp_query->get( 'update_post_term_cache' ) ) ) {
+		// We can only lazyload if the entire post object is present.
+		$posts = array();
+		foreach ( $wp_query->posts as $post ) {
+			if ( $post instanceof WP_Post ) {
+				$posts[] = $post;
+			}
+		}
+
+		if ( empty( $posts ) ) {
+			return;
+		}
+
+		// Fetch cached term_ids for each post. Keyed by term_id for faster lookup.
+		$term_ids = array();
+		foreach ( $posts as $post ) {
+			$taxonomies = get_object_taxonomies( $post->post_type );
+			foreach ( $taxonomies as $taxonomy ) {
+				$terms = get_object_term_cache( $post->ID, $taxonomy );
+				if ( false !== $terms ) {
+					foreach ( $terms as $term ) {
+						if ( ! isset( $term_ids[ $term->term_id ] ) ) {
+							$term_ids[ $term->term_id ] = 1;
+						}
+					}
+				}
+			}
+		}
+
+		if ( $term_ids ) {
+			update_termmeta_cache( array_keys( $term_ids ) );
+		}
+	}
+
+	return $check;
+}
+
+/**
  * Check if Term exists.
  *
  * Formerly is_term(), introduced in 2.3.0.
@@ -2002,6 +2156,7 @@ function wp_delete_category( $cat_ID ) {
  * @since 2.3.0
  * @since 4.2.0 Added support for 'taxonomy', 'parent', and 'term_taxonomy_id' values of `$orderby`.
  *              Introduced `$parent` argument.
+ * @since 4.4.0 Introduced `$meta_query` and `$update_term_meta_cache` arguments.
  *
  * @global wpdb $wpdb WordPress database abstraction object.
  *
@@ -2017,6 +2172,10 @@ function wp_delete_category( $cat_ID ) {
  *                           term objects being returned, 'ids' will return an array of integers, and 'names' an array
  *                           of strings.
  *     @type int    $parent  Optional. Limit results to the direct children of a given term ID.
+ *     @type bool   $update_term_meta_cache Whether to prime termmeta cache for matched terms. Only applies when
+ *                                          `$fields` is 'all', 'all_with_object_id', or 'term_id'. Default: true.
+ *     @type array  $meta_query             Meta query clauses to limit retrieved comments by.
+ *                                          See `WP_Meta_Query`. Default empty.
  * }
  * @return array|WP_Error The requested term data or empty array if no terms found.
  *                        WP_Error if any of the $taxonomies don't exist.
@@ -2044,6 +2203,8 @@ function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
 		'order'   => 'ASC',
 		'fields'  => 'all',
 		'parent'  => '',
+		'update_term_meta_cache' => true,
+		'meta_query' => '',
 	);
 	$args = wp_parse_args( $args, $defaults );
 
@@ -2117,9 +2278,21 @@ function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
 		$where[] = $wpdb->prepare( 'tt.parent = %d', $args['parent'] );
 	}
 
+	// Meta query support.
+	$meta_query_join = '';
+	if ( ! empty( $args['meta_query'] ) ) {
+		$mquery = new WP_Meta_Query( $args['meta_query'] );
+		$mq_sql = $mquery->get_sql( 'term', 't', 'term_id' );
+
+		$meta_query_join .= $mq_sql['join'];
+
+		// Strip leading AND.
+		$where[] = preg_replace( '/^\s*AND/', '', $mq_sql['where'] );
+	}
+
 	$where = implode( ' AND ', $where );
 
-	$query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id WHERE $where $orderby $order";
+	$query = "SELECT $select_this FROM $wpdb->terms AS t INNER JOIN $wpdb->term_taxonomy AS tt ON tt.term_id = t.term_id INNER JOIN $wpdb->term_relationships AS tr ON tr.term_taxonomy_id = tt.term_taxonomy_id $meta_query_join WHERE $where $orderby $order";
 
 	$objects = false;
 	if ( 'all' == $fields || 'all_with_object_id' == $fields ) {
@@ -2144,6 +2317,17 @@ function wp_get_object_terms($object_ids, $taxonomies, $args = array()) {
 		}
 	}
 
+	// Update termmeta cache, if necessary.
+	if ( $args['update_term_meta_cache'] && ( 'all' === $fields || 'all_with_object_ids' === $fields || 'term_id' === $fields ) ) {
+		if ( 'term_id' === $fields ) {
+			$term_ids = $fields;
+		} else {
+			$term_ids = wp_list_pluck( $terms, 'term_id' );
+		}
+
+		update_termmeta_cache( $term_ids );
+	}
+
 	if ( ! $terms ) {
 		$terms = array();
 	} elseif ( $objects && 'all_with_object_id' !== $fields ) {
@@ -3279,6 +3463,7 @@ function update_object_term_cache($object_ids, $object_type) {
 	$terms = wp_get_object_terms( $ids, $taxonomies, array(
 		'fields' => 'all_with_object_id',
 		'orderby' => 'none',
+		'update_term_meta_cache' => false,
 	) );
 
 	$object_terms = array();
diff --git src/wp-includes/version.php src/wp-includes/version.php
index a5bf92c..3d43523 100644
--- src/wp-includes/version.php
+++ src/wp-includes/version.php
@@ -11,7 +11,7 @@ $wp_version = '4.4-alpha-33636-src';
  *
  * @global int $wp_db_version
  */
-$wp_db_version = 34030;
+$wp_db_version = 34370;
 
 /**
  * Holds the TinyMCE version
diff --git src/wp-includes/wp-db.php src/wp-includes/wp-db.php
index c745cb2..5be4481 100644
--- src/wp-includes/wp-db.php
+++ src/wp-includes/wp-db.php
@@ -266,7 +266,7 @@ class wpdb {
 	 * @var array
 	 */
 	var $tables = array( 'posts', 'comments', 'links', 'options', 'postmeta',
-		'terms', 'term_taxonomy', 'term_relationships', 'commentmeta' );
+		'terms', 'term_taxonomy', 'term_relationships', 'termmeta', 'commentmeta' );
 
 	/**
 	 * List of deprecated WordPress tables
@@ -382,6 +382,15 @@ class wpdb {
 	 */
 	public $term_taxonomy;
 
+	/**
+	 * WordPress Term Meta table.
+	 *
+	 * @since 4.4.0
+	 * @access public
+	 * @var string
+	 */
+	public $termmeta;
+
 	/*
 	 * Global and Multisite tables
 	 */
diff --git tests/phpunit/tests/term/getTerms.php tests/phpunit/tests/term/getTerms.php
index b170648..f1db1aa 100644
--- tests/phpunit/tests/term/getTerms.php
+++ tests/phpunit/tests/term/getTerms.php
@@ -22,7 +22,7 @@ class Tests_Term_getTerms extends WP_UnitTestCase {
 		$num_queries = $wpdb->num_queries;
 
 		// last_changed and num_queries should bump
-		$terms = get_terms( 'post_tag' );
+		$terms = get_terms( 'post_tag', array( 'update_term_meta_cache' => false ) );
 		$this->assertEquals( 3, count( $terms ) );
 		$time1 = wp_cache_get( 'last_changed', 'terms' );
 		$this->assertNotEmpty( $time1 );
@@ -31,7 +31,7 @@ class Tests_Term_getTerms extends WP_UnitTestCase {
 		$num_queries = $wpdb->num_queries;
 
 		// Again. last_changed and num_queries should remain the same.
-		$terms = get_terms( 'post_tag' );
+		$terms = get_terms( 'post_tag', array( 'update_term_meta_cache' => false ) );
 		$this->assertEquals( 3, count( $terms ) );
 		$this->assertEquals( $time1, wp_cache_get( 'last_changed', 'terms' ) );
 		$this->assertEquals( $num_queries, $wpdb->num_queries );
@@ -1502,6 +1502,84 @@ class Tests_Term_getTerms extends WP_UnitTestCase {
 		}
 	}
 
+	/**
+	 * @ticket 10142
+	 */
+	public function test_termmeta_cache_should_be_primed_by_default() {
+		global $wpdb;
+
+		register_taxonomy( 'wptests_tax', 'post' );
+		$terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $terms[0], 'foo', 'bar' );
+		add_term_meta( $terms[1], 'foo', 'bar' );
+		add_term_meta( $terms[2], 'foo', 'bar' );
+
+		$found = get_terms( 'wptests_tax', array(
+			'hide_empty' => false,
+			'include' => $terms,
+		) );
+
+		$num_queries = $wpdb->num_queries;
+
+		foreach ( $terms as $t ) {
+			$this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
+		}
+
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+
+	/**
+	 * @ticket 10142
+	 */
+	public function test_termmeta_cache_should_not_be_primed_when_update_term_meta_cache_is_false() {
+		global $wpdb;
+
+		register_taxonomy( 'wptests_tax', 'post' );
+		$terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $terms[0], 'foo', 'bar' );
+		add_term_meta( $terms[1], 'foo', 'bar' );
+		add_term_meta( $terms[2], 'foo', 'bar' );
+
+		$found = get_terms( 'wptests_tax', array(
+			'hide_empty' => false,
+			'include' => $terms,
+			'update_term_meta_cache' => false,
+		) );
+
+		$num_queries = $wpdb->num_queries;
+
+		foreach ( $terms as $t ) {
+			$this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
+		}
+
+		$this->assertSame( $num_queries + 3, $wpdb->num_queries );
+	}
+
+	/**
+	 * @ticket 10142
+	 */
+	public function test_meta_query() {
+		register_taxonomy( 'wptests_tax', 'post' );
+		$terms = $this->factory->term->create_many( 5, array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $terms[0], 'foo', 'bar' );
+		add_term_meta( $terms[1], 'foo', 'bar' );
+		add_term_meta( $terms[2], 'foo', 'baz' );
+		add_term_meta( $terms[3], 'foob', 'ar' );
+
+		$found = get_terms( 'wptests_tax', array(
+			'hide_empty' => false,
+			'meta_query' => array(
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+			),
+			'fields' => 'ids',
+		) );
+
+		$this->assertEqualSets( array( $terms[0], $terms[1] ), $found );
+	}
+
 	protected function create_hierarchical_terms_and_posts() {
 		$terms = array();
 
diff --git tests/phpunit/tests/term/meta.php tests/phpunit/tests/term/meta.php
new file mode 100644
index 0000000..bdd4b36
--- /dev/null
+++ tests/phpunit/tests/term/meta.php
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * @group taxonomy
+ * @group meta
+ * @ticket 10142
+ */
+class Tests_Term_Meta extends WP_UnitTestCase {
+	public function setUp() {
+		parent::setUp();
+		register_taxonomy( 'wptests_tax', 'post' );
+	}
+
+	public function test_add() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+
+		$this->assertNotEmpty( add_term_meta( $t, 'foo', 'bar' ) );
+	}
+
+	public function test_add_unique() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+
+		$this->assertNotEmpty( add_term_meta( $t, 'foo', 'bar' ) );
+		$this->assertFalse( add_term_meta( $t, 'foo', 'bar', true ) );
+	}
+
+	public function test_delete() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $t, 'foo', 'bar' );
+
+		$this->assertTrue( delete_term_meta( $t, 'foo' ) );
+	}
+
+	public function test_delete_with_invalid_meta_key_should_return_false() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+
+		$this->assertFalse( delete_term_meta( $t, 'foo' ) );
+	}
+
+	public function test_delete_should_respect_meta_value() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $t, 'foo', 'bar' );
+		add_term_meta( $t, 'foo', 'baz' );
+
+		$this->assertTrue( delete_term_meta( $t, 'foo', 'bar' ) );
+
+		$metas = get_term_meta( $t, 'foo', false );
+		$this->assertSame( array( 'baz' ), $metas );
+	}
+
+	public function test_get_with_no_key_should_fetch_all_keys() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $t, 'foo', 'bar' );
+		add_term_meta( $t, 'foo1', 'baz' );
+
+		$found = get_term_meta( $t );
+		$expected = array(
+			'foo' => array( 'bar' ),
+			'foo1' => array( 'baz' ),
+		);
+
+		$this->assertEqualSets( $expected, $found );
+	}
+
+	public function test_get_with_key_should_fetch_all_for_key() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $t, 'foo', 'bar' );
+		add_term_meta( $t, 'foo', 'baz' );
+		add_term_meta( $t, 'foo1', 'baz' );
+
+		$found = get_term_meta( $t, 'foo' );
+		$expected = array( 'bar', 'baz' );
+
+		$this->assertEqualSets( $expected, $found );
+	}
+
+	public function test_get_should_respect_single_true() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $t, 'foo', 'bar' );
+		add_term_meta( $t, 'foo', 'baz' );
+
+		$found = get_term_meta( $t, 'foo', true );
+		$this->assertEquals( 'bar', $found );
+	}
+
+	public function test_update_should_pass_to_add_when_no_value_exists_for_key() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+
+		$actual = update_term_meta( $t, 'foo', 'bar' );
+		$this->assertInternalType( 'int', $actual );
+		$this->assertNotEmpty( $actual );
+
+		$meta = get_term_meta( $t, 'foo', true );
+		$this->assertSame( 'bar', $meta );
+	}
+
+	public function test_update_should_return_true_when_updating_existing_value_for_key() {
+		$t = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+
+		add_term_meta( $t, 'foo', 'bar' );
+
+		$actual = update_term_meta( $t, 'foo', 'baz' );
+		$this->assertTrue( $actual );
+
+		$meta = get_term_meta( $t, 'foo', true );
+		$this->assertSame( 'baz', $meta );
+	}
+
+	public function test_term_meta_should_be_lazy_loaded_for_all_terms_in_wp_query_loop() {
+		global $wpdb;
+
+		$p = $this->factory->post->create( array( 'post_status' => 'publish' ) );
+
+		register_taxonomy( 'wptests_tax', 'post' );
+		$terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
+		wp_set_object_terms( $p, $terms, 'wptests_tax' );
+		foreach ( $terms as $t ) {
+			add_term_meta( $t, 'foo', 'bar' );
+		}
+
+		// Create another term, which should *not* be lazy loaded because it's unattached.
+		$orphan_term = $this->factory->term->create( array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $orphan_term, 'foo', 'bar' );
+
+		$this->go_to( get_permalink( $p ) );
+
+		if ( have_posts() ) {
+			while ( have_posts() ) {
+				the_post();
+
+				// First request will hit the database.
+				$num_queries = $wpdb->num_queries;
+				$this->assertSame( 'bar', get_term_meta( $terms[0], 'foo', true ) );
+				$this->assertSame( $num_queries + 1, $wpdb->num_queries );
+
+				// Second and third requests should be in cache.
+				$this->assertSame( 'bar', get_term_meta( $terms[1], 'foo', true ) );
+				$this->assertSame( 'bar', get_term_meta( $terms[2], 'foo', true ) );
+				$this->assertSame( $num_queries + 1, $wpdb->num_queries );
+
+				// Querying a term not primed should result in a hit.
+				$this->assertSame( 'bar', get_term_meta( $orphan_term, 'foo', true ) );
+				$this->assertSame( $num_queries + 2, $wpdb->num_queries );
+			}
+		}
+	}
+}
diff --git tests/phpunit/tests/term/wpGetObjectTerms.php tests/phpunit/tests/term/wpGetObjectTerms.php
index 75d40bb..18ef61c 100644
--- tests/phpunit/tests/term/wpGetObjectTerms.php
+++ tests/phpunit/tests/term/wpGetObjectTerms.php
@@ -418,6 +418,86 @@ class Tests_Term_WpGetObjectTerms extends WP_UnitTestCase {
 		$this->assertEqualSets( array( $t1, $t2 ), $found );
 	}
 
+	/**
+	 * @ticket 10142
+	 */
+	public function test_termmeta_cache_should_be_primed_by_default() {
+		global $wpdb;
+
+		register_taxonomy( 'wptests_tax', 'post' );
+		$terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $terms[0], 'foo', 'bar' );
+		add_term_meta( $terms[1], 'foo', 'bar' );
+		add_term_meta( $terms[2], 'foo', 'bar' );
+
+		$p = $this->factory->post->create();
+		wp_set_object_terms( $p, $terms, 'wptests_tax' );
+
+		$found = wp_get_object_terms( $p, 'wptests_tax' );
+
+		$num_queries = $wpdb->num_queries;
+
+		foreach ( $terms as $t ) {
+			$this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
+		}
+
+		$this->assertSame( $num_queries, $wpdb->num_queries );
+	}
+
+	/**
+	 * @ticket 10142
+	 */
+	public function test_termmeta_cache_should_not_be_primed_when_update_term_meta_cache_is_false() {
+		global $wpdb;
+
+		register_taxonomy( 'wptests_tax', 'post' );
+		$terms = $this->factory->term->create_many( 3, array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $terms[0], 'foo', 'bar' );
+		add_term_meta( $terms[1], 'foo', 'bar' );
+		add_term_meta( $terms[2], 'foo', 'bar' );
+
+		$p = $this->factory->post->create();
+		wp_set_object_terms( $p, $terms, 'wptests_tax' );
+
+		$found = wp_get_object_terms( $p, 'wptests_tax', array(
+			'update_term_meta_cache' => false,
+		) );
+
+		$num_queries = $wpdb->num_queries;
+
+		foreach ( $terms as $t ) {
+			$this->assertSame( 'bar', get_term_meta( $t, 'foo', true ) );
+		}
+
+		$this->assertSame( $num_queries + 3, $wpdb->num_queries );
+	}
+
+	/**
+	 * @ticket 10142
+	 */
+	public function test_meta_query() {
+		register_taxonomy( 'wptests_tax', 'post' );
+		$terms = $this->factory->term->create_many( 5, array( 'taxonomy' => 'wptests_tax' ) );
+		add_term_meta( $terms[0], 'foo', 'bar' );
+		add_term_meta( $terms[1], 'foo', 'bar' );
+		add_term_meta( $terms[2], 'foo', 'baz' );
+		add_term_meta( $terms[3], 'foob', 'ar' );
+
+		$p = $this->factory->post->create();
+		wp_set_object_terms( $p, $terms, 'wptests_tax' );
+
+		$found = wp_get_object_terms( $p, 'wptests_tax', array(
+			'meta_query' => array(
+				array(
+					'key' => 'foo',
+					'value' => 'bar',
+				),
+			),
+		) );
+
+		$this->assertEqualSets( array( $terms[0], $terms[1] ), wp_list_pluck( $found, 'term_id' ) );
+	}
+
 	public function filter_get_object_terms( $terms ) {
 		$term_ids = wp_list_pluck( $terms, 'term_id' );
 		// all terms should still be objects
