WordPressからX(旧Twitter)への自動投稿。 これを実現するために、多くの人がプラグインを使っていると思います。
しかし、長年メディアを運用していると、必ずこの問題に直面します。
- 「プラグインが突然動かなくなった」
- 「API制限で投稿が失敗しているが、理由が分からない」
- 「アップデートで設定が飛んだ」
楽をするためのプラグインが、実は最大の不安定要因になってしまうのです。
今回、私も「WP側のPOSTが働かなくなった」というトラブルに見舞われました。 そこで、既存のプラグインに頼るのをやめ、Google Apps Script (GAS) と X API v2 を組み合わせて、「絶対に壊れない(壊れても自分で直せる)配信基盤」 を自作しました。
これが、個人メディアにおける最強の構成だと確信しています。
設計思想:WordPressは「通知するだけ」
今回のツールの肝は、「WordPressに余計な仕事をさせない」 ことです。
従来のプラグイン構成
- WordPressの中に「Xの認証情報」を持つ
- WordPressが「Xへの投稿処理」を行う
- WordPressが「エラーハンドリング」を行う
これだと、WPが重くなるし、セキュリティ的にも不安です(WPが乗っ取られたらXも乗っ取られます)。
今回の「最強構成」
- WordPress:記事が公開されたら、GASに「JSON」を投げるだけ。
- GAS:認証・投稿・ログ保存・再送処理を一手に引き受ける。
- Spreadsheet:投稿ログを記録し、可視化する。
[WordPress]
↓ (POST: Title, URL)
[Google Apps Script] ← ここに秘密鍵を隔離
↓ (OAuth 1.0a)
[X API v2]
↓
[Spreadsheet] (Log: Success/Fail)
この構成なら、WordPressが侵害されてもXのアカウントは無事です。 また、XのAPI仕様が変わっても、GAS側を1箇所直すだけで済みます。
技術構成
- WordPress:
functions.phpに数行のフックを追加 - Google Apps Script: X API v2 への署名とPOST処理(ライブラリ不使用)
- X API: Freeプランでも使える v2 API (
/2/tweets) - 認証方式: OAuth 1.0a(GAS単体で署名を生成)
実装手順:完全ガイド
ここからは、実際にこのツールを構築する手順をステップバイステップで解説します。 プログラミングに詳しくない方でも、手順通りに進めれば完成します。
手順1:ログ保存用のスプレッドシートを作成
まず、Googleドライブで新しいスプレッドシートを作成します。 1行目に以下の見出しを入力してください。
- A列:
time - B列:
post_id - C列:
text - D列:
tweet_id - E列:
status - F列:
error
このスプレッドシートのURLに含まれる ID 部分(/d/ と /edit の間の文字列)を後で使うので、メモしておきます。

手順2:GASプロジェクトを開く
スプレッドシートのメニューから 「拡張機能」→「Apps Script」 をクリックします。 これで、このシートに紐付いたGASエディタが開きます。

手順3:X Developer Portal でキーを取得
X Developer Portal にアクセスし、プロジェクトを作成します(FreeプランでOK)。 「Keys and tokens」 タブから、以下の4つの情報を取得します。
- API Key (Consumer Key)
- API Key Secret (Consumer Secret)
- Access Token
- Access Token Secret
※ Access Token を生成する際は、必ず 「Read and Write」 権限が付与されていることを確認してください。

手順4:スクリプトプロパティの設定
GASのエディタに戻り、左側の歯車アイコン(プロジェクトの設定)をクリックします。 一番下にある 「スクリプト プロパティ」 に、先ほど取得したキーと、手順1でメモしたシートIDを設定します。
| プロパティ名 | 値 |
|---|---|
X_API_KEY |
(取得したAPI Key) |
X_API_SECRET |
(取得したAPI Key Secret) |
X_ACCESS_TOKEN |
(取得したAccess Token) |
X_ACCESS_SECRET |
(取得したAccess Token Secret) |
LOG_SHEET_ID |
(スプレッドシートのID) |

手順5:コードの貼り付け
左側の「エディタ(<>アイコン)」に戻り、Code.gs の中身を以下のコードにすべて書き換えます。
外部ライブラリは一切不要です。
/**
* WordPress → GAS Web App 入口
*/
function doPost(e) {
try {
if (!e || !e.postData || !e.postData.contents) {
throw new Error('No postData');
}
const data = JSON.parse(e.postData.contents);
if (!data.text) {
throw new Error('No text');
}
const tweetId = postToX(data.text);
logPost({
post_id: data.post_id || '',
text: data.text,
tweet_id: tweetId,
status: 'success',
error: ''
});
return json({ ok: true, tweet_id: tweetId });
} catch (err) {
logPost({
post_id: '',
text: '',
tweet_id: '',
status: 'error',
error: err.toString()
});
return json({ ok: false, error: err.toString() });
}
}
/**
* X(Twitter)へ投稿
*/
function postToX(text) {
const props = PropertiesService.getScriptProperties();
const url = 'https://api.twitter.com/2/tweets';
const method = 'POST';
const oauth = {
oauth_consumer_key: props.getProperty('X_API_KEY'),
oauth_token: props.getProperty('X_ACCESS_TOKEN'),
oauth_nonce: Utilities.getUuid(),
oauth_timestamp: Math.floor(Date.now() / 1000),
oauth_signature_method: 'HMAC-SHA1',
oauth_version: '1.0'
};
const baseString =
method + '&' +
encodeURIComponent(url) + '&' +
encodeURIComponent(
Object.keys(oauth)
.sort()
.map(k => `${encodeURIComponent(k)}=${encodeURIComponent(oauth[k])}`)
.join('&')
);
const signingKey =
encodeURIComponent(props.getProperty('X_API_SECRET')) + '&' +
encodeURIComponent(props.getProperty('X_ACCESS_SECRET'));
const signatureBytes = Utilities.computeHmacSignature(
Utilities.MacAlgorithm.HMAC_SHA_1,
baseString,
signingKey
);
oauth.oauth_signature = Utilities.base64Encode(signatureBytes);
const authHeader = 'OAuth ' + Object.keys(oauth)
.sort()
.map(k => `${encodeURIComponent(k)}="${encodeURIComponent(oauth[k])}"`)
.join(', ');
const res = UrlFetchApp.fetch(url, {
method: 'post',
headers: {
Authorization: authHeader,
'Content-Type': 'application/json'
},
payload: JSON.stringify({ text }),
muteHttpExceptions: true
});
const body = JSON.parse(res.getContentText());
if (!body.data || !body.data.id) {
throw new Error(res.getContentText());
}
return body.data.id;
}
/**
* スプレッドシートにログ
*/
function logPost(row) {
const sheetId = PropertiesService
.getScriptProperties()
.getProperty('LOG_SHEET_ID');
if (!sheetId) return;
const sheet = SpreadsheetApp
.openById(sheetId)
.getSheets()[0];
sheet.appendRow([
new Date(),
row.post_id,
row.text,
row.tweet_id,
row.status,
row.error
]);
}
/**
* JSONレスポンス
*/
function json(obj) {
return ContentService
.createTextOutput(JSON.stringify(obj))
.setMimeType(ContentService.MimeType.JSON);
}
/**
* 単体テスト用
*/
function testPost() {
postToX('GASからのテスト投稿です');
}
手順6:デプロイ(Webアプリとして公開)
右上の 「デプロイ」→「新しいデプロイ」 をクリックします。
- 種類の選択: 歯車アイコンから 「ウェブアプリ」 を選択。
- 説明: 適当に入力(例:
v1)。 - 次のユーザーとして実行:
自分(Me)。 - アクセスできるユーザー:
全員(Anyone) ← ここ重要!


「デプロイ」ボタンを押すと、URL(ウェブアプリURL)が発行されます。 このURLをコピーしておきます(WordPressの設定で使います)。
実装手順:WordPress側の設定
WordPress側は、functions.php に以下のコードを追加するだけです。
記事が「公開 (publish)」された瞬間だけフックし、GASにデータを投げます。
/**<br> * WordPress → GAS → X 自動投稿<br> */<br>add_action('transition_post_status', function ($new_status, $old_status, $post) {<br><br> // publish になった瞬間のみ実行<br> if ($old_status === 'publish' || $new_status !== 'publish') return;<br><br> // 投稿タイプ制限(通常の投稿のみ)<br> if ($post->post_type !== 'post') return;<br><br> // 自動保存・リビジョン除外<br> if (wp_is_post_autosave($post->ID) || wp_is_post_revision($post->ID)) return;<br><br> // 二重投稿防止(メタデータで管理)<br> if (get_post_meta($post->ID, '_x_posted', true)) return;<br><br> // タイトルとURLを取得<br> $title = trim(wp_strip_all_tags($post->post_title));<br> if ($title === '') return;<br><br> $title = mb_substr($title, 0, 100); // 文字数制限<br> $url = get_permalink($post->ID);<br><br> // 投稿文の作成<br> $text = "【新記事】\n{$title}\n\n{$url}";<br><br> // GAS Web App URL(先ほど発行したもの)<br> $gas_url = 'https://script.google.com/macros/s/YOUR_GAS_ID/exec';<br><br> // GAS に POST<br> $response = wp_remote_post(<br> $gas_url,<br> [<br> 'timeout' => 5,<br> 'headers' => ['Content-Type' => 'application/json'],<br> 'body' => wp_json_encode([<br> 'post_id' => $post->ID,<br> 'text' => $text<br> ])<br> ]<br> );<br><br> // 成功したらフラグ保存<br> if (!is_wp_error($response)) {<br> update_post_meta($post->ID, '_x_posted', 1);<br> }<br><br>}, 10, 3);注意点:functions.php の罠
実装時、functions.php の途中に <?php を二重に書いてしまい、フックが動かないというミスがありました。
functions.php はファイルの先頭に <?php があれば十分です。途中に書くと構文エラーの原因になるので注意しましょう。
運用してわかったメリット
実際にこの構成で運用を始めましたが、安定感が段違いです。
- 失敗が見える: スプレッドシートに全てのログが残るため、「いつ」「なぜ」失敗したかが一目瞭然です。
- 再送が簡単: 失敗しても、GASを叩き直すだけで再送できます。
- 拡張性が高い: 「カテゴリによってハッシュタグを変える」「Blueskyにも同時に投げる」といった拡張が、GAS側だけで完結します。

↑投稿できました!
まとめ
「プラグインを入れる」のは簡単ですが、それは「ブラックボックスを抱える」ことでもあります。 長期的にメディアを運営するなら、「壊れたときに自分で直せる」 構成にしておくことが、結果的に一番の近道になります。
この「WordPress × GAS × X API」の構成は、個人開発者やブロガーにとっての最強の武器になるはずです。 ぜひ試してみてください。
One comment on “WordPressからXへ自動投稿する最強構成を自作した話 ― プラグインを捨てて、GAS+X APIで“壊れない配信基盤”を作る”
[…] に行ってください。こっちはコードの解説バージョンで難しいです。→WordPressからXへ自動投稿する最強構成を自作した話 ― プラグインを捨てて、GAS+X APIで“壊れない配信基盤”を作る […]