threads_publish_text
Publish text posts on Threads with optional attachments like links, polls, GIFs, topic tags, and quote posts.
Instructions
Publish a text-only post on Threads. Supports optional link attachment, poll, GIF, topic tag, and quote post.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | Post text (max 500 chars) | |
| reply_control | No | Who can reply | |
| link_attachment | No | URL to attach as a link preview card (max 5 links per post) | |
| topic_tag | No | Topic tag for the post (1-50 chars, no periods or ampersands) | |
| quote_post_id | No | ID of a post to quote | |
| poll_options | No | Poll options (2-4 choices). Creates a poll attachment. | |
| gif_id | No | GIF ID from GIPHY or Tenor | |
| gif_provider | No | GIF provider (GIPHY or TENOR). Tenor sunsets March 31, 2026. | |
| alt_text | No | Alt text for accessibility (max 1000 chars) | |
| is_spoiler | No | Mark content as spoiler |
Implementation Reference
- src/tools/threads/publishing.ts:35-59 (handler)Handler logic for threads_publish_text, which creates a text media container on Threads and then publishes it.
async ({ text, reply_control, link_attachment, topic_tag, quote_post_id, poll_options, gif_id, gif_provider, alt_text, is_spoiler }) => { try { const params: Record<string, unknown> = { media_type: "TEXT", text }; if (reply_control) params.reply_control = reply_control; if (link_attachment) params.link_attachment = link_attachment; if (topic_tag) params.topic_tag = topic_tag; if (quote_post_id) params.quote_post_id = quote_post_id; if (poll_options) { params.poll_attachment = JSON.stringify({ options: poll_options.map(o => ({ option_text: o })) }); } if (gif_id && gif_provider) { params.gif_attachment = JSON.stringify({ gif_id, provider: gif_provider }); } if (alt_text) params.alt_text = alt_text; if (is_spoiler) params.is_spoiler_media = true; const { data: container } = await client.threads("POST", `/${client.threadsUserId}/threads`, params); const containerId = (container as { id: string }).id; const { data, rateLimit } = await client.threads("POST", `/${client.threadsUserId}/threads_publish`, { creation_id: containerId, }); return { content: [{ type: "text", text: JSON.stringify({ ...data as object, _rateLimit: rateLimit }, null, 2) }] }; } catch (error) { return { content: [{ type: "text", text: `Publish text failed: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } - src/tools/threads/publishing.ts:20-34 (registration)Registration and schema definition for threads_publish_text.
server.tool( "threads_publish_text", "Publish a text-only post on Threads. Supports optional link attachment, poll, GIF, topic tag, and quote post.", { text: z.string().max(500).describe("Post text (max 500 chars)"), reply_control: z.enum(["everyone", "accounts_you_follow", "mentioned_only", "parent_post_author_only", "followers_only"]).optional().describe("Who can reply"), link_attachment: z.string().url().optional().describe("URL to attach as a link preview card (max 5 links per post)"), topic_tag: z.string().max(50).optional().describe("Topic tag for the post (1-50 chars, no periods or ampersands)"), quote_post_id: z.string().optional().describe("ID of a post to quote"), poll_options: z.array(z.string()).min(2).max(4).optional().describe("Poll options (2-4 choices). Creates a poll attachment."), gif_id: z.string().optional().describe("GIF ID from GIPHY or Tenor"), gif_provider: z.enum(["GIPHY", "TENOR"]).optional().describe("GIF provider (GIPHY or TENOR). Tenor sunsets March 31, 2026."), alt_text: z.string().max(1000).optional().describe("Alt text for accessibility (max 1000 chars)"), is_spoiler: z.boolean().optional().describe("Mark content as spoiler"), },