n8n Automation Hub
All articles
Build Breakdown

n8n Instagram & Facebook Auto-Posting, Driven by a Google Sheet

n8n workflow breakdown · Pavlo Veresiuk · August 23, 2026

I didn't want to open Meta Business Suite every day to post the same kind of content on a schedule. So the queue lives in a Google Sheet, and a Schedule Trigger in n8n does the actual posting โ€” one video a day, to both Facebook and Instagram, hands off.

Workflow at a glance
  1. Schedule Trigger fires once a day, reads the VideoQueue Sheet tab
  2. Code node filters for status == 'ready' and picks the next row
  3. Parallel branch: POST to the Facebook Page's /videos endpoint
  4. Parallel branch: POST to Instagram's /media endpoint (media_type: REELS), wait 45s, then /media_publish
  5. Sheet row gets marked published, matched by a unique video_key โ€” not row position
  6. Telegram message confirms both posts went out
Screenshot of the actual n8n auto-posting workflow canvas: Schedule Trigger, queue lookup, parallel Facebook and Instagram publishing branches, Telegram report

The actual workflow canvas, straight from my n8n instance.

The queue is just a Sheet, on purpose

The VideoQueue tab has seven columns: video_key, title, video_url, fb_caption, ig_caption, status, published_at. No dashboard, no separate app โ€” anyone who can edit a spreadsheet can queue up a week of content. The Code node's whole job is to grab the rows with status == 'ready' and take the first one, so the order in the sheet is the order it posts.

Matching by video_key, not row number

Early on I matched updates by row position, and it went wrong the first time someone (me) reordered rows while a run was in flight. Now every match โ€” reading which row is next, writing back that it published โ€” goes through video_key, a stable string I set once per video. Row position drifts. Keys don't.

The part that actually needs the Graph API

Facebook Page posting is a straightforward POST /{'{page-id}'}/videos with a file_url and a description. Instagram is a two-step dance: first POST /{'{ig-id}'}/media with media_type: REELS, the video URL, and a caption, which hands back a creation_id โ€” then a second call, POST /{'{ig-id}'}/media_publish, to actually publish it. In between, I wait 45 seconds flat rather than polling the container's processing status. That's an honest shortcut: it works fine for short 15-20 second clips, but a longer video would need real status polling instead of a fixed wait โ€” I just haven't needed it yet for what I'm posting.

The gotcha nobody mentions: video hosting

Both Graph API endpoints fetch the video by URL server-side โ€” there's no simple direct-binary-upload path through this flow. So the video needs to already be sitting somewhere with a real public HTTPS URL before either request goes out. I didn't stand up a separate video host for this: the finished MP4s just get copied into the same nginx docroot that already serves the rest of the site, so they're immediately reachable at a normal URL. One less moving part.

Closing the loop

Once both platforms confirm, a Merge node combines the results, the Sheet row flips to published with a timestamp, and a Telegram message tells me it happened โ€” or, more usefully, tells me when one side succeeded and the other didn't.

Need this wired up for your own pages?

Content-queue-to-social-media is one of the most common builds I do โ€” happy to set it up against your own Sheet and accounts.