Automations

Notion to Social Media: Publish Database Rows via API

Query a Notion database with Notion’s API and turn each ready-to-publish row into a social post through PostGate’s API.

Quick answer

Connect Notion to social media in four steps: create an internal Notion integration and share the database with it, query the database for rows marked ready to publish, map each page's properties to a POST /api/posts call, then mark the row as posted. Run the script on a cron job, GitHub Actions workflow, or serverless timer.

A content calendar built as a Notion database — one row per post, a status property to mark it ready — is a common setup, and there's no PostGate app in Notion's integrations directory to connect it automatically. Notion's own API is public and well documented, though, so this is a query, a mapping step, and a call to POST /api/posts.

01

Create an internal integration and share the database with it

At notion.so/my-integrations, create a new internal integration to get a secret token. Notion integrations only see content explicitly shared with them, so open the target database, use the Connections menu, and add the integration — the token alone isn't enough.

02

Query the database for rows ready to publish

Notion's API is versioned through a required Notion-Version header rather than the URL, so pin a version and update it deliberately. Filter server-side on a status property (a select or status column works well) so the response only contains rows you should act on.

Query a databasePostGate API
curl -X POST https://api.notion.com/v1/databases/$DATABASE_ID/query \
  -H "Authorization: Bearer $NOTION_TOKEN" \
  -H "Notion-Version: 2026-03-11" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "property": "Status",
      "select": { "equals": "Ready to publish" }
    }
  }'
03

Map a page’s properties to a post

Each result in results is a Notion page with a properties object keyed by column name. Pull the title from the title-type property, the caption from a rich-text property, and an image from a Files & media property if one is attached. One thing to know before you write this code: URLs Notion returns for its own hosted files are short-lived, signed links that expire roughly an hour after the query runs — pass them to PostGate immediately rather than queuing the mapping step for later.

Map and publishPostGate API
async function publishRow(page) {
  const props = page.properties;
  const caption = props.Caption.rich_text.map((t) => t.plain_text).join('');
  const file = props.Image?.files?.[0];
  const imageUrl = file?.type === 'file' ? file.file.url : file?.external?.url;

  const res = await fetch(
    `https://postgate.studio/api/posts?profile_group_id=grp_acme123`,
    {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.POSTGATE_API_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': `notion-${page.id}`,
      },
      body: JSON.stringify({
        post: { body: caption },
        profiles: ['pro_instagram123', 'pro_linkedin456'],
        media: imageUrl ? [imageUrl] : undefined,
      }),
    }
  );

  return res.ok;
}
04

Mark the row as posted so it isn’t sent twice

After a successful call, PATCH the page's own Posted checkbox (or move a Status select to "Published") so the next query excludes it. This, combined with the Idempotency-Key on the PostGate call, means a script that reruns mid-way through a batch never double-posts.

Update the pagePostGate API
curl -X PATCH https://api.notion.com/v1/pages/$PAGE_ID \
  -H "Authorization: Bearer $NOTION_TOKEN" \
  -H "Notion-Version: 2026-03-11" \
  -H "Content-Type: application/json" \
  -d '{ "properties": { "Posted": { "checkbox": true } } }'
05

Run it on a schedule

Wrap the query, the mapping loop, and the row update in one script and run it on a timer — a cron job, a scheduled GitHub Actions workflow, or a serverless function. For a fully no-code path instead, see calling PostGate from n8n, Make, and Zapier: each of those tools has its own native Notion trigger you can pair with an HTTP module configured the same way as the request above.

Frequently asked questions

Why can't my Notion integration see my database?

Notion integrations only see content explicitly shared with them — the secret token alone is not enough. Open the target database, use the Connections menu, and add the integration. Until the database is shared, queries against it will not return your rows.

Why do Notion image URLs stop working after an hour?

URLs Notion returns for its own hosted files are short-lived, signed links that expire roughly an hour after the query runs. Pass them to PostGate immediately rather than queuing the mapping step for later — PostGate fetches the image at publish time, so a stale link means a failed fetch.

How do I stop the same Notion row from being posted twice?

After a successful call, PATCH the page's Posted checkbox (or move a Status select to Published) so the next query excludes it. Combined with an Idempotency-Key derived from the page ID on the PostGate call, a script that reruns mid-way through a batch never double-posts.

Can I connect Notion to social media without writing code?

There is no PostGate app in Notion's integrations directory, but n8n, Make, and Zapier each have a native Notion trigger you can pair with an HTTP module configured the same way as the API request in this guide — a fully no-code path to the same result.

Build it with PostGate

One API for every social platform your customers care about — sandboxed, documented, and ready today.

Start building Read the docs