X has no button that exports a follower list. Your own data archive only lists follower IDs, and it takes a day or more to arrive. Browser extensions that scroll the follower page for you work for a few hundred accounts and then hit X's limits. This guide does it with a short script instead: it pages through the follower list of any public account and writes one CSV row per follower, with handle, name, bio, location, follower and following counts, verification and join date.

It uses TwittrAPI, a third-party API for public X data. You need an API key from us, but no X developer account and no X login.

Step 1: Get an API key

Create a key on /start; new accounts get $0.10 of test credit, enough for several thousand followers. Store it in an environment variable:

export TWITTRAPI_API_KEY="your-key"

Each page of followers is one billed call. See the cost table further down before you export an account with millions of followers.

Step 2: Request the first page of followers

The endpoint is GET /user/{userId}/followers. Pass -1 as the ID and the handle as username, or look up the numeric ID first with GET /user/by/username/{username} if you will call it many times. Add format=simple for flat user objects.

curl -G 'https://api.twittrapi.com/user/-1/followers' \
  -H "X-API-KEY: $TWITTRAPI_API_KEY" \
  --data-urlencode 'username=nasa' \
  --data-urlencode 'count=100' \
  --data-urlencode 'format=simple'

The response is {"data": [users], "nextCursor": "...", "hasNextPage": true}. Each user has id, username, name, description, location, url, createdAt, verified, verifiedType, protected, avatarUrl and counts with followers, following, tweets, likes, media and listed (user fields).

Followers come newest first: the accounts that followed most recently are on page one.

Step 3: Page through the whole list and write the CSV

This Python script follows nextCursor until hasNextPage is false and writes each page to the file as it arrives, so a stopped run still leaves you with a usable file.

Paging through followers with a cursor

Call 1

GET /user/{userId}/followers

→ ~50–70 followers

nextCursor: "A1"

Call 2

?cursor=A1

→ ~50–70 followers

nextCursor: "B2"

Call n

?cursor=…

→ last followers

hasNextPage: false

Every page is one call, billed at $0.065 to $0.09 per 1,000 calls. X decides how many followers a page holds (about 50–70), so loop until hasNextPage is false instead of counting.
import csv
import os
import sys
 
import requests
 
API = "https://api.twittrapi.com"
HEADERS = {"X-API-KEY": os.environ["TWITTRAPI_API_KEY"]}
FIELDS = ["id", "username", "name", "description", "location", "url", "followers", "following",
          "tweets", "verified", "verifiedType", "protected", "createdAt"]
 
 
def export_followers(handle: str, path: str, max_pages: int = 1000) -> int:
    cursor, rows, pages = None, 0, 0
    with open(path, "w", newline="", encoding="utf-8") as f:
        w = csv.DictWriter(f, fieldnames=FIELDS)
        w.writeheader()
        while pages < max_pages:
            params = {"username": handle, "count": 100, "format": "simple"}
            if cursor:
                params["cursor"] = cursor
            r = requests.get(f"{API}/user/-1/followers", headers=HEADERS, params=params, timeout=60)
            r.raise_for_status()
            page = r.json()
            pages += 1
            for u in page["data"]:
                w.writerow({
                    **{k: u[k] for k in ["id", "username", "name", "description", "location", "url",
                                         "verified", "verifiedType", "protected", "createdAt"]},
                    "followers": u["counts"]["followers"],
                    "following": u["counts"]["following"],
                    "tweets": u["counts"]["tweets"],
                })
                rows += 1
            f.flush()
            print(f"page {pages}: {rows} followers", file=sys.stderr)
            if not page["hasNextPage"]:
                break
            cursor = page["nextCursor"]
    return rows
 
 
if __name__ == "__main__":
    handle = sys.argv[1]
    export_followers(handle, f"{handle}_followers.csv")

Run it with python export_followers.py nasa. For the accounts someone follows rather than their followers, change the path to GET /user/{userId}/followings. For verified followers only, use GET /user/{userId}/verifiedFollowers.

With the Python SDK

The twittrapi package retries 429 and 5xx responses for you, which matters on long exports:

from twittrapi import TwittrAPI
 
client = TwittrAPI(response_format="simple", max_retries=5)
 
for page in client.paginate(client.users.followers, username="nasa", count=100, max_pages=None):
    for u in page["data"]:
        print(u["username"], u["counts"]["followers"])

max_pages=None removes the default limit of 10 pages. Write the rows with the same csv.DictWriter code as above.

Step 4: Resume a long export

A list of a million followers is thousands of calls and takes a while. Save the cursor after every page and pass it back if the script stops:

import json
from pathlib import Path
 
state = Path("nasa_cursor.json")
cursor = json.loads(state.read_text())["cursor"] if state.exists() else None
 
for page in client.paginate(client.users.followers, username="nasa", count=100,
                            max_pages=None, cursor=cursor):
    # ... write the rows (open the CSV in append mode "a" when resuming) ...
    if page["nextCursor"]:
        state.write_text(json.dumps({"cursor": page["nextCursor"]}))

Cursors are X's own and expire after a while, so resume within hours, not weeks. Pagination is sequential: page 2 needs the cursor from page 1, so one export runs at about one page per second however high your rate limit is. To export several accounts, run them in parallel.

Node.js version

npm install twittrapi
import { createWriteStream } from "node:fs";
import { TwittrAPI } from "twittrapi";
 
const client = new TwittrAPI({ format: "simple" }); // key from TWITTRAPI_API_KEY
const cols = ["id", "username", "name", "description", "location", "followers", "following", "verified", "createdAt"];
const cell = (v) => `"${String(v ?? "").replace(/"/g, '""')}"`;
 
const out = createWriteStream("nasa_followers.csv");
out.write(cols.join(",") + "\n");
 
for await (const page of client.paginate(
  client.users.followers,
  { username: "nasa", count: 100 },
  { maxPages: Infinity },
)) {
  for (const u of page.data.data) {
    const row = { ...u, followers: u.counts.followers, following: u.counts.following };
    out.write(cols.map((c) => cell(row[c])).join(",") + "\n");
  }
}
out.end();

Every value is quoted and inner quotes are doubled, which is what CSV readers expect for bios that contain commas, quotes or line breaks.

What it costs

X decides how many users a page holds. For follower lists it is usually around 50 to 70 per page, sometimes more with count=100. At 50 to 70 per page:

FollowersCalls (approx.)Cost on Standard ($0.09 per 1,000 calls)
10,000140 to 200under $0.02
100,0001,400 to 2,000about $0.13 to $0.18
1,000,00014,000 to 20,000about $1.30 to $1.80

Calls that fail on our side are refunded (billing). For a quick follower count without exporting, one call to GET /user/by/username/{username} returns counts.followers.

Limits to know about

  • Protected accounts: the follower list of a private account is not public, so you get nothing back.
  • The total will not match exactly. Suspended and deactivated accounts drop out of the list, and the count on the profile is X's own number. Expect a small difference.
  • Very large accounts: X may end the list before the profile count is reached. If you need complete coverage of a huge account, check the row count against counts.followers and contact support before you start.
  • Personal data: a follower list is personal data under the GDPR and similar laws. Have a reason to collect it, keep only the columns you need and do not use it for spam. Our acceptable use policy applies.

Frequently asked questions

Can you export Twitter followers to CSV for free?

X itself has no CSV export. Your X data archive is free but only contains follower IDs, as JSON. Browser extensions have free tiers of a few hundred accounts. With TwittrAPI the test credit for new accounts covers several thousand followers; after that a list of 10,000 costs a few cents.

Can I export the followers of an account that is not mine?

Yes, if the account is public. The follower list is visible to anyone on X, and the API reads the same list. Private (protected) accounts return no followers.

How long does an export take?

About one page of 50 to 70 followers per second, because each page needs the cursor of the previous one. 10,000 followers take three to four minutes; a million take several hours. Several accounts can run in parallel.

Which columns can the CSV have?

Every field of the user object: ID, handle, display name, bio, location, website, join date, avatar and banner URLs, verification, protected flag, and counts of followers, following, tweets, likes, media and lists.

How do I export the accounts someone follows?

Use the followings endpoint instead of followers. The script stays the same apart from the path.

Sources

TwittrAPI is not affiliated with X Corp. It reads only public data.