
WordPressの管理画面で記事を投稿するのは、1本ならなんてことない作業です。でも同じ形の記事を何本も出す日は、タイトル欄・本文欄・カテゴリー・タグ・アイキャッチと、同じクリックを何十回も繰り返すことになります。
これをブラウザの開発者コンソールから REST API を叩くだけで片付けられないか試したら、投稿もアイキャッチ設定も全部通りました。ただし5か所で詰まりました。プラグインもFTPも使わず、追加ログインもなしで、です。実際に踏んだところだけ並べます。
先に結論:管理画面を開いたタブなら、そのまま叩ける
| やりたいこと | 詰まった理由 | 解決 |
|---|---|---|
| 記事を投稿する | 認証が通らない | 管理画面のページから nonce を取る |
| 既存記事と重複していないか調べる | 100件しか返ってこない | X-WP-Total を見てページ送りする |
| よそのサイトの画像をアイキャッチにする | CORSで読めない | window.name で運ぶ |
| 画像をまとめて運ぶ | タブが固まった | 10枚ずつに割る |
| メタディスクリプションを入れる | 保存されない | REST では無理。編集画面から入れる |
大前提として、WordPressの管理画面を開いているタブで実行するのがポイントです。ログイン済みのCookieがそのまま使えるので、アプリケーションパスワードを発行しなくても通ります。
1. nonceは管理画面のページから取る
REST APIに書き込みをするには nonce が要ります。これは wp-admin 配下のページなら、グローバル変数から取れます。
const n = wpApiSettings.nonce;
await fetch('/wp-json/wp/v2/posts', {
method: 'POST',
credentials: 'include',
headers: { 'X-WP-Nonce': n, 'Content-Type': 'application/json' },
body: JSON.stringify({ title, slug, content, status: 'publish', categories, tags })
});
credentials: 'include' を忘れるとCookieが飛ばず、ログインしているのに401が返ります。ここで一度つまずきました。
あと地味な落とし穴として、セッションが切れていることがあります。「なぜか nonce が取れない」と思ったら wpApiSettings 自体が未定義で、ページはログイン画面でした。最初に typeof wpApiSettings を見ておくと早いです。
2. 既存記事の一覧は「1ページ100件」では足りない
同じネタで二重投稿しないよう、先に公開済みの一覧を取ります。ここで per_page=100 だけ指定して「全部取れた」と思い込むと事故ります。REST APIの上限が100件なので、それ以上ある場合は黙って切られます。
const r = await fetch('/wp-json/wp/v2/posts?per_page=100&status=any&_fields=id,slug', {
credentials: 'include', headers: { 'X-WP-Nonce': n }
});
const total = parseInt(r.headers.get('X-WP-Total')); // 総件数はここ
総件数は本文ではなく X-WP-Total レスポンスヘッダに入っています。これを見てページ送りします。_fields で必要な項目だけに絞ると、返ってくる量が一気に減って速くなります。
もうひとつ。重複判定は「何と突き合わせるか」で結果が変わります。私は連番IDと商品コードで照合して「全部未公開」と判断したのに、別のIDで引き直したら7件すでに公開済みでした。スラッグの付け方を途中で変えていたのが原因です。スラッグに実際に入っている文字列で照合するのが確実です。
3. よそのサイトの画像はCORSで読めない。window.nameで運ぶ
公式サイトが配布している商品画像などをアイキャッチに使いたいとき、自分のサイトのページから fetch しても通りません。画像ホストがCORSヘッダを返さないからです。crossOrigin="anonymous" を付けても同じです。
ここで使えるのが window.name です。この変数は、同じタブなら別ドメインへ移動しても中身が残ります。
// 1. タブを画像URLそのものへ移動する(originが画像側になる)
// 2. 同一オリジンなので fetch が通る
const b = new Uint8Array(await (await fetch(location.href)).arrayBuffer());
// 3. base64にして window.name へ詰める
window.name = JSON.stringify({ key: btoa(bin) });
// 4. タブを wp-admin へ戻す → window.name は残っている
// 5. Blobに戻して POST /wp-json/wp/v2/media
メディアのアップロードは Content-Type: image/jpeg と Content-Disposition: attachment; filename="..." を付けてPOSTします。返ってきた id をそのまま featured_media に入れればアイキャッチになります。
なお権利の確認は別の話です。配布元が利用を認めている画像か、加工の範囲はどこまでか、取得元はどのページか。ここは技術的に通るかどうかとは関係なく、毎回確認しています。
4. 1回に運ぶのは10枚まで。20枚でタブが固まった
「同じドメインの画像なら1回の移動でまとめて取れる」と分かったので、欲張って20枚・約3.3MBを window.name に詰めました。アップロード自体は20枚とも成功したのですが、そのあとタブのレンダラが固まりました。JavaScriptを流しても45秒待って返ってこない状態です。
10枚・1.5MB前後なら問題なく通っていたので、10枚ずつに割るのが安全圏でした。運び終わったら window.name = '' で捨てます。
救いは、固まってもサーバー側の結果は残っていることです。新しいタブを開いて /wp-json/wp/v2/media?include= にIDを並べれば、アップロード済みの画像URLを引き直せました。戻り値でIDだけは受け取っておくと、こういうときに助かります。
5. SEOプラグインのメタディスクリプションは、RESTでは保存できない
これが一番ハマりました。投稿時に meta へメタディスクリプションを渡しても黙って無視されます。エラーも出ません。context=edit で読み直すと footnotes しか返ってきませんでした。
理由は単純で、SEOプラグイン側がそのメタをREST APIに登録していないからです。登録されていないメタは、渡しても捨てられます。
回避策は、投稿だけRESTでやって、メタディスクリプションは編集画面から入れることでした。クラシックエディタの編集画面を開き、該当の textarea に値を入れて更新ボタンでフォームごと送ります。このとき編集画面がテキストモードになっていることを確認してから送ると、ブロックの記述が崩れません。
そして編集画面だけ見て完了にしないこと。公開ページの <meta name="description"> を実際に見て、自動生成のままになっていないか確認します。空欄だと本文の冒頭から自動生成され、途中で切れた説明文が検索結果に出ます。
5か所に共通していたこと
並べてみると、詰まったところはどれも「エラーが出ないまま、期待と違う結果になる」タイプでした。
- 100件しか返らないのに、エラーは出ない
- メタを渡しても、無視されるだけでエラーは出ない
- 画像は20枚とも上がったのに、そのあと固まる
なのでやったつもりを、必ず別の場所で確認するのが結局いちばん速い、という話に落ち着きました。公開したら公開URLを取得して、タイトル・画像・リンク・メタが入っているかを文字列で確認する。ここまでを一続きにしておくと、あとから直す時間がまるごと消えます。
よくある質問
アプリケーションパスワードは要りませんか?
管理画面を開いているタブで実行するなら不要です。ログイン済みのCookieと nonce で通ります。外部のスクリプトから叩く場合は、別途認証が必要になります。
プラグインは入れなくていいですか?
投稿とメディアのアップロードだけなら、追加のプラグインは要りませんでした。REST APIはWordPressに最初から入っています。
この方法で記事を量産していいですか?
手順が速くなるだけで、中身の質は別問題です。手作業でやらないような雑な記事を、速く出せるようになるだけだとむしろ逆効果です。私は「同じ形式の記事を、下調べを終えたうえで出す」場面だけに使っています。
失敗したときは戻せますか?
本文の書き換えはWordPressのリビジョンに残るので、編集画面から戻せます。ただし新規投稿を大量に作ってしまった場合は1本ずつ消すことになるので、最初の1本だけ投稿して結果を確認してから残りを流すと安全です。
おわりに
REST API自体は素直で、詰まったのはどれも「WordPress本体ではなく、その周辺」でした。上限、CORS、プラグインの実装、ブラウザの限界。この4つを知っていれば、次からは一直線で通せます。
同じことをやろうとしている方は、まず1本だけ投稿して、公開URLを取得して確認するところから始めてみてください。そこが通れば、あとは同じ処理を繰り返すだけです。



