Create Threads -- Tutorial
Step-by-step guide to composing and publishing multi-tweet threads using XActions browser scripts and Node.js/Puppeteer.
Prerequisites
- Logged into x.com in your browser
- Browser DevTools console (F12 -> Console tab)
- For Node.js usage:
npm install xactionsand a valid session cookie
Quick Start
- Navigate to x.com
- Open DevTools (F12)
- Copy the script from
src/postThread.jsorsrc/threadComposer.js - Edit the CONFIG section with your thread content
- Paste into Console and press Enter
Three Script Options
| Script | Runtime | Best For |
|---|---|---|
src/postThread.js |
Browser console | Quick one-off threads |
src/threadComposer.js |
Browser console | Advanced threads with drafts, preview, auto-numbering |
src/postComposer.js (postThread) |
Node.js / Puppeteer | Automated thread posting from scripts |
Configuration
src/postThread.js (Browser Console)
const CONFIG = {
thread: [
'First tweet in the thread',
'Second tweet continues the thought...',
'Final tweet wraps it up!',
],
delayBetweenTweets: 2000,
dryRun: true, // Set to false to actually post
};
| Option | Type | Default | Description |
|---|---|---|---|
thread |
string[] |
[] |
Array of tweet texts in order |
delayBetweenTweets |
number |
2000 |
Milliseconds between adding each tweet |
dryRun |
boolean |
true |
Preview mode -- set false to post |
src/threadComposer.js (Browser Console -- Advanced)
const CONFIG = {
dryRun: true,
autoNumber: false, // Add "1/N" numbering to each tweet
addThreadEmoji: true, // Add a thread emoji to the first tweet
delayBetween: [3000, 6000], // Random delay range between tweets
maxChars: 280,
};
This script exposes commands on window.XActions:
// Load your thread
XActions.thread([
"First tweet of my thread",
"Second tweet with more context...",
"Third tweet -- the conclusion!",
]);
// Preview with character counts
XActions.preview();
// Publish (respects CONFIG.dryRun)
XActions.publish();
// Draft management
XActions.saveDraft('myThread');
XActions.loadDraft('myThread');
XActions.listDrafts();
XActions.deleteDraft('myThread');
// Export thread as JSON file
XActions.exportThread();
// Abort mid-publish
XActions.abort();
src/postComposer.js -- postThread (Node.js / Puppeteer)
import { postThread } from './src/postComposer.js';
// Simple text-only thread
const result = await postThread(page, [
'Thread time! Here is what I learned this week...',
'Lesson 1: Always test in dry-run mode first.',
'Lesson 2: Character counts matter. Use the preview.',
'Lesson 3: Threads get more engagement than single tweets.',
]);
// Thread with media on specific tweets
const result = await postThread(page, [
{ text: 'Check out these results!', media: '/path/to/chart.png' },
'The data shows a 40% increase in engagement.',
{ text: 'Here is the methodology', media: '/path/to/diagram.jpg' },
'Thanks for reading! Follow for more.',
]);
The postThread function accepts an array where each element is either:
- A
string(text only) - An object
{ text: string, media?: string }(text with optional media)
Step-by-Step Guide
Using postThread.js (Browser Console)
- Open x.com and press F12 to open DevTools
- Copy the contents of
src/postThread.js - Edit the
CONFIG.threadarray with your tweets:thread: [ 'Here is my hot take on AI agents...', 'First, they need to understand context.', 'Second, tool use is everything.', 'Final thought: the best agents are invisible.', ], - Set
CONFIG.dryRun = falsewhen ready to post - Paste into the Console tab and press Enter
The script will:
- Validate all tweets are under 280 characters
- Print a preview with character counts
- Open the compose dialog
- Type each tweet and click the "+" button to add the next one
- Click "Post all" to publish the entire thread
Using threadComposer.js (Browser Console -- Advanced)
- Paste
src/threadComposer.jsinto the console - Load your thread content:
XActions.thread([ "Hot take: browser automation is underrated", "Most people think you need API access to automate X", "But with XActions, you can do everything from the console", "No API keys. No rate limit fees. Just paste and go.", ]); - Review the preview -- it shows character counts and warnings:
Tweet 1/4 -- 48/280 chars [OK] Tweet 2/4 -- 55/280 chars [OK] ... - Save as draft if needed:
XActions.saveDraft('my-hot-take') - Publish: Set
CONFIG.dryRun = falseand callXActions.publish()
Using postComposer.js (Node.js / Puppeteer)
- Set up a Puppeteer page with your X session
- Import and call
postThread:import { postThread } from './src/postComposer.js'; const result = await postThread(page, [ 'Tweet 1 of my thread', 'Tweet 2 continues...', 'Tweet 3 wraps up!', ]); console.log(result); // { success: true, tweetCount: 3, tweets: [...], timestamp: '...' }
How Thread Posting Works Internally
- Navigate to
https://x.com/compose/tweet - Type the first tweet in
[data-testid="tweetTextarea_0"] - Click the "+" button (
[data-testid="addButton"]) to add each subsequent tweet - Each new tweet gets a new textarea:
[data-testid="tweetTextarea_1"],tweetTextarea_2, etc. - After all tweets are typed, click
[data-testid="tweetButton"]to post the entire thread
Tips & Tricks
- Dry run first: Always test with
dryRun: truebefore posting. The preview shows character counts and validates content. - Thread length: Threads of 5-10 tweets tend to perform best. Very long threads (20+) lose readers.
- First tweet matters: The first tweet is what appears in the timeline. Make it compelling to drive clicks.
- Auto-numbering: Enable
autoNumber: trueinthreadComposer.jsto add "1/N" labels automatically. - Thread emoji: The thread composer adds a thread emoji to the first tweet by default. Disable with
addThreadEmoji: false. - Save drafts: Use
XActions.saveDraft('name')to save threads to localStorage for later editing. - Export: Use
XActions.exportThread()to download your thread as a JSON file. - Media per tweet: When using the Puppeteer
postThread, pass objects with{ text, media }to attach images to specific tweets.
Troubleshooting
| Issue | Solution |
|---|---|
| "Add tweet" button not found | The selector [data-testid="addButton"] may have changed. Check docs/agents/selectors.md for updates. |
| Tweet exceeds 280 chars | The script validates lengths. Shorten the offending tweet or upgrade to Premium for 25,000 chars. |
| Thread only partially posted | Network issues can interrupt posting. Check your posted tweets and re-post missing ones manually. |
| Compose box not found | Make sure you are logged into x.com. Try clicking the compose button manually first. |
| Draft not loading | Drafts are stored in localStorage. Clearing browser data removes them. Use XActions.exportThread() for permanent backups. |
Related Scripts
src/postComposer.js-- Single tweet posting with media supportsrc/schedulePosts.js-- Schedule threads for specific timessrc/textFormatting.js-- Format text with bold, italic, etc.src/articlePublisher.js-- For long-form content that exceeds thread length
⚡ Ready to try Create Threads -- Tutorial?
XActions is 100% free and open-source. No API keys, no fees, no signup.
Browse All Scripts