失敗しないNotion API投稿:動的スキーマ取得で「IDハードコード」を卒業する

この記事は、Growth Lab編集部 が Notion API / Automation / Node.js の観点から検証結果を整理したものです。
読了前に全体像を掴み、その後に目次から必要な節へ進める構成を想定しています。
目次を表示
TL;DR
- Notion のプロパティ ID は予告なく変わるため、ハードコードは厳禁
retrieveDatabaseを使い、タイトル(Name)から ID を動的に解決する- 存在しないプロパティに対するガード節を徹底し、エラー耐性を高める
The Pain: スクリプトが突然死ぬ理由
「ローカルで動いていたスクリプトが、本番環境で突然エラーを吐いた」。 その原因の多くは、NotionのプロパティIDの変更や予期せぬスキーマ不一致です。
// BAD: プロパティIDや名前をハードコードしている
const response = await notion.pages.create({
parent: { database_id: "..." },
properties: {
"Title": { title: ... }, // "Name" かもしれない
"Tags": { multi_select: ... } // "Tag" かもしれない
}
});
データベースのカラム名を少し変えただけで、自動化フロー全体が停止する。これでは「堅牢」とは言えません。
The Solution: 動的スキーマ取得 (Dynamic Schema Fetching)
堅牢なスクリプトを書くための鉄則は、**「送信前にデータベースの定義(Schema)を聞く」**ことです。
retrieveDatabaseで現在のプロパティ定義を取得する。- 辞書型(Map)を作成し、「欲しいプロパティ」が存在するか確認する。
- 名前が多少揺れていても吸収できるロジックを入れる(例:
slugorSlug)。
The Implementation: 実際のコード
以下は、本ブログの自動投稿スクリプトで使用しているロジックの抜粋です。
// scripts/post_notion.mjs より抜粋・簡略化
async function getDatabaseSchema(databaseId) {
const dbRes = await notion.databases.retrieve({ database_id: databaseId });
const properties = dbRes.properties;
// 利用可能なプロパティ名をリスト化
const availableProps = Object.keys(properties);
console.log("Available properties:", availableProps);
return { properties, availableProps };
}
async function safePost(article, databaseId) {
const { properties: schema, availableProps } =
await getDatabaseSchema(databaseId);
// フォールバック付きでプロパティ名を特定
const titleProp = availableProps.find(
(p) =>
["Title", "Name", "タイトル", "名前"].includes(p) ||
schema[p].type === "title",
);
const slugProp = availableProps.find((p) =>
["Slug", "slug", "スラッグ"].includes(p),
);
if (!titleProp) {
throw new Error("Critical: Title property not found in database.");
}
// ペイロード構築
const payload = {};
payload[titleProp] = { title: [{ text: { content: article.title } }] };
if (slugProp && schema[slugProp].type === "rich_text") {
payload[slugProp] = { rich_text: [{ text: { content: article.slug } }] };
}
return await notion.pages.create({
parent: { database_id: databaseId },
properties: payload,
// ...
});
}
ポイント
- Type Check: 名前だけでなく
type === 'title'など型でも検索する。 - Graceful Degradation: 必須でないプロパティ(Slugなど)は見つからなければ送信しない(エラーにしない)。
- Logging: 何が見つかって何が見つからなかったかをログに残す。
The Takeaway: 運用の安定化
この「動的スキーマ取得」を導入してから、Notion側でプロパティ名を「Slug」から「URL Slug」に変えたときも、スクリプトは止まることなく動作し続けました。
自動化スクリプトは「作った瞬間」が完成ではありません。「運用環境の変化にどれだけ耐えられるか」がエンジニアの腕の見せ所です。
次回は、生成AIの「JSON崩れ」を許さないパース戦略について解説します。
Implementation: Dynamic Resolver
Notion のプロパティ ID は、データベースの構成を変更するたびに再生成される可能性があります。これを防ぐために、実行時にスキーマを読み込み、名前と ID のマッピングテーブルをオンメモリで作るのが最適解です。
const response = await notion.databases.retrieve({ database_id: DATABASE_ID });
const propertyIdMap = Object.fromEntries(
Object.entries(response.properties).map(([name, val]) => [name, val.id]),
);
// 例: "Status" プロパティの ID を名前から取得
const statusId = propertyIdMap["Status"];
⏳ 比類なき「リトライ戦略」の重要性
API 連携においてスキーマ崩れと同じくらい致命的なのが、429 Too Many Requests(レートリミット)です。
Notion API は比較的厳格な制限があります。単純なループで投稿を行うと、すぐに制限に達します。
私たちは p-retry などのライブラリを使い、指数バックオフを伴う自動リトライを実装しています。これにより、一時的なネットワークエラーやリミット超過であっても、システム全体が停止することなく、数秒待機して処理を完遂させることができます。
graph TD
A[Start] --> B{Retrieve Database Schema};
B --> C{Extract Property Names and IDs};
C --> D{Create Name-to-ID Map};
D --> E{Look up Property ID by Name};
E --> F[Use ID in API Call];
F --> G[End];
References
Growth Lab編集部
Notion API / Automation / Node.js
AIエージェント開発、記事制作フロー、デザインシステム運用の接続を実装ベースで検証し、再現可能な手順へ落とし込むことを目的に運営しています。
あわせて読む
同じテーマや近い文脈の記事を続けて読めるようにする。
失敗しないNotionブログ自動化の教科書:堅牢なAPI連携からコスト管理まで
Notionブログ自動化の失敗パターンを分析し、堅牢なAPI連携やLLMパース、コスト管理まで、手放し運用を可能にするための実践的な教科書です。
マルチモーダル・ブログ戦略:テキスト、図解、動画を「1回の思考」で生成する未来
テキスト・図解・動画を『1回の思考』で生成するマルチモーダル戦略の全体像を解説。AIエージェントを繋ぎ込み、エンジニアの発信力を最大化する未来型パイプラインを紹介します。
継続接点
更新を追いかける
新着記事、特集、検証ログをまとめて追える入口として使う。メール購読導線の本実装前でも、継続接点を切らさない。
- 新着記事をまとめて確認できる
- 関連記事や特集ページへつながる
- 実験ログを継続的に追える
本実装ではメール購読や通知機能へ差し替え可能。