DEV Community

Cover image for How to Export Substack Newsletter Posts
veenkes
veenkes

Posted on

How to Export Substack Newsletter Posts

How to Export Substack Newsletter Posts

Substack is now a primary publishing platform, not just a mailing list. If you research a niche, track competitors, or want an archive of a newsletter you already read, it is reasonable to want a structured export: titles, dates, free versus paid, and engagement. Substack does not offer a simple 'download every post as CSV' button for any publication. This guide shows how to export Substack newsletter posts, including publications that run on a custom domain.

You will use the Substack Scraper on Apify. It collects post metadata (title, URL, date, audience, authors, tags, likes, comments, restacks, word count) and can optionally add the plain text of free posts. Paywalled content is never accessed.

By the end of this article you will have a CSV or JSON file on your machine, and a short Python snippet you can rerun or schedule. The same Actor is also callable from JavaScript and from AI agents through the Apify MCP server.

What you need

  • An Apify account. The Free plan is enough to try a small run.
  • A list of inputs for the Actor (URLs, IDs or search terms — details in the steps below).
  • Optional: Python 3 with the apify-client package if you want to automate the run.

No Substack login is required. You only need the public URL of the publication or of a single post.

How to run it in the Apify Console

Step 1: Open the Actor

Open https://apify.com/veenkes/veenkes-substack-posts and click Try for free. A free Apify account is enough to run a first export.

Step 2: Paste the publication URL

Use the newsletter's home page, whether it lives on *.substack.com or a custom domain such as https://www.lennysnewsletter.com. You can also paste individual post URLs.

Step 3: Choose how many posts, and whether you need full text

Set a max posts value for a first run (10 is plenty). Leave full text off if you only need an inventory of titles and engagement; turn it on only for free posts when you need the body.

Step 4: Export

Start the run. When it finishes, download the dataset as CSV or JSON. Filter later by audience (free vs paid) or by date.

The since date and the audience filter are useful for incremental archives. Full text is billed as a separate event, only for free posts you actually retrieve.

How to run it from Python

Install the official client and call the Actor by its Store id veenkes/veenkes-substack-posts. Replace YOUR_APIFY_TOKEN with a token from Apify Console > Integrations. Do not commit the token to a public repository.

pip install apify-client
Enter fullscreen mode Exit fullscreen mode
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("veenkes/veenkes-substack-posts").call(run_input={
    "publications": [
        "https://www.lennysnewsletter.com"
    ],
    "maxPostsPerPublication": 50,
    "includeContent": False
})

# Write every dataset item, then export from the Console or keep going in code.
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
Enter fullscreen mode Exit fullscreen mode

The JavaScript client (apify-client on npm) uses the same Actor id and the same input object. You can also start the run with a plain HTTP POST to the Apify API if you prefer.

Sample output

Here is a shortened example of one dataset record (field values are illustrative of the schema, taken from a real sample run shape):

{
  "publicationUrl": "https://www.lennysnewsletter.com",
  "postId": 224206488,
  "title": "State of the tech job market: late 2026",
  "subtitle": "PMs are winning, engineers are plateauing, designers are hurting, and the biggest AI hiring boom isn’t in software",
  "url": "https://www.lennysnewsletter.com/p/state-of-the-tech-job-market-late",
  "publishedAt": "2026-11-10T15:04:03.497Z",
  "type": "newsletter",
  "audience": "paid",
  "authors": [
    {
      "name": "Lenny Rachitsky",
      "handle": "lenny"
    }
  ],
  "tags": [
    "Career"
  ],
  "section": null,
  "wordCount": 2183,
  "likes": 323,
  "comments": 33,
  "restacks": 30,
  "description": "PMs are winning, engineers are plateauing, designers are hurting, and the biggest AI hiring boom isn’t in software",
  "previewText": "👋 Hey there, I’m Lenny. Each week, I share deeply researched product, growth, and career advice. For more: Lenny’s Jobs | Lenny’s Podcast | Lennybot | How I AI | Become an AI-Nativ…",
  "contentText": null,
  "coverImage": "https://substackcdn.com/image/fetch/$s_!-6sL!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8be609d0-ed10-464c-8b94…",
  "podcastUrl": null,
  "scrapedAt": "2026-11-12T20:04:50.732Z"
}
Enter fullscreen mode Exit fullscreen mode

Once the run has finished, open the dataset in the Console and use Export > CSV if a spreadsheet is your destination. JSON is usually better for code and for language models.

What to use the data for

Writers use this to audit their own archive or to study how often a newsletter hits the paywall. Researchers track topic frequency and engagement over time. Growth teams watch a set of newsletters in a niche and note which posts get restacks and comments. Because the schema is flat, you can load the file into a spreadsheet or a notebook and join it with your own annotations.

Pricing

Pay-per-result pricing starts at $1.50 per 1,000 posts on the Apify Free plan. Adding the full text of a free post is a separate event (from $1.00 per 1,000). There is also a small per-run start fee. Paywalled bodies are never accessed. Always check the live pricing on the Actor page, because plans and unit prices can change. For a first test, keep the input tiny so you can inspect the schema before you scale up.

Scheduling and automations

In the Apify Console you can schedule the Actor (daily or hourly) and leave the same input in place. For incremental jobs, prefer a "since" or "posted after" style filter when the Actor offers one, so you are not paying to re-collect old rows. When a run finishes you can send a webhook, or connect the Actor in Zapier, Make or n8n. AI agents can discover and call veenkes/veenkes-substack-posts through Apify's MCP server using the same input.

Good practices and limits

Only publicly visible metadata (and optional free-post text) is collected. Do not treat this as a way to bypass a paywall. Respect the writer's copyright: an export for research, analysis or personal archiving is a different thing from republishing someone else's issues. The Actor is not affiliated with Substack.

If a run returns fewer rows than you expected, check the input caps, language or country filters, and the Actor log. A zero-row run is usually a filter or an unavailable source, not a silent crash — read the log before you increase limits.

Next step

Open the Actor and run a small job: Substack Scraper: Export Newsletter Posts.

If you later want this on a schedule, save the input that worked, add a date filter where it applies, and export CSV automatically from the dataset or from the API snippet above.

Top comments (0)