【WordPress】REST APIを使ったカスタム投稿データの取得方法

WordPress

WordPressのREST APIを使うと、テーマやフロントエンドアプリからカスタム投稿タイプのデータをシンプルに取得できます。正しく設定すれば、/wp-json/wp/v2/以下に自動でエンドポイントが公開され、fetchでJSONを受け取って描画するだけで一覧や詳細ページを作れます。ここではカスタム投稿タイプの登録から、REST APIの取得、サムネイルやタクソノミーの扱い、ページネーション、軽量化、認証のポイントまで実運用に必要な流れをまとめます。

カスタム投稿タイプをREST対応で登録する

show_in_restをtrueにし、必要なサポートやタクソノミーを定義します。これで/wp-json/wp/v2/{post_type}にエンドポイントが自動生成されます。

<?php
// functions.php
add_action('init', function () {
  register_post_type('news', [
    'label'        => 'ニュース',
    'public'       => true,
    'has_archive'  => true,
    'show_in_rest' => true,                   // これがREST公開のキー
    'supports'     => ['title','editor','excerpt','thumbnail'],
    'rewrite'      => ['slug' => 'news'],
    'taxonomies'   => ['news_category'],      // カスタム分類も公開したい場合
  ]);

  register_taxonomy('news_category', 'news', [
    'label'        => 'ニュース分類',
    'public'       => true,
    'show_in_rest' => true,                   // タクソノミーもRESTに出す
    'hierarchical' => true,
  ]);
});

基本の取得方法と代表的なクエリ

RESTエンドポイントは/wp-json/wp/v2/newsのようにアクセスします。クエリパラメータで件数やページ、並び順、タクソノミー条件を指定できます。

// 例)最新10件
GET /wp-json/wp/v2/news?per_page=10

// 例)2ページ目
GET /wp-json/wp/v2/news?per_page=10&page=2

// 例)公開日の降順
GET /wp-json/wp/v2/news?orderby=date&order=desc

// 例)特定タクソノミー(ID=12)で絞り込み
GET /wp-json/wp/v2/news?news_category=12

// 例)必要項目だけ返す(軽量化)
GET /wp-json/wp/v2/news?_fields=id,slug,title,date,excerpt,link

フロント側でfetchして描画する(一覧)

必要なフィールドだけ取得し、innerHTMLで描画します。X-WP-TotalとX-WP-TotalPagesヘッダで総件数と総ページ数が取れます。

<ul id="news-list"></ul>
<nav id="pager"></nav>
<script>
(async () => {
  const perPage = 6;
  const page = new URL(location.href).searchParams.get('page') || 1;
  const api = `/wp-json/wp/v2/news?per_page=${perPage}&page=${page}&_fields=id,slug,title,date,excerpt,link`;

  const res = await fetch(api);
  if (!res.ok) {
    document.getElementById('news-list').textContent = '読み込みに失敗しました。';
    return;
  }
  const items = await res.json();
  const total = res.headers.get('X-WP-Total');
  const totalPages = res.headers.get('X-WP-TotalPages');

  const fmt = (s) => new Date(s).toLocaleDateString('ja-JP');
  const listHtml = items.map(p => `
    <li>
      <a href="${p.link}">${p.title.rendered}</a>
      <small>${fmt(p.date)}</small>
      <p>${p.excerpt?.rendered ?? ''}</p>
    </li>
  `).join('');
  document.getElementById('news-list').innerHTML = listHtml;

  const makeLink = (n) => `?page=${n}`;
  const current = Number(page);
  const pager = [];
  if (current > 1) pager.push(`<a href="${makeLink(current-1)}">前へ</a>`);
  pager.push(`<span>${current} / ${totalPages}</span>`);
  if (current < totalPages) pager.push(`<a href="${makeLink(current+1)}">次へ</a>`);
  document.getElementById('pager').innerHTML = pager.join(' ');
})();
</script>

アイキャッチ画像(サムネイル)を取得する

_embEDを付けると関連リソースが同時取得でき、featured_mediaのURLへ即アクセスできます。

// 画像URLも欲しい場合
GET /wp-json/wp/v2/news?per_page=6&_embed&_fields=id,title,link,_embedded.wp:featuredmedia

// JS側での取り出し例
const media = post._embedded?.['wp:featuredmedia']?.[0];
const img = media?.media_details?.sizes?.medium?.source_url || media?.source_url || '';

個別記事の取得(スラッグやID指定)

詳細ページではスラッグで絞り、1件取得して描画します。

// スラッグで取得
GET /wp-json/wp/v2/news?slug=my-article&_embed

// JS例(詳細)
<article id="single"></article>
<script>
(async () => {
  const slug = location.pathname.split('/').filter(Boolean).pop();
  const res = await fetch(`/wp-json/wp/v2/news?slug=${slug}&_embed`);
  const [post] = await res.json();
  const media = post?._embedded?.['wp:featuredmedia']?.[0];
  const img = media?.source_url || '';
  document.getElementById('single').innerHTML = `
    <h1>${post.title.rendered}</h1>
    ${img ? `<img src="${img}" alt="">` : ''}
    <div class="content">${post.content.rendered}</div>
  `;
})();
</script>

カスタムフィールド(メタ)をRESTに出す

Advanced Custom Fieldsなどを使わない素のmetaを公開したい場合はregister_rest_fieldで必要なキーを追加します。読み取り専用の例を示します。

<?php
add_action('rest_api_init', function () {
  register_rest_field('news', 'meta_summary', [
    'get_callback' => function ($obj) {
      return get_post_meta($obj['id'], 'meta_summary', true);
    },
    'schema' => ['description' => '要約', 'type' => 'string', 'context' => ['view']]
  ]);
});

REST URLとNonceをJSに渡す(認証が必要な操作向け)

公開データのGETは認証不要ですが、POST/PATCH/DELETEや下書きの取得には認証が要ります。wp_create_nonce(‘wp_rest’)をwp_localize_scriptで渡し、ヘッダに付与します。

<?php
add_action('wp_enqueue_scripts', function () {
  wp_enqueue_script('news-app', get_template_directory_uri().'/assets/js/news-app.js', [], '1.0', true);
  wp_localize_script('news-app', 'WPAPI', [
    'root'  => esc_url_raw(rest_url()),
    'nonce' => wp_create_nonce('wp_rest'),
  ]);
});
// 認証が必要な場合のfetch例(Cookieログイン前提)
fetch(WPAPI.root + 'wp/v2/news', {
  method: 'POST',
  headers: { 'X-WP-Nonce': WPAPI.nonce },
  body: new FormData(/* ... */)
});

パフォーマンス最適化とエラー対策

一覧取得では_fieldsで必要最小限のみを返し、_embedは必要時に限定します。クエリが多段になる場合はキャッシュ層(WP Object Cacheやページキャッシュ)を併用すると安定します。フロント側はres.okの判定とタイムアウト、リトライを実装し、UIには読み込み中表示と失敗時のフォールバックを用意します。日本語ソートや細かい検索条件が必要な場合はpre_get_postsでRESTリクエスト時のクエリを調整し、独自のエンドポイントが必要ならregister_rest_routeでカスタムコントローラを定義します。

まとめ

カスタム投稿タイプをshow_in_rest=trueで登録すると、/wp-json/wp/v2/{post_type}からJSONで安全に取得でき、クエリパラメータで件数や並び、タクソノミー絞り込み、_fieldsで軽量化、_embedで関連リソース取得といった実用要件を網羅できます。サムネイルやメタの公開が必要ならregister_rest_fieldで拡張し、認証操作ではREST Nonceをヘッダに付与します。これらの基本設計を押さえれば、テーマ内JSはもちろん、SPAやモバイルアプリからも一貫したデータ取得ができ、WordPressを堅牢なヘッドレスCMSとして活用できます。