WordPress REST APIをJavaScriptから操作する方法

WordPress REST APIをJavaScriptから操作する方法

WordPressの投稿データをフロントエンドのJavaScriptから取得したり更新したりしたいケースは多い。WordPressのREST APIはデフォルトで有効になっており、fetchAPIを使えば簡単に操作できる。認証が必要な操作と不要な操作の違いも含めて解説する。

WordPress REST APIの基本

ベースURLは以下の形式:

https://example.com/wp-json/wp/v2/

代表的なエンドポイント:

エンドポイント 説明
/wp/v2/posts 投稿の取得・作成
/wp/v2/posts/{id} 特定投稿の取得・更新・削除
/wp/v2/categories カテゴリ一覧
/wp/v2/tags タグ一覧
/wp/v2/media メディア(画像等)
/wp/v2/users ユーザー情報

認証なしで取得できる情報

公開されている投稿や固定ページは認証なしで取得できる。

// 最新10件の投稿を取得
async function getLatestPosts(siteUrl, count = 10) {
  const url = `${siteUrl}/wp-json/wp/v2/posts?per_page=${count}&_embed`;

  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP error: ${response.status}`);
  }

  const posts = await response.json();
  return posts;
}

// 使用例
const posts = await getLatestPosts('https://example.com');
posts.forEach(post => {
  console.log(`${post.title.rendered}: ${post.link}`);
});

_embedパラメータを付けると、アイキャッチ画像などの関連データを一緒に取得できる。

カテゴリを指定して投稿を絞り込む

async function getPostsByCategory(siteUrl, categoryId, page = 1) {
  const params = new URLSearchParams({
    categories: categoryId,
    per_page: 10,
    page: page,
    _embed: true,
    orderby: 'date',
    order: 'desc'
  });

  const response = await fetch(`${siteUrl}/wp-json/wp/v2/posts?${params}`);

  // ヘッダーからページング情報を取得
  const totalPosts = response.headers.get('X-WP-Total');
  const totalPages = response.headers.get('X-WP-TotalPages');

  const posts = await response.json();

  return {
    posts,
    totalPosts: parseInt(totalPosts),
    totalPages: parseInt(totalPages),
    currentPage: page
  };
}

REST APIのレスポンスヘッダーにはX-WP-Total(総件数)とX-WP-TotalPages(総ページ数)が含まれる。ページネーションに使える。

Application Passwordsによる認証

投稿の作成・更新・削除には認証が必要。WordPress 5.6以降はApplication Passwordsが標準機能として使える。

WordPress管理画面 → ユーザー → プロフィール → アプリケーションパスワードで発行する。

class WordPressClient {
  constructor(siteUrl, username, appPassword) {
    this.siteUrl = siteUrl;
    // Basic認証のトークンを作成
    this.authToken = btoa(`${username}:${appPassword}`);
  }

  getHeaders(includeAuth = false) {
    const headers = {
      'Content-Type': 'application/json',
    };
    if (includeAuth) {
      headers['Authorization'] = `Basic ${this.authToken}`;
    }
    return headers;
  }

  // 認証なし: 公開投稿の取得
  async getPosts(params = {}) {
    const query = new URLSearchParams({ per_page: 10, ...params });
    const response = await fetch(
      `${this.siteUrl}/wp-json/wp/v2/posts?${query}`,
      { headers: this.getHeaders(false) }
    );
    if (!response.ok) throw new Error(`Error: ${response.status}`);
    return response.json();
  }

  // 認証あり: 投稿の作成
  async createPost(postData) {
    const response = await fetch(
      `${this.siteUrl}/wp-json/wp/v2/posts`,
      {
        method: 'POST',
        headers: this.getHeaders(true),
        body: JSON.stringify(postData)
      }
    );
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message);
    }
    return response.json();
  }

  // 認証あり: 投稿の更新
  async updatePost(postId, postData) {
    const response = await fetch(
      `${this.siteUrl}/wp-json/wp/v2/posts/${postId}`,
      {
        method: 'POST',  // PATCHでもPOSTでもどちらでも更新できる
        headers: this.getHeaders(true),
        body: JSON.stringify(postData)
      }
    );
    if (!response.ok) {
      const error = await response.json();
      throw new Error(error.message);
    }
    return response.json();
  }
}

投稿を作成する

const client = new WordPressClient(
  'https://example.com',
  'username',
  'xxxx xxxx xxxx xxxx'  // Application Password(スペースは含めて問題ない)
);

// 下書きとして投稿を作成
const newPost = await client.createPost({
  title: '新しい記事タイトル',
  content: '<p>記事の本文です。</p>',
  status: 'draft',        // 'publish'で公開、'draft'で下書き
  categories: [12, 15],  // カテゴリIDの配列
  tags: [3, 7],          // タグIDの配列
  excerpt: 'この記事の概要です。'
});

console.log(`投稿作成完了: ID ${newPost.id}, URL: ${newPost.link}`);

カテゴリIDを名前から検索する

カテゴリIDは直接わからないので、名前から検索する:

async function getCategoryIdByName(siteUrl, categoryName) {
  const params = new URLSearchParams({ search: categoryName, per_page: 10 });
  const response = await fetch(`${siteUrl}/wp-json/wp/v2/categories?${params}`);
  const categories = await response.json();

  // 完全一致するカテゴリを探す
  const match = categories.find(cat => cat.name === categoryName);
  return match ? match.id : null;
}

// 使用例
const pythonCategoryId = await getCategoryIdByName('https://example.com', 'Python');

エラーハンドリング

async function safeApiCall(apiFunction, ...args) {
  try {
    return await apiFunction(...args);
  } catch (error) {
    if (error.message.includes('401')) {
      console.error('認証エラー: Application Passwordを確認してください');
    } else if (error.message.includes('403')) {
      console.error('権限エラー: このユーザーには権限がありません');
    } else if (error.message.includes('404')) {
      console.error('見つかりません: エンドポイントまたはリソースが存在しないか確認してください');
    } else {
      console.error('APIエラー:', error.message);
    }
    throw error;
  }
}

CORSに注意

ブラウザのJavaScriptから別ドメインのWordPressに直接アクセスするとCORSエラーが出ることがある。

WordPressのCORSを許可する設定(functions.phpに追加):

add_action('init', function() {
    header("Access-Control-Allow-Origin: https://your-frontend-domain.com");
    header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
    header("Access-Control-Allow-Headers: Authorization, Content-Type");
});

本番環境では*(全ドメイン許可)ではなく、フロントエンドのドメインを明示的に指定すること。

まとめ

JavaScript(fetch API)でWordPress REST APIを操作するポイント:

  • 認証なし:公開投稿・カテゴリ・タグの取得は誰でもできる
  • 認証あり:投稿の作成・更新・削除にはApplication Passwordが必要
  • ページングX-WP-Totalヘッダーで総件数を取得できる
  • CORS:フロントエンドと別ドメインの場合はfunctions.phpでCORS設定が必要

WordPressをヘッドレスCMSとして使ったり、管理ツールをSPAで作ったりする際の参考にしてほしい。