How to Search Tweets with twitter-api-v2 in Node.js (2026)
Searching Tweets with twitter-api-v2
twitter-api-v2 is the most popular Node.js/TypeScript client for the X (Twitter) API, and searching recent tweets is one of the first things most developers reach for. It's also where people hit the most friction — the method returns a *paginator*, not a plain array, so accessing the actual tweets (tweets.data.data) and paging through results trips up newcomers.
This guide walks through searching tweets with twitter-api-v2 end to end: authenticating, calling client.v2.search(), reading the results correctly, paginating, the query operators worth knowing, and the rate-limit/cost realities. At the end we look at a simpler alternative for when the official API's setup and pricing are more than your project needs.
Installing and Authenticating
npm install twitter-api-v2
Recent tweet search is app-only, so all you need is a Bearer token from the X developer portal — no user OAuth flow:
import { TwitterApi } from 'twitter-api-v2'; // App-only client from a Bearer token const client = new TwitterApi(process.env.X_BEARER_TOKEN); // Use the read-only sub-client for search/lookups const roClient = client.readOnly;
Two things to know up front:
Your First Search: client.v2.search()
const results = await roClient.v2.search('javascript lang:en -is:retweet', { 'tweet.fields': ['created_at', 'public_metrics', 'author_id'], max_results: 100, // 10–100 per page });
The first argument is your query string (operators covered below). The options object mirrors the API's query params — tweet.fields, user.fields, expansions, start_time, end_time, and max_results (capped at 100 per request).
Reading the Results: the tweets.data.data Gotcha
client.v2.search() does not return an array of tweets. It returns a TweetSearchRecentV2Paginator. There are three correct ways to read it, and mixing them up is the single most common source of confusion:
// 1. Recommended — .tweets is every tweet fetched so far, as a flat array for (const tweet of results.tweets) { console.log(tweet.id, tweet.text); } // 2. The RAW current-page payload — this is where "tweets.data.data" comes from console.log(results.data.data); // TweetV2[] for the current page console.log(results.data.meta); // { result_count, newest_id, next_token, ... } // 3. Async iterator — automatically pages through results (mind rate limits) for await (const tweet of results) { console.log(tweet.text); }
Why results.data.data — the double .data? The paginator's .data property is the *raw API response*, whose shape is { data: Tweet[], meta: {...}, includes: {...} }. So .data.data is the tweet array inside that response. Prefer results.tweets in application code; reach for results.data.data only when you need the raw page exactly as the API returned it.
Guard against empty results. When a search matches nothing, result_count is 0 and results.data.data can be undefined:
if (!results.meta.result_count) { console.log('No tweets matched.'); } else { for (const tweet of results.tweets) console.log(tweet.text); }
Pagination: Getting More Than One Page
Each request returns at most 100 tweets. The paginator handles the rest for you:
// Fetch one more page and merge it into results.tweets await results.fetchNext(); // Keep fetching until you have ~1000 tweets (or the pages run out) await results.fetchLast(1000); console.log('Collected ' + results.tweets.length + ' tweets'); console.log('Exhausted?', results.done);
The async iterator (for await ... of results) pages automatically — convenient, but it will happily burn through your rate limit on a broad query, so cap it yourself.
Search Query Operators Worth Knowing
The query string is where most of the power lives:
Combine them freely: from:vitalikbuterin (ethereum OR rollup) -is:retweet has:links.
Rate Limits and the Cost Problem
This is where teams get stuck. Recent search on the X API v2 is rate-limited per 15-minute window *and* capped by a monthly tweet-pull quota tied to your plan. In practice:
429 responses and building backoff, queueing, and quota tracking around them.For a detailed breakdown see our guides on X API pricing and rate limits.
Common Errors and Fixes
401 Unauthorized — bad or missing Bearer token, or your app lacks read access. Regenerate the token and confirm the project's permissions.403 Forbidden — your access tier can't use this endpoint (recent search needs Basic+, full-archive needs Pro/Academic).429 Too Many Requests — rate-limited. Read the x-rate-limit-reset header and back off with jitter.results.data.data is undefined — the search matched nothing; check results.meta.result_count before indexing.A Simpler Alternative: XCROP
If the developer-app setup, the 7-day window, and the $200/month floor are more than your project needs, XCROP exposes the same tweet-search capability behind a single API key — no OAuth app, no Bearer-token juggling, and credit-based pricing that runs up to 50% cheaper than the official API.
curl -X POST https://xcrop.io/api/v2/search \ -H "Authorization: Bearer xc_live_..." \ -H "Content-Type: application/json" \ -d '{"query": "javascript lang:en", "count": 100, "sort": "latest"}'
The response is a plain array of tweets — no paginator, no .data.data to unwrap:
const res = await fetch('https://xcrop.io/api/v2/search', { method: 'POST', headers: { 'Authorization': 'Bearer ' + process.env.XCROP_API_KEY, 'Content-Type': 'application/json', }, body: JSON.stringify({ query: 'javascript lang:en', count: 100, sort: 'latest' }), }); const { data } = await res.json(); for (const tweet of data) console.log(tweet.text);
You can sort by latest, popular, or engagement, filter by min_likes / min_retweets / lang, and request up to 1,000 results in a single call — see every parameter in the interactive builder.
Which Should You Use?
If you're building a product deeply tied to the X platform, the official twitter-api-v2 library is the right tool. If you just need clean tweet data — for a dashboard, a bot, research, or a side project — without wrestling paginators, OAuth apps, and a $200/month minimum, XCROP gets you there in one curl.
One API for X/Twitter data — profiles, tweets, followers, search and real-time streams. Start free with 5,000 credits/month, no card required.