Posthive Documentation
Posthive is an open-source social media scheduling SaaS. Write once, publish to Bluesky, Threads, Instagram, LinkedIn, Mastodon, and YouTube — all from a single clean interface. Self-hostable under AGPL-3.0.
Quick start
Get Posthive running locally in under five minutes.
1. Clone the repository
git clone https://github.com/posthive/posthive.git
cd posthive2. Copy environment files
cp apps/api/.env.example apps/api/.env
# Edit apps/api/.env and fill in the required values3. Run database migrations
cd apps/api
pnpm db:migrate4. Start the dev server
# From the repo root
pnpm devThis starts both the API on http://localhost:3001 and the web app on http://localhost:3000 in parallel.
Installation
Prerequisites
- Node.js ≥ 20
- pnpm ≥ 9 (npm i -g pnpm)
- Redis — Upstash free tier or Railway Redis (required for BullMQ job queue)
Clone and install
git clone https://github.com/posthive/posthive.git
cd posthive
pnpm installThe monorepo uses pnpm workspaces. Running pnpm install at the root installs dependencies for both apps/api and apps/web.
Environment variables
All configuration lives in apps/api/.env. Copy .env.example and fill in the values below.
| Variable | Required | Description |
|---|---|---|
| DATABASE_URL | Yes | SQLite path (file:./dev.db) for dev, or a Postgres connection string for prod. |
| ENCRYPTION_KEY | Yes | 64-char hex string. Used for AES-256-GCM encryption of stored credentials. Never change after data is written. |
| REDIS_URL | Yes | Redis connection string (e.g. rediss://...) for BullMQ job queue. |
| JWT_ACCESS_SECRET | Yes | 64-char hex. Signs short-lived access tokens. |
| JWT_REFRESH_SECRET | Yes | 64-char hex. Signs long-lived refresh tokens. |
| WEB_URL | Yes | URL of the web app. Use http://localhost:3000 in dev so auth cookies stay stable across tunnel restarts. |
| PUBLIC_API_URL | Meta only | Public HTTPS URL of the API. Meta fetches images from this URL, so it must be reachable from the internet. |
| ENABLE_BILLING | No | Set to true to enable Dodo Payments billing. Defaults to off for self-hosters. |
| AUTH_PROVIDER | No | local (default) or supabase. Switches the auth backend. |
Bluesky
Bluesky uses app passwords — no OAuth flow required. Connection is straightforward and does not need a public callback URL.
How to connect
- Go to bsky.app → Settings → Privacy and Security → App Passwords.
- Create a new app password and copy it.
- Open the Accounts page in Posthive, click Connect Bluesky.
- Enter your Bluesky handle (e.g. yourname.bsky.social) and the app password.
Posthive stores the app password encrypted with AES-256-GCM. Your main account password is never used or stored.
Threads
Threads uses Meta OAuth 2.0. You need a Meta Developer app with the Threads use case enabled.
Setup
- Go to developers.facebook.com and create an app.
- Add the Threads API use case to your app.
- Add a redirect URI matching THREADS_REDIRECT_URI in your env — must be a public HTTPS URL.
- Copy your App ID and App Secret into THREADS_APP_ID and THREADS_APP_SECRET.
- Click Connect Threads on the Accounts page and complete the OAuth flow.
Threads tokens expire every 60 days. Posthive automatically refreshes them before each scheduled post.
Instagram publishing requires a Professional (Business or Creator) account linked to a Facebook Page, and a Meta Developer app with the Instagram product enabled.
Supported media types
- Post — single image or carousel (up to 10 images on Pro/Team)
- Reel — short video (Pro/Team plans only)
- Story — image or video story (Pro/Team plans only)
Setup
- Create a Meta Developer app and add the Instagram product.
- Set your redirect URI to INSTAGRAM_REDIRECT_URI (must be public HTTPS).
- Fill in INSTAGRAM_APP_ID, INSTAGRAM_APP_SECRET, and PUBLIC_API_URL.
- Click Connect Instagram on the Accounts page.
LinkedIn uses OAuth 2.0 via the LinkedIn Developer platform.
Setup
- Create an app at developer.linkedin.com.
- Add the Share on LinkedIn and Sign In with LinkedIn using OpenID Connect products.
- Add your callback URL under Auth → Authorized redirect URLs.
- Set LINKEDIN_CLIENT_ID and LINKEDIN_CLIENT_SECRET in your env.
- Click Connect LinkedIn on the Accounts page.
Mastodon
Posthive works with any Mastodon instance. You register an application within your own instance and paste the credentials into Posthive.
How to connect
- Log in to your Mastodon instance (e.g. mastodon.social).
- Go to Settings → Development → New application.
- Give it a name, set the redirect URI to your Posthive callback URL, and enable the write:statuses and write:media scopes.
- Copy the Client key and Client secret.
- Click Connect Mastodon in Posthive, enter your instance URL and the credentials.
YouTube
Posthive publishes to YouTube as Shorts (or regular videos — your choice per post) using Google OAuth 2.0 and the YouTube Data API v3. Every post requires a video attached.
How to connect
- Create a project at console.cloud.google.com and enable the YouTube Data API v3.
- Configure the OAuth consent screen — add the youtube.upload, youtube.readonly, and youtube.force-ssl scopes, and add your own Google account under Audience → Test users while the app is unverified.
- Create an OAuth client (Web application) and copy the Client ID and Secret into YOUTUBE_CLIENT_ID / YOUTUBE_CLIENT_SECRET.
- Click Connect YouTube on the Accounts page and authorize.
Shorts vs. regular video
In Compose, the YouTube section has a Short / Video toggle. YouTube classifies Shorts by the video file itself — vertical (9:16) and 60 seconds or under is the reliable threshold. Posthive auto-appends #Shorts to the description when "Short" is selected, but that tag alone does nothing if the video doesn't already qualify by aspect ratio and duration. Posthive checks the attached video's dimensions and warns you in the UI if it won't actually classify as a Short.
Title and description are separate dedicated fields (not the shared Post box other platforms use) — title is capped at 100 characters, description at 5,000. Selecting only YouTube accounts hides the shared Post box entirely since it isn't used.
Scheduling posts
The Compose page is where you write and schedule posts. Everything happens in a single panel — no multi-step wizard.
Steps
- Select one or more connected accounts from the account picker at the top.
- Write your post in the text area. The character counter updates per-platform.
- Optionally attach images or video using the media button.
- Pick a scheduled time using the date-time picker. You can also post immediately.
- Click Schedule. The post is enqueued and will fire at the chosen time.
Calendar view
The Posts page (/jobs) has both a list view and a calendar view. Toggle between them with the view switcher in the top-right corner.
The calendar supports month, week, and day modes. Pending posts can be dragged to a new time slot to reschedule them without opening the edit dialog.
First comment
The first comment field lets you attach a reply that is posted immediately after the main post goes live. This is commonly used to add hashtags without cluttering the main post body, or to add a thread continuation.
The comment is published per-platform — each connected account gets its own first comment published to that platform.
Per-platform overrides
Available on Pro and Team plans. Per-platform overrides let you customise the post text for individual accounts without creating separate posts.
Click the Customize button next to any selected account in the Compose page. A dialog opens where you can edit the content independently for that account. Accounts without an override use the main post body.
Media uploads
Posthive supports image and video attachments. Images can be attached by clicking the media button, dragging files into the compose area, or pasting from the clipboard.
- Creator plan: up to 4 images per post (carousel).
- Pro / Team plans: up to 10 images per carousel.
- Alt text is supported — click any thumbnail to add descriptive text for accessibility.
- In development, files are stored on local disk. In production, set STORAGE_PROVIDER=supabase and configure your bucket.
Docker setup
Posthive can be self-hosted on any platform that runs Docker containers — Railway, Render, Fly.io, or your own VPS.
Recommended stack
- Deploy the API container and point DATABASE_URL to a managed Postgres instance.
- Set REDIS_URL to an Upstash Redis or Railway Redis URL.
- Update the Prisma provider to postgresql in apps/api/prisma/schema.prisma.
- Run pnpm db:migrate as part of your release command.
# Example Railway / Dockerfile release command
pnpm db:migrate && node dist/index.jsDatabase
Posthive uses Prisma 5 with SQLite in development and Postgres in production. Switching is a one-line change in the schema.
// apps/api/prisma/schema.prisma
datasource db {
provider = "postgresql" // change from "sqlite"
url = env("DATABASE_URL")
}Run migrations after any schema change:
cd apps/api
pnpm db:migrateRedis
Redis is used exclusively for the BullMQ job queue that powers scheduled post delivery. No application state is stored in Redis — it is safe to flush between deploys as long as no posts are currently queued.
Options
- Upstash — free tier is sufficient for most self-hosters. Use the rediss:// TLS URL.
- Railway Redis — add the Redis plugin to your Railway project and copy the connection string.
- Self-hosted — any Redis 6+ instance works.
REDIS_URL="rediss://default:<password>@<host>:<port>"Storage
Uploaded media can be stored locally (dev) or in Supabase Storage (prod). Switch with the STORAGE_PROVIDER env variable.
Local storage (default)
STORAGE_PROVIDER=localFiles are written to apps/api/uploads/. Not recommended for production — files are lost on redeploy.
Supabase Storage
- Create a Supabase project.
- In Storage, create a public bucket named media.
- Copy your project URL and service role key.
STORAGE_PROVIDER=supabase
SUPABASE_URL="https://your-project.supabase.co"
SUPABASE_SERVICE_KEY="eyJ..."Plans & pricing
Posthive has four tiers. The trial is available immediately after sign-up with no card required.
| Plan | Accounts | Posts / month | Reels & Stories | Overrides | Images / post |
|---|---|---|---|---|---|
| Trialing | 3 | 30 | No | No | 4 |
| Creator | 5 | 400 | No | No | 4 |
| Pro | 15 | Unlimited | Yes | Yes | 10 |
| Team | 50 | Unlimited | Yes | Yes | 10 |
Billing is handled through Dodo Payments. Set ENABLE_BILLING=true and configure your Dodo API key and product IDs to activate billing.
Webhooks
Posthive listens for Dodo Payments webhook events to update subscription status in real time. Configure the webhook endpoint in your Dodo dashboard.
Webhook URL
POST https://your-api-url/billing/webhookWebhook secret
# Dodo dashboard shows: whsec_abc123...
# Set in .env:
DODO_WEBHOOK_SECRET="abc123..."Handled events
| Event | Effect |
|---|---|
| payment.succeeded | Sets the user's plan to active for the purchased tier. |
| subscription.cancelled | Marks the subscription as cancelled. Access continues until the end of the billing period. |