A tool that renders tweets as PNG images using Playwright (headless Chromium). Works with usernames, tweet IDs, URLs, local JSON files, or stdin.
This style is based on nitter using Midnight theme as default.
![]() |
![]() |
|---|
pip install tw2img
playwright install chromiumgit clone https://github.com/cmj/tw2img.git
cd tw2img
pip install playwright
playwright install chromium# By @username (fetch latest tweet)
tw2img @AP --guest
# By @username, fetch the 3rd most recent tweet
tw2img @AP 3 --guest
# By tweet ID
tw2img 2041557036274475228 --guest
# By tweet URL
tw2img https://x.com/NASA/status/2041557036274475228 --guestWhen running from source, replace
tw2imgwithpython tw2img.py.
Here is a list of popular Twitter accounts sorted by most recent, and useful for guest access: https://github.com/cmj/twitter-tools/wiki/RSS%E2%80%90Friendly
You need your Twitter auth tokens. Export them as environment variables:
export TWITTER_AUTH_TOKEN="your_auth_token_here"
export TWITTER_CSRF_TOKEN="your_ct0_token_here"
# alternative, only requires setting auth_token
export TWITTER_CSRF_TOKEN=$(openssl rand -hex 16)Then run:
tw2img 2054583770045386950Where to find tokens: Open browser devtools, network tab, any x.com request, select cookies tab - auth_token and ct0.
| Option | Description |
|---|---|
@user |
Fetch latest tweet from this user |
--user <name> |
Same as above |
--light |
Use light theme (default is dark) |
--no-source |
Hide the "Twitter for iPhone" source text |
--no-context |
Show only the focal tweet, no thread/replies |
--no-retina |
Disable 2x retina rendering (smaller file) |
--full-stats |
Show full numbers instead of abbreviated (e.g. 12,345 instead of 12.3K) |
--output-dir <path> |
Directory to save output PNG (default: current working directory) |
--width 800 |
Set output width in pixels (default: 598) |
--css theme.css |
File to override the theme (ex: nitter/public/css/themes/pleroma.css) |
--nitter |
Use Nitter default theme |
--html-only |
Print HTML to stdout instead of rendering PNG |
--save-html |
Save HTML to this file instead of rendering PNG |
--imgur |
Upload PNG to imgur after rendering |
--dump-json |
Print raw API JSON to stdout and exit |
--trans <[SOURCE:]TARGET> |
Translate tweet text before rendering. Target-only (e.g. --trans en) auto-detects source; SOURCE:TARGET (e.g. --trans ja:en) sets both. Requires pip install deep-translator |
--print-line |
Print a one-line text summary of the focal tweet to stdout |
--view |
Automatically open the rendered output file after creation |
--viewer <cmd> |
Specify custom viewer executable/command (e.g., viewnior, firefox, or kitty +icat {}) |
-c <file> |
Load config from a custom path (see Config below) |
Options can be set as persistent defaults in a config file (INI format). Config is loaded in this order - later sources override earlier ones:
~/.config/tw2img/tw2img.conf- user default<script_dir>/tw2img.conf- next to the script, if present-c /path/to/custom.conf- explicit override- Command options / flags always have highest priority
A default config is included as tw2img.conf. To install it:
mkdir -p ~/.config/tw2img
cp tw2img.conf ~/.config/tw2img/tw2img.confSet a default download directory in the config so you don't have to specify it each run:
[tw2img]
output_dir = ~/Pictures/tweetsIf output_dir is set, all PNGs are saved there unless you pass an explicit output path (absolute or with a directory component) on the command line.
Use -c to load an alternate config for a specific run without touching your defaults:
tw2img 2054583770045386950 -c ~/work/tw2img-work.conf --light# @username shorthand - latest tweet
tw2img @NASA --guest
# @username shorthand - Nth most recent tweet (1-20, skips RTs and replies)
tw2img @NASA 5 --guest
# Explicit --user flag (equivalent to @username)
tw2img --user NASA --guest
# Tweet ID
tw2img 2054583770045386950 --guest
# Full URL
tw2img "https://x.com/username/status/123456789" --guest
# Local JSON file (from API)
tw2img tweet.json
# Stdin (pipe JSON)
cat tweet.json | tw2img -By default, saves as <screen_name>-<tweet_id>.png in current directory. Specify a custom filename as the argument after the input (or after the tweet index when using @username):
# Custom output with tweet ID
tw2img 2054583770045386950 --guest my_screenshot.png
# Custom output with @username shorthand
tw2img @NASA my_screenshot.png --guest
# Custom output with @username and tweet index
tw2img @NASA 3 my_screenshot.png --guest
# Open with a specific GUI viewer
tw2img @NASA --guest --view --viewer viewnior
# Render directly inline inside a supported terminal (like kitty)
tw2img @NASA --guest --view --viewer "kitty +icat {}"
# View directly in Firefox (ideal when combined with --save-html)
tw2img 2054583770045386950 --save-html tweet.html --view --viewer firefox
# Print a one-line text summary of the focal tweet to stdout
tw2img 21 --print-line --guest
@biz (Biz Stone) ✔ just setting up my twttr | ↳ 153 ⇅ 4.8K ‟ 302 ♥ 4.3K | Web Client | https://x.com/i/status/21
Basic screenshot with thread (dark mode):
tw2img 2054583770045386950 --guestLatest tweet from a user:
tw2img @NASA --guest5th most recent tweet from a user:
tw2img @NASA 5 --guestAutomatically open the snapshot after rendering:
tw2img @NASA --guest --viewUpload to imgur:
tw2img @NASA --guest --imgurLight theme, focal tweet only:
tw2img 2054583770045386950 --guest --light --no-contextWide screenshot without source:
tw2img 2054583770045386950 --guest --width 800 --no-sourceFull stat numbers:
tw2img 2054583770045386950 --guest --full-statsPrint HTML to stdout (for inspection or debugging):
tw2img 2054583770045386950 --guest --html-onlySave HTML to a file:
tw2img 2054583770045386950 --guest --save-html tweet.htmlSave HTML to a file and load in Firefox:
tw2img --guest @barackobama --full-stats --save-html /tmp/tweet.html && firefox /tmp/tweet.htmlPrint tweet text:
$ tw2img --print-line --guest 22
@noah (noah glass) just setting up my twttr | ↳ 86 ⇅ 3.9K ‟ 167 ♥ 3.4K | Web Client | https://x.com/i/status/22
Print tweet text and translate with --trans:
# Install the translation dependency once
pip install deep-translator
# Auto-detect source language, translate to English
tw2img --print-line --guest 'https://x.com/PEKETTER_TECH/status/2059593901607153975' --trans en
# Explicitly set source -> target (Japanese -> English)
tw2img --print-line --guest 2059593901607153975 --trans ja:en
# Render a translated PNG (auto-detect -> English)
tw2img 2059593901607153975 --guest --trans en
# Translate to French
tw2img @NASA --guest --trans fr
--transtranslates the tweet text (and any quoted tweet) before rendering to PNG or printing with--print-line. UseSOURCE:TARGETto specify both languages, or justTARGETto let Google Translate auto-detect the source. Language codes follow BCP-47 / ISO 639-1 (e.g.en,ja,fr,de,zh-CN).
You can also set a default in tw2img.conf so every run translates automatically:
[tw2img]
trans = ja:enPrint tweet text and translate output (legacy, external tool):
# Requires Translate Shell: https://github.com/soimort/translate-shell
$ tw2img --print-line --guest 'https://nitter.net/PEKETTER_TECH/status/2059593901607153975' | trans -b@PEKETTER_TECH (YODARE @ PEKETTER TECH) Speaking of the search bug in X (Twitter), I don't know when it started, but the "source:" command has been completely disabled. Moreover, if you specify it, you will get the "current time - 7 days worth of results drop" bug 👇 Search Twitter "lang:ja source:twitter_for_iphone" https://x.com/search?q=lang%3Aja%20source%3Atwitter_for_iphone&f=live | ↳ 0 ⇅ 0 “ 0 ♥ 3 🡕 483 | Web App | https://x.com/i/status/2059593901607153975
Save article and auto-load rendered HTML in Firefox:
# BEST METHOD: Save as HTML and open (uses article_viewer from conf, or xdg-open)
# Use the tweet url that contains the article link (or simply just the id)
article2img --guest --save-html out.html --view https://x.com/ARCRaidersGame/status/2054607629738037736
# Simplify with an alias
alias tw-article='article2img --guest --save-html /tmp/out.html --view > /dev/null'
tw-article https://x.com/XDevelopers/status/2041295840325636551
