feat: add posts:settings command to patch a post's provider settings

Add `postiz posts:settings <id> --settings '<json>'` calling
PUT /public/v1/posts/:id/settings to patch a post's provider-specific
settings without recreating it. Modeled on the existing posts:status
command. Settings are merged server-side (only passed keys change);
only DRAFT/QUEUE posts can be updated.

Also document that posts:list responses now include each post's current
`settings` (returned as a JSON string).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Gilad Resisi
2026-07-20 20:36:42 +07:00
parent 41c5a9dbd6
commit e41dc9b5e5
6 changed files with 95 additions and 2 deletions
+8
View File
@@ -5,6 +5,14 @@ All notable changes to the Postiz CLI will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `posts:settings` - Update a post's provider settings via `PUT /public/v1/posts/:id/settings` (merged — only the keys you pass change; unpublished DRAFT/QUEUE posts only).
### Changed
- `posts:list` responses now include each post's current `settings`.
## [1.0.0] - 2026-02-13 ## [1.0.0] - 2026-02-13
### Added ### Added
+11 -1
View File
@@ -183,7 +183,7 @@ postiz posts:list --startDate "2024-01-01T00:00:00Z" --endDate "2024-12-31T23:59
postiz posts:list --customer "customer-id" postiz posts:list --customer "customer-id"
``` ```
Defaults to last 30 days to next 30 days if dates not specified. Defaults to last 30 days to next 30 days if dates not specified. Each returned post includes its current `settings` (returned as a JSON string — `JSON.parse` it). The intended workflow is `posts:list` (read current settings) → `posts:settings` (patch them).
**Delete post** **Delete post**
```bash ```bash
@@ -198,6 +198,14 @@ postiz posts:status <post-id> --status schedule
Move a scheduled post back to a draft, or promote a draft into the publishing queue. Switching to `draft` also terminates any workflow that's already running for the post, so it won't publish. Switching to `schedule` queues the post for publishing at its stored date. Move a scheduled post back to a draft, or promote a draft into the publishing queue. Switching to `draft` also terminates any workflow that's already running for the post, so it won't publish. Switching to `schedule` queues the post for publishing at its stored date.
**Update a post's provider-specific settings**
```bash
postiz posts:settings <post-id> --settings '{"content_posting_method":"DIRECT_POST"}'
postiz posts:settings <post-id> --settings '{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}'
```
Patches a post's settings server-side. The backend **merges** the object — only the keys you pass change, everything else is preserved — so pass a partial object, not the full settings blob. Only **DRAFT/QUEUE** (unpublished) posts can be updated; published posts are rejected. Pass the **main post id**, not a comment id. Do **not** include `__type` — the backend adds it automatically from the integration.
--- ---
### Analytics ### Analytics
@@ -560,6 +568,7 @@ The CLI interacts with these Postiz API endpoints:
| `/public/v1/posts` | POST | Create a post | | `/public/v1/posts` | POST | Create a post |
| `/public/v1/posts` | GET | List posts | | `/public/v1/posts` | GET | List posts |
| `/public/v1/posts/:id` | DELETE | Delete a post | | `/public/v1/posts/:id` | DELETE | Delete a post |
| `/public/v1/posts/:id/settings` | PUT | Update a post's provider settings (merged; unpublished only) |
| `/public/v1/posts/:id/missing` | GET | Get missing content from provider | | `/public/v1/posts/:id/missing` | GET | Get missing content from provider |
| `/public/v1/posts/:id/release-id` | PUT | Update release ID for a post | | `/public/v1/posts/:id/release-id` | PUT | Update release ID for a post |
| `/public/v1/integrations` | GET | List integrations (optional `?group=` filter) | | `/public/v1/integrations` | GET | List integrations (optional `?group=` filter) |
@@ -679,6 +688,7 @@ postiz posts:list # List posts
postiz posts:delete <id> # Delete post postiz posts:delete <id> # Delete post
postiz posts:status <id> --status draft # Move to draft (stops workflow) postiz posts:status <id> --status draft # Move to draft (stops workflow)
postiz posts:status <id> --status schedule # Queue draft for publishing postiz posts:status <id> --status schedule # Queue draft for publishing
postiz posts:settings <id> --settings '{}' # Patch a post's settings (merged; DRAFT/QUEUE only)
postiz upload <file> # Upload media postiz upload <file> # Upload media
# Analytics # Analytics
+10
View File
@@ -194,6 +194,8 @@ postiz posts:create --json post.json
```bash ```bash
# List posts (defaults to last 30 days to next 30 days) # List posts (defaults to last 30 days to next 30 days)
# Each returned post includes its current `settings` (as a JSON string — JSON.parse it).
# Workflow: run posts:list to read a post's current settings, then posts:settings to patch them.
postiz posts:list postiz posts:list
# List posts in date range # List posts in date range
@@ -205,6 +207,12 @@ postiz posts:delete <post-id>
# Change post status (draft ↔ schedule) # Change post status (draft ↔ schedule)
postiz posts:status <post-id> --status draft # Move back to draft, terminates any running publish workflow postiz posts:status <post-id> --status draft # Move back to draft, terminates any running publish workflow
postiz posts:status <post-id> --status schedule # Promote a draft into the publishing queue (uses the post's stored date) postiz posts:status <post-id> --status schedule # Promote a draft into the publishing queue (uses the post's stored date)
# Update a post's provider-specific settings (merged — only the keys you pass change)
# Only DRAFT/QUEUE (unpublished) posts can be updated. Pass the MAIN post id, not a comment id.
# Do NOT include __type — the backend adds it automatically from the integration.
postiz posts:settings <post-id> --settings '{"content_posting_method":"DIRECT_POST"}' # Switch a TikTok draft to direct publishing
postiz posts:settings <post-id> --settings '{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}' # Set a Reddit post's subreddit
``` ```
### Analytics ### Analytics
@@ -749,6 +757,7 @@ https://clawhub.ai/nevo-david/agent-media
9. **Required settings** - Some platforms require specific settings (Reddit needs title, YouTube needs title) 9. **Required settings** - Some platforms require specific settings (Reddit needs title, YouTube needs title)
10. **Media MIME types** - CLI auto-detects from file extension, ensure correct extension 10. **Media MIME types** - CLI auto-detects from file extension, ensure correct extension
11. **Analytics returns `{"missing": true}`** - The post was published but the platform didn't return a post ID. Run `posts:missing <post-id>` to get available content, then `posts:connect <post-id> --release-id "<id>"` to link it. Analytics will work after connecting. 11. **Analytics returns `{"missing": true}`** - The post was published but the platform didn't return a post ID. Run `posts:missing <post-id>` to get available content, then `posts:connect <post-id> --release-id "<id>"` to link it. Analytics will work after connecting.
12. **`posts:settings` merges** - Only the keys you pass change; everything else on the post is preserved, so pass a partial object, not the full settings blob. Only **DRAFT/QUEUE** (unpublished) posts can be updated — published posts are rejected. Pass the **main post id**, not a comment id. Never include `__type` — the backend adds it automatically from the integration.
--- ---
@@ -781,6 +790,7 @@ postiz posts:list # List posts
postiz posts:delete <id> # Delete post postiz posts:delete <id> # Delete post
postiz posts:status <id> --status draft # Move to draft (stops workflow) postiz posts:status <id> --status draft # Move to draft (stops workflow)
postiz posts:status <id> --status schedule # Queue draft for publishing postiz posts:status <id> --status schedule # Queue draft for publishing
postiz posts:settings <id> --settings '{}' # Patch a post's settings (merged; DRAFT/QUEUE only)
postiz upload <file> # Upload media postiz upload <file> # Upload media
# Analytics # Analytics
+7
View File
@@ -157,6 +157,13 @@ export class PostizAPI {
}); });
} }
async updatePostSettings(postId: string, settings: Record<string, any>) {
return this.request(`/public/v1/posts/${postId}/settings`, {
method: 'PUT',
body: JSON.stringify({ settings }),
});
}
async getAnalytics(integrationId: string, date: string) { async getAnalytics(integrationId: string, date: string) {
return this.request(`/public/v1/analytics/${integrationId}?date=${encodeURIComponent(date)}`, { return this.request(`/public/v1/analytics/${integrationId}?date=${encodeURIComponent(date)}`, {
method: 'GET', method: 'GET',
+33
View File
@@ -205,6 +205,39 @@ export async function changePostStatus(args: any) {
} }
} }
export async function updatePostSettings(args: any) {
const config = getConfig();
const api = new PostizAPI(config);
if (!args.id) {
console.error('❌ Post ID is required');
process.exit(1);
}
if (!args.settings) {
console.error('❌ --settings is required');
process.exit(1);
}
let settings: any;
try {
settings = JSON.parse(args.settings);
} catch (error: any) {
console.error('❌ Failed to parse settings JSON:', error.message);
process.exit(1);
}
try {
const result = await api.updatePostSettings(args.id, settings);
console.log(`✅ Post ${args.id} settings updated`);
console.log(JSON.stringify(result, null, 2));
return result;
} catch (error: any) {
console.error('❌ Failed to update post settings:', error.message);
process.exit(1);
}
}
export async function deletePost(args: any) { export async function deletePost(args: any) {
const config = getConfig(); const config = getConfig();
const api = new PostizAPI(config); const api = new PostizAPI(config);
+26 -1
View File
@@ -1,6 +1,6 @@
import yargs from 'yargs'; import yargs from 'yargs';
import { hideBin } from 'yargs/helpers'; import { hideBin } from 'yargs/helpers';
import { createPost, listPosts, deletePost, getMissingContent, connectPost, changePostStatus } from './commands/posts'; import { createPost, listPosts, deletePost, getMissingContent, connectPost, changePostStatus, updatePostSettings } from './commands/posts';
import { listIntegrations, listGroups, getIntegrationSettings, triggerIntegrationTool } from './commands/integrations'; import { listIntegrations, listGroups, getIntegrationSettings, triggerIntegrationTool } from './commands/integrations';
import { getAnalytics, getPostAnalytics } from './commands/analytics'; import { getAnalytics, getPostAnalytics } from './commands/analytics';
import { uploadFile } from './commands/upload'; import { uploadFile } from './commands/upload';
@@ -202,6 +202,31 @@ yargs(hideBin(process.argv))
}, },
changePostStatus as any changePostStatus as any
) )
.command(
'posts:settings <id>',
'Update a post\'s provider-specific settings (merged; only unpublished draft/scheduled posts)',
(yargs: Argv) => {
return yargs
.positional('id', {
describe: 'Post ID',
type: 'string',
})
.option('settings', {
describe: 'Partial settings as a JSON string — only the keys you pass change; do not include __type',
type: 'string',
demandOption: true,
})
.example(
'$0 posts:settings post-123 --settings \'{"content_posting_method":"DIRECT_POST"}\'',
'Switch a TikTok draft to direct publishing'
)
.example(
'$0 posts:settings post-123 --settings \'{"subreddit":[{"value":{"subreddit":"/r/selfhosted","title":"My title","type":"self","is_flair_required":true}}]}\'',
'Set a Reddit post\'s subreddit'
);
},
updatePostSettings as any
)
.command( .command(
'posts:connect <id>', 'posts:connect <id>',
'Connect a post to its published content by updating the release ID', 'Connect a post to its published content by updating the release ID',