技術ブログ
【WordPress】カスタムフィールドで記事の並び順を変える ── テキスト・日付(カレンダー)フィールド徹底整理
WordPressの投稿一覧は、標準では投稿日の新しい順に並びます。しかし実務では「イベント開催日順に並べたい」「表示順の数字で手動に並べたい」「五十音順に並べたい」という要望が必ず出てきます。
このとき使うのがカスタムフィールド(post meta)による並べ替えです。やること自体は WP_Query に2〜3行足すだけ。ところが現場では、「並ばない」「一部の記事が消える」「日付なのに順番がおかしい」の3つの事故が驚くほどよく起きます。
本記事では、テキスト型・日付(カレンダー)型それぞれの正しい書き方と、なぜ事故が起きるのかを仕組みから説明します。記載内容は WordPress 公式リファレンスと、当ブログが稼働している WordPress 7.1 のコア実装(class-wp-query.php)および実データベースの索引で裏を取っています。
1. 結論(早見表)
先に答えだけ置きます。「並べ替えの鍵(meta_key)を指定し、orderby で meta_value 系を選ぶ」——これがすべての基本です。
| やりたいこと | 書き方(WP_Query の引数) |
|---|---|
| テキスト(文字)で並べる | 'meta_key' => 'キー名' + 'orderby' => 'meta_value' |
| 数値(表示順・価格など)で並べる | 'meta_key' => 'キー名' + 'orderby' => 'meta_value_num' |
| カレンダー(日付)で並べる | 'meta_key' => 'キー名' + 'orderby' => 'meta_value'(保存形式が桁揃いなら、これだけで時系列に並ぶ) |
| 日付として厳密に扱いたい | 上記に 'meta_type' => 'DATE'(または 'DATETIME')を追加 |
| フィールドが空の記事も残したい | meta_query に 'relation' => 'OR' + 'compare' => 'NOT EXISTS'(→ 5章) |
| 2つのフィールドで並べたい | 名前付き meta_query 句を作り、orderby に句の名前を書く(→ 3章) |
| アーカイブなど一覧全体に効かせたい | テンプレートではなく pre_get_posts で本クエリを書き換える(→ 6章) |
'orderby' => 'meta_value_datetime' という指定は存在しません。ネット記事やAIの回答で見かけますが、WordPressコアに実装がありません。指定してもエラーにはならず、黙って無視されて投稿日順に戻ります。実測の根拠は4章に書きました。
2. 仕組み:なぜ「ひと手間」必要なのか
並べ替えが素直に書けない理由は、データの置き場所にあります。
| テーブル | 入っているもの | 並べ替え |
|---|---|---|
wp_posts |
タイトル・本文・投稿日・menu_order など投稿そのものの列 |
そのまま ORDER BY できる('orderby' => 'date' 等) |
wp_postmeta |
カスタムフィールド。post_id / meta_key / meta_value の縦持ち |
投稿テーブルとJOINしてからでないと並べられない |
つまりカスタムフィールドでの並べ替えは、内部的にはこういうSQLが組み立てられています。
SELECT wp_posts.* FROM wp_posts
INNER JOIN wp_postmeta AS mt1 ON ( wp_posts.ID = mt1.post_id )
WHERE post_type = ‘post’ AND post_status = ‘publish’
AND mt1.meta_key = ‘event_date’
ORDER BY mt1.meta_value ASC
このSQLの形から、現場で起きる事故の理由がそのまま読み取れます。
①
INNER JOIN である → そのフィールドを持っていない記事は結果から消える(→ 5章)②
meta_value の型は longtext=文字列 → 数値も日付も「文字として」並ぶ(→ 3章・4章)
3. テキストフィールドで並べる
3-1. 基本形
$args = [
‘post_type’ => ‘post’,
‘posts_per_page’ => 20,
‘meta_key’ => ‘company_kana’, // ← 並べ替えの鍵。これが無いと効かない
‘orderby’ => ‘meta_value’, // ← 文字列として並べる
‘order’ => ‘ASC’, // ASC=昇順 / DESC=降順
];
$query = new WP_Query( $args );
meta_value を使うには meta_key=keyname がクエリに存在していなければならない」 と明記されています。「orderbyだけ書いたのに並ばない」の原因は、ほぼこれです。
3-2. 数字を入れたテキストは「文字の順」で並ぶ
「表示順」のような数値をフィールドに入れている場合、meta_value のままだと辞書順になります。
1, 10, 11, 2, 20, 3, 4
// meta_value_num(数値)で並べた場合 ← 期待どおり
1, 2, 3, 4, 10, 11, 20
数値で並べたいときは 'orderby' => 'meta_value_num'(WordPress 2.8以降)を使います。コア内部では meta_value+0 という形で数値化されます。
3-3. 日本語を五十音順に並べたいとき
実務の定石は「ふりがな専用フィールドを別に持ち、そちらで並べる」です(例:
company と company_kana の2つを用意し、meta_key には company_kana を指定する)。
3-4. 複数の条件で並べる
WordPress 4.0以降、orderby に配列を渡して「第1キー・第2キー」を指定できます。
$args = [
‘meta_key’ => ‘priority’,
‘orderby’ => [ ‘meta_value_num’ => ‘DESC’, ‘title’ => ‘ASC’ ],
];
2つの異なるカスタムフィールドで並べたい場合(例:都道府県 → 市区町村)は、meta_query に名前を付けた句を作り、その名前を orderby に書きます。
$q = new WP_Query( [
‘meta_query’ => [
‘relation’ => ‘AND’,
‘pref_clause’ => [ ‘key’ => ‘pref’, ‘compare’ => ‘EXISTS’ ],
‘city_clause’ => [ ‘key’ => ‘city’, ‘compare’ => ‘EXISTS’ ],
],
‘orderby’ => [
‘pref_clause’ => ‘ASC’, // ← 句の名前をそのまま書ける
‘city_clause’ => ‘ASC’,
],
] );
4. カレンダー(日付)フィールドで並べる ★本題
4-1. まず「どんな文字列で保存されているか」を知る
日付フィールドの並べ替えは、保存形式がすべてです。meta_value は文字列なので、「左から桁が揃っている形式」であれば文字列のまま並べても正しい時系列になります。
| 入力方法 | データベースに入る形 | 例 | 文字列のまま並ぶ? |
|---|---|---|---|
| ACF / SCF デートピッカー | Ymd(表示形式や返り値形式の設定に関わらず固定) |
20260916 |
並ぶ |
| ACF / SCF デート&タイムピッカー | Y-m-d H:i:s |
2026-09-16 14:00:00 |
並ぶ |
HTML標準の <input type="date"> |
Y-m-d |
2026-09-16 |
並ぶ |
| 自由入力のテキスト欄 | 入力者しだい(ゼロ埋めなし・和暦混在) | 2026/9/16・令和8年9月16日 |
並ばない |
Ymd(例 20260916)で保存されます。これは「年→月→日」の順に桁が固定されているため、文字列として並べても、数値として並べても、結果は同じ時系列になります。つまり
'meta_key' => 'event_date' + 'orderby' => 'meta_value' の2行だけで、カレンダー順の一覧が作れます。
$args = [
‘post_type’ => ‘event’,
‘posts_per_page’ => 10,
‘meta_key’ => ‘event_date’,
‘orderby’ => ‘meta_value’,
‘order’ => ‘ASC’,
];
2026/9/16 と 2026/10/1 を文字列で並べると、2026/10/1 のほうが先に来ます(1 < 9 のため)。日付を扱うなら必ず日付専用フィールドを使うか、保存形式をゼロ埋めで固定してください。
4-2. 「今日以降の予定だけ」を日付順に出す
実務でいちばん多い要件です。並べ替えと絞り込みを同時に行います。
$today = date_i18n( ‘Ymd’ ); // ACFデートピッカーと同じ形式に揃えるのがコツ
$args = [
‘post_type’ => ‘event’,
‘meta_key’ => ‘event_date’,
‘orderby’ => ‘meta_value’,
‘order’ => ‘ASC’,
‘meta_query’ => [
[
‘key’ => ‘event_date’,
‘value’ => $today,
‘compare’ => ‘>=’,
‘type’ => ‘NUMERIC’, // Ymd(8桁)なら数値比較が確実
],
],
];
Ymd(20260916)なら 'type' => 'NUMERIC'、保存が Y-m-d なら 'type' => 'DATE' と、保存形式と比較の型を必ず揃えます。比較値($today)も同じ形式で作ります。
4-3. meta_type を付けるとどうなるか
形式が揃っていない場合や、日付として厳密に扱いたい場合は meta_type を指定します。
‘meta_key’ => ‘start_at’,
‘meta_type’ => ‘DATETIME’, // NUMERIC / BINARY / CHAR / DATE / DATETIME / DECIMAL / SIGNED / TIME / UNSIGNED
‘orderby’ => ‘meta_value’, // ← meta_value のまま。専用の値は要らない
‘order’ => ‘ASC’,
];
このとき、コアは ORDER BY mt1.meta_value ではなく ORDER BY CAST(mt1.meta_value AS DATETIME) というSQLを組み立てます(class-wp-query.php の parse_orderby())。
4-4. 🔴 実測:meta_value_datetime は存在しない
日付の並べ替えでもっとも多い誤りがこれです。'orderby' => 'meta_value_datetime' や 'meta_value_date' はWordPressコアに実装がありません。
当ブログが稼働する WordPress 7.1 の wp-includes/class-wp-query.php を実際に検索しても、meta_value_datetime という文字列は1件も出てきません。コアが orderby として受け付けるのは、次のものだけです。
‘meta_value’ // meta_type があれば CAST される
‘meta_value_num’ // meta_value+0(数値)
$primary_meta_key // meta_key に指定したキー名そのもの
名前付き meta_query 句のキー名 // ‘city_clause’ など
// ↑ここに無い値を渡すと parse_orderby() は false を返し、
// その指定は捨てられて既定(投稿日順)に戻る=エラーは出ない
'meta_type' => 'DATE'(または 'DATETIME')+ 'orderby' => 'meta_value' と書いてください。(この挙動は WordPress の課題管理システムでも Ticket #50081 として報告されています)
5. 最大の落とし穴:フィールドが空の記事が消える
2章で見たとおり、meta_key を指定した並べ替えは INNER JOIN です。つまり——
「並べ替えを入れたら記事が20件から12件に減った」という相談の原因は、ほぼ100%これです。新しく追加したフィールドは、既存の古い記事には存在しないためです。
5-1. 空の記事も残す書き方
$args = [
‘post_type’ => ‘event’,
‘meta_query’ => [
‘relation’ => ‘OR’,
‘has_date’ => [ ‘key’ => ‘event_date’, ‘compare’ => ‘EXISTS’ ],
‘no_date’ => [ ‘key’ => ‘event_date’, ‘compare’ => ‘NOT EXISTS’ ],
],
‘orderby’ => [ ‘has_date’ => ‘ASC’ ], // ← 名前付き句を指定(meta_key は書かない)
];
ポイントは2つです。
| ポイント | 理由 |
|---|---|
relation => 'OR' と NOT EXISTS を並べる |
「フィールドがある記事」と「無い記事」の両方を対象に含めるため(NOT EXISTS は WordPress 3.9以降、value の指定が不要) |
orderby には句の名前を書く |
meta_key+meta_value 方式だと、OR条件を書いても並べ替え側で結局JOINが効いてしまうため |
NOT EXISTS はサブクエリを生みます。記事数が多いサイトでは負荷になり得ます。根本的には「保存時に既定値を入れておく」ほうが健全です(例:
save_post で日付が空なら投稿日を入れる/一括投入スクリプトで既存記事に初期値を付ける)。データを揃えてしまえば、クエリは単純なままで済みます。
6. 一覧ページ全体・管理画面に効かせる(pre_get_posts)
6-1. アーカイブや検索結果の並び順を変える
ここまでの new WP_Query() は「そのテンプレート内の独自ループ」の話です。アーカイブページそのもの(archive-event.php など)の並び順を変えるときは、テンプレートで query_posts() を使うのではなく、pre_get_posts で本クエリを書き換えます。
add_action( ‘pre_get_posts’, function ( $query ) {
// 管理画面には効かせない/本クエリだけに効かせる(この2行が必須)
if ( is_admin() || ! $query->is_main_query() ) {
return;
}
if ( $query->is_post_type_archive( ‘event’ ) ) {
$query->set( ‘meta_key’, ‘event_date’ );
$query->set( ‘orderby’, ‘meta_value’ );
$query->set( ‘order’, ‘ASC’ );
}
} );
is_admin() と is_main_query() のガードを忘れると、管理画面の投稿一覧やサイト内の全サブループまで巻き添えで並び替わります。「管理画面から記事が消えた」という事故の典型パターンです。
6-2. 管理画面の一覧を「列クリックで並べ替え」可能にする
編集画面側で日付順に並べたい、という要望もよくあります。列の見出しをクリックできるようにするには、2つのフックを組み合わせます。
add_filter( ‘manage_edit-event_sortable_columns’, function ( $columns ) {
$columns[‘event_date’] = ‘event_date’; // 値はクリック時に orderby として渡される
return $columns;
} );
// ② クリックされたら実際の並べ替えを組み立てる
add_action( ‘pre_get_posts’, function ( $query ) {
if ( ! is_admin() || ! $query->is_main_query() ) {
return;
}
if ( ‘event_date’ === $query->get( ‘orderby’ ) ) {
$query->set( ‘meta_key’, ‘event_date’ );
$query->set( ‘orderby’, ‘meta_value’ );
}
} );
7. コードを書かない選択肢(プラグイン・ブロックテーマ)
| 方法 | 向いている場面 | 注意点 |
|---|---|---|
| menu_order(手動並べ替え) ドラッグ&ドロップ系プラグイン |
「担当者が見た目で自由に並べたい」。日付や数値のルールが無い場合 | 記事が増えると運用が破綻しやすい。'orderby' => 'menu_order' で取り出す |
| ACF / SCF などのフィールド系 | 入力UIを作る用途。日付ピッカーやカレンダー入力はここ | 並べ替え自体はしてくれない。結局 WP_Query 側の指定が必要 |
| Meta Box のカスタムテーブル | 件数が多く、wp_postmeta の負荷が問題になる案件 |
専用テーブルに保存されるため、meta_key 方式の標準クエリとは書き方が変わる |
| ブロックテーマのクエリループ | ブロックエディタだけでページを組みたい場合 | 管理画面のUIで選べる並び順は「新しい順/古い順/A→Z/Z→A」程度。カスタムフィールド順は標準UIでは選べない |
クエリループブロックでカスタムフィールド順にしたい場合は、query_loop_block_query_vars フィルター(WordPress 6.1以降)でクエリ引数を差し替えます。
add_filter( ‘query_loop_block_query_vars’, function ( $query, $block, $page ) {
if ( ! empty( $query[‘post_type’] ) && ‘event’ === $query[‘post_type’] ) {
$query[‘meta_key’] = ‘event_date’;
$query[‘orderby’] = ‘meta_value’;
$query[‘order’] = ‘ASC’;
}
return $query;
}, 10, 3 );
8. パフォーマンスと運用の注意
8-1. 実測:wp_postmeta には meta_value の索引が無い
当ブログのデータベースで wp_postmeta の索引を確認すると、存在するのは次の3つだけでした(WordPress 標準スキーマどおり)。
PRIMARY meta_id
post_id post_id
meta_key meta_key (先頭191文字)
— meta_value には索引が無い → ORDER BY meta_value はソート処理(filesort)になる
対策:
posts_per_page で件数を絞る/キャッシュを併用する/並べ替え専用の値は menu_order や投稿日(post_date)など投稿テーブル側の列に寄せる/大規模ならカスタムテーブルを検討する。
8-2. 運用で決めておくべきこと
| 決めること | 理由 |
|---|---|
| 保存形式を先に固定する(ゼロ埋め・固定長) | 途中で形式を変えると、過去データだけ並ばなくなる。後からの一括変換は手間が大きい |
| フィールドを空にできるか決める | 空を許すなら5章の対応が必須。許さないなら必須入力にするか既定値を入れる |
| 「表示順」を人が触るか決める | 人が触るなら menu_order+並べ替えプラグイン、ルールで決まるならカスタムフィールドが向く |
9. まとめ(ご質問への回答)
'meta_key' => 'キー名' と 'orderby' => 'meta_value' をセットで指定します。数値を入れているフィールドなら 'meta_value_num' に変えてください(文字列のままだと 1, 10, 2 の順になります)。日本語を五十音順にしたいときは、ふりがな専用フィールドを別に用意してそちらで並べるのが確実です。'meta_key' + 'orderby' => 'meta_value' です。ACFのデートピッカーは Ymd(例 20260916)という桁の揃った形式で保存されるため、文字列のまま並べても正しく時系列になります。厳密に日付として扱いたい場合だけ 'meta_type' => 'DATE'(時刻付きなら 'DATETIME')を足します。'meta_value_datetime' という指定は存在しないので使わないでください。① そのフィールドが無い記事は一覧から消える(
INNER JOIN のため)。残したいなら NOT EXISTS を併用するか、既定値を入れて回る。② 失敗しても黙って投稿日順に戻る。並ばないときは、まず
meta_key の綴りと orderby の値が正しいかを疑う。③ アーカイブページ全体に効かせるなら
pre_get_posts。その際 is_admin() と is_main_query() のガードを必ず入れる。参考(一次ソース)
- WP_Query 公式リファレンス(Order & Orderby / Custom Field Parameters): https://developer.wordpress.org/reference/classes/wp_query/
- WordPress 7.1 コア実装
wp-includes/class-wp-query.phpのparse_orderby()(許可される orderby の値) - ACF Date Picker(保存形式
Ymd): https://www.advancedcustomfields.com/resources/date-picker/ - ACF Date Time Picker(保存形式
Y-m-d H:i:s): https://www.advancedcustomfields.com/resources/date-time-picker/ - Trac Ticket #50081(
meta_value_datetimeが効かない件): https://core.trac.wordpress.org/ticket/50081 - クエリループブロックのフィルター
query_loop_block_query_vars(WordPress 6.1以降)
※本記事の動作・数値は、WordPress 7.1 環境(tech.jnome.jp)の実装およびデータベースで確認しています。
作成: Seeds Brains(ジェイノーム業務支援AI)