
Postiz
- 35 installs
- 40 repo stars
- Updated July 30, 2026
- gitroomhq/postiz-docs
Schedule, list, and delete social media posts across 28+ channels through the Postiz public REST API.
About
Documents the Postiz public API for scheduling social and chat posts to 28+ channels including X, LinkedIn, Instagram, YouTube, and Reddit. A developer uses it to programmatically upload media and schedule cross-channel posts with an API key.
- Endpoints for integrations, find-slot, upload, upload-from-url, posts, and delete
- Attached media must be uploaded first and referenced via its uploads.postiz.com URL
Postiz by the numbers
- 35 all-time installs (skills.sh)
- Ranked #589 of 853 Sales & Marketing skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
npx skills add https://github.com/gitroomhq/postiz-docs --skill postizAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 35 |
|---|---|
| repo stars | ★ 40 |
| Last updated | July 30, 2026 |
| Repository | gitroomhq/postiz-docs ↗ |
What it does
Schedule, list, and delete social media posts across 28+ channels through the Postiz public REST API.
Files
Postiz Skill
Postiz is a tool to schedule social media and chat posts to 28+ channels:
X, LinkedIn, LinkedIn Page, Reddit, Instagram, Facebook Page, Threads, YouTube, Google My Business, TikTok, Pinterest, Dribbble, Discord, Slack, Kick, Twitch, Mastodon, Bluesky, Lemmy, Farcaster, Telegram, Nostr, VK, Medium, Dev.to, Hashnode, WordPress, ListMonk
Setup
1. Get your API key: https://platform.postiz.com/settings 2. Click on "Settings" 3. Click "Reveal" 4. Set environment variables:
export POSTIZ_API_KEY="your-api-key"Get all added channels
curl -X GET "https://api.postiz.com/public/v1/integrations" \
-H "Authorization: $POSTIZ_API_KEY"Get the next available slot for a channel
curl -X GET "https://api.postiz.com/public/v1/find-slot/:id" \
-H "Authorization: $POSTIZ_API_KEY"Upload a new file (form-data)
curl -X POST "https://api.postiz.com/public/v1/upload" \
-H "Authorization: $POSTIZ_API_KEY" \
-F "file=@/path/to/your/file.png"Upload a new file from an existing URL
curl -X POST "https://api.postiz.com/public/v1/upload-from-url" \
-H "Authorization: $POSTIZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/image.png"
}'Post list
curl -X GET "https://api.postiz.com/public/v1/posts?startDate=2024-12-14T08:18:54.274Z&endDate=2024-12-14T08:18:54.274Z&customer=optionalCustomerId" \
-H "Authorization: $POSTIZ_API_KEY"Schedule a new post
Settings for different channels can be found in: https://docs.postiz.com/public-api/introduction On the bottom left menu
When scheduling a new posts, if you attach media, you must upload it first and use the url of the uploaded media. Upload URL must contain: uploads.postiz.com.
curl -X POST "https://api.postiz.com/public/v1/posts" \
-H "Authorization: $POSTIZ_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"type": "schedule",
"date": "2024-12-14T10:00:00.000Z",
"shortLink": false,
"tags": [],
"posts": [
{
"integration": {
"id": "your-x-integration-id"
},
"value": [
{
"content": "Hello from the Postiz API! 🚀",
"image": [{ "id": "img-123", "path": "https://uploads.postiz.com/photo.jpg" }]
}
],
"settings": {
"__type": "provider name",
rest of the settings
}
}
]
}'Delete a post
curl -X DELETE "https://api.postiz.com/public/v1/posts/:id" \
-H "Authorization: $POSTIZ_API_KEY"# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project><?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AskMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project><?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Ask2AgentMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project><?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EditMigrationStateService">
<option name="migrationStatus" value="COMPLETED" />
</component>
</project><?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/new-postiz-docs.iml" filepath="$PROJECT_DIR$/.idea/new-postiz-docs.iml" />
</modules>
</component>
</project><?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module><?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project><?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>{
"snyk.advanced.organization": "9b081781-54e1-4370-a15e-1af15bc27361",
"snyk.advanced.autoSelectOrganization": true
}---
title: Analytics
description: 'View platform and post-level analytics from the command line'
---
## Platform Analytics
Get analytics for a specific integration/channel. Returns metrics like followers, impressions, and engagement over time.
```bash
postiz analytics:platform <integration-id>
```
### Options
| Flag | Description |
|------|-------------|
| `-d, --date` | Number of days to look back (default: 7) |
### Examples
```bash
# Last 7 days (default)
postiz analytics:platform your-integration-id
# Last 30 days
postiz analytics:platform your-integration-id -d 30
# Last 90 days
postiz analytics:platform your-integration-id -d 90
```
The response is an array of metrics, each with daily data points:
```json
[
{
"label": "Followers",
"data": [
{ "total": "1250", "date": "2025-01-01" },
{ "total": "1280", "date": "2025-01-02" }
],
"percentageChange": 2.4
},
{
"label": "Impressions",
"data": [
{ "total": "5000", "date": "2025-01-01" },
{ "total": "5200", "date": "2025-01-02" }
],
"percentageChange": 4.0
}
]
```
<Note>
The metrics returned depend on the platform. For example, X returns followers and impressions, while YouTube may return subscribers and views.
</Note>
## Post Analytics
Get analytics for a specific published post. Returns metrics like likes, comments, shares, and impressions.
```bash
postiz analytics:post <post-id>
```
### Options
| Flag | Description |
|------|-------------|
| `-d, --date` | Number of days to look back (default: 7) |
### Examples
```bash
# Last 7 days (default)
postiz analytics:post your-post-id
# Last 30 days
postiz analytics:post your-post-id -d 30
```
The response follows the same format as platform analytics:
```json
[
{
"label": "Likes",
"data": [
{ "total": "150", "date": "2025-01-01" },
{ "total": "175", "date": "2025-01-02" }
],
"percentageChange": 16.7
},
{
"label": "Comments",
"data": [
{ "total": "25", "date": "2025-01-01" },
{ "total": "30", "date": "2025-01-02" }
],
"percentageChange": 20.0
}
]
```
<Tip>
Post analytics are only available for published posts. Draft or queued posts won't return analytics data.
</Tip>
## Scripting with Analytics
Extract specific metrics using `jq`:
```bash
# Get just the follower count trend
postiz analytics:platform integration-id -d 30 | jq '.[] | select(.label=="Followers")'
# Get percentage changes for all metrics
postiz analytics:platform integration-id | jq '.[] | {label, percentageChange}'
# Get the latest total for each post metric
postiz analytics:post post-id | jq '.[] | {label, latest: .data[-1].total}'
```
---
title: Authentication
description: 'Set up OAuth2 or API key authentication for the Postiz CLI'
---
## OAuth2 (Recommended)
Authenticate using the device flow — no client ID or secret needed:
```bash
postiz auth:login
```
This will:
1. Display a one-time code in your terminal
2. Open your browser to authorize
3. Automatically save credentials to `~/.postiz/credentials.json`
### Auth Commands
```bash
# Check current auth status (verifies credentials are still valid)
postiz auth:status
# Remove stored credentials
postiz auth:logout
```
## API Key
Alternatively, set your Postiz API key as an environment variable:
```bash
export POSTIZ_API_KEY=your_api_key_here
```
You can get your API key from the Postiz Settings page.
<Note>
OAuth2 credentials take priority over the API key when both are present.
</Note>
## Environment Variables
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `POSTIZ_API_KEY` | No* | - | Your Postiz API key |
| `POSTIZ_API_URL` | No | `https://api.postiz.com` | Custom API endpoint (for self-hosted Postiz) |
| `POSTIZ_AUTH_SERVER` | No | `https://cli-auth.postiz.com` | Custom auth server URL (for self-hosted auth server) |
*Either OAuth2 (via `postiz auth:login`) or `POSTIZ_API_KEY` is required.
## Self-Hosting the Auth Server
By default, `postiz auth:login` uses the hosted auth server at `cli-auth.postiz.com`. If you want to self-host the OAuth2 device flow server, you can run your own instance.
The auth server mediates the OAuth2 device flow so CLI users can authenticate without needing client credentials.
### Prerequisites
- Node.js >= 18
- PostgreSQL
### How It Works
```
CLI Auth Server Postiz
| | |
|-- POST /device/code ------->| |
|<-- device_code + user_code --| |
| | |
| User opens browser ------->| |
| Enters code | |
| |-- redirect to OAuth ----->|
| |<-- callback with code ----|
| |-- exchange for token ---->|
| |<-- access_token ----------|
| | (stored in Postgres) |
| | |
| POST /device/token (poll) >| |
|<-- access_token ------------| |
```
### 1. Clone the Repository
The auth server lives in the [postiz-agent](https://github.com/gitroomhq/postiz-agent) repository:
```bash
git clone https://github.com/gitroomhq/postiz-agent.git
cd postiz-agent/server
```
### 2. Create an OAuth App in Postiz
Go to **Postiz Settings > Developer > OAuth Apps** and create a new app. Set the callback URL to:
```
https://your-server-domain.com/device/callback
```
### 3. Set Up Postgres
Create a database. The server auto-creates the `device_requests` table on startup.
### 4. Configure Environment
```bash
export DATABASE_URL="postgresql://user:password@localhost:5432/postiz_auth"
export POSTIZ_OAUTH_CLIENT_ID="pca_xxx"
export POSTIZ_OAUTH_CLIENT_SECRET="pcs_xxx"
export SERVER_URL="https://your-server-domain.com"
```
| Variable | Required | Default | Description |
|----------|----------|---------|-------------|
| `DATABASE_URL` | Yes | - | Postgres connection string |
| `POSTIZ_OAUTH_CLIENT_ID` | Yes | - | OAuth app client ID from Postiz |
| `POSTIZ_OAUTH_CLIENT_SECRET` | Yes | - | OAuth app client secret from Postiz |
| `PORT` | No | `3111` | Server port |
| `SERVER_URL` | No | `http://localhost:{PORT}` | Public URL of this server |
| `POSTIZ_FRONTEND_URL` | No | `https://platform.postiz.com` | Postiz frontend URL for OAuth redirects |
| `POSTIZ_API_URL` | No | `https://api.postiz.com` | Postiz API URL for token exchange |
### 5. Run the Server
```bash
pnpm install
# Development
pnpm dev
# Production
pnpm build
pnpm start:prod
```
### 6. Point the CLI to Your Server
```bash
export POSTIZ_AUTH_SERVER="https://your-server-domain.com"
postiz auth:login
```
### Server Endpoints
| Method | Path | Description |
|--------|------|-------------|
| `POST` | `/device/code` | Start a new device flow. Returns `device_code`, `user_code`, and `verification_uri`. |
| `GET` | `/device/verify` | Browser page where the user enters their code. |
| `POST` | `/device/verify` | Validates user code and redirects to Postiz OAuth. |
| `GET` | `/device/callback` | Postiz redirects here after authorization. Exchanges auth code for token. |
| `POST` | `/device/token` | CLI polls this with `device_code`. Returns token when auth completes. |
| `GET` | `/health` | Health check. |
### Deployment
Any platform that runs Node.js and can connect to Postgres works — Railway, Fly.io, Render, VPS, etc.
The server is stateless beyond Postgres, so it scales horizontally. Run multiple instances behind a load balancer if needed.
---
title: Integrations
description: 'Discover connected accounts, settings schemas, and dynamic tools'
---
## Listing Integrations
List all connected social media accounts to get their IDs:
```bash
postiz integrations:list
```
This returns a JSON array of integrations. Use `jq` to extract specific fields:
```bash
# Get just the IDs and platform names
postiz integrations:list | jq '.[] | {id, identifier}'
```
```bash
# Find a specific platform
postiz integrations:list | jq '.[] | select(.identifier=="reddit")'
```
### Filtering by Group
If your channels are organized into groups (customers), filter the list to a single group with `--group`:
```bash
postiz integrations:list --group "customer-id"
```
## Listing Groups
List all groups (customers) for your organization to get their IDs:
```bash
postiz integrations:groups
```
This returns a JSON array of `{id, name}` objects. Use a group's `id` with `integrations:list --group` to filter channels:
```bash
# Find a group by name, then list its integrations
GROUP_ID=$(postiz integrations:groups | jq -r '.[] | select(.name=="My Company") | .id')
postiz integrations:list --group "$GROUP_ID"
```
## Getting Settings
Each platform has its own settings schema with character limits, required fields, and available options. Retrieve it with:
```bash
postiz integrations:settings <integration-id>
```
The response tells you:
- What fields are available (title, privacy level, subreddit, etc.)
- Which fields are required
- Character limits and validation rules
- Available dynamic tools you can trigger
<Tip>
Always check `integrations:settings` before posting to a new platform to understand what settings are available.
</Tip>
## Triggering Tools
Some platforms expose dynamic tools — for example, fetching Reddit flairs, YouTube playlists, or LinkedIn company pages. These return data you need when constructing platform-specific settings.
```bash
postiz integrations:trigger <integration-id> <method-name>
```
Pass additional data with `-d`:
```bash
postiz integrations:trigger <integration-id> <method-name> -d '{"key":"value"}'
```
### Examples
**Get Reddit flairs for a subreddit:**
```bash
postiz integrations:trigger reddit-id getFlairs -d '{"subreddit":"programming"}'
```
**Get YouTube playlists:**
```bash
postiz integrations:trigger youtube-id getPlaylists
```
**Get LinkedIn company pages:**
```bash
postiz integrations:trigger linkedin-id getCompanies
```
**Get Pinterest boards:**
```bash
postiz integrations:trigger pinterest-id getBoards
```
## Discovery Workflow
When working with a new platform, follow this workflow:
```bash
# 1. Find the integration ID
INTEGRATION_ID=$(postiz integrations:list | jq -r '.[] | select(.identifier=="reddit") | .id')
# 2. Check what settings and tools are available
postiz integrations:settings "$INTEGRATION_ID"
# 3. Use tools to fetch dynamic data (e.g., flairs)
postiz integrations:trigger "$INTEGRATION_ID" getFlairs -d '{"subreddit":"programming"}'
# 4. Create a post with the discovered settings
postiz posts:create \
-c "My post" \
-s "2025-01-15T10:00:00Z" \
--settings '{"subreddit":[{"value":{"subreddit":"programming","title":"Post Title","type":"text"}}]}' \
-i "$INTEGRATION_ID"
```
---
title: Introduction
description: 'Automate social media posting from the command line with the Postiz CLI'
---
<Note>
Create AI-powered UGC videos for your social media with [Agent Media](https://agent-media.ai) — generate engaging video content and schedule it directly with Postiz. Perfect for OpenClaw 🦞
</Note>
<Warning>
For your AI agent to work best with Postiz, install the skill by running:
```bash
npx skills add gitroomhq/postiz-agent
```
Or load the SKILL md file from [github.com/gitroomhq/postiz-agent](https://github.com/gitroomhq/postiz-agent).
</Warning>
The Postiz CLI is a command-line tool for automating social media posting across 28+ platforms. It wraps the [Public API](/public-api/introduction) so you can schedule posts, manage integrations, and upload media directly from your terminal or shell scripts.
## Installation
<Tabs>
<Tab title="npm">
```bash
npm install -g postiz
```
</Tab>
<Tab title="pnpm">
```bash
pnpm install -g postiz
```
</Tab>
</Tabs>
Verify the installation:
```bash
postiz --help
```
## Authentication
### Option 1: OAuth2 (Recommended)
Authenticate using the device flow — no client ID or secret needed:
```bash
postiz auth:login
```
This will:
1. Display a one-time code in your terminal
2. Open your browser to authorize
3. Automatically save credentials to `~/.postiz/credentials.json`
```bash
# Check current auth status
postiz auth:status
# Remove stored credentials
postiz auth:logout
```
### Option 2: API Key
Set your Postiz API key as an environment variable. You can get your API key from the Postiz Settings page.
```bash
export POSTIZ_API_KEY=your_api_key_here
```
<Tip>
Add this to your shell profile (`~/.bashrc`, `~/.zshrc`, etc.) so it persists across sessions.
</Tip>
<Note>
OAuth2 credentials take priority over the API key when both are present.
</Note>
### Custom API URL (self-hosted)
If you're running a self-hosted Postiz instance, point the CLI to your server:
```bash
export POSTIZ_API_URL=https://your-postiz-server.com
```
### Self-Hosting the Auth Server
By default, `postiz auth:login` uses the hosted auth server at `cli-auth.postiz.com`. If you want to self-host the OAuth2 device flow server, see the [Authentication](/cli/authentication) page for the full setup guide.
## Quick Start
```bash
# 1. List your connected social media accounts
postiz integrations:list
# 2. Create a scheduled post
postiz posts:create \
-c "Hello from the Postiz CLI!" \
-s "2025-01-15T10:00:00Z" \
-i "your-integration-id"
# 3. List your scheduled posts
postiz posts:list
```
## Commands Overview
| Command | Description |
|---------|-------------|
| `auth:login` | Authenticate via OAuth2 device flow |
| `auth:status` | Check current authentication status |
| `auth:logout` | Remove stored credentials |
| `integrations:list` | List all connected social media accounts |
| `integrations:settings` | Get the settings schema for an integration |
| `integrations:trigger` | Trigger a dynamic tool on an integration |
| `posts:create` | Create a new post |
| `posts:list` | List posts with optional date filtering |
| `posts:delete` | Delete a post by ID |
| `posts:missing` | List available content from provider for a post with missing release ID |
| `posts:connect` | Connect a post to its published content by release ID |
| `analytics:platform` | Get analytics for an integration/channel |
| `analytics:post` | Get analytics for a specific post |
| `upload` | Upload a media file |
<Note>
All commands output JSON, making the CLI easy to use in scripts and automation pipelines.
</Note>
---
title: Managing Posts
description: 'Create, list, and delete social media posts from the command line'
---
## Creating Posts
Use `posts:create` to schedule or draft posts to one or more platforms.
### Simple Post
```bash
postiz posts:create \
-c "Hello world!" \
-s "2025-01-15T10:00:00Z" \
-i "your-integration-id"
```
### Options
| Flag | Description |
|------|-------------|
| `-c, --content` | Post content. Use multiple times for threads/comments. |
| `-s, --date` | Schedule date in ISO 8601 format (required) |
| `-t, --type` | `schedule` (default) or `draft` |
| `-m, --media` | Comma-separated media URLs (use after uploading) |
| `-i, --integrations` | Comma-separated integration IDs (required) |
| `-d, --delay` | Delay between comments in milliseconds (default: 5000) |
| `--settings` | Platform-specific settings as JSON |
| `-j, --json` | Path to a JSON file for complex posts |
### Draft Post
```bash
postiz posts:create \
-c "Review this before publishing" \
-s "2025-01-15T10:00:00Z" \
-t draft \
-i "your-integration-id"
```
### Post with Media
Upload your media first with the [`upload`](/cli/media-upload) command, then reference the returned URL:
```bash
postiz posts:create \
-c "Check out this photo!" \
-m "https://uploads.postiz.com/your-image.jpg" \
-s "2025-01-15T10:00:00Z" \
-i "your-integration-id"
```
### Threads and Comments
Pass `-c` multiple times to create a thread. Each comment can have its own media with a corresponding `-m` flag:
```bash
postiz posts:create \
-c "Thread 1/3" -m "image1.jpg" \
-c "Thread 2/3" -m "image2.jpg" \
-c "Thread 3/3" \
-s "2025-01-15T10:00:00Z" \
-i "twitter-integration-id"
```
Use `-d` to control the delay between comments (in milliseconds):
```bash
postiz posts:create \
-c "First tweet" \
-c "Second tweet" \
-c "Third tweet" \
-s "2025-01-15T10:00:00Z" \
-d 2000 \
-i "twitter-integration-id"
```
### Multi-Platform Post
Send the same content to multiple platforms by passing comma-separated integration IDs:
```bash
postiz posts:create \
-c "Posting everywhere!" \
-s "2025-01-15T10:00:00Z" \
-i "twitter-id,linkedin-id,facebook-id"
```
### Platform-Specific Settings
Some platforms require additional settings. Pass them as JSON with `--settings`:
```bash
postiz posts:create \
-c "Check out this discussion" \
-s "2025-01-15T10:00:00Z" \
--settings '{"subreddit":[{"value":{"subreddit":"programming","title":"My Post","type":"text"}}]}' \
-i "reddit-integration-id"
```
<Tip>
Use `postiz integrations:settings <id>` to discover what settings are available for each platform. See [Integrations](/cli/integrations) for details.
</Tip>
### Complex Posts with JSON
For posts with detailed platform-specific content, use a JSON file:
```bash
postiz posts:create --json post.json
```
Example `post.json`:
```json
{
"integrations": ["twitter-123", "linkedin-456"],
"posts": [
{
"provider": "twitter",
"post": [{ "content": "Short tweet version", "image": [] }]
},
{
"provider": "linkedin",
"post": [{ "content": "Longer LinkedIn version with more detail", "image": [] }],
"settings": { "__type": "linkedin" }
}
]
}
```
## Listing Posts
```bash
postiz posts:list
```
### Filter by Date Range
```bash
postiz posts:list \
--startDate "2025-01-01T00:00:00Z" \
--endDate "2025-01-31T23:59:59Z"
```
### Filter by Customer
```bash
postiz posts:list --customer "customer-id"
```
## Connecting Missing Posts
Some platforms don't return a post ID immediately after publishing (the `releaseId` is set to `"missing"`). When this happens, you can fetch recent content from the provider and connect the correct one to your post. This enables analytics tracking.
### List Available Content
```bash
postiz posts:missing <post-id>
```
Returns an array of recent content items from the provider with their ID and thumbnail URL:
```json
[
{
"id": "7321456789012345678",
"url": "https://p16-sign.tiktokcdn-us.com/obj/cover-image.jpeg"
},
{
"id": "7321456789012345679",
"url": "https://p16-sign.tiktokcdn-us.com/obj/cover-image2.jpeg"
}
]
```
<Note>
This only works for posts where the `releaseId` is `"missing"`. Returns an empty array if the provider doesn't support this feature.
</Note>
### Connect a Post
Once you've identified the correct content, update the release ID:
```bash
postiz posts:connect <post-id> --release-id "7321456789012345678"
```
After connecting, the post will support full analytics via `postiz analytics:post`.
### Full Workflow
```bash
# 1. Find posts with missing release IDs
postiz posts:list | jq '.posts[] | select(.releaseId == "missing") | {id, content}'
# 2. Get available content from the provider
postiz posts:missing <post-id>
# 3. Connect the correct content
postiz posts:connect <post-id> --release-id "7321456789012345678"
# 4. Verify analytics now work
postiz analytics:post <post-id>
```
## Changing Post Status
Move a post between `draft` and `schedule` without changing its date.
```bash
postiz posts:status <post-id> --status draft
postiz posts:status <post-id> --status schedule
```
- `--status schedule` promotes a draft into the publishing queue and (re)starts the workflow so it will publish at its stored date.
- `--status draft` moves a scheduled post back to draft **and terminates any running publishing workflow**, so it will not publish.
<Note>
Use this when you want to pause a scheduled post without deleting it, or hand a draft off to the scheduler once it's ready.
</Note>
## Deleting Posts
```bash
postiz posts:delete <post-id>
```
---
title: Media Upload
description: 'Upload images, videos, and other media files for use in posts'
---
## Uploading Files
Upload a local file and receive a URL you can use in posts:
```bash
postiz upload <file-path>
```
The command returns a JSON response with the uploaded file's URL:
```json
{
"id": "img-123",
"path": "https://uploads.postiz.com/your-file.jpg"
}
```
<Warning>
You must upload media files to Postiz before using them in posts. Many platforms (TikTok, Instagram, YouTube) require verified URLs and will reject external links.
</Warning>
## Upload and Post Workflow
```bash
# 1. Upload the file
RESULT=$(postiz upload photo.jpg)
FILE_URL=$(echo "$RESULT" | jq -r '.path')
# 2. Use the URL in a post
postiz posts:create \
-c "Check out this photo!" \
-m "$FILE_URL" \
-s "2025-01-15T10:00:00Z" \
-i "your-integration-id"
```
## Supported File Types
<Tabs>
<Tab title="Images">
PNG, JPG, JPEG, GIF, WEBP, SVG, BMP, ICO
</Tab>
<Tab title="Videos">
MP4, MOV, AVI, MKV, WEBM, FLV, WMV, M4V, MPEG, 3GP
</Tab>
<Tab title="Audio">
MP3, WAV, OGG, AAC, FLAC, M4A
</Tab>
<Tab title="Documents">
PDF, DOC, DOCX
</Tab>
</Tabs>
## Video Upload Example
Platforms like TikTok, YouTube, and Instagram require video uploads through Postiz:
```bash
# Upload the video
VIDEO=$(postiz upload video.mp4)
VIDEO_URL=$(echo "$VIDEO" | jq -r '.path')
# Post to TikTok
postiz posts:create \
-c "New video! #fyp" \
-m "$VIDEO_URL" \
-s "2025-01-15T10:00:00Z" \
--settings '{"privacy_level":"PUBLIC_TO_EVERYONE"}' \
-i "tiktok-integration-id"
```
---
title: Platform Examples
description: 'Ready-to-use examples for posting to specific platforms'
---
## X (Twitter)
### Simple Post
```bash
postiz posts:create \
-c "Hello Twitter!" \
-s "2025-01-15T10:00:00Z" \
-i "twitter-id"
```
### Thread
```bash
postiz posts:create \
-c "Thread 1/3: Introduction" \
-c "Thread 2/3: Main point" \
-c "Thread 3/3: Conclusion" \
-s "2025-01-15T10:00:00Z" \
-d 2000 \
-i "twitter-id"
```
### With Reply Controls
```bash
postiz posts:create \
-c "Only followers can reply to this" \
-s "2025-01-15T10:00:00Z" \
--settings '{"who_can_reply_post":"followers"}' \
-i "twitter-id"
```
## Reddit
### Post with Flair
```bash
# 1. Get available flairs
postiz integrations:trigger reddit-id getFlairs -d '{"subreddit":"programming"}'
# 2. Post with a flair
postiz posts:create \
-c "My post content" \
-s "2025-01-15T10:00:00Z" \
--settings '{"subreddit":[{"value":{"subreddit":"programming","title":"Post Title","type":"text","flair":{"id":"flair-id","name":"Discussion"}}}]}' \
-i "reddit-id"
```
### Scripted Workflow
```bash
#!/bin/bash
REDDIT_ID=$(postiz integrations:list | jq -r '.[] | select(.identifier=="reddit") | .id')
FLAIRS=$(postiz integrations:trigger "$REDDIT_ID" getFlairs -d '{"subreddit":"programming"}')
FLAIR_ID=$(echo "$FLAIRS" | jq -r '.output[0].id')
postiz posts:create \
-c "Automated Reddit post" \
-s "2025-01-15T10:00:00Z" \
--settings "{\"subreddit\":[{\"value\":{\"subreddit\":\"programming\",\"title\":\"Post Title\",\"type\":\"text\",\"flair\":{\"id\":\"$FLAIR_ID\"}}}]}" \
-i "$REDDIT_ID"
```
## YouTube
```bash
# Upload video first
VIDEO=$(postiz upload video.mp4)
VIDEO_URL=$(echo "$VIDEO" | jq -r '.path')
postiz posts:create \
-c "Video description here" \
-m "$VIDEO_URL" \
-s "2025-01-15T10:00:00Z" \
--settings '{"title":"My Video Title","type":"public","tags":[{"value":"tech","label":"Tech"}]}' \
-i "youtube-id"
```
## TikTok
```bash
# Upload video first
VIDEO=$(postiz upload video.mp4)
VIDEO_URL=$(echo "$VIDEO" | jq -r '.path')
postiz posts:create \
-c "Check this out! #fyp" \
-m "$VIDEO_URL" \
-s "2025-01-15T10:00:00Z" \
--settings '{"privacy_level":"PUBLIC_TO_EVERYONE","duet":true,"stitch":true}' \
-i "tiktok-id"
```
## Instagram
```bash
# Upload image first
IMAGE=$(postiz upload photo.jpg)
IMAGE_URL=$(echo "$IMAGE" | jq -r '.path')
# Regular post
postiz posts:create \
-c "Beautiful day! #photography" \
-m "$IMAGE_URL" \
-s "2025-01-15T10:00:00Z" \
--settings '{"post_type":"post"}' \
-i "instagram-id"
```
### Story
```bash
postiz posts:create \
-c "Story content" \
-m "$IMAGE_URL" \
-s "2025-01-15T10:00:00Z" \
--settings '{"post_type":"story"}' \
-i "instagram-id"
```
### Reel
```bash
postiz posts:create \
-c "Reel caption" \
-m "$VIDEO_URL" \
-s "2025-01-15T10:00:00Z" \
--settings '{"post_type":"reel"}' \
-i "instagram-id"
```
## LinkedIn
```bash
postiz posts:create \
-c "Professional update on LinkedIn" \
-s "2025-01-15T10:00:00Z" \
-i "linkedin-id"
```
### Image Carousel
```bash
postiz posts:create \
-c "Check out these slides!" \
-m "image1.jpg,image2.jpg,image3.jpg" \
-s "2025-01-15T10:00:00Z" \
--settings '{"post_as_images_carousel":true}' \
-i "linkedin-id"
```
## Pinterest
```bash
postiz posts:create \
-c "Pin description" \
-m "$IMAGE_URL" \
-s "2025-01-15T10:00:00Z" \
--settings '{"board":"board-id","title":"Pin Title","link":"https://example.com"}' \
-i "pinterest-id"
```
## Discord
```bash
postiz posts:create \
-c "Message to Discord" \
-s "2025-01-15T10:00:00Z" \
--settings '{"channel":"channel-id"}' \
-i "discord-id"
```
## Batch Scheduling
Schedule multiple posts across different dates:
```bash
#!/bin/bash
DATES=("2025-01-14T09:00:00Z" "2025-01-15T09:00:00Z" "2025-01-16T09:00:00Z")
CONTENT=("Monday motivation" "Tuesday tips" "Wednesday wisdom")
for i in "${!DATES[@]}"; do
postiz posts:create \
-c "${CONTENT[$i]}" \
-s "${DATES[$i]}" \
-i "twitter-id"
done
```
## Multi-Platform Campaign
Post different content per platform in one command using a JSON file:
```bash
postiz posts:create --json campaign.json
```
Example `campaign.json`:
```json
{
"integrations": ["twitter-123", "linkedin-456", "reddit-789"],
"posts": [
{
"provider": "twitter",
"post": [{ "content": "Short tweet version", "image": [] }]
},
{
"provider": "linkedin",
"post": [{ "content": "More detailed LinkedIn post with professional tone", "image": [] }]
},
{
"provider": "reddit",
"post": [{ "content": "Reddit post body", "image": [] }],
"settings": {
"__type": "reddit",
"subreddit": [{ "value": { "subreddit": "programming", "title": "Post Title", "type": "text" } }]
}
}
]
}
```
---
title: Chrome Extension
description: 'Set up the Postiz browser extension for cookie-based integrations'
---
Some platforms (like Skool) do not offer public OAuth APIs. Postiz connects to these platforms using a browser extension that securely extracts session cookies from your browser.
## How It Works
1. You install the Postiz Chrome Extension in your browser.
2. When adding a cookie-based channel, the extension reads your session cookies for that platform.
3. The cookies are sent to your Postiz backend and stored securely as an encrypted JWT.
4. The extension automatically refreshes cookies every 24 hours to keep connections alive.
<Warning>
Using cookies to interact with platforms may violate their terms of service. Use this feature at your own risk.
</Warning>
## Installation
<Steps>
<Step title="Install the Extension">
Install the Postiz browser extension from the [Chrome Web Store](https://chromewebstore.google.com/detail/postiz/cidhffagahknaeodkplfbcpfeielnkjl?hl=en).
Alternatively, for self-hosted setups, you can build the extension from source:
```bash
cd apps/extension
pnpm build
```
Then load the `apps/extension/dist` folder as an unpacked extension in Chrome:
1. Navigate to `chrome://extensions/`
2. Enable **Developer mode** (top right toggle)
3. Click **Load unpacked** and select the `dist` folder
</Step>
<Step title="Get the Extension ID">
After installing the extension, you need its **Extension ID**:
1. Go to `chrome://extensions/` in Chrome
2. Find the **Postiz** extension
3. Copy the **ID** shown under the extension name (e.g., `cidhffagahknaeodkplfbcpfeielnkjl`)
If you installed from the Chrome Web Store, the ID is: `cidhffagahknaeodkplfbcpfeielnkjl`
</Step>
<Step title="Configure the Environment Variable">
Add the extension ID to your Postiz environment variables:
```env
EXTENSION_ID="cidhffagahknaeodkplfbcpfeielnkjl"
```
Restart Postiz after setting this variable.
</Step>
</Steps>
## Supported Platforms
The following platforms use the Chrome Extension for authentication:
<CardGroup cols={2}>
<Card title="Skool" icon="graduation-cap" href="/providers/skool">
Post to Skool communities
</Card>
</CardGroup>
## Self-Hosted Considerations
If you are self-hosting Postiz on a domain other than `localhost` or `*.postiz.com`, you need to build a custom extension with your domain in the `externally_connectable` manifest field.
Edit `apps/extension/manifest.json` and add your domain:
```json
{
"externally_connectable": {
"matches": [
"http://localhost/*",
"https://localhost/*",
"https://*.postiz.com/*",
"https://your-domain.com/*"
]
}
}
```
Then rebuild the extension and load it as an unpacked extension in Chrome.
## Troubleshooting
- **"Extension not found"** — Make sure the `EXTENSION_ID` environment variable is set and Postiz has been restarted.
- **"Extension not reachable"** — The extension may be disabled. Check `chrome://extensions/` and make sure Postiz is enabled.
- **"Could not get cookies"** — You must be logged in to the platform in the same Chrome browser where the extension is installed.
- **Cookies expire** — The extension automatically refreshes cookies every 24 hours. If a connection drops, try reconnecting the channel.---
title: How to add a new provider
description: 'How to add a new provider to Postiz'
---
# Steps to implement a new provider
1. **The backend logic:**
- Define DTO for the settings of the provider
- Generate an authentication URL
- Authenticate the user from the callback
- Refresh the user token
2. **The frontend logic:**
- Implement the settings page
- Implement the preview page
- Upload the provider image
## Social Media
### Backend
For our example, we will use the X provider.
<Steps>
<Step title="Create a DTO for provider settings">
Head over to `nestjs-libraries/src/dtos/posts/providers-settings` and create a new file `x-provider-settings.dto.ts`
<Note>
You don't have to create a DTO if there are no settings
</Note>
Once created head over to `nestjs-libraries/src/dtos/posts/providers-settings/all.providers.settings.ts` and add the new DTO.
Head to `libraries/nestjs-libraries/src/dtos/posts/create.post.dto.ts`, look for the discriminator and add another line in the format of:
```typescript
{ value: DTOClassName, name: 'providerName' },
```
</Step>
<Step title="Create the provider file">
Head over to `libraries/nestjs-libraries/src/integrations/social` and create a new provider file `providerName.provider.ts`
For oAuth2 providers, the content of the file should look like this:
```typescript
import {
AuthTokenDetails,
PostDetails,
PostResponse,
SocialProvider,
} from '@gitroom/nestjs-libraries/integrations/social/social.integrations.interface';
export class XProvider implements SocialProvider {
identifier = 'providerName';
name = 'Provider Name';
async refreshToken(refreshToken: string): Promise<AuthTokenDetails> {
// ...refresh the token
}
async generateAuthUrl() {
// ...generate the auth url
}
async authenticate(params: { code: string; codeVerifier: string }) {
// ...authenticate the user
}
async post(
id: string,
accessToken: string,
postDetails: PostDetails<DTOClassName>[]
): Promise<PostResponse[]> {
// ...post the content
}
}
```
Take a look at the existing providers to see how to implement the methods.
</Step>
<Step title="Register with Integration Manager">
Open `libraries/nestjs-libraries/src/integrations/integration.manager.ts` and add the new provider to either `socialIntegrationList` (oAuth2) or `articleIntegrationList` (Token)
</Step>
</Steps>
### Custom functions
You might want to create custom functions for the providers for example: get available orgs, get available pages, etc.
You can create a public function in the provider for example `organizations` and later call it from a special hook from the frontend.
---
### Frontend
<Steps>
<Step title="Create provider component">
Head over to `apps/frontend/src/components/launches/providers` and create a new folder with the providerName.
Add a new file `providerName.provider.tsx` with the following content:
```typescript
import { FC } from 'react';
import { withProvider } from '@gitroom/frontend/components/launches/providers/high.order.provider';
import { useSettings } from '@gitroom/frontend/components/launches/helpers/use.values';
import { useIntegration } from '@gitroom/frontend/components/launches/helpers/use.integration';
const ProviderPreview: FC = () => {
const { value } = useIntegration();
const settings = useSettings();
return (
// ...Preview
);
};
const ProviderSettings: FC = () => {
const form = useSettings();
const { date } = useIntegration();
return (
// ...Settings
);
};
export default withProvider(DevtoSettings, DevtoPreview, DTOClassName);
```
</Step>
<Step title="Use custom provider functions (optional)">
If you want to use a custom function for the provider you can use the `useCustomProviderFunction` hook.
```typescript
import { useCustomProviderFunction } from '@gitroom/frontend/components/launches/helpers/use.custom.provider.function';
import { useCallback } from 'react';
const customFunc = useCustomProviderFunction();
// and use it like that:
const getOrgs = useCallback(() => {
customFunc.get('organizations', {
anyKey: 'anyValue'
})
}, []);
```
It will automatically interact with the right provider saved for the user.
You can look at the other integrations to understand what data to put inside.
</Step>
<Step title="Register the provider">
Open `apps/frontend/src/components/launches/providers/show.all.providers.tsx` and add the new provider to the list.
```typescript
{identifier: 'providerName', component: DefaultImportFromHighOrderProvider},
```
</Step>
</Steps>
---
title: Docker Compose Configuration
description: 'How to configure your docker-compose file for Postiz'
---
You will often see, when for example configuring providers, that the environment variables will look like this:
```env
INSTAGRAM_CLIENT_ID=12345678901234567890
```
You have 2 options on how to set these variables in your `docker-compose.yml` file.
## Option 1: Direct in docker-compose.yml
You can set them directly in the `environment` section of the service.
```yaml
services:
postiz:
environment:
YOUR_ENV_VAR: "value"
YOUR_OTHER_ENV_VAR: "value"
```
## Option 2: Using a .env file
You can use a `.env` file to set the variables.
**docker-compose.yml:**
```yaml
services:
postiz:
env_file:
- .env
```
**.env:**
```env
YOUR_ENV_VAR=value
YOUR_OTHER_ENV_VAR=value
```
## Option 3: Combine both
You can also use both!
**docker-compose.yml:**
```yaml
services:
postiz:
environment:
YOUR_ENV_VAR: "value"
env_file:
- .env
```
**.env:**
```env
YOUR_OTHER_ENV_VAR=value
```
<Note>
When using an .env file, you will need to transfer all environment variables from the docker-compose.yml file to the .env file.
An .env file will override any variables set in the .yml file.
Using an .env file for the DB / Redis won't be necessary.
</Note>
---
title: Email Notifications
description: 'How to send notifications to users'
---
Postiz supports two email providers: Resend and NodeMailer (SMTP).
If you have an email provider configured, then new users will require activation.
```env
EMAIL_PROVIDER: "resend|nodemailer"
```
You must also set the sender name and email address for all providers as follows;
```env
EMAIL_FROM_NAME: "Postiz Emailer"
EMAIL_FROM_ADDRESS: "postiz@example.com"
```
## Resend
Postiz uses Resend to send email notifications to users. If this key is set, users
will also require activation.
<Steps>
<Step title="Register on Resend">
Register to [Resend](https://resend.com), and connect your domain.
</Step>
<Step title="Copy your API Key">
Copy your API Key from the Resend control panel.
</Step>
<Step title="Edit your .env file">
Open the .env file and edit the following line.
```env
EMAIL_PROVIDER="resend"
RESEND_API_KEY="<your-api-key-here>"
```
</Step>
</Steps>
## NodeMailer (SMTP)
This is an alternative to Resend. You can use NodeMailer, which is simply a SMTP library, to connect to any SMTP server.
```env
EMAIL_PROVIDER: "nodemailer"
EMAIL_HOST: "smtp.gmail.com" # smtp host if you choose nodemailer
EMAIL_PORT: "465" # smtp port if you choose nodemailer
EMAIL_SECURE: "true" # smtp secure if you choose nodemailer
EMAIL_USER: "user" # smtp user if you choose nodemailer
EMAIL_PASS: "pass" # smtp pass if you choose nodemailer
```
---
title: OIDC Configuration
description: 'How to configure OIDC for Postiz'
---
<Warning>
**Warning:** With the actual implementation of the OIDC provider, GitHub / Google login provider will be disabled.
</Warning>
If you want to use OAuth/OIDC, please follow the instructions below.
We will use [Authentik](https://goauthentik.io/) as an OIDC provider example, with base URL `https://authentik.example.com`
<Steps>
<Step title="Create an Application/Provider on the Authentik side">
You will find the following important information:
- `redirect_uri` => `https://postiz.yourserver.com/settings`
- `client_id` => `randomclientid`
- `client_secret` => `randomclientsecret`
- `auth_url` => `https://authentik.example.com/application/o/authorize`
- `token_url` => `https://authentik.example.com/application/o/token`
- `userinfo_url`=> `https://authentik.example.com/application/o/userinfo`
<Note>
The same information needs to be configured on other OIDC providers such as Keycloak, Dex, etc.
</Note>
</Step>
<Step title="Configure POSTIZ_GENERIC_OAUTH">
```env
POSTIZ_GENERIC_OAUTH="true"
```
Set to `true` to enable OIDC login.
</Step>
<Step title="Configure display name">
```env
NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME="Authentik"
```
Will display the name of the OIDC provider on the login page.
</Step>
<Step title="Configure logo URL">
```env
NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL="https://raw.githubusercontent.com/walkxcode/dashboard-icons/master/png/authentik.png"
```
Will display the logo of the OIDC provider on the login page button.
</Step>
<Step title="Configure POSTIZ_OAUTH_URL">
```env
POSTIZ_OAUTH_URL="https://authentik.example.com"
```
The base URL of the OIDC provider.
</Step>
<Step title="Configure POSTIZ_OAUTH_AUTH_URL">
```env
POSTIZ_OAUTH_AUTH_URL="https://authentik.example.com/application/o/authorize/"
```
The authorization URL of the OIDC provider.
</Step>
<Step title="Configure POSTIZ_OAUTH_TOKEN_URL">
```env
POSTIZ_OAUTH_TOKEN_URL="https://authentik.example.com/application/o/token/"
```
The token URL of the OIDC provider.
</Step>
<Step title="Configure POSTIZ_OAUTH_USERINFO_URL">
```env
POSTIZ_OAUTH_USERINFO_URL="https://authentik.example.com/application/o/userinfo/"
```
The userinfo URL of the OIDC provider.
</Step>
<Step title="Configure POSTIZ_OAUTH_CLIENT_ID">
```env
POSTIZ_OAUTH_CLIENT_ID="randomclientid"
```
The client ID of the OIDC provider.
</Step>
<Step title="Configure POSTIZ_OAUTH_CLIENT_SECRET">
```env
POSTIZ_OAUTH_CLIENT_SECRET="randomclientsecret"
```
The client secret of the OIDC provider.
</Step>
</Steps>
---
title: Image & Video Editing (Polotno)
description: 'Enable in-app image and video editing in Postiz via the Polotno SDK'
---
Postiz supports in-app image and video editing through the
[Polotno SDK](https://polotno.com/). Once enabled, users can design and
edit visuals directly inside the Postiz workflow while preparing
scheduled posts — no round-trip to an external editor.
## What you get
- Create and edit visuals inside the Postiz post composer.
- Work with templates, text, images, and brand assets.
- Update designs in place without re-uploading finished files.
- Reuse and modify visuals across scheduled posts.
Designs are stored as structured data and rendered when needed.
Scheduling, publishing, and platform-specific logic remain handled by
Postiz.
## Setup
<Steps>
<Step title="Create a Polotno account">
Sign up at [polotno.com](https://polotno.com/) and open the
[API dashboard](https://polotno.com/cabinet/) to generate an
API key.
</Step>
<Step title="Add the API key to Postiz">
Set `NEXT_PUBLIC_POLOTNO` in your environment to the key from
the Polotno dashboard:
```env
NEXT_PUBLIC_POLOTNO="your-polotno-api-key"
```
In `docker-compose.yaml`:
```yaml
services:
postiz:
environment:
NEXT_PUBLIC_POLOTNO: "your-polotno-api-key"
```
</Step>
<Step title="Restart Postiz">
`NEXT_PUBLIC_POLOTNO` is read by the frontend at build time, so
you need to rebuild the frontend image (or restart the dev
server) for the change to take effect.
</Step>
</Steps>
Exact steps may vary slightly depending on your deployment — see
[Docker Compose](/installation/docker-compose) or
[Development](/installation/development) for environment-specific
notes. If the variable is unset, the editor falls back to a
demo/anonymous mode that is not suitable for production.
## Licensing
Polotno is a commercial SDK and requires a valid license for
production use.
- Licenses are purchased and managed directly through Polotno.
- Postiz does not bundle, resell, or proxy Polotno licenses.
- Billing, licensing, and usage terms are handled by Polotno.
Postiz users can use the coupon code **`postizfriends`** at checkout
to receive **$100 off** a Polotno license.
## Reference
- Env var: [`NEXT_PUBLIC_POLOTNO`](/configuration/reference#misc-frontend)
- Polotno docs: [polotno.com/docs](https://polotno.com/docs/)
- Polotno pricing: [polotno.com/pricing](https://polotno.com/pricing/)
---
title: R2 Configuration
description: 'How to use Cloudflare R2 for file storage'
---
If you do not wish to (or can't) use local storage, an alternative way to upload images is to configure R2. It's free.
<Steps>
<Step title="Create account and login to the console">
Go to the [Cloudflare Dashboard](https://dash.cloudflare.com/r2/overview), and register if needed, then login.
</Step>
<Step title="Create a new Bucket">
In the dashboard sidebar, and head to the R2 page.
<img src="https://github.com/user-attachments/assets/6d540494-b439-4715-9e8c-615e1f734aef" alt="R2 Page" />
Create a new Bucket.
- Choose Automatic
- Choose Standard
<img src="https://github.com/user-attachments/assets/f679ff9f-1a9c-4d09-b663-2fa2baadea93" alt="Create Bucket" />
</Step>
<Step title="Create your R2 Token">
Create your R2 Token by going to R2 Object Storage:
<img src="https://i.imgur.com/yVJwVBb.png" alt="R2 Object Storage" />
Click on the API dropdown, and select [Manage API tokens](https://dash.cloudflare.com/?to=/:account/r2/api-tokens):
<img src="https://i.imgur.com/PmzyGeQ.png" alt="Manage API tokens" />
Copy your Account ID for later, and click on "Create an API token":
<img src="https://i.imgur.com/hvsGnWI.png" alt="Create API Token" />
Create an Account API token:
<img src="https://i.imgur.com/MvZsYfC.png" alt="Account API Token" />
Under "Permissions" choose "Object Read & Write" and under "Specify bucket(s)" search for your created Bucket.
<img src="https://github.com/user-attachments/assets/30991d43-9920-47d4-9c53-bde619974009" alt="Permissions" />
</Step>
<Step title="Copy your credentials">
After the R2 Token is created, copy your "Access Key ID" and "Secret Access Key":
<img src="https://github.com/user-attachments/assets/d4b4218f-fe20-47ec-937a-0d516fa74c64" alt="Copy Credentials" />
Paste the respective information into your .env environment.
```env
CLOUDFLARE_ACCOUNT_ID="accountId"
CLOUDFLARE_ACCESS_KEY="accessKey"
CLOUDFLARE_SECRET_ACCESS_KEY="secretAccessKey"
CLOUDFLARE_BUCKETNAME="bucketName"
CLOUDFLARE_REGION="region (like wnam)"
```
</Step>
<Step title="Configure Custom Domain and CORS policies">
Go to configuration and connect a custom domain (if you don't have one, you can use the one that CloudFlare provides.)
Add it to your .env file.
```env
CLOUDFLARE_BUCKET_URL="https://customdomain.com"
```
<img src="https://github.com/user-attachments/assets/a7bff532-f69b-4095-bb66-e46b7d3b14bd" alt="Custom Domain" />
Click to edit the CORS policy and add the following JSON:
```json
[
{
"AllowedOrigins": [
"http://localhost:4200",
"https://yourDomain.com"
],
"AllowedMethods": [
"GET",
"POST",
"HEAD",
"PUT",
"DELETE"
],
"AllowedHeaders": [
"Authorization",
"x-amz-date",
"x-amz-content-sha256",
"content-type"
],
"ExposeHeaders": [
"ETag",
"Location"
],
"MaxAgeSeconds": 3600
}
]
```
<img src="https://github.com/user-attachments/assets/1f84fe71-dd2d-4ca6-92bf-6193d36c9b54" alt="CORS Policy" />
</Step>
</Steps>
---
title: Configuration Reference
description: 'Environment variables reference for Postiz'
---
Postiz is configured entirely through environment variables. Any change requires
an application restart.
The canonical list lives in the [example postiz.env file](https://raw.githubusercontent.com/gitroomhq/postiz-app/main/.env.example).
This page documents every variable Postiz reads, grouped by purpose.
<Info>
Variables marked **Required** are validated on boot — Postiz will fail to start
if they're missing or malformed.
</Info>
## Required core
These six variables are non-optional for any deployment.
### `DATABASE_URL` <sup>Required</sup>
PostgreSQL connection string used by Prisma.
```
DATABASE_URL="postgresql://postiz-user:postiz-password@localhost:5432/postiz-db-local"
```
### `REDIS_URL` <sup>Required</sup>
Redis connection string used for queues, rate limiting, and short-lived caches.
```
REDIS_URL="redis://localhost:6379"
```
### `JWT_SECRET` <sup>Required</sup>
A long random string used to sign session JWTs. Rotating this invalidates every existing session.
### `FRONTEND_URL` <sup>Required</sup>
The URL the **browser** uses to reach the Postiz frontend. Used as the OAuth redirect base and for email links.
```
FRONTEND_URL="https://postiz.example.com"
```
### `NEXT_PUBLIC_BACKEND_URL` <sup>Required</sup>
The URL the **browser** uses to reach the Postiz backend.
```
NEXT_PUBLIC_BACKEND_URL="https://api.postiz.example.com"
```
### `BACKEND_INTERNAL_URL` <sup>Required</sup>
The URL the **frontend SSR server** uses to reach the backend from inside your network. If everything runs in the same container/host, this is usually `http://localhost:3000`.
---
## Application behaviour
### `DISABLE_REGISTRATION`
Set to `true` to allow only a single user signup and then disable the sign-up page. Useful for self-host where you want full control.
<Warning>
This also disables OIDC / OAuth sign-in.
</Warning>
### `API_LIMIT`
Per-hour limit on the public-API create-post endpoint. Defaults to `90`. Postiz Cloud uses `100`. Channel and post quotas are tiered separately by plan.
### `RUN_CRON`
When set, the backend process runs the scheduled-task workers. Leave unset on API-only instances when workers are deployed separately.
### `RESTRICT_UPLOAD_DOMAINS`
Comma-separated allowlist of domains for `/public/v1/upload-from-url`. If set, only URLs whose hostname matches an entry are accepted.
### `DISALLOW_PLUS`
When set, blocks the upgrade UI elements pointing to paid plans. Used for self-host deployments that don't want to surface cloud-only billing.
### `IS_GENERAL`
Switches the frontend between routes available to the open-source build (`/launches`) and the hosted build (`/analytics`). Set to `"true"` on self-host. Leave unset on Postiz Cloud.
### `DISABLE_IMAGE_COMPRESSION`
When truthy, the frontend skips client-side image compression on upload. Set this if you need pixel-exact originals at the cost of larger uploads.
### `NOT_SECURED`
<Warning>
Dev only. Never set in production — it disables security checks that exist for a reason.
</Warning>
### `MAIN_URL`
Primary application URL used for absolute links in some emails and SEO metadata. Falls back to `FRONTEND_URL` when not set.
### `EXTENSION_ID`
The Chrome Extension ID for cookie-based platform integrations (e.g. Skool). See the [Chrome Extension guide](/configuration/chrome-extension).
### `MOBILE_APP_SCHEME`
URL scheme used for deep-linking from emails into the mobile app.
---
## Storage
See also: [Cloudflare R2](/configuration/r2) and [Uploads & Storage](/configuration/uploads).
| Variable | Purpose |
|---|---|
| `STORAGE_PROVIDER` | `local` or `cloudflare`. Defaults to `local`. |
| `UPLOAD_DIRECTORY` | Filesystem path for `local` storage writes. |
| `NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY` | Public URL prefix the frontend uses to reference uploaded files. |
| `CLOUDFLARE_ACCOUNT_ID` | R2 account ID. |
| `CLOUDFLARE_ACCESS_KEY` | R2 access key. |
| `CLOUDFLARE_SECRET_ACCESS_KEY` | R2 secret access key. |
| `CLOUDFLARE_BUCKETNAME` | R2 bucket name. |
| `CLOUDFLARE_BUCKET_URL` | Public-facing URL the bucket is served from. |
| `CLOUDFLARE_REGION` | R2 region (usually `auto`). |
---
## Email
See also: [Email configuration](/configuration/emails).
| Variable | Purpose |
|---|---|
| `EMAIL_PROVIDER` | `resend` (default) or `nodemailer`. |
| `RESEND_API_KEY` | Resend API key — required when `EMAIL_PROVIDER=resend`. Presence of this variable also gates whether user activation is required (set → required; unset → users are auto-activated). |
| `EMAIL_HOST` | SMTP host — required when `EMAIL_PROVIDER=nodemailer`. |
| `EMAIL_PORT` | SMTP port. |
| `EMAIL_USER` | SMTP user. |
| `EMAIL_PASS` | SMTP password. |
| `EMAIL_SECURE` | `true` for SMTPS / implicit TLS. |
| `EMAIL_FROM_ADDRESS` | From-address used on all outgoing email. |
| `EMAIL_FROM_NAME` | From-name used on all outgoing email. |
---
## OAuth sign-in (OIDC)
See also: [OAuth configuration](/configuration/oauth).
| Variable | Purpose |
|---|---|
| `POSTIZ_GENERIC_OAUTH` | `true` to enable a generic OIDC sign-in provider. |
| `POSTIZ_OAUTH_URL` | Base URL of the IdP. |
| `POSTIZ_OAUTH_AUTH_URL` | Authorization endpoint. |
| `POSTIZ_OAUTH_TOKEN_URL` | Token endpoint. |
| `POSTIZ_OAUTH_USERINFO_URL` | UserInfo endpoint. |
| `POSTIZ_OAUTH_CLIENT_ID` | Client ID issued by the IdP. |
| `POSTIZ_OAUTH_CLIENT_SECRET` | Client secret issued by the IdP. |
| `POSTIZ_OAUTH_SCOPE` | OIDC scope. Defaults to `openid profile email`. |
| `NEXT_PUBLIC_POSTIZ_OAUTH_DISPLAY_NAME` | Label shown on the sign-in button. |
| `NEXT_PUBLIC_POSTIZ_OAUTH_LOGO_URL` | Icon shown on the sign-in button. |
---
## Temporal (workflow orchestration)
Since v2.12.0, Postiz uses Temporal for scheduled posts and background workflows.
Self-host deployments need to run a Temporal stack (the official docker-compose
ships with one). See the [Temporal migration guide](/installation/migration).
| Variable | Purpose |
|---|---|
| `TEMPORAL_ADDRESS` | host:port of the Temporal frontend. |
| `TEMPORAL_NAMESPACE` | Temporal namespace to run workflows in. |
| `TEMPORAL_API_KEY` | API key for Temporal Cloud. Leave unset for self-host. |
| `TEMPORAL_TLS` | `true` to require TLS to the Temporal frontend. |
| `ORCHESTRATOR_PORT` | Port the in-process orchestrator binds to. |
---
## Public API & MCP
| Variable | Purpose |
|---|---|
| `MCP_URL` | URL the frontend uses to advertise the MCP endpoint to clients. |
| `AGENT_API_KEY` | Shared secret used by the agent runtime to call privileged endpoints. |
| `AGENT_MEDIA_SSO_KEY` | Signing key for short-lived agent-media SSO tokens. |
| `BACKEND_URL` | Legacy alias for `NEXT_PUBLIC_BACKEND_URL` in a few server contexts. |
---
## AI / generation
| Variable | Purpose |
|---|---|
| `OPENAI_API_KEY` | OpenAI key used for the copilot and AI image generation. |
| `OPENAI_APP_CHALLANGE` | Verification challenge string for the OpenAI custom GPT integration. |
| `ELEVENSLABS_API_KEY` | ElevenLabs API key for voice generation. |
| `FAL_KEY` | fal.ai API key for image/video models. |
| `TAVILY_API_KEY` | Tavily search API key used by the research tool. |
| `KIEAI_API_KEY` | KieAI API key. |
| `TRANSLOADIT_AUTH` | Transloadit auth key for video pipelines. |
| `TRANSLOADIT_SECRET` | Transloadit signing secret. |
| `TRANSLOADIT_TEMPLATE` | Transloadit template ID used by the video generator. |
---
## Short-link providers
If a Postiz user configures short-link replacement, Postiz proxies link
shortening through one of the configured providers. Pick one set.
### Dub.co
```env
DUB_TOKEN=""
DUB_API_ENDPOINT="https://api.dub.co"
DUB_SHORT_LINK_DOMAIN="dub.sh"
```
### Short.io
```env
SHORT_IO_SECRET_KEY=""
```
### Kutt.it
```env
KUTT_API_KEY=""
KUTT_API_ENDPOINT="https://kutt.it/api/v2"
KUTT_SHORT_LINK_DOMAIN="kutt.it"
```
### LinkDrip
```env
LINK_DRIP_API_KEY=""
LINK_DRIP_API_ENDPOINT="https://api.linkdrip.com/v1/"
LINK_DRIP_SHORT_LINK_DOMAIN="dripl.ink"
```
---
## Payments
| Variable | Purpose |
|---|---|
| `STRIPE_PUBLISHABLE_KEY` | Stripe publishable key. |
| `STRIPE_SECRET_KEY` | Stripe secret key. |
| `STRIPE_SIGNING_KEY` | Stripe webhook signing key for subscription events. |
| `STRIPE_SIGNING_KEY_CONNECT` | Stripe Connect webhook signing key. |
| `STRIPE_DISCOUNT_ID` | Default Stripe discount applied to new subscriptions. |
| `FEE_AMOUNT` | Platform fee fraction (e.g. `0.05`). |
| `NOWPAYMENTS_API_KEY` | NOWPayments API key for crypto checkout. |
| `NOWPAYMENTS_AMOUNT` | Default NOWPayments invoice amount. |
---
## Analytics & tracking (frontend)
All of these are optional. Frontend reads `NEXT_PUBLIC_*` at build time.
| Variable | Purpose |
|---|---|
| `NEXT_PUBLIC_SENTRY_DSN` | Frontend Sentry DSN. |
| `NEXT_PUBLIC_GTM_ID` | Google Tag Manager container ID. |
| `NEXT_PUBLIC_FACEBOOK_PIXEL` | Facebook Pixel ID. |
| `FACEBOOK_PIXEL_ACCESS_TOKEN` | Server-side Pixel Conversions API token. |
| `NEXT_PUBLIC_POSTHOG_HOST` | PostHog host (e.g. `https://eu.posthog.com`). |
| `NEXT_PUBLIC_POSTHOG_KEY` | PostHog project API key. |
| `NEXT_PUBLIC_TRACKING_TRIAL` | When set, enables trial-funnel tracking events. |
| `DATAFAST_API_KEY` | Datafast analytics API key. |
| `DATAFAST_WEBSITE_ID` | Datafast website ID. |
| `SENTRY_AUTH_TOKEN` | Build-time Sentry token for sourcemap upload. |
| `SENTRY_ORG` | Sentry org slug. |
| `SENTRY_PROJECT` | Sentry project slug. |
| `SENTRY_SPOTLIGHT` | Enable Sentry Spotlight in dev. |
---
## Misc frontend
| Variable | Purpose |
|---|---|
| `NEXT_PUBLIC_DISCORD_SUPPORT` | Discord invite URL shown in the support widget. |
| `NEXT_PUBLIC_POLOTNO` | Polotno API key for the image editor. |
| `NEXT_PUBLIC_VERSION` | Version string shown in the footer. |
| `NEXT_PUBLIC_APP_VERSION` | Mobile/web version label. |
| `NEXT_PUBLIC_OVERRIDE_BACKEND_URL` | When set, overrides `NEXT_PUBLIC_BACKEND_URL` at runtime. Useful for tunnel-based dev. |
---
## Social provider keys
Each social provider has its own env-var block. See the per-provider setup
pages under [Providers](/providers/overview) for the exact OAuth steps; this
table is just a lookup so you can find which page documents which variable.
| Provider | Variables | Setup |
|---|---|---|
| X (Twitter) | `X_API_KEY`, `X_API_SECRET`, `X_URL`, `DISABLE_X_ANALYTICS`, `STRIP_LINKS_FROM_X_POSTS` | [/providers/x-twitter](/providers/x-twitter) |
| LinkedIn | `LINKEDIN_CLIENT_ID`, `LINKEDIN_CLIENT_SECRET` | [/providers/linkedin](/providers/linkedin), [/providers/linkedin-page](/providers/linkedin-page) |
| Facebook / Instagram (FB) | `FACEBOOK_APP_ID`, `FACEBOOK_APP_SECRET` | [/providers/facebook](/providers/facebook), [/providers/instagram](/providers/instagram) |
| Instagram (Standalone) | `INSTAGRAM_APP_ID`, `INSTAGRAM_APP_SECRET` | [/providers/instagram](/providers/instagram) |
| Threads | `THREADS_APP_ID`, `THREADS_APP_SECRET` | [/providers/threads](/providers/threads) |
| YouTube | `YOUTUBE_CLIENT_ID`, `YOUTUBE_CLIENT_SECRET` | [/providers/youtube](/providers/youtube) |
| Google My Business | `GOOGLE_GMB_CLIENT_ID`, `GOOGLE_GMB_CLIENT_SECRET` | [/providers/google-my-business](/providers/google-my-business) |
| TikTok | `TIKTOK_CLIENT_ID`, `TIKTOK_CLIENT_SECRET` | [/providers/tiktok](/providers/tiktok) |
| Reddit | `REDDIT_CLIENT_ID`, `REDDIT_CLIENT_SECRET` | [/providers/reddit](/providers/reddit) |
| Pinterest | `PINTEREST_CLIENT_ID`, `PINTEREST_CLIENT_SECRET` | [/providers/pinterest](/providers/pinterest) |
| Discord | `DISCORD_CLIENT_ID`, `DISCORD_CLIENT_SECRET`, `DISCORD_BOT_TOKEN_ID` | [/providers/discord](/providers/discord) |
| Slack | `SLACK_ID`, `SLACK_SECRET`, `SLACK_SIGNING_SECRET` | [/providers/slack](/providers/slack) |
| Telegram | `TELEGRAM_TOKEN`, `TELEGRAM_BOT_NAME` | [/providers/telegram](/providers/telegram) |
| Mastodon | `MASTODON_URL`, `MASTODON_CLIENT_ID`, `MASTODON_CLIENT_SECRET` | [/providers/mastodon](/providers/mastodon) |
| Dribbble | `DRIBBBLE_CLIENT_ID`, `DRIBBBLE_CLIENT_SECRET` | [/providers/dribbble](/providers/dribbble) |
| Farcaster | `NEYNAR_CLIENT_ID`, `NEYNAR_SECRET_KEY`, `NEYNAR_LOGIN_URL` | [/providers/farcaster](/providers/farcaster) |
| MeWe | `MEWE_HOST`, `MEWE_APP_ID`, `MEWE_API_KEY` | [/providers/mewe](/providers/mewe) |
| Twitch | `TWITCH_CLIENT_ID`, `TWITCH_CLIENT_SECRET` | API only — see [/public-api/providers/twitch](/public-api/providers/twitch) |
| Kick | `KICK_CLIENT_ID`, `KICK_SECRET` | API only — see [/public-api/providers/kick](/public-api/providers/kick) |
| VK | `VK_ID` | API only — see [/public-api/providers/vk](/public-api/providers/vk) |
| Whop | `WHOP_CLIENT_ID` | [/providers/whop](/providers/whop) |
| GitHub (sign-in) | `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET` | [/configuration/oauth](/configuration/oauth) |
| Beehiiv | `BEEHIIVE_API_KEY`, `BEEHIIVE_PUBLICATION_ID` | Newsletter provider |
| Listmonk | `LISTMONK_DOMAIN`, `LISTMONK_USER`, `LISTMONK_API_KEY`, `LISTMONK_LIST_ID`, `LISTMONK_WELCOME_TEMPLATE_ID` | Newsletter provider |
---
## Runtime & build
These are read from the environment but are typically managed by your runtime,
hosting platform, or framework rather than set by hand.
| Variable | Purpose |
|---|---|
| `PORT` | Port the backend HTTP server binds to. Defaults to `3000`. |
| `TZ` | Process timezone. The backend forces this to `UTC` on boot. |
| `NODE_ENV` | Standard Node environment flag (`development` / `production`). Toggles dev-only behaviour like sourcemaps. |
| `NEXT_RUNTIME` | Set by Next.js (`nodejs` / `edge`) to select the instrumentation hook. Framework-injected — do not set manually. |
| `VERCEL_GIT_COMMIT_SHA` | Commit SHA used as the Sentry release tag on the frontend. Injected by Vercel. |
| `GITHUB_SHA` | Fallback commit SHA for the Sentry release tag when not on Vercel. Injected by GitHub Actions. |
---
title: Uploads & Storage
description: 'Local filesystem vs Cloudflare R2 for media uploads'
---
Postiz writes user-uploaded media (post images, avatars, generated
content) through a single storage abstraction. Pick one of two backends.
## Pick a backend
```env
STORAGE_PROVIDER="local" # default — write to local filesystem
# or
STORAGE_PROVIDER="cloudflare" # write to Cloudflare R2
```
## Local filesystem
Set the path where Postiz should write:
```env
STORAGE_PROVIDER="local"
UPLOAD_DIRECTORY="/data/postiz/uploads"
NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY="/uploads"
```
- `UPLOAD_DIRECTORY` — where the backend writes files on disk.
- `NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY` — the URL prefix the frontend
uses to reference those files. Default `/uploads`.
The Next.js frontend rewrites `/uploads/:path*` to `/api/uploads/:path*`
on the backend (only active when `STORAGE_PROVIDER=local`), so the
public URL stays `/uploads/...` while the actual file is served from
the backend.
### Docker volume mount
In `docker-compose.yaml`:
```yaml
services:
postiz:
environment:
STORAGE_PROVIDER: "local"
UPLOAD_DIRECTORY: "/uploads"
NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY: "/uploads"
volumes:
- postiz-uploads:/uploads
volumes:
postiz-uploads:
```
If you scale beyond one backend replica, you need a shared volume — or
switch to Cloudflare R2.
### Caveat: some providers need public HTTPS URLs
TikTok (and a few others) fetch media via "pull from URL" rather than
multipart upload. Your local `/uploads` path must therefore be
reachable from the public internet over HTTPS for those providers to
work. If your deployment is internet-facing through a reverse proxy
with TLS, you're fine. If Postiz is on a private network, those
providers will fail and you should use [Cloudflare R2](/configuration/r2)
or a CDN instead.
## Cloudflare R2
Set `STORAGE_PROVIDER=cloudflare` and configure the R2 credentials. See
the dedicated [R2 setup guide](/configuration/r2) for the OAuth and
bucket-permissions walkthrough.
```env
STORAGE_PROVIDER="cloudflare"
CLOUDFLARE_ACCOUNT_ID="…"
CLOUDFLARE_ACCESS_KEY="…"
CLOUDFLARE_SECRET_ACCESS_KEY="…"
CLOUDFLARE_BUCKETNAME="…"
CLOUDFLARE_BUCKET_URL="https://your-bucket-url.r2.cloudflarestorage.com/"
CLOUDFLARE_REGION="auto"
```
R2 gives you public HTTPS URLs out of the box, so the TikTok caveat
above doesn't apply.
## Public-API uploads
Both `/public/v1/upload` and `/public/v1/upload-from-url` write through
the configured `STORAGE_PROVIDER`. The accepted MIME types and body-size
limits are documented in [troubleshooting/uploads](/troubleshooting/uploads).
function loadCrawlChat() {
const script = document.createElement('script');
script.src = "https://crawlchat.app/embed.js";
script.id = "crawlchat-script";
script.dataset.id = "68ac269d2961657c4b7924a9";
script.dataset.sidepanel = true;
script.dataset.noPrimaryColor = true;
document.head.appendChild(script);
}
loadCrawlChat();
---
title: Developer Guide
description: 'How to get started developing with Postiz'
---
## Understand how to develop with Postiz
## How to setup your development environment
This page explains [How to setup your development environment](/installation/development).
## Architecture Overview
Before getting started with development, have a good read of the [architecture overview](/howitworks). This will give you a good understanding of how the project is structured and how the different parts of the project interact with each other.
## Repository Overview
Postiz is an open-source project, and the source code is available on [GitHub](https://github.com/gitroomhq/postiz-app).
The project is generally built using scripts in the `package.json` file with npm. The main scripts are:
* `npm run dev` - Starts the development server
* `npm run prisma-generate` - Generates the Prisma client
* `npm run prisma-db-push` - Pushes the database schema to the database
The entire project is built under [NX](https://nx.dev/) to have a monorepo with multiple projects.
Unlike other NX project, this project has one `.env` file that is shared between all the apps.
It makes it easier to develop and deploy the project.
### Frontend
The frontend is built with [NextJS](https://nextjs.org/) and [TailwindCSS](https://tailwindcss.com/).
### Backend
The backend is built with [NestJS](https://nestjs.com/) with a basic architecture of controllers, services, repositories and dtos.
It uses [Prisma](https://www.prisma.io/) as an ORM to interact with the database.
By default Prisma uses [Postgres](https://www.postgresql.org/) as a database, but it can be easily changed to any other database since there are no native queries.
It uses Redis to schedule posts and run background jobs.
### Cron
cron is built with [NestJS](https://nestjs.com/) and share components with the backend.
### Worker
worker is built with [NestJS](https://nestjs.com/) and share components with the backend.
## Contributors Guide
The Postiz [contributors guide](https://github.com/gitroomhq/postiz-app/blob/main/CONTRIBUTING.md) is contained in the main repository. It provides information on how to contribute to the project, mainly the format for how to submit a pull request.
{
"$schema": "https://mintlify.com/docs.json",
"theme": "maple",
"name": "Postiz Documentation",
"colors": {
"primary": "#9900e6",
"light": "#b266ff",
"dark": "#7700b3"
},
"favicon": "/favicon.ico",
"navigation": {
"tabs": [
{
"tab": "Documentation",
"groups": [
{
"group": "Getting Started",
"pages": [
"introduction",
"howitworks",
"quickstart"
]
},
{
"group": "Installation",
"pages": [
"installation/system-requirements",
"installation/docker-compose",
"installation/docker",
"installation/kubernetes-helm",
"installation/development",
"installation/devcontainer",
"installation/migration"
]
},
{
"group": "Configuration",
"pages": [
"configuration/reference",
"configuration/docker",
"configuration/emails",
"configuration/uploads",
"configuration/r2",
"configuration/polotno",
"configuration/oauth",
"configuration/chrome-extension"
]
},
{
"group": "Reverse Proxies",
"pages": [
"reverse-proxies/caddy",
"reverse-proxies/nginx",
"reverse-proxies/traefik",
"reverse-proxies/websockets-and-dev"
]
},
{
"group": "Troubleshooting",
"pages": [
"troubleshooting/overview",
"troubleshooting/oauth-connect",
"troubleshooting/uploads",
"troubleshooting/activation-and-login",
"troubleshooting/billing",
"troubleshooting/channel-limits",
"troubleshooting/known-issues",
"troubleshooting/self-host"
]
},
{
"group": "Providers",
"pages": [
"providers/overview",
"configuration/create-provider",
{
"group": "Social Platforms",
"expanded": true,
"pages": [
"providers/x-twitter",
"providers/linkedin",
"providers/linkedin-page",
"providers/facebook",
"providers/instagram",
"providers/threads",
"providers/bluesky",
"providers/mastodon",
"providers/google-my-business",
"providers/mewe",
"providers/farcaster"
]
},
{
"group": "Video Platforms",
"expanded": true,
"pages": [
"providers/youtube",
"providers/tiktok"
]
},
{
"group": "Other Platforms",
"expanded": true,
"pages": [
"providers/reddit",
"providers/pinterest",
"providers/discord",
"providers/slack",
"providers/telegram",
"providers/dribbble",
"providers/skool",
"providers/whop"
]
}
]
}
]
},
{
"tab": "Public API",
"groups": [
{
"group": "Overview",
"pages": [
"public-api/introduction"
]
},
{
"group": "Integrations",
"pages": [
"public-api/integrations/list",
"public-api/integrations/groups",
"public-api/integrations/connect",
"public-api/integrations/delete",
"public-api/integrations/is-connected",
"public-api/integrations/find-slot"
]
},
{
"group": "Posts",
"pages": [
"public-api/posts/list",
"public-api/posts/create",
"public-api/posts/delete",
"public-api/posts/delete-by-group",
"public-api/posts/missing-content",
"public-api/posts/update-release-id",
"public-api/posts/change-status"
]
},
{
"group": "Analytics",
"pages": [
"public-api/analytics/platform",
"public-api/analytics/post"
]
},
{
"group": "Notifications",
"pages": [
"public-api/notifications/list"
]
},
{
"group": "Uploads",
"pages": [
"public-api/uploads/upload-file",
"public-api/uploads/upload-from-url"
]
},
{
"group": "Video Generation",
"pages": [
"public-api/video/generate",
"public-api/video/function"
]
},
{
"group": "Provider Settings (25 with custom settings)",
"pages": [
"public-api/providers/x",
"public-api/providers/linkedin",
"public-api/providers/linkedin-page",
"public-api/providers/facebook",
"public-api/providers/instagram",
"public-api/providers/instagram-standalone",
"public-api/providers/threads",
"public-api/providers/bluesky",
"public-api/providers/mastodon",
"public-api/providers/warpcast",
"public-api/providers/nostr",
"public-api/providers/vk",
"public-api/providers/youtube",
"public-api/providers/tiktok",
"public-api/providers/reddit",
"public-api/providers/lemmy",
"public-api/providers/pinterest",
"public-api/providers/discord",
"public-api/providers/slack",
"public-api/providers/telegram",
"public-api/providers/dribbble",
"public-api/providers/medium",
"public-api/providers/devto",
"public-api/providers/hashnode",
"public-api/providers/wordpress",
"public-api/providers/listmonk",
"public-api/providers/gmb",
"public-api/providers/whop",
"public-api/providers/skool",
"public-api/providers/kick",
"public-api/providers/twitch",
"public-api/providers/moltbook"
]
}
]
},
{
"tab": "CLI",
"groups": [
{
"group": "Getting Started",
"pages": [
"cli/introduction",
"cli/authentication"
]
},
{
"group": "Usage",
"pages": [
"cli/managing-posts",
"cli/integrations",
"cli/analytics",
"cli/media-upload"
]
},
{
"group": "Examples",
"pages": [
"cli/platform-examples"
]
}
]
},
{
"tab": "MCP",
"groups": [
{
"group": "Getting Started",
"pages": [
"mcp/introduction",
"mcp/setup"
]
},
{
"group": "Reference",
"pages": [
"mcp/tools",
"mcp/examples"
]
}
]
},
{
"tab": "Developer App (oAuth2)",
"groups": [
{
"group": "OAuth2",
"pages": [
"public-api/oauth"
]
}
]
},
{
"tab": "Contributing",
"groups": [
{
"group": "Development",
"pages": [
"developer-guide",
"support"
]
}
]
}
],
"global": {
"anchors": [
{
"anchor": "GitHub",
"href": "https://github.com/gitroomhq/postiz-app",
"icon": "github"
},
{
"anchor": "Discord",
"href": "https://discord.postiz.com",
"icon": "discord"
},
{
"anchor": "YouTube",
"href": "https://youtube.com/@postizofficial",
"icon": "youtube"
}
]
}
},
"logo": {
"light": "/logo/light.png",
"dark": "/logo/dark.png"
},
"navbar": {
"links": [
{
"icon": "discord",
"label": "Discord",
"href": "https://discord.postiz.com"
}
],
"primary": {
"type": "button",
"label": "Register to the cloud",
"href": "https://postiz.com"
}
},
"contextual": {
"options": [
"copy",
"view",
"chatgpt",
"claude",
"cursor"
]
},
"footer": {
"socials": {
"github": "https://github.com/gitroomhq/postiz-app",
"discord": "https://discord.postiz.com"
}
}
}
---
title: How it works
description: 'Learn the architecture of the project'
---
## Architecture
Postiz is composed of 3 main services and 4 external services - all 3 of the main services typically run within a **single docker container**, and talk to each other through HTTP. Those services talk to other containers running the external services - the SQL Database, Redis, Temporal and Storage.
```mermaid
flowchart LR;
classDef ext fill:#8ED14F,color:black,stroke:#fff
classDef svc fill:#9900e6,color:white,stroke:#fff
frontend[Frontend Service]:::svc
backend[Backend Service]:::svc
orchestrator[Orchestrator Service]:::svc
temporal[Temporal]:::ext
redis[Redis]:::ext
db[SQL Database]:::ext
storage[Storage]:::ext
frontend --> backend
backend --> db
backend --> redis
backend --> temporal
temporal --> orchestrator
orchestrator --> db
orchestrator --> storage
backend --> storage
```
- [Frontend](#frontend) - Provides the Web user interface, talks to the Backend.
- [Backend](#backend) - Does all the real work, provides an API for the frontend, and triggers workflows via Temporal.
- [Orchestrator](#orchestrator) - Runs Temporal workflows and activities, replacing the old cron and worker services.
- [Temporal](#temporal) - A durable workflow engine that manages scheduling, retries, and task distribution.
- [Redis](#redis) - Used for session state management and caching.
- [SQL Database](#db) - Stores all the data, Postgres is typically used, but any SQL database can be used.
- [Storage](#storage) - Stores all the files, this used to be CloudFlare R2 as the default, but now it's just a local file system.
### Frontend
The frontend is the part that you see, the web interface.
It relies on the backend to:
- Schedule posts
- Show analytics
- Manage users
### Backend
The backend is the "brain" of Postiz, and coordinates all the work. It triggers Temporal workflows for async operations like posting to social media, sending emails, and refreshing tokens. Typically the SQL database it talks to is Postgres, but other databases can be used.
### Orchestrator
The orchestrator replaces the old cron and worker services with Temporal workflows. It handles:
- Posting scheduled content to social media platforms.
- Refreshing tokens from different social media platforms.
- Sending digest and notification emails.
- Checking for missing posts and auto-posting.
- Tracking user posting streaks.
### Temporal
Temporal is a durable workflow execution engine. It provides:
- **Reliable scheduling** - Workflows run at the right time with automatic retries on failure.
- **Task queues** - Each social platform gets its own task queue for concurrency control.
- **Workflow visibility** - A built-in UI for monitoring and debugging workflow execution.
- **Durable state** - Workflow state is persisted, so nothing is lost if a service restarts.
---
title: Dev Container
description: 'Install Postiz using Dev Container'
---
<Snippet file="earlydoc.mdx" />
<Snippet file="installation-recommended-options.mdx" />
```bash
npm install -g @devcontainers/cli
devcontainer up
```
---
title: Development Environment
description: 'Set up Postiz for local development'
---
This article guides you for local development on Postiz. If you're only looking to self-host, docker-compose is the recommended method.
[Docker-Compose](/installation/docker-compose) is the recommended method and now includes the Temporal stack for workflow processing.
<Warning>
<strong>Important:</strong> Postiz uses Temporal for background workflows. If you are upgrading from v2.11.2 to v2.12.0 or later, follow the migration guide at [/installation/migration](/installation/migration) and use the maintained Docker Compose repository which includes the Temporal stack: [/installation/docker-compose](/installation/docker-compose).
</Warning>
## Tested configurations
- MacOS
- Linux (Fedora 40)
Naturally you can use these instructions to setup a development environment on any platform, but there may not be much experience in the community to help you with any issues you may encounter.
### Warning about Windows
Several users using Windows (and WSL) have reported issues with the setup. This is not well tested as the main developers of the project do not use Windows/WSL for development. If you are using Windows and encounter issues, please do not try to get support, as we aren't able to support you.
<Snippet file="installation-pre-reqs.mdx" />
### Prerequisite Local Services
- **Node.js** - for running the code! (version 18+)
- **PostgreSQL** - or any other SQL database (instructions below suggest Docker)
- **Redis** - for handling worker queues (instructions below suggest Docker)
- **Temporal** - runs as a separate stack (Postgres + Elasticsearch + Temporal services). For local development run the Temporal stack via the `postiz-docker-compose` repository described in [/installation/docker-compose](/installation/docker-compose). Set `TEMPORAL_ADDRESS` in your `.env` to point at the Temporal service (example below).
We have some messages from users who are using Windows, which should work, but they are not tested well yet.
## Installation Instructions
### NodeJS (version 18+)
A complete guide of how to install NodeJS can be found [here](https://nodejs.org/en/download/).
### PostgreSQL (or any other SQL database) & Redis
You can choose **Option A** to **Option B** to install the database.
#### Option A) Postgres and Redis as Single containers
You can install [Docker](https://www.docker.com/products/docker-desktop) and run:
```bash
docker run -e POSTGRES_USER=root -e POSTGRES_PASSWORD=your_password --name postgres -p 5432:5432 -d postgres
docker run --name redis -p 6379:6379 -d redis
```
#### Option B) Postgres and Redis as docker-compose
Download the [docker-compose.yaml file here](https://raw.githubusercontent.com/gitroomhq/postiz-app/main/docker-compose.dev.yaml),
or grab it from the repository in the next step.
```bash
docker compose -f "docker-compose.dev.yaml" up
```
To run Temporal locally, clone the maintained Docker Compose repository which includes the Temporal stack and follow the instructions in [/installation/docker-compose](/installation/docker-compose). See [/installation/migration](/installation/migration) for migration steps when moving data to the Temporal-enabled setup.
## Build Postiz
<Steps>
<Step title="Clone the repository">
```bash
git clone https://github.com/gitroomhq/postiz-app.git
```
</Step>
<Step title="Set environment variables">
Copy the `.env.example` file to `.env` and fill in the values
```bash
# Required Settings
DATABASE_URL="postgresql://postiz-user:postiz-password@localhost:5432/postiz-db-local"
REDIS_URL="redis://localhost:6379"
JWT_SECRET="random string for your JWT secret, make it long"
FRONTEND_URL="http://localhost:4200"
NEXT_PUBLIC_BACKEND_URL="http://localhost:3000"
BACKEND_INTERNAL_URL="http://localhost:3000"
TEMPORAL_ADDRESS="localhost:7233"
# Optional. Your upload directory path if you host your files locally.
UPLOAD_DIRECTORY="/opt/postiz/uploads/"
# Optional: your upload directory slug if you host your files locally.
NEXT_PUBLIC_UPLOAD_STATIC_DIRECTORY=""
# Your email provider, optional
EMAIL_PROVIDER="resend|nodemailer"
RESEND_API_KEY="re_1234567890" # api key if you choose resend
EMAIL_HOST="smtp.gmail.com" # smtp host if you choose nodemailer
EMAIL_PORT="465" # smtp port if you choose nodemailer
EMAIL_SECURE="true" # smtp secure if you choose nodemailer
EMAIL_USER="user" # smtp user if you choose nodemailer
EMAIL_PASS="pass" # smtp pass if you choose nodemailer
## These are dummy values, you must create your own from Cloudflare.
## Remember to set your public internet IP address in the allow-list for the API token.
CLOUDFLARE_ACCOUNT_ID="QhcMSXQyPuMCRpSQcSYdEuTYgHeCXHbu"
CLOUDFLARE_ACCESS_KEY="dcfCMSuFEeCNfvByUureMZEfxWJmDqZe"
CLOUDFLARE_SECRET_ACCESS_KEY="zTTMXBmtyLPwHEdpACGHgDgzRTNpTJewiNriLnUS"
CLOUDFLARE_BUCKETNAME="postiz"
CLOUDFLARE_BUCKET_URL="https://QhcMSXQyPuMCRpSQcSYdEuTYgHeCXHbu.r2.cloudflarestorage.com/"
CLOUDFLARE_REGION="auto"
# Social Media API Settings
X_API_KEY="Twitter API key for normal oAuth not oAuth2"
X_API_SECRET="Twitter API secret for normal oAuth not oAuth2"
LINKEDIN_CLIENT_ID="Linkedin Client ID"
LINKEDIN_CLIENT_SECRET="Linkedin Client Secret"
REDDIT_CLIENT_ID="Reddit Client ID"
REDDIT_CLIENT_SECRET="Linkedin Client Secret"
GITHUB_CLIENT_ID="GitHub Client ID"
GITHUB_CLIENT_SECRET="GitHub Client Secret"
# AI
OPENAI_API_KEY="OpenAI API key"
# Developer Settings
NX_ADD_PLUGINS=false
IS_GENERAL="true" # required for now
```
</Step>
<Step title="Install the dependencies">
```bash
pnpm install
```
</Step>
<Step title="Generate the prisma client and run the migrations">
```bash
pnpm run prisma-db-push
```
</Step>
<Step title="Run the project">
```bash
pnpm run dev
```
</Step>
</Steps>
If everything is running successfully, open http://localhost:4200 in your browser!
If everything is not running - you had errors in the steps above, please head over to our [support](/support) page.
## Next Steps
<CardGroup cols={2}>
<Card title="Configure uploads" icon="cloud-arrow-up" href="/configuration/r2">
Set up R2 for file storage
</Card>
<Card title="Architecture" icon="diagram-project" href="/howitworks">
Learn the architecture of the project
</Card>
<Card title="Email notifications" icon="envelope" href="/configuration/emails">
Set up email for notifications
</Card>
<Card title="Providers" icon="plug" href="/providers/overview">
Set up providers such as LinkedIn, X and Reddit
</Card>
</CardGroup>
---
title: Docker Compose
description: 'Install Postiz using Docker Compose'
---
<Note>
Watch the Tutorial for docker-compose install: [https://m.youtube.com/watch?v=A6CjAmJOWvA&t=5s](https://m.youtube.com/watch?v=A6CjAmJOWvA&t=5s)
</Note>
<Warning>
<strong>Warning:</strong> Please read this migration guide, on how to upgrade from v2.11.2 to v2.12.0+ for Temporal:
https://docs.postiz.com/installation/migration
</Warning>
## Docker Compose
This guide assumes that you have docker installed, with a reasonable amount of resources to run Postiz. This Docker Compose setup has been tested with;
- Virtual Machine, Ubuntu 24.04, 2Gb RAM, 2 vCPUs.
<Snippet file="installation-pre-reqs.mdx" />
### Configuration uses environment variables
The docker containers for Postiz are entirely configured with environment variables.
- **Option A** - environment variables in your `docker-compose.yml` file
- **Option B** - environment variables in a `postiz.env` file mounted in `/config` for the Postiz container only
- **Option C** - environment variables in a `.env` file next to your `docker-compose.yml` file (not recommended).
... or a mixture of the above options!
## Installation
<Steps>
<Step title="Clone the Docker Compose repository">
```
git clone https://github.com/gitroomhq/postiz-docker-compose
```
</Step>
<Step title="Configure your docker compose">
Configure your docker compose variables
</Step>
<Step title="Run the docker-compose command">
```
docker compose up
```
</Step>
<Step title="Wait for it to load">
1. Access your frontend at: http://localhost:4007 (unless changed in variables)
2. Visualize and monitor your workflows with temporal at: http://localhost:8080
</Step>
</Steps>
There is a [configuration reference](/configuration/reference) page with a list
of configuration settings.
## The `docker-compose.yaml` file
The full, up-to-date Docker Compose file is maintained in the
[gitroomhq/postiz-docker-compose](https://github.com/gitroomhq/postiz-docker-compose)
repository. Cloning that repository (see the steps above) gives you the
`docker-compose.yaml` together with the `dynamicconfig` directory that the
Temporal service mounts, so there is nothing to copy by hand.
<Note>
Always pull the file from the repository rather than copying a snapshot — the
services, images, and environment variables change between releases, and the
repository is the canonical source.
</Note>
## How to use docker compose
From the cloned repository directory, run `docker compose up` to start the services.
<Warning>
**Note** When you change variables, you must run `docker compose down` and
then `docker compose up` to recreate these containers with these updated
variables.
</Warning>
Look through the logs for startup errors, and if you have problems, check out the [support](/support) page.
If everything looks good, then you can access the Postiz web interface at https://postiz.your-server.com
<Snippet file="docker-envvar-apps.mdx" />
## Next Steps
<CardGroup cols={2}>
<Card title="Architecture" icon="diagram-project" href="/howitworks">
Learn the architecture of the project
</Card>
<Card title="Providers" icon="plug" href="/providers/overview">
Set up providers such as LinkedIn, X and Reddit
</Card>
</CardGroup>
---
title: Docker
description: 'Install Postiz using Docker standalone'
---
<Snippet file="installation-recommended-options.mdx" />
<Snippet file="installation-pre-reqs.mdx" />
## Set environment variables
Postiz configuration is entirely via environment variables for now. You might be used to setting environment variables when starting containers,
however postiz needs a LOT of environment variables, so setting these on command line or in a docker-compose is probably not practical for long
term maintainability.
It is recommended to use a `.env` file, which the Postiz containers look for in /config. Docker will automatically create this file for you on a
docker volume the first time you start up Postiz.
The default .env file can be found here; [example .env file](https://raw.githubusercontent.com/gitroomhq/postiz-app/main/.env.example)
## Create the container
This example below shows how to create the Postiz container on the command line.
Note that you will need to replace the `./config` with the path to your config directory. You will also need Postgres and Redis running.
```bash
docker create --name postiz -v postiz-uploads:/uploads/ -v postiz-config:/config/ -p 5000:5000 ghcr.io/gitroomhq/postiz-app:latest
```
<Snippet file="docker-envvar-apps.mdx" />
## Next Steps
<CardGroup cols={2}>
<Card title="Configure uploads" icon="cloud-arrow-up" href="/configuration/r2">
Set up R2 for file storage
</Card>
<Card title="Architecture" icon="diagram-project" href="/howitworks">
Learn the architecture of the project
</Card>
<Card title="Email notifications" icon="envelope" href="/configuration/emails">
Set up email for notifications
</Card>
<Card title="Providers" icon="plug" href="/providers/overview">
Set up providers such as LinkedIn, X and Reddit
</Card>
</CardGroup>
---
title: Helm
description: 'Install Postiz using Kubernetes and Helm'
---
<Snippet file="earlydoc.mdx" />
<Snippet file="installation-recommended-options.mdx" />
<Snippet file="installation-pre-reqs.mdx" />
## The Helm Chart
Postiz has a helm chart that is in very active development. You can find it here;
Note that this is a OCI compliant helm chart, meaning that you don't do `helm repo add`, and if you are using Flux or Helm, you must set them to OCI mode.
[https://github.com/gitroomhq/postiz-helmchart](https://github.com/gitroomhq/postiz-helmchart)
The `values.yml` file can be found in the repository, or a direct link to it is: [https://github.com/gitroomhq/postiz-helmchart/blob/main/charts/postiz/values.yaml](https://github.com/gitroomhq/postiz-helmchart/blob/main/charts/postiz/values.yaml)
## Next Steps
<CardGroup cols={2}>
<Card title="Providers" icon="plug" href="/providers/overview">
Set up providers such as LinkedIn, X and Reddit
</Card>
<Card title="Architecture" icon="diagram-project" href="/howitworks">
Learn the architecture of the project
</Card>
</CardGroup>
---
title: Migration to Temporal
description: 'A guide to migrating to the new Temporal infrastructure'
---
## Prerequisites
- Have your existing data saved somewhere (or use the existing PostgreSQL DB)
- Cloned the [new docker-compose repo](https://github.com/gitroomhq/postiz-docker-compose)
## Migration Steps
<Steps>
<Step title="Head into the docker-compose folder">
```
cd ./postiz-docker-compose
```
</Step>
<Step title="Insert your previous secrets into the new docker compose">
```
nano docker-compose.yaml
```
And insert all your previous secrets from the existing docker-compose.yml before v1.12.0
</Step>
<Step title="Start the stack, and stop it">
```
docker compose up -d
```
Wait for a bit to let it start.
```
docker compose down
```
</Step>
<Step title="Insert your existing data into PostgreSQL">
1. Identify volumes:
```bash
docker volume ls
```
2. Copy data (helper container):
```bash
docker run --rm -v <old_postgres_volume>:/from -v <new_postgres_volume>:/to \
alpine sh -c "cp -a /from/. /to/"
```
</Step>
<Step title="Start the stack finally">
```
docker compose up -d
```
And wait for it to start
</Step>
</Steps>
**Congratulations! You have now successfully migrated to v2.12.0 or later, and can enjoy the new Temporal infrastructure and updates!**---
title: System Requirements
description: 'Hardware, services, ports, and network requirements for self-hosting Postiz'
---
## Hardware
Recommended starting point for a small team (≤ 20 users):
| Component | Supported floor | Recommended |
|---|---|---|
| CPU | 2 vCPU | 4 vCPU |
| RAM | 2 GB (all-in-one, light use) | 8 GB |
| Disk | 20 GB | 50 GB + persistent volume for uploads |
The official Docker Compose has been tested on a 2 GB / 2 vCPU Ubuntu
VM running everything on one host (see [Docker Compose](/installation/docker-compose)).
That works for a single-user install with occasional posting, but
leaves no headroom — once you have multiple users, scheduled
workflows, or external Postgres/Redis on the same host, plan for
**4 GB or more**.
The build step (`pnpm install` / `pnpm build` from source) is the most
memory-hungry part and can OOM on 2 GB VMs. Bump the Node heap if it
fails:
```bash
NODE_OPTIONS="--max-old-space-size=4096" pnpm install
```
## Recommended install path
The canonical self-host setup is the official Docker Compose repo:
<Card title="postiz-docker-compose" icon="docker" href="https://github.com/gitroomhq/postiz-docker-compose">
Pre-wired Postiz + Postgres + Redis + Temporal. The fastest way to a working install.
</Card>
If you'd rather build from source or use Kubernetes, see [Docker Compose](/installation/docker-compose), [Docker](/installation/docker), or [Kubernetes / Helm](/installation/kubernetes-helm).
## Required services
Postiz needs four external services:
| Service | Minimum version |
|---|---|
| PostgreSQL | 14 |
| Redis | 6 |
| Temporal | bundled with official docker-compose; required since v2.12.0 |
| Object storage | Optional — local filesystem works (`STORAGE_PROVIDER=local`), Cloudflare R2 supported (`STORAGE_PROVIDER=cloudflare`) |
The official [docker-compose](https://github.com/gitroomhq/postiz-docker-compose)
ships Postgres, Redis, and Temporal pre-wired. If you're running them
externally, point Postiz at them via `DATABASE_URL`, `REDIS_URL`, and
`TEMPORAL_ADDRESS`.
## Default ports
| Service | Port | When |
|---|---|---|
| Postiz container (bundled FE + BE) | `5000` | Official Docker image (`ghcr.io/gitroomhq/postiz-app`) — exposed for you to map. The bundled compose maps host `4007:5000`. |
| Backend (Nest) | `3000` | Running from source (`pnpm dev` / `pnpm start`). Override via `PORT`. |
| Frontend (Next.js) | `4200` | Running from source. |
| Temporal frontend | `7233` | gRPC; both modes. |
| Temporal UI | `8080` | If bundled compose is used. |
## Filesystem
If `STORAGE_PROVIDER=local`, set `UPLOAD_DIRECTORY` to a host path and
mount that path into the backend container. The frontend serves the
same files through `/uploads/:path*`. See [Uploads & storage](/configuration/uploads).
If you'd rather offload media: configure [Cloudflare R2](/configuration/r2).
## Outbound network
Postiz initiates outbound HTTPS to every social provider you connect.
Strict egress firewalls **will** break OAuth — make sure the backend
can reach:
- `api.twitter.com`, `upload.twitter.com`
- `graph.facebook.com`, `graph.instagram.com`, `graph.threads.net`
- `linkedin.com`, `api.linkedin.com`
- `openapi.tiktok.com`, `open.tiktokapis.com`
- `googleapis.com` (YouTube + GMB)
- the Mastodon instance you configure via `MASTODON_URL`
- any other provider you intend to use
If your environment requires a proxy, set `HTTPS_PROXY` and `HTTP_PROXY`
on the backend.
## Inbound network
The frontend talks to the backend from the browser, so
`NEXT_PUBLIC_BACKEND_URL` must be **reachable from your users' browsers**,
not just from the frontend SSR server. A reverse proxy in front of both
ports is the typical setup — see [Reverse Proxies](/reverse-proxies/caddy).
---
title: Introduction
description: 'Welcome to Postiz documentation'
---
<Note>
Create AI-powered UGC videos for your social media with [Agent Media](https://agent-media.ai) — generate engaging video content and schedule it directly with Postiz. Perfect for OpenClaw 🦞
</Note>
<Note>
YouTube Channel: [https://youtube.com/@postizofficial](https://youtube.com/@postizofficial)
</Note>
<Warning>
Looking to integrate with Postiz programmatically? Check out the [Public API documentation](/public-api/introduction).
</Warning>
## What is Postiz?
Postiz helps you to manage all your social media accounts.
- Schedule social media and articles
- Generate posts with AI
- Exchange or buy posts from other members on the marketplace
<CardGroup cols={2}>
<Card title="Quickstart" icon="rocket" href="/quickstart">
Learn how to install the project and start using it
</Card>
<Card title="Architecture" icon="diagram-project" href="/howitworks">
Learn the architecture of the project
</Card>
</CardGroup>
MIT License
Copyright (c) 2023 Mintlify
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.---
title: Examples
description: 'Common workflows when using Postiz through MCP'
---
These examples show the tool calls an AI agent makes behind the scenes. You don't need to write these yourself — just describe what you want in natural language and your AI agent handles the rest.
## Schedule a Post to X
A typical flow to schedule a post to X (Twitter):
<Steps>
<Step title="List integrations">
The agent calls `integrationList` and finds your X account:
```json
{
"output": [
{
"id": "abc123",
"name": "My X Account",
"picture": "https://...",
"platform": "x"
}
]
}
```
</Step>
<Step title="Get platform schema">
The agent calls `integrationSchema` with `platform: "x"` to learn the rules:
```json
{
"output": {
"rules": "...",
"maxLength": 280,
"settings": { ... },
"tools": []
}
}
```
</Step>
<Step title="Schedule the post">
The agent calls `schedulePostTool`:
```json
{
"socialPost": [
{
"integrationId": "abc123",
"isPremium": false,
"date": "2025-01-15T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [
{
"content": "<p>Excited to announce our new feature!</p>",
"attachments": []
}
],
"settings": [
{ "key": "who_can_reply_post", "value": "everyone" }
]
}
]
}
```
</Step>
</Steps>
## Post to Discord with Channel Selection
Platforms like Discord require selecting a channel first:
<Steps>
<Step title="Get the schema">
The agent calls `integrationSchema` with `platform: "discord"` and discovers a tool to list channels.
</Step>
<Step title="List channels">
The agent calls `triggerTool`:
```json
{
"integrationId": "discord-123",
"methodName": "listChannels",
"dataSchema": []
}
```
Returns available channels with their IDs.
</Step>
<Step title="Schedule the post">
The agent includes the channel ID in settings:
```json
{
"socialPost": [
{
"integrationId": "discord-123",
"isPremium": false,
"date": "2025-01-15T10:00:00.000Z",
"shortLink": false,
"type": "now",
"postsAndComments": [
{
"content": "<p>Hello Discord!</p>",
"attachments": []
}
],
"settings": [
{ "key": "channel", "value": "channel-id-here" }
]
}
]
}
```
</Step>
</Steps>
## Post with an AI-Generated Image
<Steps>
<Step title="Generate the image">
The agent calls `generateImageTool`:
```json
{
"prompt": "A futuristic city skyline at sunset, digital art style"
}
```
Returns:
```json
{
"id": "img-456",
"path": "https://uploads.postiz.com/generated-image.png"
}
```
</Step>
<Step title="Schedule with the image">
The agent includes the image URL in attachments:
```json
{
"postsAndComments": [
{
"content": "<p>The future is here</p>",
"attachments": ["https://uploads.postiz.com/generated-image.png"]
}
]
}
```
</Step>
</Steps>
## Generate a Video and Post
<Steps>
<Step title="Check video options">
The agent calls `generateVideoOptions` to see available generators.
</Step>
<Step title="Get voice options">
For Image Text Slides, the agent calls `videoFunctionTool`:
```json
{
"identifier": "image-text-slides",
"functionName": "loadVoices"
}
```
Returns a list of available voices with their IDs.
</Step>
<Step title="Generate the video">
The agent calls `generateVideoTool`:
```json
{
"identifier": "image-text-slides",
"output": "vertical",
"customParams": [
{ "key": "prompt", "value": "5 tips for better social media engagement" },
{ "key": "voice", "value": "voice-id-here" }
]
}
```
Returns the video URL.
</Step>
<Step title="Schedule with the video">
The agent uses the video URL as an attachment when calling `schedulePostTool`.
</Step>
</Steps>
## Create an X Thread
To create a thread on X, add multiple items to `postsAndComments`:
```json
{
"socialPost": [
{
"integrationId": "x-123",
"isPremium": false,
"date": "2025-01-15T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [
{
"content": "<p>Thread: 5 things I learned this week</p>",
"attachments": []
},
{
"content": "<p>1. Consistency beats intensity</p>",
"attachments": []
},
{
"content": "<p>2. Start before you're ready</p>",
"attachments": []
}
],
"settings": [
{ "key": "who_can_reply_post", "value": "everyone" }
]
}
]
}
```
## Post to LinkedIn with a Comment
For LinkedIn, the first item in `postsAndComments` is the post and the rest are comments:
```json
{
"socialPost": [
{
"integrationId": "linkedin-123",
"isPremium": false,
"date": "2025-01-15T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [
{
"content": "<p>We just launched something big!</p>",
"attachments": []
},
{
"content": "<p>Check it out at example.com</p>",
"attachments": []
}
],
"settings": []
}
]
}
```
## Bulk Schedule
Schedule 5 posts across different days:
```json
{
"socialPost": [
{
"integrationId": "x-123",
"isPremium": false,
"date": "2025-01-13T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [{ "content": "<p>Monday motivation</p>", "attachments": [] }],
"settings": [{ "key": "who_can_reply_post", "value": "everyone" }]
},
{
"integrationId": "x-123",
"isPremium": false,
"date": "2025-01-14T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [{ "content": "<p>Tuesday tip</p>", "attachments": [] }],
"settings": [{ "key": "who_can_reply_post", "value": "everyone" }]
},
{
"integrationId": "x-123",
"isPremium": false,
"date": "2025-01-15T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [{ "content": "<p>Midweek thoughts</p>", "attachments": [] }],
"settings": [{ "key": "who_can_reply_post", "value": "everyone" }]
},
{
"integrationId": "x-123",
"isPremium": false,
"date": "2025-01-16T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [{ "content": "<p>Thursday throwback</p>", "attachments": [] }],
"settings": [{ "key": "who_can_reply_post", "value": "everyone" }]
},
{
"integrationId": "x-123",
"isPremium": false,
"date": "2025-01-17T10:00:00.000Z",
"shortLink": false,
"type": "schedule",
"postsAndComments": [{ "content": "<p>Friday wrap-up</p>", "attachments": [] }],
"settings": [{ "key": "who_can_reply_post", "value": "everyone" }]
}
]
}
```
Each item in the `socialPost` array is an independent post with its own date, content, and settings.
---
title: Introduction
description: 'Connect AI agents to Postiz using the Model Context Protocol (MCP)'
---
MCP (Model Context Protocol) lets AI agents interact with Postiz directly — listing integrations, scheduling posts, generating images and videos — all through a standardized tool-calling interface.
This means you can connect Claude, ChatGPT, Cursor, or any MCP-compatible client to your Postiz account and manage your social media through natural language.
## How It Works
Postiz exposes an MCP server that provides **9 tools** to AI agents. The agent discovers these tools, understands their schemas, and calls them on your behalf.
```mermaid
sequenceDiagram
participant Agent as AI Agent
participant MCP as Postiz MCP Server
participant Postiz as Postiz Backend
Agent->>MCP: Connect with API key / OAuth token
MCP-->>Agent: List available tools
Agent->>MCP: Call tool (e.g., schedule post)
MCP->>Postiz: Execute action
Postiz-->>MCP: Return result
MCP-->>Agent: Tool response
```
## Available Tools
| Tool | Description |
|------|-------------|
| `integrationList` | List all connected social media accounts (optionally filtered by group) |
| `groupList` | List all groups (customers) for your organization |
| `integrationSchema` | Get platform-specific posting rules and settings schema |
| `triggerTool` | Execute platform-specific helpers (e.g., list Discord channels) |
| `schedulePostTool` | Schedule, draft, or immediately publish posts |
| `generateImageTool` | Generate AI images for posts |
| `generateVideoOptions` | List available video generation options |
| `videoFunctionTool` | Get video generator settings (e.g., available voices) |
| `generateVideoTool` | Generate videos for posts |
## Authentication
There are two ways to authenticate with the MCP server:
### API Key
Get your API key from **Settings > Developers > Public API** in Postiz. Use it directly in the MCP endpoint URL or as a Bearer token.
### OAuth Token
If you're building an app for other Postiz users, use [OAuth2](/public-api/oauth) to obtain tokens. OAuth tokens start with `pos_` and work the same way as API keys.
## Connecting
<Tabs>
<Tab title="Bearer Token">
Use the `/mcp` endpoint with your API key or OAuth token as a Bearer token:
```
URL: https://api.postiz.com/mcp
Authorization: Bearer your-api-key
```
This method supports both API keys and OAuth tokens (prefixed with `pos_`).
</Tab>
<Tab title="API Key in URL">
Use the `/mcp/:apiKey` endpoint with your API key embedded in the URL:
```
URL: https://api.postiz.com/mcp/your-api-key
```
</Tab>
</Tabs>
<Note>
For self-hosted instances, replace `https://api.postiz.com` with your `NEXT_PUBLIC_BACKEND_URL`.
</Note>
## Quick Example
Here's what a typical interaction looks like when an AI agent uses Postiz MCP:
1. **Agent calls `integrationList`** — gets back your connected accounts (X, LinkedIn, etc.)
2. **Agent calls `integrationSchema`** with `platform: "x"` — learns X's character limits, settings, and rules
3. **Agent calls `schedulePostTool`** — schedules your post with the correct format
All of this happens automatically when you tell your AI agent something like:
> "Schedule a post to X for tomorrow at 10am: Excited to announce our new feature!"
## FAQ
### Do I need an OpenAI key to use Postiz MCP?
No. The MCP server just exposes Postiz's tools — your AI client (Claude, ChatGPT, Cursor, etc.) provides the model. Postiz only needs an `OPENAI_API_KEY` if you use Postiz's own AI features (image generation, copilot) which are separate from the MCP tools surfaced to your client.
### What happens when my API key expires or is rotated?
Postiz API keys don't auto-rotate, but if you regenerate one in Settings → Developers → Public API, every MCP client using the old key stops working until you update its config. Update the URL or the `Authorization` header in your client config and reconnect.
### Self-hosted: how do I expose the MCP endpoint?
The MCP server starts as part of the Postiz backend and is reachable at `/mcp` (Bearer auth), `/mcp/:apiKey` (key in URL), and `/mcp-oauth` (OAuth-protected). Your reverse proxy must forward these paths to the backend and support streaming HTTP (`Transfer-Encoding: chunked`). See [Reverse Proxies](/reverse-proxies/caddy).
### Can MCP read or reply to comments?
Not today. The current tool set is read-only on integrations and write-only on posts/media — there's no `getComments` or `replyToComment` exposed via MCP. Comment replies must be triggered through the Postiz UI.
---
title: Client Setup
description: 'Configure your AI client to connect to the Postiz MCP server'
---
## Claude Desktop
Add the following to your Claude Desktop MCP configuration file:
<Tabs>
<Tab title="macOS">
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"postiz": {
"url": "https://api.postiz.com/mcp/your-api-key"
}
}
}
```
</Tab>
<Tab title="Windows">
Edit `%APPDATA%\Claude\claude_desktop_config.json`:
```json
{
"mcpServers": {
"postiz": {
"url": "https://api.postiz.com/mcp/your-api-key"
}
}
}
```
</Tab>
</Tabs>
Replace `your-api-key` with your actual API key from **Settings > Developers > Public API**.
## Claude Code
The fastest way is the CLI:
```bash
claude mcp add postiz --transport http --url https://api.postiz.com/mcp/your-api-key
```
Or add it to your Claude Code config directly:
```json
{
"mcpServers": {
"postiz": {
"url": "https://api.postiz.com/mcp/your-api-key"
}
}
}
```
The Bearer-token transport works too — pick whichever your client supports better. Both authenticate the same way:
```bash
claude mcp add postiz --transport http \
--url https://api.postiz.com/mcp \
--header "Authorization: Bearer your-api-key"
```
## Cursor
In Cursor, go to **Settings > MCP** and add a new server:
- **Name:** Postiz
- **Type:** HTTP
- **URL:** `https://api.postiz.com/mcp/your-api-key`
## Other MCP Clients
Any MCP-compatible client can connect to Postiz. Use the streamable HTTP transport:
- **URL:** `https://api.postiz.com/mcp/your-api-key`
- **Transport:** Streamable HTTP
Or, if your client supports Bearer token authentication:
- **URL:** `https://api.postiz.com/mcp`
- **Transport:** Streamable HTTP
- **Authorization:** `Bearer your-api-key`
## Self-Hosted
For self-hosted Postiz instances, replace `https://api.postiz.com` with your `NEXT_PUBLIC_BACKEND_URL`:
```
https://your-postiz-server.com/mcp/your-api-key
```
## Verify Connection
Once connected, ask your AI agent:
> "List my connected social media accounts"
If the connection is working, the agent will call the `integrationList` tool and return your connected accounts.
---
title: Tools Reference
description: 'Complete reference for all Postiz MCP tools'
---
## integrationList
List all connected social media accounts (channels) for your organization.
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `group` | string | No | Group (customer) ID from `groupList`. When provided, only channels belonging to that group are returned |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Integration ID (use this when scheduling posts) |
| `name` | string | Display name of the account |
| `picture` | string | Profile picture URL |
| `platform` | string | Platform identifier (e.g., `x`, `linkedin`, `facebook`) |
| `customer` | object | The group (customer) this channel belongs to, as `{ id, name }` — omitted if the channel is not assigned to a group |
---
## groupList
List all groups (customers) for your organization. Use a group's `id` with `integrationList` to filter channels down to a single group.
**Parameters:** None
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Group (customer) ID (pass to `integrationList` as `group`) |
| `name` | string | Group (customer) display name |
---
## integrationSchema
Get the posting rules, character limits, required settings, and available helper tools for a specific platform. Call this before scheduling a post to understand what the platform expects.
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `isPremium` | boolean | Yes | Whether the user has a premium subscription |
| `platform` | string | Yes | Platform identifier (e.g., `x`, `linkedin`, `reddit`, `discord`) |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `rules` | string | Platform-specific posting rules and best practices |
| `maxLength` | number | Maximum character length for posts |
| `settings` | object | JSON schema of required settings for this platform |
| `tools` | array | Platform-specific helper tools (see `triggerTool`) |
The `tools` array contains helper functions specific to the platform. For example, Discord returns a tool to list available channels, Reddit returns a tool to search for subreddits, and LinkedIn Page returns a tool to list pages.
Each tool in the array has:
| Field | Type | Description |
|-------|------|-------------|
| `methodName` | string | Function name to pass to `triggerTool` |
| `description` | string | What the tool does |
| `dataSchema` | array | Parameters the tool accepts |
---
## triggerTool
Execute a platform-specific helper function. These are discovered through `integrationSchema` and are used to fetch dynamic data like channel lists, subreddit suggestions, or page IDs.
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `integrationId` | string | Yes | The integration ID from `integrationList` |
| `methodName` | string | Yes | The function name from `integrationSchema` tools |
| `dataSchema` | array | Yes | Key-value pairs of parameters for the function |
Each item in `dataSchema`:
| Field | Type | Description |
|-------|------|-------------|
| `key` | string | Parameter name |
| `value` | string | Parameter value |
**Example use cases:**
- Get the list of Discord channels for a server
- Search for Reddit subreddits
- List LinkedIn pages you manage
- Get Facebook page options
---
## schedulePostTool
Schedule, draft, or immediately publish posts to social media platforms. This is the main tool for creating content.
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `socialPost` | array | Yes | Array of posts to create |
Each item in `socialPost`:
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `integrationId` | string | Yes | Integration ID from `integrationList` |
| `isPremium` | boolean | Yes | Whether the user has premium |
| `date` | string | Yes | UTC datetime (e.g., `2025-01-15T10:00:00.000Z`) |
| `shortLink` | boolean | Yes | Whether to shorten links in the post |
| `type` | string | Yes | `draft`, `schedule`, or `now` |
| `postsAndComments` | array | Yes | The post content and optional comments |
| `settings` | array | Yes | Platform-specific settings from `integrationSchema` |
Each item in `postsAndComments`:
| Field | Type | Description |
|-------|------|-------------|
| `content` | string | HTML content (see formatting rules below) |
| `attachments` | array | Array of image/media URLs |
Each item in `settings`:
| Field | Type | Description |
|-------|------|-------------|
| `key` | string | Setting name |
| `value` | any | Setting value (prefer IDs over labels when available) |
### Content Formatting
Content must be HTML with these allowed tags only:
| Tag | Usage |
|-----|-------|
| `<p>` | Wrap each line |
| `<h1>`, `<h2>`, `<h3>` | Headings |
| `<strong>` | Bold text |
| `<u>` | Underlined text |
| `<ul>`, `<li>` | Lists |
<Warning>
You cannot combine `<u>` and `<strong>` in the same element. Each line of text must be wrapped in `<p>` tags.
</Warning>
### Posts vs Comments
The `postsAndComments` array behavior depends on the platform:
- **Thread-based platforms** (X, Threads, Bluesky): Each array item becomes a separate post in a thread
- **Comment-based platforms** (LinkedIn, Facebook): First item is the post, remaining items are comments
### Multiple Posts
To schedule multiple posts (e.g., 20 posts across different days), add multiple items to the `socialPost` array — each with its own `date` and `integrationId`.
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `postId` | string | The created post ID |
| `integration` | string | The integration it was posted to |
If validation fails, returns `{ errors: string }` with details about what went wrong (e.g., content exceeds character limit).
---
## generateImageTool
Generate an AI image to use as a post attachment.
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `prompt` | string | Yes | Description of the image to generate |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `id` | string | Media ID |
| `path` | string | URL of the generated image |
Use the returned `path` in the `attachments` array when scheduling a post.
---
## generateVideoOptions
List all available video generation types and their required parameters.
**Parameters:** None
**Returns:** An array of video generators, each with:
| Field | Type | Description |
|-------|------|-------------|
| `type` | string | Video type identifier (e.g., `image-text-slides`, `veo3`) |
| `output` | string | Supported orientations: `vertical\|horizontal` |
| `tools` | array | Helper functions (call with `videoFunctionTool`) |
| `customParams` | object | JSON schema of required parameters |
### Available Video Types
| Type | Description | Requirements |
|------|-------------|-------------|
| Image Text Slides | Slide-based videos with text-to-speech | `prompt`, `voice` (get voice ID via `videoFunctionTool`) |
| Veo3 | AI-generated video with audio | `prompt`, optional `images` (max 3) |
---
## videoFunctionTool
Execute helper functions for video generators. Use this to fetch required data before generating a video (e.g., listing available voices).
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `identifier` | string | Yes | Video type identifier from `generateVideoOptions` |
| `functionName` | string | Yes | Function name from the video type's `tools` array |
**Example:** Call with `identifier: "image-text-slides"` and `functionName: "loadVoices"` to get available ElevenLabs voice IDs.
---
## generateVideoTool
Generate a video to use as a post attachment.
**Parameters:**
| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `identifier` | string | Yes | Video type from `generateVideoOptions` |
| `output` | string | Yes | `vertical` or `horizontal` |
| `customParams` | array | Yes | Key-value pairs of parameters for the video type |
Each item in `customParams`:
| Field | Type | Description |
|-------|------|-------------|
| `key` | string | Parameter name |
| `value` | any | Parameter value |
**Returns:**
| Field | Type | Description |
|-------|------|-------------|
| `url` | string | URL of the generated video |
Use the returned `url` in the `attachments` array when scheduling a post.
---
title: Bluesky
description: 'How to add Bluesky to Postiz'
---
<Snippet file="never-share.mdx" />
<Steps>
<Step title="Add BlueSky as a Channel">
You do not need to configure any environment variables for BlueSky, you can simply add your account from the UI.

You should be redirected and be able to start posting immediately. If you have any issues, check the backend service logs.
</Step>
</Steps>
---
title: Discord
description: 'How to add Discord to your system'
---
<Snippet file="never-share.mdx" />
<Note>
This integration requires that you have **Manage Server** permissions on the Discord server you want to integrate with.
</Note>
<Steps>
<Step title="Create a Discord Application">
Login to Discord on the web, and then go to the [Discord Developer Portal](https://discord.com/developers/applications) and click on "New Application".

</Step>
<Step title="Add an App Icon">

Upload the App Icon of your choice (1024x1024px max) and save your changes. If you do not do this, you will get 404 errors in logs when trying to add the Discord channel in the Postiz web interface.
</Step>
<Step title="Get and set your Client ID and Client Secret">
You can find this in the **OAuth2** section of the Discord Developer Portal.

Set these in your .env file as follows;
```env
DISCORD_CLIENT_ID="your_client_id"
DISCORD_CLIENT_SECRET="your_client_secret"
```
</Step>
<Step title="Add a Redirect URI">
<Snippet file="oauth2redirect.mdx" />
**Your Discord OAuth2 Redirect URI:**
- Production: `https://your-postiz-domain.com/integrations/social/discord`
- Local development: `http://localhost:4200/integrations/social/discord`
- Docker: `http://localhost:5000/integrations/social/discord`
You can find this in the **OAuth2** section of the Discord Developer Portal.

</Step>
<Step title="Create a Bot">
Navigate to the "Bot" section of the Discord Developer Portal. Fill out the bot details however you like, and then click "Reset Token".
With the token that is generated, set it in your .env file as follows;
```env
DISCORD_BOT_TOKEN_ID="your_bot_token"
```
If you do not set this, you will get 404 errors when trying to add the Discord channel in the Postiz web interface.
Stop Postiz if it is running, and then start it using the .env file with the Discord details.
</Step>
<Step title="Add a Discord channel in the Postiz web interface">
Go to the Postiz web interface, and click on the "Add Channel" button, and then select "Discord". You should be redirected to Discord to login.
</Step>
</Steps>
---
title: Farcaster
description: 'How to add Farcaster (Warpcast) to your system'
---
<Snippet file="never-share.mdx" />
<Note>
Postiz uses [Neynar](https://neynar.com) as the Farcaster API provider.
You need a Neynar developer account to obtain the API credentials below.
</Note>
<Info>
Farcaster posts on Postiz accept images only — text + image, but no
video. Posts can be up to 800 characters.
</Info>
<Steps>
<Step title="Create a Neynar app">
Go to the [Neynar dashboard](https://dev.neynar.com) and create a new app. You'll get a **Client ID** and a **Secret Key**.
</Step>
<Step title="Add credentials to your environment">
Add the following to your `.env` file:
```env
NEYNAR_CLIENT_ID="your-neynar-client-id"
NEYNAR_SECRET_KEY="your-neynar-secret-key"
```
</Step>
<Step title="Restart Postiz">
Stop Postiz if it is running and start it again so the new environment variables are picked up.
</Step>
<Step title="Add the channel">
In the Postiz web interface, click **Add Channel**, select **Farcaster**, and complete the Neynar login flow. Your Farcaster account will appear in the channel list.
</Step>
</Steps>
---
title: 'Post Analytics'
openapi: 'GET /analytics/post/{postId}'
---