Engineering

n8n social media automation: a workflow that actually posts

n8n ships app nodes for LinkedIn, X, YouTube and Reddit - and none for Instagram, TikTok, Threads, Bluesky or Pinterest. This is the four-node workflow that posts to all of them from a single HTTP Request node, plus where credentials live when you self-host and when to drop n8n and call the API from your own code.

Search for n8n social media automation and you get workflow templates. Import one, and it posts — to X and LinkedIn. That is usually where the enthusiasm meets the brief, because the brief said six platforms and the template covers two.

The reason is not that the templates are bad. It is that n8n ships app nodes for the platforms whose APIs are stable enough to be worth maintaining, and that set is smaller than your content calendar. This guide covers what exists, a workflow that posts to everything from a single HTTP Request node, where credentials live when you self-host, and the point at which you should stop building in n8n and call the API from your own code.

Node availability below was checked on 1 October 2026 against n8n's own documentation — I probed the docs page for each platform's node and recorded which ones exist. Links go to the pages.

What the community templates actually cover

The social templates on n8n.io are good scaffolding and honest about their scope. The social media content generator and publisher template — one of the most-imported in the category — publishes to X and LinkedIn, using the X node and the LinkedIn node with OAuth2, with a Gemini chat model generating the copy and a Merge node combining the two results.

That is the pattern across the category: an AI generation step, then one node per platform, for the two or three platforms that have nodes. The content half generalises. The publishing half does not — you cannot add TikTok to that template, because there is no TikTok node to add.

The nodes n8n ships, and the ones it doesn't

Platform

Built-in node?

What it does

LinkedIn

Yes

One operation: Post › Create. Posts as a Person or an Organization; for an organization you enter the organization number in the URN field. Media Category handles images or article URLs.

X (Twitter)

Yes

Create or reply a tweet, Delete, Search, Like, Retweet, Create a direct message. Needs your own X developer app and a paid tier for meaningful write access.

Facebook Graph API

Yes

A generic Graph API client, not a publishing node. You construct the edge and the parameters yourself.

YouTube

Yes

Video upload and metadata.

Reddit

Yes

Submit and manage posts.

Instagram

No

No built-in node. Via Facebook Graph API by hand, or the HTTP Request node.

TikTok

No

No built-in node.

Threads

No

No built-in node.

Bluesky

No

No built-in node.

Pinterest

No

No built-in node.

So the practical coverage of per-platform nodes is LinkedIn, X, YouTube and Reddit, plus whatever you are willing to hand-build on the Graph API. For Instagram, TikTok, Threads, Bluesky and Pinterest you are writing HTTP calls regardless — which is the argument for writing *one* set of them.

A workflow that actually posts

Here is the four-node version. It replaces the router-and-one-node-per-platform shape with a single HTTP Request node, so adding a platform is editing an array rather than editing the canvas.

1. `Schedule Trigger`. *Trigger Interval* = Minutes, *Minutes Between Triggers* = 15. The workflow is a queue drainer, not a webhook endpoint.

2. `Google Sheets` › Get Row(s). Your calendar sheet: publish_at, copy, image_url, accounts, status. Add a filter for status = queued.

3. `Filter`. Condition: {{ $json.publish_at }} → *Date & Time* → *is before* → {{ $now }}. Without this, the first run publishes your entire calendar at once. It happens to everyone exactly once.

4. `HTTP Request`. This is the whole publishing layer:

  • Method — POST
  • URL — https://api.outstand.so/v1/posts/
  • Authentication — Generic Credential Type → Header Auth. Create the credential once with *Name* = Authorization and *Value* = Bearer YOUR_API_KEY. Do not type the key into the Headers section — a credential is encrypted at rest and is not exported with the workflow JSON; a header value is neither.
  • Send Body — on
  • Body Content Type — JSON
  • Specify Body — Using JSON

The JSON, with expressions pulling from the sheet row:

json
1{
2  "containers": [
3    {
4      "content": "{{ $json.copy }}",
5      "media": [
6        { "url": "{{ $json.image_url }}", "filename": "card.jpg" }
7      ]
8    }
9  ],
10  "accounts": ["Kx7vQ", "Tm2bN", "9pLwR"],
11  "scheduledAt": "{{ $json.publish_at }}"
12}

Then a fifth node — Google Sheets › Update Row — setting status to posted, wired off the success output only. A write-back that runs regardless of outcome is the bug that makes a failed post look published and guarantees nobody retries it.

Three details in that payload that will cost you an evening if you skip them:

  1. `accounts` takes account identifiers, not network names. Each entry is the opaque id of a connected account from GET /v1/social-accounts. "linkedin" is not an identifier and will not resolve. Fetch the list once, paste the IDs in, and treat them as opaque strings — never construct one and never assume a format.
  2. Unresolvable entries are dropped silently. The request only fails if *none* of the identifiers resolve. ["Kx7vQ", "linkedin"] returns success: true and publishes to Kx7vQ alone, with no error. Compare post.socialAccounts in the response against what you asked for instead of trusting the 200.
  3. `scheduledAt` is capped at 30 days ahead. Omit it, or pass a timestamp in the past, and the post publishes immediately; a timestamp beyond 30 days is rejected with a 400 and nothing is created. That is exactly why the Schedule Trigger and the publish_at filter stay in the workflow — the sheet is your long calendar, and the API holds the next 30 days of it.

Adding Threads, TikTok and Bluesky to this workflow is three more strings in the accounts array. No new node, no new OAuth app, no new credential.

Self-hosting: where the credentials live

This is the part that matters more on n8n than on a hosted automation platform, because you own the database.

n8n encrypts credentials in its own database. The key comes from N8N_ENCRYPTION_KEY, documented as: "Provide a custom key used to encrypt credentials in the n8n database. By default n8n generates a random key on first launch." Two consequences worth internalising before you go to production:

  • Set it explicitly and back it up. If you let n8n generate the key and then lose the volume it lives on, every stored credential in your database becomes undecryptable. Restoring the database without the key restores nothing usable.
  • The same key must be present on every instance. Main process, workers, anything reading that database. A worker with a different key cannot decrypt the credentials the main instance wrote.

The practical argument for the one-node pattern shows up here too. Six per-platform OAuth credentials means six token stores in your database, six refresh paths, and six things to re-authorise after a key rotation. One header credential against one API is one.

Where per-platform nodes stop

Four failure modes, in roughly the order you will meet them:

Tokens expire on their own schedule, and nothing announces it. Every social platform expires access tokens, and the lifetimes differ by platform. n8n has no node that fires when a stored credential goes stale — you find out when an execution fails, which is after the post did not go out. If nobody watches the executions list, the gap between broken and noticed is however long until someone asks why LinkedIn went quiet.

The platforms with no node are the ones with the hardest APIs. This is not a coincidence. TikTok's Content Posting API has a direct-post audit you have to pass; until you do, posts are forced to SELF_ONLY visibility and you are capped at five distinct creators per 24 hours. Instagram publishing runs through a container-then-publish flow on the Graph API with its own media constraints. These are not "add a node" problems — we wrote up the TikTok content posting API separately because it needs its own page.

You supply the developer apps. The X node needs your own X developer account and a paid API tier for meaningful write access. That is a cost and a review process per platform, before any code runs.

There is no single view of what happened. With one node per platform, "did this post go out everywhere?" is a question you answer by reading an execution log branch by branch. With one call you read one response.

Retries, token refresh, and what to log

Publishing is a network call to someone else's rate-limited service. Build for it to fail.

Retries go in node settings. On the HTTP Request node, open the Settings tab and turn on Retry On Fail; set Max Tries and Wait Between Tries there. The same tab carries On Error, with three documented options: *Stop Workflow*, *Continue*, and *Continue (using error output)*. For a publishing node, *Continue (using error output)* is the useful one — it gives you a second branch to write failed back to the sheet on.

Catch the rest with an error workflow. Build one workflow starting with the Error Trigger node, name it, then set it as the Error workflow under Options › Settings in your publishing workflow. One catch-all that posts to Slack beats per-node alerting you will not maintain. Note that a workflow containing an Error Trigger uses itself as its own error workflow by default, and that you cannot test an error workflow by running it manually.

Subscribe to token expiry instead of polling for it. Outstand emits webhooks for account.token_expired, post.published and post.error; point them at an n8n Webhook node in a second small workflow. The payload is signed with HMAC-SHA256 in an X-Outstand-Signature header, so verify it before you trust the body. That converts a silent expiry into a message in a channel.

Log per account, not per post. A fan-out publish is not binary. Each target carries its own status (pending, published, failed), its own error string, and its own platformPostId once it lands. "Four of five succeeded and Instagram hit a media error" is the normal outcome, and alerting that only asks "did the post work?" will either page you constantly or never.

When to drop n8n and call the API from your own code

n8n earns its place when the workflow is *your* operational process: a content calendar, a repurposing pipeline, an approval step, something a human owns and a 15-minute delay cannot hurt. The workflow above plus an error workflow is a good place to stop.

Move the call into your own codebase when any of these is true:

  • Publishing is in your product's path. If a user presses a button and expects a post, you want a synchronous HTTP call with your own error handling, not a workflow run. Our practical guide to social posting APIs covers that end to end.
  • You are building a scheduler, not using one. Then the queue, the retries and the calendar are your product, and n8n is a layer between you and the thing you are building. Start from how to build a social media scheduler instead.
  • You need the publishing logic reviewed and versioned. A workflow is JSON in a database. You can export it; you will not diff it in a pull request the way you would diff a function.

If you are on Make rather than n8n, the same one-call pattern applies with a different runtime — see Make.com social media automation. If you are still picking a category, we compared the social media automation tools and the unified social media APIs for developers on exactly this axis.

One HTTP Request node, twelve networks, including the five n8n has no node for. Outstand is free to start.