do_action( 'deleted_term_taxonomy', $tt_id );
// Delete the term if no taxonomies use it.
if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->term_taxonomy WHERE term_id = %d", $term ) ) ) {
$wpdb->delete( $wpdb->terms, array( 'term_id' => $term ) );
clean_term_cache( $term, $taxonomy );
* Fires after a term is deleted from the database and the cache is cleaned.
* The {@see 'delete_$taxonomy'} hook is also available for targeting a specific
* @since 4.5.0 Introduced the `$object_ids` argument.
* @param int $term Term ID.
* @param int $tt_id Term taxonomy ID.
* @param string $taxonomy Taxonomy slug.
* @param WP_Term $deleted_term Copy of the already-deleted term.
* @param array $object_ids List of term object IDs.
do_action( 'delete_term', $term, $tt_id, $taxonomy, $deleted_term, $object_ids );
* Fires after a term in a specific taxonomy is deleted.
* The dynamic portion of the hook name, `$taxonomy`, refers to the specific
* taxonomy the term belonged to.
* @since 4.5.0 Introduced the `$object_ids` argument.
* @param int $term Term ID.
* @param int $tt_id Term taxonomy ID.
* @param WP_Term $deleted_term Copy of the already-deleted term.
* @param array $object_ids List of term object IDs.
do_action( "delete_{$taxonomy}", $term, $tt_id, $deleted_term, $object_ids );
* Deletes one existing category.
* @param int $cat_ID Category term ID.
* @return bool|int|WP_Error Returns true if completes delete action; false if term doesn't exist;
* Zero on attempted deletion of default Category; WP_Error object is also a possibility.
function wp_delete_category( $cat_ID ) {
return wp_delete_term( $cat_ID, 'category' );
* Retrieves the terms associated with the given object(s), in the supplied taxonomies.
* @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. When `$fields` is 'all' or
* 'all_with_object_id', an array of `WP_Term` objects will be returned.
* @since 4.7.0 Refactored to use WP_Term_Query, and to support any WP_Term_Query arguments.
* @param int|int[] $object_ids The ID(s) of the object(s) to retrieve.
* @param string|string[] $taxonomies The taxonomy names to retrieve terms from.
* @param array|string $args See WP_Term_Query::__construct() for supported arguments.
* @return WP_Term[]|WP_Error Array of terms or empty array if no terms found.
* WP_Error if any of the taxonomies don't exist.
function wp_get_object_terms( $object_ids, $taxonomies, $args = array() ) {
if ( empty( $object_ids ) || empty( $taxonomies ) ) {
if ( ! is_array( $taxonomies ) ) {
$taxonomies = array( $taxonomies );
foreach ( $taxonomies as $taxonomy ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
if ( ! is_array( $object_ids ) ) {
$object_ids = array( $object_ids );
$object_ids = array_map( 'intval', $object_ids );
$args = wp_parse_args( $args );
* Filters arguments for retrieving object terms.
* @param array $args An array of arguments for retrieving terms for the given object(s).
* See {@see wp_get_object_terms()} for details.
* @param int[] $object_ids Array of object IDs.
* @param string[] $taxonomies Array of taxonomy names to retrieve terms from.
$args = apply_filters( 'wp_get_object_terms_args', $args, $object_ids, $taxonomies );
* When one or more queried taxonomies is registered with an 'args' array,
* those params override the `$args` passed to this function.
if ( count( $taxonomies ) > 1 ) {
foreach ( $taxonomies as $index => $taxonomy ) {
$t = get_taxonomy( $taxonomy );
if ( isset( $t->args ) && is_array( $t->args ) && array_merge( $args, $t->args ) != $args ) {
unset( $taxonomies[ $index ] );
$terms = array_merge( $terms, wp_get_object_terms( $object_ids, $taxonomy, array_merge( $args, $t->args ) ) );
$t = get_taxonomy( $taxonomies[0] );
if ( isset( $t->args ) && is_array( $t->args ) ) {
$args = array_merge( $args, $t->args );
$args['taxonomy'] = $taxonomies;
$args['object_ids'] = $object_ids;
// Taxonomies registered without an 'args' param are handled here.
if ( ! empty( $taxonomies ) ) {
$terms_from_remaining_taxonomies = get_terms( $args );
// Array keys should be preserved for values of $fields that use term_id for keys.
if ( ! empty( $args['fields'] ) && 0 === strpos( $args['fields'], 'id=>' ) ) {
$terms = $terms + $terms_from_remaining_taxonomies;
$terms = array_merge( $terms, $terms_from_remaining_taxonomies );
* Filters the terms for a given object or objects.
* @param WP_Term[] $terms Array of terms for the given object or objects.
* @param int[] $object_ids Array of object IDs for which terms were retrieved.
* @param string[] $taxonomies Array of taxonomy names from which terms were retrieved.
* @param array $args Array of arguments for retrieving terms for the given
* object(s). See wp_get_object_terms() for details.
$terms = apply_filters( 'get_object_terms', $terms, $object_ids, $taxonomies, $args );
$object_ids = implode( ',', $object_ids );
$taxonomies = "'" . implode( "', '", array_map( 'esc_sql', $taxonomies ) ) . "'";
* Filters the terms for a given object or objects.
* The `$taxonomies` parameter passed to this filter is formatted as a SQL fragment. The
* {@see 'get_object_terms'} filter is recommended as an alternative.
* @param WP_Term[] $terms Array of terms for the given object or objects.
* @param string $object_ids Comma separated list of object IDs for which terms were retrieved.
* @param string $taxonomies SQL fragment of taxonomy names from which terms were retrieved.
* @param array $args Array of arguments for retrieving terms for the given
* object(s). See wp_get_object_terms() for details.
return apply_filters( 'wp_get_object_terms', $terms, $object_ids, $taxonomies, $args );
* Add a new term to the database.
* A non-existent term is inserted in the following sequence:
* 1. The term is added to the term table, then related to the taxonomy.
* 2. If everything is correct, several actions are fired.
* 3. The 'term_id_filter' is evaluated.
* 4. The term cache is cleaned.
* 5. Several more actions are fired.
* 6. An array is returned containing the `term_id` and `term_taxonomy_id`.
* If the 'slug' argument is not empty, then it is checked to see if the term
* is invalid. If it is not a valid, existing term, it is added and the term_id
* If the taxonomy is hierarchical, and the 'parent' argument is not empty,
* the term is inserted and the term_id will be given.
* If `$taxonomy` does not exist or `$term` is empty,
* a WP_Error object will be returned.
* If the term already exists on the same hierarchical level,
* or the term slug and name are not unique, a WP_Error object will be returned.
* @global wpdb $wpdb WordPress database abstraction object.
* @param string $term The term name to add.
* @param string $taxonomy The taxonomy to which to add the term.
* @param array|string $args {
* Optional. Array or query string of arguments for inserting a term.
* @type string $alias_of Slug of the term to make this term an alias of.
* Default empty string. Accepts a term slug.
* @type string $description The term description. Default empty string.
* @type int $parent The id of the parent term. Default 0.
* @type string $slug The term slug to use. Default empty string.
* @return array|WP_Error {
* An array of the new term data, WP_Error otherwise.
* @type int $term_id The new term ID.
* @type int|string $term_taxonomy_id The new term taxonomy ID. Can be a numeric string.
function wp_insert_term( $term, $taxonomy, $args = array() ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
return new WP_Error( 'invalid_taxonomy', __( 'Invalid taxonomy.' ) );
* Filters a term before it is sanitized and inserted into the database.
* @param string|WP_Error $term The term name to add, or a WP_Error object if there's an error.
* @param string $taxonomy Taxonomy slug.
$term = apply_filters( 'pre_insert_term', $term, $taxonomy );
if ( is_wp_error( $term ) ) {
if ( is_int( $term ) && 0 === $term ) {
return new WP_Error( 'invalid_term_id', __( 'Invalid term ID.' ) );
if ( '' === trim( $term ) ) {
return new WP_Error( 'empty_term_name', __( 'A name is required for this term.' ) );
$args = wp_parse_args( $args, $defaults );
if ( (int) $args['parent'] > 0 && ! term_exists( (int) $args['parent'] ) ) {
return new WP_Error( 'missing_parent', __( 'Parent term does not exist.' ) );
$args['taxonomy'] = $taxonomy;
// Coerce null description to strings, to avoid database errors.
$args['description'] = (string) $args['description'];
$args = sanitize_term( $args, $taxonomy, 'db' );
// expected_slashed ($name)
$name = wp_unslash( $args['name'] );
$description = wp_unslash( $args['description'] );
$parent = (int) $args['parent'];
$slug_provided = ! empty( $args['slug'] );
if ( ! $slug_provided ) {
$slug = sanitize_title( $name );
if ( $args['alias_of'] ) {
$alias = get_term_by( 'slug', $args['alias_of'], $taxonomy );
if ( ! empty( $alias->term_group ) ) {
// The alias we want is already in a group, so let's use that one.
$term_group = $alias->term_group;
} elseif ( ! empty( $alias->term_id ) ) {
* The alias is not in a group, so we create a new one
* and add the alias to it.
$term_group = $wpdb->get_var( "SELECT MAX(term_group) FROM $wpdb->terms" ) + 1;
'term_group' => $term_group,
* Prevent the creation of terms with duplicate names at the same level of a taxonomy hierarchy,
* unless a unique slug has been explicitly provided.
$name_matches = get_terms(
'parent' => $args['parent'],
'update_term_meta_cache' => false,
* The `name` match in `get_terms()` doesn't differentiate accented characters,
* so we do a stricter comparison here.
foreach ( $name_matches as $_match ) {
if ( strtolower( $name ) === strtolower( $_match->name ) ) {
$slug_match = get_term_by( 'slug', $slug, $taxonomy );
if ( ! $slug_provided || $name_match->slug === $slug || $slug_match ) {
if ( is_taxonomy_hierarchical( $taxonomy ) ) {
'update_term_meta_cache' => false,
$sibling_names = wp_list_pluck( $siblings, 'name' );
$sibling_slugs = wp_list_pluck( $siblings, 'slug' );
if ( ( ! $slug_provided || $name_match->slug === $slug ) && in_array( $name, $sibling_names, true ) ) {
$existing_term = $name_match;
} elseif ( $slug_match && in_array( $slug, $sibling_slugs, true ) ) {
$existing_term = $slug_match;
return new WP_Error( 'term_exists', __( 'A term with the name provided already exists with this parent.' ), $existing_term->term_id );
return new WP_Error( 'term_exists', __( 'A term with the name provided already exists in this taxonomy.' ), $name_match->term_id );
$slug = wp_unique_term_slug( $slug, (object) $args );
$data = compact( 'name', 'slug', 'term_group' );
* Filters term data before it is inserted into the database.
* @param array $data Term data to be inserted.
* @param string $taxonomy Taxonomy slug.
* @param array $args Arguments passed to wp_insert_term().
$data = apply_filters( 'wp_insert_term_data', $data, $taxonomy, $args );
if ( false === $wpdb->insert( $wpdb->terms, $data ) ) {
return new WP_Error( 'db_insert_error', __( 'Could not insert term into the database.' ), $wpdb->last_error );
$term_id = (int) $wpdb->insert_id;
// Seems unreachable. However, is used in the case that a term name is provided, which sanitizes to an empty string.
$slug = sanitize_title( $slug, $term_id );
/** This action is documented in wp-includes/taxonomy.php */
do_action( 'edit_terms', $term_id, $taxonomy );
$wpdb->update( $wpdb->terms, compact( 'slug' ), compact( 'term_id' ) );
/** This action is documented in wp-includes/taxonomy.php */
do_action( 'edited_terms', $term_id, $taxonomy );
$tt_id = $wpdb->get_var( $wpdb->prepare( "SELECT tt.term_taxonomy_id FROM $wpdb->term_taxonomy AS tt INNER JOIN $wpdb->terms AS t ON tt.term_id = t.term_id WHERE tt.taxonomy = %s AND t.term_id = %d", $taxonomy, $term_id ) );
if ( ! empty( $tt_id ) ) {
'term_taxonomy_id' => $tt_id,
if ( false === $wpdb->insert( $wpdb->term_taxonomy, compact( 'term_id', 'taxonomy', 'description', 'parent' ) + array( 'count' => 0 ) ) ) {
return new WP_Error( 'db_insert_error', __( 'Could not insert term taxonomy into the database.' ), $wpdb->last_error );
$tt_id = (int) $wpdb->insert_id;
* Sanity check: if we just created a term with the same parent + taxonomy + slug but a higher term_id than
* an existing term, then we have unwittingly created a duplicate term. Delete the dupe, and use the term_id
* and term_taxonomy_id of the older term instead. Then return out of the function so that the "create" hooks
$duplicate_term = $wpdb->get_row( $wpdb->prepare( "SELECT t.term_id, t.slug, tt.term_taxonomy_id, tt.taxonomy FROM $wpdb->terms t INNER JOIN $wpdb->term_taxonomy tt ON ( tt.term_id = t.term_id ) WHERE t.slug = %s AND tt.parent = %d AND tt.taxonomy = %s AND t.term_id < %d AND tt.term_taxonomy_id != %d", $slug, $parent, $taxonomy, $term_id, $tt_id ) );
* Filters the duplicate term check that takes place during term creation.
* Term parent+taxonomy+slug combinations are meant to be unique, and wp_insert_term()
* performs a last-minute confirmation of this uniqueness before allowing a new term
* to be created. Plugins with different uniqueness requirements may use this filter
* to bypass or modify the duplicate-term check.
* @param object $duplicate_term Duplicate term row from terms table, if found.
* @param string $term Term being inserted.
* @param string $taxonomy Taxonomy name.
* @param array $args Term arguments passed to the function.
* @param int $tt_id term_taxonomy_id for the newly created term.
$duplicate_term = apply_filters( 'wp_insert_term_duplicate_term_check', $duplicate_term, $term, $taxonomy, $args, $tt_id );
$wpdb->delete( $wpdb->terms, array( 'term_id' => $term_id ) );
$wpdb->delete( $wpdb->term_taxonomy, array( 'term_taxonomy_id' => $tt_id ) );
$term_id = (int) $duplicate_term->term_id;
$tt_id = (int) $duplicate_term->term_taxonomy_id;
clean_term_cache( $term_id, $taxonomy );
'term_taxonomy_id' => $tt_id,
* Fires immediately after a new term is created, before the term cache is cleaned.
* The {@see 'create_$taxonomy'} hook is also available for targeting a specific
* @param int $term_id Term ID.
* @param int $tt_id Term taxonomy ID.
* @param string $taxonomy Taxonomy slug.
do_action( 'create_term', $term_id, $tt_id, $taxonomy );
* Fires after a new term is created for a specific taxonomy.
* The dynamic portion of the hook name, `$taxonomy`, refers
* to the slug of the taxonomy the term was created for.
* @param int $term_id Term ID.
* @param int $tt_id Term taxonomy ID.
do_action( "create_{$taxonomy}", $term_id, $tt_id );
* Filters the term ID after a new term is created.
* @param int $term_id Term ID.
* @param int $tt_id Term taxonomy ID.
$term_id = apply_filters( 'term_id_filter', $term_id, $tt_id );
clean_term_cache( $term_id, $taxonomy );
* Fires after a new term is created, and after the term cache has been cleaned.
* The {@see 'created_$taxonomy'} hook is also available for targeting a specific