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 を自分で確認する。