forge-social launched with Mastodon, added LinkedIn in v0.2.0, and held off on X until the OAuth story was clear. v0.5.0 ships X support.
Connecting an account
X uses OAuth 2.0 Authorization Code Flow with PKCE. Before connecting an account, platform config for X needs to be in place — client ID and secret from developer.x.com, stored via create_platform_config. See DB-driven platform config.
Once config is set, connect an X account:
forge-cli social credential create --platform x --name "forge-cms X account"
This prints an authorization URL. Open it in a browser, approve access, and the callback stores the encrypted access and refresh tokens. The credential is live.
Posting: 280 characters, enforced
The 280-character limit is a terminal constraint. If a post body exceeds 280 characters, the publish attempt fails immediately — the scheduler does not retry it, and the post does not go out truncated.
This is intentional. Truncating without the operator's knowledge would produce content that was never reviewed. The failure surfaces where it can be fixed: in the draft.
Automatic token refresh
X access tokens expire. forge-social checks the expiry before every publish attempt. If the token expires within 5 minutes, it exchanges the refresh token for a new pair before posting. The refresh happens silently.
If the refresh fails — expired refresh token, revoked access — the publish attempt fails and the credential surfaces as needing reconnection. Nothing is dropped silently.
Image upload (v0.6.0)
X does not accept image URLs in tweet payloads. Images must be uploaded to the X media API first to get a media ID, which is then attached to the tweet.
forge-social handles this automatically. When a ScheduledPost has media_url set, the image is fetched and uploaded to X before the tweet is sent:
create_scheduled_post
platform: "x"
credential_id: "<id>"
body: "New post on forge-cms.dev"
media_url: "https://yoursite.com/media/og-image.png"
If the upload fails, the publish attempt fails and the scheduler retries. Media is never silently dropped in favour of a text-only post.
Supported formats: JPEG, PNG, WebP, BMP, TIFF.
Re-authorisation required
Image upload requires the media.write OAuth scope, which was not included in the v0.5.0 OAuth flow. Existing X credentials need to be reconnected to pick up this scope:
forge-cli social credential create --platform x --name "forge-cms X account"
Update your AgentJob or scheduled posts to reference the new credential ID.
forge-cli v0.8.0
Full CLI coverage for X:
social credential create --platform x
social credential get <id>
social credential delete <id>
social post create --platform x --credential <id> --body "..."
social post queue --credential <id> --body "..."
All social CLI commands that existed for Mastodon and LinkedIn work identically for X.
forge-social v0.5.0-v0.6.0, forge-cli v0.8.0.
*See forge-social for full installation and configuration reference.* *See DB-driven platform config for how OAuth app credentials are stored.*