| lib | ||
| test | ||
| .gitattributes | ||
| .gitignore | ||
| index.js | ||
| package.json | ||
| README.md | ||
postiz-poster
A Saltcorn plugin that publishes table rows to social networks through a Postiz instance -- self-hosted or Postiz Cloud. Postiz handles the per-platform fan-out (Mastodon, Bluesky, X, LinkedIn, Facebook, Instagram, Threads, Telegram, and more); this plugin just maps a Saltcorn row into a Postiz post and calls Postiz's public API.
How it works
- Plugin config holds the Postiz connection: a base URL and a public API
key. Self-hosted and cloud use the same
/public/v1API, so switching between them is only a base-URL change -- no code change. - One action,
post_to_postiz, is attached to any trigger (row Insert / Update, or a button). When it fires it reads the post text (and an optional media URL) from the row, optionally uploads the media to Postiz, and creates the post against the channels you selected. - The channel picker in the action config is populated from the live Postiz
instance (
GET /public/v1/integrations), so admins choose real connected accounts instead of pasting ids. If Postiz is unreachable at config time the field degrades to a free-text id list.
Layout
postiz/
index.js plugin entry: wires config workflow + the action
lib/
constants.js plugin name/version, Postiz endpoints, post types
postizClient.js the only module that speaks HTTP to Postiz
config.js configuration_workflow (base URL + API key)
action.js post_to_postiz: configFields, payload builder, run
secretsAtRest.js AES-256-GCM seal/open for the API key (shared helper)
test/
e2e.js payload + secrets gates, plus optional live gate
Secrets at rest
The Postiz API key is configured through the normal plugin UI but is not
left plaintext in the database. Saltcorn stores plugin configuration unencrypted
in _sc_plugins.configuration, which would otherwise expose the key to DB
backups, admin reads, and dev-deploy's journal sync. Instead:
lib/secretsAtRest.jsseals the key with AES-256-GCM under a key derived (HKDF-SHA256) from Saltcorn'ssession_secret-- which lives in the env / config file, not the database. Ciphertext in the DB is useless without it.- Because Saltcorn's config form autosaves raw values,
onLoadre-seals any plaintext key the moment the plugin (re)loads after a save; the action decrypts only at the point of use. - Threat model: this protects DB-only exposure (backups, sync, config-table
reads). It does not protect a host that leaks both the database and the env /
config file. Rotating
session_secretinvalidates the sealed key -- you must re-enter it in the plugin config (the action raises a clear error saying so). - For the same reason, the sealed key is environment-specific: it will not
decrypt on a peer with a different
session_secret, so exclude it from any dev-deploy sync and set it per environment.
Configuring
- In Postiz, connect your social accounts, then create a key under Settings -> Developers -> Public API.
- In Saltcorn: Settings -> Plugins -> postiz-poster, set:
- Postiz base URL -- self-hosted
https://postiz.example.com, or cloudhttps://api.postiz.com. - Public API key.
- Postiz base URL -- self-hosted
- Create a trigger on the table you want to publish from, choose action
post_to_postiz, then pick the content field, optional media field, target channels, and timing (now / schedule / draft).
Testing
node test/e2e.js # pure payload gate only
POSTIZ_BASE_URL=http://localhost:5000 \
POSTIZ_API_KEY=xxxxxxxx node test/e2e.js # + live gate vs a real Postiz
The live gate lists integrations and creates a draft (safe to delete) using the first connected channel. With no env vars it is skipped, not failed.
To confirm before production
These are sketched against the documented Postiz API and isolated to one or two spots each, but verify against docs.postiz.com/public-api, which has changed across Postiz's weekly releases:
- Create-post body shape (
type/date/posts[].integration/value[].content/value[].image) -- seebuildPostPayloadinlib/action.js. - Auth header format -- bare key vs
Bearer(AUTH_HEADERinlib/constants.js). - Media upload -- whether
/uploadaccepts a URL fetch or wants multipart, and the id/path field it returns (uploadMediainlib/postizClient.js).
Notes
- Postiz removes the subscription cost (self-hosted) but not the gatekeeping: you still register your own developer apps with each walled-garden platform (Meta App Review, LinkedIn Community Management API, separate Threads app) and own the OAuth token upkeep. Mastodon / Bluesky / X are straightforward.
- Underlying platform fees still pass through (e.g. X per-post pricing). Postiz brokers the call; it does not make any platform's API free.