A local web app for managing your X (Twitter) posts. Browse, search, filter, and bulk-delete your tweets, replies, and retweets — all from a single dashboard.
- Fetch from X — Connect your X account via OAuth 2.0 and pull down your full tweet history (posts, replies, and retweets) with live progress and cost tracking
- Bulk import — Upload a previously exported Twitter JSON archive
- Search & filter — Full-text search and date-range filtering across all your posts
- Bulk delete — Delete selected posts or everything matching the current filter, both locally and from X
- Background delete queue — Large deletions are processed in the background with automatic rate-limit handling and resume-on-restart
- Conversation context — See what each reply was responding to
| Layer | Technology |
|---|---|
| Frontend | React 18, Vite |
| Backend | Node.js, Express |
| Database | SQLite (better-sqlite3, WAL mode) |
| X API | twitter-api-v2 (OAuth 2.0 PKCE) |
- Node.js 18+
- An X Developer App with OAuth 2.0 credentials (developer portal)
npm run install-allCreate a .env file in the server/ directory:
X_CLIENT_ID=your_client_id
X_CLIENT_SECRET=your_client_secret
Alternatively, you can enter your credentials through the Settings panel in the app (gear icon).
In the X Developer Portal:
- Set the Callback URL to
http://localhost:3001/auth/callback - Set the Website URL to
https://example.com(or any valid HTTPS URL) - Enable the following OAuth 2.0 scopes:
tweet.read,tweet.write,users.read,offline.access
npm run devThis starts both the Express server (port 3001) and the Vite dev server (port 5173). Open http://localhost:5173 in your browser.
| Command | Description |
|---|---|
npm run dev |
Run server and client concurrently |
npm run dev:server |
Server only (with --watch) |
npm run dev:client |
Client only (Vite dev server) |
npm run build |
Build the client for production |
npm start |
Run the server in production mode |
npm run setup |
Install all dependencies and build |
xtool/
├── client/
│ ├── src/
│ │ ├── App.jsx # Main React component
│ │ ├── App.css # Styling (dark theme)
│ │ └── main.jsx # Entry point
│ ├── index.html
│ └── vite.config.js # Dev proxy to Express server
├── server/
│ ├── index.js # Express routes and fetch logic
│ ├── xClient.js # X API client (auth, fetch, delete)
│ ├── deleteQueue.js # Background delete queue processor
│ ├── db.js # SQLite schema and connection
│ ├── .env # API credentials (not committed)
│ └── data/
│ └── xtool.db # SQLite database (auto-created)
└── package.json # Root scripts (concurrently)
The app uses OAuth 2.0 with PKCE. Click Connect in the header, authorize in the X popup, and you're connected. Tokens are stored in SQLite and automatically refreshed before expiry.
Fetch from X pulls your timeline using the GET /2/users/:id/tweets endpoint, paginating through up to 100 tweets per API request. Progress is tracked in real-time, including an estimated API cost (~$0.005 per request). Fetching clears existing data and replaces it with fresh results.
- Small batches (50 or fewer) are deleted synchronously from both X and the local database.
- Large batches are queued for background processing. The queue handles rate limits automatically (waits for the 15-minute reset window) and resumes where it left off if the server restarts.
- Retweets are handled automatically — if
deleteTweetfails, the app falls back tounretweet.
All data lives in a SQLite database at server/data/xtool.db. It persists across server restarts. The database uses WAL mode for better concurrent read/write performance.
The X API charges per request on the pay-as-you-go plan:
| Operation | Cost per request | Tweets per request |
|---|---|---|
| Posts Read | $0.005 | Up to 100 |
| Posts Delete | $0.005 | 1 |
The Fetch dialog shows a running cost estimate as it works.
MIT