2026年8月31日

2026年8月31日

WordPressのwp_insert_term()とwp_set_post_terms()でタームを作成・紐付けする方法

プログラムからカテゴリーやタグを作るには wp_insert_term()、作ったタームを投稿に付けるには wp_set_post_terms() を使います。どちらも短く書けますが、「戻り値が WP_Error になる場合」「階層型タクソノミーでは名前ではなくIDを渡す」「第4引数を忘れると既存のタームが外れる」という三つの落とし穴があります。この記事では、二つの関数の引数と戻り値、CSVインポートなどで使える「なければ作って付ける」処理、大量処理と権限の注意点を説明します。

wp_insert_term() の基本

wp_insert_term( string $term, string $taxonomy, array $args = array() )

第1引数がターム名、第2引数がタクソノミー名、第3引数で次の項目を指定できます。

  • slug:URLに使うスラッグ。省略すると名前から作られます。日本語名のまま作るとスラッグがURLエンコードされた長い文字列になるので、英数字で指定しておくと管理しやすくなります。
  • parent:親タームのID。階層型タクソノミー(カテゴリーなど)でのみ意味を持ちます。
  • description:説明文。
  • alias_of:別のタームと同じグループにまとめるための指定で、通常は使いません。

成功すると term_id と term_taxonomy_id を持つ配列が返り、失敗すると WP_Error が返ります。よくある失敗は次のとおりです。

  • term_exists:同じ名前(階層型なら同じ親の下で同じ名前)のタームがすでにある。エラーデータに既存のタームIDが入っています。
  • invalid_taxonomy:タクソノミーが登録されていない。init より前に実行した場合にも起こります。
  • empty_term_name:名前が空。
<?php
$result = wp_insert_term(
	'お知らせ',
	'category',
	array(
		'slug'        => 'news',
		'description' => 'サイトからのお知らせ',
	)
);

if ( is_wp_error( $result ) ) {
	if ( 'term_exists' === $result->get_error_code() ) {
		$term_id = (int) $result->get_error_data();
	} else {
		error_log( 'wp_insert_term failed: ' . $result->get_error_message() );
		$term_id = 0;
	}
} else {
	$term_id = (int) $result['term_id'];
}

term_exists を「すでにあるならそのIDを使う」と扱えるようにしておくと、同じ処理を何度実行しても重複が増えません。

wp_set_post_terms() の基本

wp_set_post_terms( int $post_id, string|array $terms, string $taxonomy = 'post_tag', bool $append = false )

投稿ID、付けたいターム、タクソノミー名、追加モードかどうか、の順です。タクソノミーを省略するとタグ(post_tag)として扱われる点に注意してください。カテゴリーに付けるつもりで省略すると、同じ名前のタグが作られてしまいます。

第4引数 $append を忘れると既存のタームが外れる

$append の既定値は false で、この場合は「指定したタームだけにする」という置き換えの動作になります。すでに付いているタグに一つ足したいだけなら true を渡します。

<?php
// 既存のタグを残したまま「セール」を追加する
wp_set_post_terms( $post_id, array( 'セール' ), 'post_tag', true );

// カテゴリーを ID 12 と 15 だけにする(ほかのカテゴリーは外れる)
wp_set_post_terms( $post_id, array( 12, 15 ), 'category' );

階層型タクソノミーにはIDを渡す

最もつまずきやすいのがここです。wp_set_post_terms() は、カテゴリーのような階層型タクソノミーに対しては受け取った値をすべて整数に変換してから処理します。親が違う同名のターム(「東京/イベント」と「大阪/イベント」など)を取り違えないための仕様ですが、その結果、名前の文字列を渡すと意図したタームが付きません。

<?php
// 誤り:カテゴリーに名前を渡しても付かない
wp_set_post_terms( $post_id, array( 'お知らせ' ), 'category' );

// 正しい:IDを渡す
$term = get_term_by( 'slug', 'news', 'category' );
if ( $term ) {
	wp_set_post_terms( $post_id, array( $term->term_id ), 'category', true );
}

逆に、タグのような非階層型タクソノミーでは、文字列は名前として扱われ、存在しなければ新しく作られます。数字だけの文字列('12')を渡すと「12」という名前のタグができることがあるので、IDを渡したいときは (int) で整数にしておきます。

名前やスラッグで階層型タクソノミーにタームを付けたい場合は、wp_set_object_terms() を使う方法もあります。こちらは整数をID、文字列を名前またはスラッグとして扱います。ただし同名のタームが複数あるときにどれが選ばれるかを意識する必要があるため、IDで指定するほうが予測しやすいでしょう。

戻り値は、成功時に付けたタームの term_taxonomy_id の配列、失敗時に false か WP_Error です。

「なければ作って付ける」処理を関数にまとめる

CSVからの移行や外部サービスとの同期では、「ターム名を受け取り、存在すればそれを、なければ作って投稿に付ける」処理が繰り返し必要になります。関数にしておくと再利用しやすくなります。

<?php
/**
 * ターム名から term_id を取得する。存在しなければ作成する。
 *
 * @return int|WP_Error
 */
function myprefix_get_or_create_term_id( $name, $taxonomy, $parent = 0 ) {
	$name = trim( wp_strip_all_tags( $name ) );
	if ( '' === $name ) {
		return new WP_Error( 'empty_name', 'ターム名が空です。' );
	}

	$exists = term_exists( $name, $taxonomy, $parent );
	if ( $exists ) {
		return (int) $exists['term_id'];
	}

	$result = wp_insert_term( $name, $taxonomy, array( 'parent' => (int) $parent ) );
	if ( is_wp_error( $result ) ) {
		if ( 'term_exists' === $result->get_error_code() ) {
			return (int) $result->get_error_data();
		}
		return $result;
	}
	return (int) $result['term_id'];
}

/**
 * 投稿にターム名の配列を追加で付ける。
 */
function myprefix_assign_terms_by_name( $post_id, array $names, $taxonomy ) {
	$ids = array();
	foreach ( $names as $name ) {
		$term_id = myprefix_get_or_create_term_id( $name, $taxonomy );
		if ( is_wp_error( $term_id ) ) {
			error_log( sprintf( 'term error (%s): %s', $taxonomy, $term_id->get_error_message() ) );
			continue;
		}
		$ids[] = $term_id;
	}
	if ( $ids ) {
		return wp_set_post_terms( $post_id, $ids, $taxonomy, true );
	}
	return false;
}

term_exists() はタクソノミーを指定すると term_id と term_taxonomy_id の配列を返し、見つからなければ null を返します。IDで揃えて wp_set_post_terms() に渡しているので、階層型でも非階層型でも同じ関数で扱えます。

実行するタイミングと場所

タクソノミーは init フックで登録されるため、それより前に呼ぶと invalid_taxonomy になります。一度だけ実行したい移行処理を functions.php に書くと、ページを開くたびに実行されてしまいます。単発の処理は次のいずれかで行うのが安全です。

  • WP-CLI の wp eval-file でスクリプトを実行する
  • 管理者だけが実行できる管理画面のボタンや、プラグインの有効化時に一度だけ動かす

WP-CLI には wp term create や wp post term set / wp post term add といったコマンドもあり、少量ならコードを書かずに済みます。オプションの細かな指定はバージョンで変わることがあるので、wp help post term で手元の環境の説明を確認してください(2026年10月現在)。

いずれの方法でも、事前にデータベースのバックアップを取り、ステージング環境で件数と結果を確かめてから本番で実行してください。

大量処理のときの工夫

数千件の投稿にタームを付けると、そのたびにタームの投稿数(count)が再計算されて時間がかかります。wp_defer_term_counting() で再計算を最後にまとめられます。

<?php
wp_defer_term_counting( true );

foreach ( $rows as $row ) {
	myprefix_assign_terms_by_name( (int) $row['post_id'], $row['tags'], 'post_tag' );
}

wp_defer_term_counting( false ); // ここでまとめて再計算される

途中でエラー終了すると再計算が行われないままになるので、件数表示がずれた場合は、もう一度少量で実行するか、WP-CLI の wp term recount で数え直します。

フォームや API から呼ぶときは権限を確認する

wp_insert_term() や wp_set_post_terms() は、呼び出したユーザーの権限を確認しません。ユーザーの入力を受けて実行する処理では、自分で確認する必要があります。

<?php
$tax = get_taxonomy( 'post_tag' );

if ( ! current_user_can( $tax->cap->assign_terms ) || ! current_user_can( 'edit_post', $post_id ) ) {
	wp_die( esc_html( '権限がありません。' ), '', array( 'response' => 403 ) );
}
// 新しいタームを作る場合は $tax->cap->edit_terms も確認する

フォームから送られた値は nonce を確認したうえで sanitize_text_field( wp_unslash( ... ) ) で整えます。投稿者に「既存のタグは付けられるが、新しいタグは作れない」ようにしたい場合は、assign_terms と edit_terms を分けて判定すると実現できます。

関連する関数

  • wp_update_term():名前・スラッグ・親を変更する
  • wp_delete_term():タームを削除する(付いていた投稿からは外れる)
  • wp_remove_object_terms():特定の投稿から指定のタームだけを外す
  • get_the_terms() / wp_get_post_terms():投稿に付いているタームを取得する

まとめ

  • wp_insert_term() は配列か WP_Error を返す。term_exists エラーのデータには既存のIDが入っている。
  • wp_set_post_terms() はタクソノミーを省略するとタグ扱いになり、$append を省略すると置き換えになる。
  • 階層型タクソノミーにはIDを渡す。名前を渡しても付かない。
  • 単発の移行処理は WP-CLI などで一度だけ実行し、大量処理では wp_defer_term_counting() を使う。
  • 入力を受ける処理では assign_terms・edit_terms などの権限と nonce を自分で確認する。

お気軽にご相談ください

お見積りへ お問い合わせへ
▲