WordPress から Qiita へ予約投稿するプラグインを自作している途中、画像を Qiita サーバーへ送ろうとして詰まりました。予約投稿そのものの設計(WP-Cron をどう信じるか)は別記事にまとめていて、この記事は画像の受け渡しに絞った記録です。先に答えを書くと、Qiita の公開 API に画像アップロードの口はありません。私は WordPress のメディアや Cloudflare R2 の URL を本文に埋め込む方式へ切り替えました。たどった検証の記録です。
テキストだけの記事はすんなり通るのに、画像を 1 枚はさんだ瞬間に手が止まりました。その画像を、どうやって Qiita 側へ渡せばいいのか。半日ほど API リファレンスを行き来して、ようやく「渡す入り口がそもそも無い」ことに気づきます。同じところで止まる人がいそうなので、順にたどります。
画像アップロードの口を探して、見つからなかった
Qiita の編集画面では、画像をドラッグして放り込むだけでアップロードできます。だから当然、それを叩く API があると思っていました。記事を投稿する POST /api/v2/items があるのだから、画像にも対応する口があるはずだ、と。ありませんでした。Qiita の公式 API(v2、個人用アクセストークンで叩けるもの)が用意しているのは、記事の投稿・編集・取得、タグ、ユーザー情報あたりまでです。画像をアップロードするエンドポイントは、この一覧に含まれていません。公式ヘルプで API でできることとして挙がっているのもデータの取得と記事・コメントの投稿で、画像アップロードはそこに入っていない。無いものは、探しても見つからないわけです。
編集画面のドラッグ&ドロップが使っているのは、ログインセッション(Cookie)前提の内部的な仕組みでした。個人用アクセストークンの権限(read_qiita / write_qiita)に、画像アップロードは含まれていません。トークンでそこを叩こうとしても、認証が通らないか、口そのものが見当たらない。「ブラウザでできることは、だいたい API でもできるはず」という思い込みは、わりと裏切られます。ブラウザはログイン状態という強い文脈を持っていて、その文脈に依存した機能は、トークン認証の外側にあることが多い。内部エンドポイントを Cookie ごと再現すれば技術的には叩けるのかもしれませんが、公開された手段ではないので、仕様変更ひとつで壊れます。配布するプラグインの土台に、壊れやすい裏口は据えられません。ここで方針を切り替えました。
置けないなら、URL を本文に書けばいい
Qiita に置けないなら、自分の管理下に置いて、その URL を本文に書けばいい。Markdown なら  の一行です。Qiita の記事投稿 API には、この Markdown を body として渡すだけ。画像そのものを転送する処理は、まるごと要らなくなります。置き場所は 3 つ用意しました。追加の契約も設定も要らない WordPress のメディアを既定にして、自サイトの負荷から配信を切り離したい人や、すでにオブジェクトストレージを運用している人向けに、Cloudflare R2 と AWS S3 も選べるようにしています。記事に使っている画像はそのまま公開 URL を持っているので、入れてすぐ動く状態を優先しました。最初から全員にバケットを用意させるのは、入り口として重すぎます。
この方式には、地味な副産物もあります。画像が自分の手元に残り続けることです。Qiita 側にホストさせると画像の管理は Qiita のものになり、こちらからは触れません。URL 埋め込みなら、差し替えも削除も自分の都合でできる。当初は「Qiita に置けないから仕方なく」と思っていた切り替えが、振り返るとむしろ素直な設計でした。

画像は自分の側に置いて、本文には URL だけを渡します。
埋めても、表示されるとは限らない
URL を埋め込めば終わり、とはいきませんでした。埋め込んだ画像が相手から見えるかどうかは、別の話です。確認したのは WordPress メディアの URL と Cloudflare R2 の URL の両方で、今回の環境ではどちらも Qiita 上で問題なく表示できました。
一点だけ、Cloudflare を使っている人が気に留めておくと安全な設定があります。Hotlink Protection です。別ドメインからの画像直リンクを Referrer で判定してブロックする機能で、オンにすると、Qiita の記事ページから読み込まれた画像は「別サイトからのリクエスト」と見なされ、表示されなくなります。今回やりたいのは、まさに別サイトからの直リンク表示なので、この機能とは相性が悪い。私の環境は Cloudflare 既定(Hotlink Protection はオフ)だったので素通りでしたが、過去にオンにしていたら、ここで詰まっていたはずです。R2 配下の画像が Qiita で表示されないときは、まずここを疑うといいと思います。

公開後の見え方も、念のため確かめておくと安心です。自分がログインしている画面では見えても、ログアウト状態の第三者からは見えない、という取り違えがたまにあります。私はプライベートウィンドウで開いて、画像が出ることを確認しました。
次にやるなら、前提から確かめる
今回ぶつかったのは、機能の作り込み以前の、前提の確認でした。Qiita に画像アップロードの公開 API があるかどうか。最初にここを 15 分調べていれば、半日は浮いていた気がします。次に外部サービスへ何かを送る実装を始めるときは、まず公式 API の範囲をひと通り眺めてから手を動かす。そう自分に言い聞かせています。残っている問いもあります。R2 や S3 を選んだ人向けに、Hotlink Protection 相当の設定をプラグイン側でどこまで案内すべきか。表示されない原因が利用者の CDN 設定にあると、プラグインのログだけでは切り分けが難しい。動くものはできたけれど、人に渡したときに迷わせない形になっているかは、これから確かめるところです。
画像をどこかへ送る前に、まずその送り先に公開された入り口があるかを確かめてみてください。私はそこを後回しにして、半日を溶かしました。
その後の運用で、方式の答え合わせができた
この記事を書いたあと、Qiita への投稿経路には公式の Qiita CLI と GitHub Actions を組み合わせた自動投稿も加えました。そこで分かったのは、公式の CLI でも事情は同じだということです。Qiita CLI が扱うのは Markdown の本文とフロントマターで、画像をアップロードする機能は持っていません。画像は Qiita の Web 画面からアップロードするか、外部の URL を参照するか。つまり、どの経路から投稿しても、画像だけは「Qiita の外に置いて URL で渡す」に行き着きます。プラグインで選んだ URL 埋め込み方式は、回避策のつもりでいたら、実は Qiita 周辺ツールの標準的な作法でした。方式を変える必要は、いまのところ一度も出ていません。
15分の前提確認を、手順にしておく
最後の教訓「最初に15分調べていれば半日浮いた」を、次に活かせる形にしておきます。外部サービスへ何かを送る実装を始める前に、私はこの順で見るようになりました。まず公式 API リファレンスのエンドポイント一覧を、実装を考えずに上から下まで眺める。送りたいもの(今回なら画像)に対応する口がその一覧にあるかだけを確認します。次に認証方式とスコープの一覧を見る。Qiita なら個人用アクセストークンの read_qiita / write_qiita で、そこに含まれない操作はトークンでは叩けません。最後に「ブラウザではできるのに一覧に無いもの」を書き出す。それはログインセッション前提の内部機能である可能性が高く、公開 API の外です。ここまでで15分。口が無いと分かったら、作り込みを始める前に、今回の URL 埋め込みのような迂回路を先に設計します。順番が逆だと、私のように半日を溶かします。この予約投稿プラグインを WordPress.org に出すまでの過程は、審査で2回差し戻された話に別途まとめています。
検証環境:2026 年 6 月 16 日 / WordPress 7.0 / PHP 8.3.30 / Rapls Relay 0.9.0(開発版)/ Cloudflare 既定設定。Qiita の API 仕様は変わることがあり、画像アップロードの口がいつか公式に増える可能性もあります。試すときは、そのときのリファレンスを一度確認してみてください。



コメント