Schedule a Week of Posts From Your Terminal
Your content calendar lives in a browser tab you keep forgetting to open. Every Monday you paste roughly the same five posts into roughly the same five forms, change a timestamp, pick the channels again, and click publish. It is not hard work. It is repetitive work that a computer should be doing, and it happens to live in the one tool you spend the least time in.
If your day already happens in a terminal, the queue can happen there too. This guide covers the commands worth memorizing, how to parse what they hand back, how to script a week of posts from files you can review before they ship, and the cron patterns that will not wake you up at 4am.
Why the queue belongs in text
A dashboard is optimized for writing one post. It is not optimized for the twentieth post of the week, and it is actively bad at the thing developers want most: repeating a known good process without re-deciding every part of it each time.
Text fixes that. When posts start as files, they diff. A teammate can read next week's queue in a pull request before a single character reaches an audience, and the discussion happens where your team already discusses things. When posts live only in a scheduler's database, the only history you have is whatever the interface chooses to show you, and rebuilding after a mistake is an afternoon of clicking rather than a re-run.
Install and connect once
The CLI ships on npm as postqueen. Install it globally, then give it an API key generated from Settings, then Developers in your dashboard. Authenticating stores credentials under ~/.postqueen, or you can keep the key in the environment, which is what you want on a build machine or anywhere a human is not typing.
# Install the CLInpm install -g postqueen # Key from Settings > Developersexport POSTQUEEN_API_KEY=your_api_key # Self-hosted? Point it at your own backendexport POSTQUEEN_API_URL=https://social.example.com/api # Smoke test: does it see your channels?postqueen integrations:listThat last command is the only smoke test you need. If it prints your channels, everything downstream works. If it does not, the problem is the key or the URL, and you have learned that now rather than at midnight when a scheduled job learns it for you.
The commands worth memorizing
The surface is small on purpose. Nearly every workflow is a recombination of four commands, and once you know what each one returns you can stop reading documentation and start writing scripts.
integrations:list gives you the IDs
Everything else takes channel IDs, not channel names. List once, keep the IDs you actually post to, and stop looking them up. If a channel carries extras such as a subreddit flair, a YouTube playlist or a Pinterest board, integrations:settings pulls the valid options for that specific channel, so you can fill them in before you compose instead of after a post bounces.
posts:create takes flags or a file
Flags are for the one-off you are typing right now. The file form is for anything you want to keep: content, media, schedule and target channels in one payload that can live in a repository, get reviewed, and be re-run when you inevitably change your mind about the wording.
# One-off, straight from the shellpostqueen posts:create \ -c "Release 2.4 is out: faster uploads, fewer clicks." \ -m "release-2-4.png" \ -s "2026-07-20T09:00:00Z" \ -i "x-id,linkedin-id" # The version you keep: a reviewable filepostqueen posts:create --json campaign.json # Media you plan to reusepostqueen upload demo.mp4posts:list and analytics close the loop
posts:list tells you what is genuinely queued, which is a different question from what you believe you queued. The analytics commands pull numbers back out, so the same session that fills next week's calendar can also tell you what last month's calendar actually did.
# What is queued right nowpostqueen posts:list # Last 30 days for one channelpostqueen analytics:platform <id> -d 30 # One post, first week of its lifepostqueen analytics:post <post-id> -d 7Parsing what comes back
Commands return JSON. That is the whole reason this works as a scripting target rather than a demo: you can feed one command's output into the next step without writing regexes against human-readable text that changes the moment somebody improves the formatting.
jq is the only extra tool you need. Run a command once, look at the shape yourself, then pin the fields you depend on.
# Read the shape before you build on itpostqueen integrations:list | jq '.[0]' # Every channel ID, one per linepostqueen integrations:list | jq -r '.[].id' # The comma separated form that -i expects,# saved once and reused by your scriptspostqueen integrations:list \ | jq -r '[.[].id] | join(",")' > channels.txt- Inspect the output yourself before trusting any example, including this one. Field names are a contract you should read, not assume.
- Keep channel IDs in a file your scripts read, not in your shell history where they get lost the next time you clear it.
- Fail loudly. A script that reads a missing field should exit non-zero, not quietly schedule an empty post to thirty channels.
- Keep POSTQUEEN_API_KEY in the environment or a secret manager, never in the repository that holds your posts.
Scripting a week
The payoff is a queue you can regenerate. Keep next week's posts as files, one per post, and let a script turn them into a schedule. The pattern below reads a directory, assigns each file a slot on consecutive mornings, and creates the post. Every flag in it is one you already saw above.
#!/usr/bin/env bashset -euo pipefail CHANNELS=$(cat channels.txt) # One post per file, consecutive mornings,# starting tomorrow. GNU date shown here;# on macOS use: date -u -v+1dday=1for file in ./queue/*.txt; do when=$(date -u -d "+${day} day" +%Y-%m-%dT09:00:00Z) postqueen posts:create \ -c "$(cat "$file")" \ -s "$when" \ -i "$CHANNELS" day=$((day + 1))done # Receiptspostqueen posts:listThirty lines of bash and next week is a directory. The real gain is not the typing you saved, it is that the queue became reviewable: a colleague can read the diff, catch the sentence that reads badly, and approve the branch. Nothing about that workflow is new to anyone on your team, which is precisely why it survives contact with a busy week.
Cron patterns that will not surprise you
One mistake is worth calling out before you write any cron at all: do not use cron to publish. The scheduler already publishes. Use cron to fill the queue ahead of time and let the scheduler own the clock. A cron job that posts right now is a job that posts during an outage, posts a launch that got delayed by a day, and posts at 4am the weekend the clocks change.
Do not cron the publish. Cron the queue, and let the scheduler own the clock.
- Run the fill job weekly and schedule posts several days out, so there is a review window between the machine deciding and the audience seeing.
- Cron runs in the server's local time and your timestamps are UTC. Pick one, convert deliberately, and expect the two to disagree twice a year.
- Wrap the job in flock so a slow run never overlaps a fast one and files the same week twice.
- Make it safe to re-run. Track which files you already queued, or a retry becomes a duplicate in front of an audience.
- Log somewhere you will actually read, and exit non-zero on failure so your monitoring has something to notice.
# Fill next week every Monday at 8 AM,# never overlapping with itself0 8 * * 1 /usr/bin/flock -n /tmp/pq.lock \ /home/deploy/fill-queue.sh >> /var/log/queue.log 2>&1Guardrails before you automate
Two habits keep this pleasant. First, schedule instead of publishing while you are still building. A post queued for tomorrow morning can be edited or deleted from the calendar; a post that already went out to five networks cannot be recalled from any of them, and the screenshot is forever. Second, keep the key narrow and revocable. It is one string in one environment variable, and revoking it stops every script and every agent at once, which is exactly the property you want the day a laptop goes missing.
postqueen is open source under AGPL-3.0, so if you would rather the whole thing run on hardware you control, the same CLI talks to a self-hosted install by pointing POSTQUEEN_API_URL at your own backend. Cloud or self-hosted, the commands do not change. That is the part that makes the scripts worth writing: they outlive the decision about where the thing runs.