Free, unlimited export of X (Twitter) posts, followers, and following to CSV, JSON, XLSX, or AI-friendly TXT.
A Chrome extension — no servers or subscriptions; your exported data stays local.
Features · Installation · How It Works · Export Output · Configuration · Languages · Project Structure · License
The v1.6.4 tag matches the package released through the Chrome Web Store.
The main branch now reports version 1.6.5, packaged on September 6, 2026,
with verified date-range capture, completion, partial downloads and Resume fixes.
This package is separate from the 1.6.4 Store release; Store publication is not implied.
Unlike the withdrawn 1.5.9, this build declares pbs.twimg.com only in
optional_host_permissions. When you first select Embed photo previews,
XPorter explains why the access is needed before Chrome asks for permission.
Declining keeps every export working with photo links. Each download checks the
grant once and falls back to the same links-only workbook when access is missing.
Warning
XPorter 1.5.9 was withdrawn and is not recommended. Its update added a
required pbs.twimg.com host permission for an optional XLSX photo-embedding
feature. Chrome could therefore disable the extension until users reviewed
the new access. The release also introduced experimental Bookmarks and
profile-feed paths that proved unsuitable for dependable large exports:
real-world runs could hit X limits or exhaust the available timeline after
only hundreds of rows. Do not use 1.5.9 when a large or complete export
matters.
See the XPorter 1.5.9 withdrawn release notes for the complete change list, incident explanation, and safer permission design.
- Full engagement metrics — views, likes, retweets, replies, quotes, bookmarks
- Passive seen-post dataset — stores one local row per non-reply post already loaded while you browse X, with first/latest metrics and no extra API requests
- Multiple export modes — posts, personal bookmarks, followers, following, and verified followers
- CSV, JSON, XLSX, and TXT output — download the AI-friendly TXT or copy it straight to your clipboard
- Two XLSX photo modes — keep links for the smallest file, or add bounded previews to a separate Media sheet
- Date range filtering — export posts from an inclusive calendar-date window in your browser’s timezone. For today, the cutoff is the moment you start; Resume keeps that cutoff. Keep the search tab open. Search uses extra boundary days and filters rows to the exact interval. A stalled search preserves partial results and offers Resume; it does not claim completeness from date coverage. X search availability still limits which posts can be returned. An ending without explicit confirmation remains unconfirmed, with partial rows available to download. Date exports saved by older versions must be downloaded and restarted; Resume never silently changes their date semantics.
- Pause and resume — stop mid-export and continue later with zero data loss
- Smart rate limiting — six Export Speed modes plus live quota-aware pauses and retries
- 100% local processing — everything happens in your browser; your exported data is never transmitted anywhere
- Dark and light themes — glassmorphism UI with a one-click toggle
- 14 languages — auto-detects Chrome's UI language on first launch
- Dynamic API discovery — extracts fresh GraphQL query IDs from X's JS bundles at runtime; gracefully falls back to hardcoded IDs
- Crash-resilient — export progress is persisted to Chrome storage and survives browser restarts
- Zero dependencies — no npm packages, no build step, pure vanilla JavaScript
git clone https://github.com/Lemelson/xporter-extension.git- Open
chrome://extensions/in Chrome - Enable Developer Mode (top-right toggle)
- Click Load unpacked and select the cloned
xporter-extension/directory - Navigate to x.com and log in
- Click the XPorter icon in the toolbar
XPorter leverages your existing authenticated X session to access X's internal GraphQL API. No API keys, no OAuth flow — it piggybacks on the cookies you already have.
Popup UI ──▶ Service Worker ──▶ X APIs ──▶ CSV / JSON / XLSX / TXT File
│
Chrome Storage
(incremental saves)
│
Content Script ── detects username from active tab
└─ records posts already loaded by X into local IndexedDB
Export flow:
- Content script detects the currently viewed profile from the X tab URL
- Popup collects the target username and export settings
- Service worker resolves the username to a user ID via
UserByScreenName, then fetches the selected data type in paginated batches - Items are saved incrementally to Chrome local storage (batches of 50)
- On completion (or manual download), items are compiled locally into CSV, JSON, XLSX, or posts-only TXT; large exports download as safe, numbered parts, and XLSX previews show separate photo and workbook-building progress
While you browse X, the page hook also extracts non-reply posts from timeline responses X has already loaded. They are deduplicated by post ID in a local IndexedDB database; repeat sightings update metrics and exposure count instead of adding rows. The Settings tab can export this dataset as CSV/JSON or clear it. Collection is capped at the 50,000 most recently seen unique posts.
Endpoint discovery:
X periodically rotates its GraphQL queryId values. XPorter handles this by:
- Fetching the X main page HTML
- Scanning linked JS bundles for
queryId+operationNamepatterns - Caching discovered IDs for 24 hours (stale IDs self-heal on failure)
- Falling back to hardcoded IDs if discovery fails
- Automatically re-discovering on
STALE_QUERY_ID(HTTP 400) errors
Rate limiting:
- Five named speed presets target roughly 2 / 3 / 4 / 7 / 12 seconds between requests; Standard (~4 s) is the default
- A Custom mode uses the exact request delay you enter; decimal seconds accept either
.or, - Optional Scheduled breaks are configured independently for Posts & Bookmarks and for User Lists; fractional minutes accept either
.or, - Valid
x-rate-limit-*headers always take priority so XPorter stops at the live quota instead of overrunning it - Missing headers use conservative mode-specific fallback delays; 429s and network timeouts retry automatically
- Stale GraphQL query IDs trigger live capture or endpoint re-discovery before the export fails
Post exports include:
| Field | Description |
|---|---|
id |
Tweet ID |
text |
Full text (including long-form notes) |
tweet_url |
Direct link |
language |
ISO language code |
type |
tweet · retweet · reply · quote |
author_name |
Display name |
author_username |
Handle (without @) |
view_count |
Views |
bookmark_count |
Bookmarks |
favorite_count |
Likes |
retweet_count |
Retweets |
reply_count |
Replies |
quote_count |
Quote tweets |
created_at |
Timestamp |
source |
Posting client |
hashtags |
Comma-separated |
urls |
Expanded URLs, comma-separated |
media_type |
photo · video · animated_gif |
media_urls |
Direct media URLs (highest quality) |
media_alt_texts |
Author-written media descriptions, when present |
article_title / article_url / article_text |
X Article metadata and available plain text |
User-list exports include:
| Field | Description |
|---|---|
id |
User ID |
name |
Display name |
username |
Handle without @ |
bio |
Profile description |
location |
Profile location |
url |
Profile website |
followers_count |
Follower count |
following_count |
Following count |
tweet_count |
Post count |
listed_count |
List count |
verified |
Verification status |
protected |
Protected/private status |
created_at |
Account creation timestamp |
profile_image_url |
Profile image URL |
profile_url |
Direct X profile URL |
All settings are persisted in Chrome storage and reused across popup sessions.
| Setting | Default | Description |
|---|---|---|
| Original posts | On | Export standalone posts written by the profile |
| Quotes | On | Export posts that add the author's comment above another post |
| Replies | On | Export the author's replies, including replies to other accounts; mixed selections use X's combined posts-and-replies feed and keep only the selected row types |
| Reposts | On | Export posts reshared without an added comment |
| Articles | On | Export X long-form Articles and the text X returns |
| Photos in Excel — Posts / Bookmarks | Keep links / Keep links | Store each mode independently: the recommended Links choice keeps source URLs only, while the other choice adds bounded previews to a separate Media sheet |
| Selection UI | — | Post types, XLSX photo modes, and stopped/resumable states use official Tabler outline SVGs while keeping native keyboard-accessible controls |
| Export mode | Posts | Data type to export: posts, personal bookmarks, followers, following, or verified followers |
| Output format | CSV | File format: CSV, JSON, XLSX, or AI-friendly TXT for post-shaped exports |
| Quantity limit | 500 | Maximum posts or users per export (0 = unlimited); changing it retargets an ordinary active export |
| Export Speed | Standard | Turbo, Fast, Standard, Careful, Turtle, or Custom request pacing |
| Custom pacing | 5 seconds | Exact delay between requests; seconds may be fractional with . or , |
| Scheduled breaks | Off | Optional independent break after every N requests; separate controls for Posts & Bookmarks and User Lists |
| Include “About this Account” details | Off | Add public account location, Premium date, connection source, affiliated account, and username-change history to user-list exports; requires up to one extra X request per user |
| About Details Speed | Standard / 5 at once | Control how many “About this Account” requests run concurrently; higher values are faster but more likely to hit X rate limits or a temporary restriction; Custom accepts 1–50 |
| About rate-limit retries | 5 / one minute apart | Retry rejected “About this Account” requests; a successful request resets the counter, and the configurable range is 1–1,440 retries |
| Auto-clear old exports | On / 4 hours | Removes old downloadable payloads while keeping history metadata |
| Localize column titles | On | Translate CSV/XLSX headers; JSON keys always remain English |
Preset quantity options: 100, 500, 1,000, unlimited, or a custom value.
The UI auto-detects your Chrome language on first launch and can be changed at any time via the header dropdown.
| Language | Code | Language | Code |
|---|---|---|---|
| English | en |
Français | fr |
| Español | es |
Deutsch | de |
| Português | pt |
日本語 | ja |
| हिन्दी | hi |
한국어 | ko |
| 中文 | zh |
Türkçe | tr |
| Русский | ru |
Bahasa Indonesia | id |
| العربية | ar |
Italiano | it |
xporter/
├── manifest.json # Manifest V3 configuration
├── agent.md / CLAUDE.md # Developer & AI context docs
├── background/
│ ├── service-worker.js # Export engine, message router, state machine
│ ├── downloads.js # File serialization + Chrome download handoff
│ └── uninstall-feedback.js # Anonymous uninstall-summary URL
├── content/
│ ├── feed-parser.js # Extracts compact non-reply post rows from page responses
│ ├── content.js # Username detection from the active X tab
│ └── interceptor.js # Page-context hook for GraphQL IDs and seen-post capture
├── popup/ # Compact popup UI
│ ├── popup.html/.css/.js # Markup, glassmorphism styles (dark + light), logic
│ ├── theme-init.js/theme.js# Theme bootstrap (anti-FOUC) + toggle
│ ├── i18n.js # In-app translation engine
│ ├── rate-prompt.js/.css # "Rate XPorter" prompt
│ ├── history.js # Export-history UI
│ ├── seen-posts.js # Passive seen-post dataset UI
│ ├── ladybug.js # Easter-egg ladybug on the About tab
│ └── locales/*.json # UI strings for 14 languages (en = fallback)
├── utils/
│ ├── api.js # X GraphQL client, endpoint discovery
│ ├── api-parsers.js # Pure X response parsers
│ ├── api-features.js # GraphQL feature-flag constants
│ ├── config.js # Tunable constants + logger
│ ├── rateLimit.js # Batch rate limiter with cooldowns
│ ├── csv.js # CSV / XLSX generation (JSON is built in the worker)
│ ├── columns-i18n.js # Localized CSV/XLSX column headers
│ ├── storage.js # Chrome storage abstraction + settings
│ ├── post-database.js # Deduplicated seen-post IndexedDB store
│ ├── usage-tracker.js # Anonymous local usage counters (opens, active time)
│ └── shared.js # Shared popup/UI helpers
├── _locales/ # Chrome Web Store metadata translations
├── icons/ # icon16/48/128.png + bolt16/48/128.png (toolbar action icons)
├── docs/ # GitHub Pages site (landing, feedback, privacy policy)
└── scripts/ # Dev/debug scripts + CWS packaging (not shipped)
- Manifest V3 — service workers instead of persistent background pages
- No bundler — load directly from source; no webpack, no Vite
- No npm dependencies — the entire extension is vanilla JS
- Incremental persistence — export items are saved in batches of 50 to prevent data loss on service worker termination
- BOM-prefixed CSV — ensures correct Unicode rendering in Excel
- All exported data stays local — your X data never leaves your browser
- The seen-post dataset is stored only in local IndexedDB and can be exported or cleared from Settings
- No third-party analytics, advertising, or tracking SDKs
- No extension-owned backend; normal export traffic goes only to X.com
- Authentication uses your existing X session cookies — XPorter never stores or transmits credentials
- One exception: when you uninstall XPorter, an anonymous usage summary (no X data, no usernames, nothing that identifies you) is sent once to help improve the extension — see the live privacy policy for exactly what it contains
Contributions are welcome. Please open an issue for bugs or feature requests, or submit a pull request directly.
The extension has no build step or npm dependency installation. After changing runtime code or documentation, run the canonical deterministic gate from the repository root:
node scripts/test-all.js
git diff --checkOpening generated XLSX files in LibreOffice is checked separately with node scripts/test-xlsx-libreoffice.js. This requires LibreOffice and fails explicitly if it is missing or cannot complete the conversion. Release packaging runs both checks. CI tests four timezones and has a separate LibreOffice compatibility job. See Testing.
For focused iteration, the individual scripts/test-*.js files remain directly runnable; test-all.js is the required complete gate and the source of truth for their order.
For a real unpacked-Chromium check, run node scripts/test-extension-smoke.mjs from a normal Terminal session or an approved unsandboxed environment. Do not launch it inside CODEX_SANDBOX: macOS can abort GUI-backed Chromium during LaunchServices registration, and the script intentionally fails fast there.
Build a Chrome Web Store archive only with:
scripts/package.shThe packager reads the version from manifest.json, runs the deterministic gates, and writes an allowlist-only ZIP beside the repository.
Passing local tests proves repository contracts such as syntax, parsers, persistence, pacing, localization structure, and file generation. It does not prove that X's current private endpoints, query IDs, cookies, feature flags, or live response shapes still work. That requires a separate authenticated live-X verification and should be reported independently from deterministic results.
This project is not affiliated with, endorsed by, or connected to X Corp. It is an independent tool that uses your own authenticated browser session to export publicly available data. Use in accordance with applicable terms of service.
Licensed under the MIT License with Commons Clause.
You are free to use, modify, and redistribute this software at no cost. You may not sell it, offer it as a paid service, or publish paid derivatives on browser extension marketplaces.
See LICENSE for full terms.
Built by Lemelson