Projects

postkit — Cross-Posting CLI

2026·4 min read

Node.js
JavaScript
CLI
OAuth
ffmpeg
Private repo

postkit posts one pack of text, images or a reel to LinkedIn, Facebook, Instagram, Threads, Bluesky, Mastodon, Telegram, Discord and Reddit from the command line, over each platform's official free API. It dry-runs by default, checks every limit before it sends anything, and keeps a ledger so the same post never goes out twice.

On this page6 sections
  • Status
  • Why I built it
  • What it does
  • Channels
  • What it can't post to, and why
  • Under the hood

postkit posts one pack of text, images or a reel to LinkedIn, Facebook, Instagram, Threads, Bluesky, Mastodon, Telegram, Discord and Reddit from the command line, over each platform's official free API. It dry-runs by default, checks every limit before it sends anything, and keeps a ledger so the same post never goes out twice.

pnpm post                      # dry run: prints every request, sends nothing
pnpm post -- --live            # publishes
pnpm post -- --to linkedin --live

Status#

Version 0.1.0, MIT licensed, in a private repo.

Why I built it#

Posting one release to six places by hand took half an hour, and it went wrong the same four ways every time. A post went out with no image because the picker didn't take. A caption ran 18 characters over a limit nobody counted. The same post went out twice because nothing remembered the first one. A network got skipped because the list lived in someone's head.

Each of those is a rule, so postkit checks them instead of me:

  • Official APIs only. No browser automation, no headless Chrome and no session cookies to keep alive. Every call is a documented endpoint on the platform's free tier.
  • Dry run by default. Nothing leaves the machine until you type --live. A dry run still runs the whole pipeline (validation, image conversion, hosting) and prints every request it would have sent.
  • No double posts. A ledger records every successful publish and refuses the same pack on the same channel again unless you pass --force.
  • It doesn't write the copy. A publisher that can also compose can post something no person read. Packs hold captions someone wrote, and postkit only ships them.

What it does#

  • One pack goes to every channel you enable, in one command.
  • Text limits, image counts and media formats are checked per channel before any request leaves.
  • Images are converted once per source and settings (format, aspect ratio, quality), so two channels that want the same JPEG share one conversion and one upload.
  • A quality check refuses soft images and under-encoded video before they post anywhere.
  • Instagram, Threads and Meta reels fetch media from a URL instead of taking the bytes, so postkit hosts those files in a public GitHub repo for free.
  • Retries cover a dropped connection, never a call that may already have reached the platform.
  • postkit doctor runs one live identity check per network and is safe to put on a schedule.
  • Secrets are redacted from every line it prints, in a dry run or a live one.
  • It has no npm dependencies at runtime: only Node's built-ins, plus ffmpeg and ffprobe for media.

Channels#

Network What it posts
LinkedIn Text and up to 9 images on your own profile
Facebook Page Feed posts (one photo or an album), stories and reels
Instagram Feed posts and carousels, stories and reels
Threads Text, images or a carousel, and video
Bluesky Text and up to 4 images, with link facets counted in bytes
Mastodon A status and up to 4 images
Telegram Channel posts with text, a photo, an album or a video
Discord A channel message and up to 10 files, through a webhook
Reddit A text post to your profile or a subreddit

postkit channels prints the full table from the code: every channel's limits, how it signs in, how long its token lasts, and what the current project has turned on.

What it can't post to, and why#

Some networks have no API for this, and postkit says so instead of quietly leaving them off:

  • X has had no free tier since February 2026. Posting is pay per use, $0.20 a post if it carries a link.
  • Facebook groups have no route. Every plausible endpoint was called live in September 2026, on API versions v6.0 through v23.0, and all of them refused. Meta removed group publishing from every version at once in April 2024.
  • YouTube community posts have never had a public API.

Others have a working API behind an audit or an approval. TikTok keeps an unaudited client's posts visible to the posting account only, until the client passes TikTok's audit. YouTube Shorts uploads stay private until the Google Cloud project passes YouTube's compliance audit. Reddit's channel is built and works, but Reddit stopped granting new API access in August 2026, so it needs an app that was already approved.

Under the hood#

Runtime Node.js 20 or later, ES modules
Dependencies None at runtime; ffmpeg and ffprobe for images and video
Sign-in OAuth with a loopback or HTTPS redirect where the network requires it; app passwords, app tokens, bot tokens or webhooks where it doesn't
Tests node --test, run in CI on every push and pull request to main
License MIT

More projects

  • SilkroadBot — .NET 8 Silkroad Online Bot2026 · C#
  • Freeby — Invoicing & Time Tracking2026 · Next.js
  • ClipForge — Lemon Squeezy SaaS Starter2026 · Next.js
All projects
  • Status
  • Why I built it
  • What it does
  • Channels
  • What it can't post to, and why
  • Under the hood