Skip to content

How to export Reddit comments to CSV or JSON

Export Reddit comments from any thread to CSV or JSON: fetch the thread, page through comments, rebuild replies with parentId and write both files. Script included.

Piyushh · October 14, 2026 · 5 min read

To export Reddit comments, fetch the thread through an API that returns its comments, keep requesting pages until none are left, then write one row per comment to CSV and the nested tree to JSON. Replies are the tricky part. APIs return comments as a flat list, so you rebuild the conversation from each comment's parentId. Below is a complete Python script that does all four steps, plus the output from running it on a real 186-comment thread on October 14, 2026.

If you only need one thread and don't want to write code, paste the URL into the free Reddit comment exporter. It shows the first 25 comments without an account, and once you sign in you can export the thread to CSV, JSON or PDF.

The fastest way to export Reddit comments

There are three ways to do it, listed roughly from least to most effort:

OptionSetupGood for
Comment exporter toolFree preview; sign in to exportOne thread, now
A hosted API plus the script belowAn API keyMany threads, scheduled jobs, pipelines
PRAW with replace_more(limit=None)Approved Reddit API credentialsPython projects that already have keys

If you have credentials, PRAW works. Its comment docs explain that replace_more loads hidden "load more comments" stubs, and "each replacement requires one network request." Since November 2025, new credentials need Reddit's approval, and Reddit stops accepting new requests on October 31, 2026. Our PRAW vs hosted API comparison covers that route. The other old standby is copying from the browser. Unauthenticated .json scraping stopped working in May 2026 (why).

This guide uses ChatterAPI's GET /v1/reddit/threads/{id}, which returns a post and its comments without a Reddit developer account. ChatterAPI is independent and not affiliated with Reddit.

What an export contains

Every comment comes back with the fields below, and you'll want most of them in your file:

FieldKeep it?Why
idYesPrimary key; replies point at it
parentIdYesThe comment this one replies to, or null for a top-level comment
authorYesEmpty string when the account is deleted
bodyYesComment text; empty when deleted or removed
scoreYesUpvotes minus downvotes as Reddit reports it
createdAtYesISO 8601 UTC
urlYesLink back to the comment
bodyTruncatedCheckTrue when the text is a preview from a fallback source

The thread response also includes two counts worth logging: reportedCommentCount (the comment count Reddit reports for the post) and unloadedReplyStubs (replies Reddit didn't load on this page).

Step 1: fetch the thread

One call returns the post and the first page of comments. The id can be the post id or the full post URL:

curl "https://api.chatterapi.com/v1/reddit/threads/1wdihhj" \
  -H "Authorization: Bearer $CHATTERAPI_KEY"

Here is the trimmed response, a recorded sample from a test key (test keys return one real response per endpoint for free):

{
  "data": {
    "post": {
      "id": "1wdihhj",
      "title": "What CRM should a small business use?",
      "author": "NotedWhisky",
      "subreddit": "CRM",
      "score": 52,
      "numComments": 186
    },
    "comments": [
      {
        "id": "p9636y0",
        "parentId": null,
        "author": "OstrichSome3412",
        "body": "Just a FYI, don’t buy for the company you imagine having in 5 years. Buy for the...",
        "score": 6,
        "createdAt": "2026-09-11T15:02:24.000Z"
      },
      {
        "id": "p963opq",
        "parentId": "p9636y0",
        "author": "NotedWhisky",
        "body": "Okay that’s probably the balance I need tbf. Enough room for revenue to grow...",
        "score": 1,
        "createdAt": "2026-09-11T15:04:37.000Z"
      }
    ],
    "unloadedReplyStubs": 0,
    "reportedCommentCount": 186
  },
  "page": { "nextCursor": null, "hasMore": false },
  "credits": { "charged": 0, "remaining": 719 }
}

The second comment is OP replying to the first, and parentId shows that. On a live key each page costs 3 credits.

Step 2: page through the comments

Each page holds up to about 100 comments. When page.hasMore is true, pass page.nextCursor back as cursor. Cursors are opaque strings like "AQx3Lr9dK2vQ…", so pass them through untouched. Store comments in a dict keyed by id, and a comment that appears on two pages is kept only once.

def fetch_thread(post_id):
    """Return the post and every comment the API loads, across all pages."""
    post, comments, cursor = None, {}, None
    while True:
        params = {"cursor": cursor} if cursor else {}
        r = requests.get(f"{BASE}/reddit/threads/{post_id}", headers=HEADERS, params=params, timeout=60)
        r.raise_for_status()
        body = r.json()
        data = body["data"]
        post = post or data["post"]
        for c in data["comments"]:
            comments[c["id"]] = c  # dedupe across pages
        if not body["page"]["hasMore"]:
            return post, list(comments.values()), data["reportedCommentCount"]
        cursor = body["page"]["nextCursor"]

Step 3: rebuild the tree with parentId

Comments come back as a flat list. To rebuild the reply tree, index them by id and attach each one to its parent. A null parentId marks a top-level comment. The endpoint docs describe parentId as a fullname, but the responses we got carried bare ids like p9636y0. The helper below accepts both and treats a post id (t3_) as top level:

def parent_key(c):
    """Normalise parentId: None or a post id means top level."""
    p = c.get("parentId")
    if not p or p.startswith("t3_"):
        return None
    return p.removeprefix("t1_")


def build_tree(comments):
    by_id = {c["id"]: {**c, "replies": []} for c in comments}
    roots = []
    for c in by_id.values():
        parent = by_id.get(parent_key(c))
        (parent["replies"] if parent else roots).append(c)
    return by_id, roots


def depth(c, by_id):
    d, p = 0, parent_key(c)
    while p in by_id:
        d, p = d + 1, parent_key(by_id[p])
    return d

If a reply's parent wasn't loaded, by_id.get returns None and the reply goes to the top level instead of disappearing. For an export that's the safe default, because you keep every comment and can see where the gap is.

Step 4: write CSV and JSON

CSV gives you one flat row per comment, the shape spreadsheets, pandas and SQL loaders expect. JSON keeps the nesting, so use it when you want to read a conversation or feed one to an LLM.

import csv
import json
import os
import sys
import requests

BASE = "https://api.chatterapi.com/v1"
HEADERS = {"Authorization": f"Bearer {os.environ['CHATTERAPI_KEY']}"}

# fetch_thread, parent_key, build_tree and depth from above go here


def main(post_id):
    post, comments, reported = fetch_thread(post_id)
    by_id, roots = build_tree(comments)

    with open(f"{post_id}.csv", "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["id", "parent_id", "depth", "author", "score", "created_at", "body", "url"])
        for c in sorted(comments, key=lambda c: c["createdAt"]):
            w.writerow([c["id"], parent_key(c) or "", depth(c, by_id), c["author"] or "[deleted]",
                        c["score"], c["createdAt"], c["body"], c["url"]])

    with open(f"{post_id}.json", "w", encoding="utf-8") as f:
        json.dump({"post": post, "comments": roots}, f, ensure_ascii=False, indent=2)

    print(f"{len(comments)} comments exported ({len(roots)} top level); Reddit reports {reported}")


if __name__ == "__main__":
    main(sys.argv[1])

We ran it with python export_thread.py 1wdihhj against the r/CRM thread above. It printed:

101 comments exported (71 top level); Reddit reports 186

The first rows of the CSV:

id,parent_id,depth,author,score,created_at,body,url
p961pv9,,0,Prestigious-Swim69,3,2026-09-11T14:55:54.000Z,"I don't really have a recommendation but you should prioritize how easy the CRM is to keep useful...",https://www.reddit.com/r/CRM/comments/1wdihhj/what_crm_should_a_small_business_use/p961pv9/
p962kyb,,0,[deleted],5,2026-09-11T14:59:43.000Z,,https://www.reddit.com/r/CRM/comments/1wdihhj/what_crm_should_a_small_business_use/p962kyb/

Of the 101 comments, 4 were deleted (empty author and body), and the deepest reply chain went 5 levels down. On a live key the script costs 3 credits per page, so this thread cost 3 credits.

Edge cases

Don't expect the count to match. Reddit said 186 comments and we got 101. Reddit's number can include deleted and removed comments, and Reddit hides deep replies behind "load more" stubs. The endpoint docs say it plainly: "no thread API returns every comment on very large threads." Log len(comments) next to reportedCommentCount so whoever reads the file knows what's in it.

A deleted comment still has structure. It keeps its id and its place in the tree, and its replies still point at it. Keep the row with [deleted] as the author instead of dropping it, or its replies will look orphaned.

Reddit comments are full of emoji and curly quotes, which Excel can mangle. If you see garbled characters, write the CSV with encoding="utf-8-sig" so Excel detects UTF-8.

Comment bodies contain line breaks. Python's csv module quotes those fields correctly, and a CSV hand-built with string joins won't.

To export every thread that mentions a keyword, run GET /v1/reddit/search first and loop the ids through main. Our Reddit search API guide covers the query side. If you only want comments, without the thread around them, comment search is cheaper at 2 credits per page.

For agents, the same call is the reddit_thread tool on ChatterAPI's MCP server at https://api.chatterapi.com/mcp, so Claude or Cursor can pull a thread and read the JSON tree directly.

People export threads for everything from sentiment work to sports trivia. One r/CFB user scraped every game thread comment for a season to see which fanbase swears the most. The script above is the boring part of projects like that, and it should stay boring.

Export your first thread

Pick a thread you care about and run the script, or paste it into the comment exporter first to see the shape. Sign up with Google to get 100 free credits without a card, enough for about 33 thread pages. Plans start at $19 a month for 25,000 credits. For background on Reddit's own API and its limits, see the Reddit API guide.

FAQ

How do I export Reddit comments?
Fetch the thread through an API that returns its comments, keep paging until no pages are left, and write the rows to CSV or JSON. With ChatterAPI that call is GET /v1/reddit/threads/{id}, at 3 credits per page. If you'd rather skip the code, paste the thread URL into the Reddit comment exporter tool and sign in to export.
Can I download all comments from a Reddit thread?
Not always all of them. Reddit hides deep replies behind "load more" stubs, and deleted or removed comments can still count toward the number Reddit shows. In our test export the thread reported 186 comments and the API returned 101, 4 of them deleted. Compare comments.length with reportedCommentCount to see how much you got.
How do I turn a Reddit thread into a CSV?
Flatten the comment list to one row per comment with id, parent_id, depth, author, score, created_at, body and url columns, then write it with Python's csv module. Keep parent_id, because it lets you rebuild who replied to whom in a spreadsheet or database.
Is there a free Reddit comment scraper?
ChatterAPI's Reddit comment exporter at /tools/reddit-comment-exporter shows the first 25 comments of any thread for free, and signed-in users can export CSV, JSON or PDF. If you want to write code, new accounts get 100 free credits, enough for about 33 thread pages. Open-source options built on PRAW need approved Reddit API credentials.

More from the blog

Make your first call on 100 free credits.

Sign up with Google, no card. One key for Reddit and X, over REST or MCP.