2026年8月7日
2026年8月7日
ACFのフィールドをREST APIで取得・更新してWordPressをヘッドレスCMSとして使う方法
ACF(Advanced Custom Fields)は、フィールドグループの設定で REST API への公開を有効にするだけで、/wp-json/wp/v2/ の投稿データに acf というキーでフィールド値を載せられます。この記事では、その有効化から、フロントエンド(Next.js など)での取得、認証付きの更新、オプションページのように標準では出てこない値の扱いまでを、ヘッドレス構成で運用する前提で説明します。
ACFのREST API対応の仕組みを押さえる
ACF は 5.11 以降、WordPress コアの REST API に自分のフィールドを載せる機能を持っています。無料版でも使える機能で、フィールドグループごとに「REST API で表示する」趣旨の設定(表記はバージョンにより異なります)を有効にすると、そのグループが割り当てられた投稿タイプのレスポンスに acf オブジェクトが加わります。初期状態では無効なので、何もしなければ値は API に出てきません。
前提として、投稿タイプ自体が REST API に公開されている必要があります。標準の投稿・固定ページは公開済みですが、register_post_type() で作ったカスタム投稿タイプは 'show_in_rest' => true を指定していないと、エンドポイント自体が存在しません。ACF の管理画面から投稿タイプを作った場合も、詳細設定で REST API 関連の項目を確認してください。
ここで無料版と ACF PRO の違いも整理しておきます。テキスト・数値・画像・関連(リレーションシップ)などの基本的なフィールドは無料版で使えます。一方、繰り返しフィールド(Repeater)、柔軟コンテンツ(Flexible Content)、ギャラリー、オプションページは ACF PRO の機能です(2026年10月現在)。ヘッドレスで「セクションを自由に並べるページ」を作りたい場合は柔軟コンテンツが候補になりますが、PRO が前提になる点を設計の初期に決めておくと手戻りがありません。
取得してみる:レスポンスの形を確認する
設定を有効にしたら、まずブラウザやコマンドラインでレスポンスを見ます。
# 投稿ID 123 のタイトルと ACF の値だけを取得する
curl -s "https://example.com/wp-json/wp/v2/posts/123?_fields=id,title,acf"
# カスタム投稿タイプ(REST のベースが "products" の例)の一覧
curl -s "https://example.com/wp-json/wp/v2/products?per_page=10&_fields=id,slug,acf"
_fields は WordPress コアのパラメーターで、必要なキーだけに絞り込めます。ヘッドレスではレスポンスの大きさがそのまま表示速度に響くため、一覧取得では特に指定しておく価値があります。
レスポンスを見ると、画像フィールドが画像の配列ではなく数値の ID だけで返ってくることがあります。ACF の REST 出力は、初期状態では値をあまり加工しない形式で返します。画像の URL やサイズ別の情報まで欲しい場合は、リクエストに acf_format=standard を付けると、テンプレートで get_field() を呼んだときに近い整形済みの値が返ります。どちらの形式が既定か、どのフィールドがどう変わるかはバージョンで差が出ることがあるので、実際のレスポンスで確かめてから型定義を書いてください。
画面側で取得する:秘密情報をブラウザに渡さない
公開済みの投稿の値は認証なしで取得できるため、フロントエンドからは単純な GET で足ります。Next.js の App Router のようにサーバー側でデータを取れる仕組みなら、そこで取得してHTMLにしてから返すのが基本です。
// app/products/[slug]/page.tsx の一部(Next.js の例)
type Product = {
id: number;
slug: string;
title: { rendered: string };
acf: { price?: number; spec?: string };
};
async function getProduct(slug: string): Promise<Product | null> {
const url = `${process.env.WP_API_BASE}/wp/v2/products?slug=${encodeURIComponent(slug)}&_fields=id,slug,title,acf`;
const res = await fetch(url, { next: { revalidate: 300 } }); // 5分ごとに再検証
if (!res.ok) return null;
const items: Product[] = await res.json();
return items[0] ?? null;
}
注意したいのは、REST API で公開したフィールドは、公開済みの投稿である限り誰でも読めるという点です。仕入れ値や社内メモのような値を同じフィールドグループに入れて REST 公開を有効にすると、そのまま外部から見えてしまいます。公開してよい値と社内向けの値はフィールドグループを分け、社内向けのグループは REST 公開を無効にしておくのが安全です。どうしても同じグループに置く必要がある場合は、コアの rest_prepare_{post_type} フィルターでレスポンスから特定のキーを取り除く方法もあります。
title.rendered や WYSIWYG フィールドの値は HTML を含みます。React で dangerouslySetInnerHTML を使って描画する場合、入力できるのが信頼できる編集者に限られているかを確認し、必要に応じてサニタイズ用のライブラリを通してください。
認証付きで値を更新する
外部システムから ACF の値を書き込みたい場合は、同じエンドポイントに acf キーを含む JSON を送ります。認証には WordPress 5.6 以降のコア機能であるアプリケーションパスワードが手軽です。管理画面のユーザープロフィールから発行でき、通常のログインパスワードとは別に失効させられます。
# 値はダミー。アプリケーションパスワードは環境変数から読む
curl -s -X POST "https://example.com/wp-json/wp/v2/products/123" \
-u "api-editor:${WP_APP_PASSWORD}" \
-H "Content-Type: application/json" \
-d '{"acf": {"price": 4800, "spec": "幅120cm / 奥行60cm"}}'
運用上のポイントは次のとおりです。
- 専用ユーザーを作り、権限を最小にする:書き込み対象の投稿タイプを編集できる権限だけを持つユーザーを用意し、管理者アカウントのアプリケーションパスワードは使いません。
- HTTPS 必須:アプリケーションパスワードは Basic 認証で送られるため、HTTPS でない環境では使えない、または使うべきではありません。
- ブラウザに置かない:フロントエンドの JavaScript にパスワードを埋め込むと、誰でも取り出せます。更新はサーバー側の処理やバッチからだけ行います。
書き込みが反映されない場合は、そのフィールドグループの REST 公開が有効か、送っているキーがフィールド名(ラベルではなく名前)と一致しているか、値の型がフィールドの設定(数値・選択肢など)に合っているかを順に確認します。エラー時のレスポンス本文には原因のヒントが入っているので、ステータスコードだけでなく本文も記録しておくと調査が早くなります。
標準で出てこない値は独自エンドポイントで返す
オプションページ(ACF PRO)の値は特定の投稿に属さないため、投稿のエンドポイントには載りません。サイト全体の電話番号やバナー設定などをフロントに渡したい場合は、register_rest_route() で読み取り専用のエンドポイントを作ります。
<?php
// 子テーマの functions.php か mu-plugin に記述
add_action( 'rest_api_init', function () {
register_rest_route(
'mysite/v1',
'/site-settings',
array(
'methods' => WP_REST_Server::READABLE,
'permission_callback' => '__return_true', // 公開してよい値だけを返す前提
'callback' => function () {
if ( ! function_exists( 'get_field' ) ) {
return new WP_Error( 'acf_missing', 'ACF が無効です', array( 'status' => 500 ) );
}
return rest_ensure_response(
array(
'phone' => sanitize_text_field( (string) get_field( 'site_phone', 'option' ) ),
'banner_text' => sanitize_text_field( (string) get_field( 'banner_text', 'option' ) ),
)
);
},
)
);
} );
permission_callback は省略すると WordPress が警告を出すため、公開エンドポイントでも明示します。返す値は必要なものだけを選んで詰め直し、オプションを丸ごと返すようなことはしません。認証が必要な値を返すなら、permission_callback で current_user_can() を使って権限を確認します。
ヘッドレス運用で気をつけること
ヘッドレスにすると、WordPress のテーマが担っていた役割をフロント側で持つことになります。特に次の3点は、公開後に気づくと手間がかかります。
1つ目はキャッシュと更新の反映です。上の例のように一定時間ごとに再検証する方式なら、編集してから反映されるまでに遅れが出ます。即時反映が必要なら、WordPress の save_post などのフックからフロント側の再検証用URLを呼ぶ仕組みを用意します。
2つ目はプレビューです。下書きは認証なしでは取得できないため、WordPress のプレビューボタンからフロントのプレビュー用ページへ渡す仕組みを別に作る必要があります。
3つ目は、REST API 以外の選択肢です。関連フィールドの入れ子が深く、何度もリクエストが必要になる場合は、WPGraphQL と ACF 用の拡張を組み合わせて必要な値を一度に取る構成も検討に値します。どちらが向くかはフロントのフレームワークやチームの慣れで変わるので、小さなページで両方を試してから決めるのが無難です。
まとめ
- フィールドグループの REST 公開を有効にし、投稿タイプが
show_in_restで公開されていることを確認する。 _fieldsで取得項目を絞り、画像などの整形済みの値が必要ならacf_format=standardを試して実際のレスポンスで型を決める。- REST 公開したフィールドは誰でも読めるので、社内向けの値は別グループにして公開しない。
- 更新は専用ユーザーのアプリケーションパスワードで、HTTPS 経由・サーバー側からだけ行う。
- オプションページなど投稿に属さない値は、
register_rest_route()とpermission_callbackを明示した独自エンドポイントで返す。