Syncing Substack drafts with a Chrome extension
Lately I’m trying to write & post often1 on Substack2 — so now I have lots of Substack drafts ranging from to-do-lists & single sentences to polished posts awaiting final review from my editors3.
With all those drafts piling up, I found myself wanting to make sure I’ve got my own backups. I also want to talk to LLMs about my drafts without constantly copy+pasting and juggling tabs. And I want to experiment with draft management: Substack's authoring interface is great, but its “list of drafts” screen is not; I want more control over my drafts’ organization & versioning.
So I built a Chrome extension to solve this problem.
The problem
If you write on Substack, you might have wondered:
What happens to my drafts if something goes horribly wrong?
How can I back up my work-in-progress posts?
Is there an easy way to sync drafts across different tools?
Why can’t I just use full text search to track down which of my thirty “Untitled” posts is the one I’m trying to get back to?
While Substack autosaves your work, having a local backup provides peace of mind and opens up interesting workflow possibilities.
Why a Chrome extension?
While building this, I explored several approaches to backing up Substack content. Here's why a Chrome extension ended up being a good solution:
Unofficial APIs & fighting with CAPTCHAs
Substack doesn't provide an official API.
I’ve done a bunch of work using their unofficial API endpoints, and there are some nice community attempts to document & formalize these for use by authors, readers, and researchers.
On the “consuming published content” side this is all reasonably robust — between unofficial REST API endpoints, RSS feeds, and parsing the headers in their emails to recipients.
But on the “authoring content” side things are a lot murkier, mainly because of authentication:
Substack uses session-based auth with cookies
They employ aggressive CAPTCHA protection against programmatic access
Any non-browser automation requires starting from a browser and handing off session cookies after login
Even browser-based automation will require storing end user passwords, and hoping the CAPTCHA doesn't kick in
Manual exports are clunky
Substack does have a built-in export feature for authors but:
It's manual-only — you need to remember to do it
It takes a little time to process, so you can't just mindlessly take followup steps while you’re thinking of it
Exports come as zip files with HTML content
All of your posts are exported fresh every time
There’s no differentiation whatsoever4 between published vs draft post in the exported file
This does more or less provide “I can get my stuff if I need to”-level peace of mind, but it isn’t ideal for ongoing usage.
Extensions just work
A browser extension makes these problems go away.
We can just leverage the user's existing authenticated session:
fetch('https://thirdbear.substrack.com/api/v1/post_management/drafts');
// we don't even need credentials: 'include'
We can run it on a recurring schedule without needing to remember:
chrome.alarms.create('fetchDrafts', {
periodInMinutes: 60, // Sync our drafts every hour, automatically
});
chrome.alarms.onAlarm.addListener((alarm) => {
if (alarm.name === 'fetchDrafts') {
fetchAndSaveDrafts();
}
});
And we can put the files wherever we want5:
chrome.downloads.download({
url: `data:application/json;charset=utf-8,${encodeURIComponent(draftContent)}`,
filename: `my-substack-drafts/${draftTitle}.json`,
saveAs: false,
});
This approach means:
No authentication headaches — we just use our existing login
Nothing suspicious — we're just a normal browser session
Set-and-forget functionality — it runs in the background as long as Chrome is open6
Drafts land right in our filesystem — so we can hook into the directory for git commits and any other CLI-driven next steps.
Beyond backup: practical uses
This extension also opens the door to some really useful workflows beyond backup:
Search across all drafts: Substack’s interface isn’t great at finding a specific draft based on what’s in it. With local copies, you can just
grep
or use Spotlight.Plug in to AI: use the
llm
CLI to provide your drafts as context for AI chats — great for getting some initial feedback or thinking through next steps!Offline writing: if you’re traveling with unreliable internet, local copies let you just pick up a draft in progress without having remembered to open it up beforehand for web-based offline editing.
Organization & review: having all your drafts locally makes it easier to see your work in progress, find abandoned ideas worth revisiting, and track your writing pipeline.
Technical notes
A few specifics on what happens under the hood —
Fetching the drafts
The actual API calls are totally straightforward — fetch a page of drafts7, repeat until we’re out of pages, and then fetch each draft’s contents8. That’s it!
Downloading them
The downloads are a bit thornier:
We have to put everything somewhere inside the user’s Chrome downloads folder — we’re not allowed to access the rest of the filesystem. But we can put them in any (arbitrarily nested) subfolder, so they’re nicely organized.
If a file already exists with the same name, Chrome doesn’t overwrite it by default — instead it will save a new parallel file every time with one of those (1), (2), (3), … suffixes. To actually overwrite files in-place we need to specify a
{ conflictAction: "overwrite" }
option in the download call.Downloading a bunch of files, even without any user interaction, temporarily pops open the little “Recent Downloads” window in the top right corner of the screen. That will get annoying when it happens every hour. We can’t disable that behavior — except by hiding the downloads shelf altogether. We should be able to hide it while we’re working on the downloads, and then re-show it when we’re done. So far I’ve found this a little finicky — it doesn’t always re-show the download shelf after it wraps up, which is a little annoying.
No matter what, we don’t want our Download History to get cluttered up with all these files — so after saving a file, we should also clean up after ourselves with
downloads.erase
(which removes it from the Download History without deleting the file) so that we’re effectively downloading it invisibly. Here too I haven’t quite gotten it working all the way — sometimes older copies still seem to show up in there, and the latest copies of the downloads always show up in there. On the other hand, the latest copies thing is sort of a nice feature because it provides an obvious and very transparent status indicator, and easy clickable access to the files.
Content format
Each draft is stored in Substack's internal format (a custom JSON structure using Tiptap) — so it’s good for searching content, tracking changes, and using LLMs, but less good for human viewing or direct editing.
If you want to work with the content directly, you'll need to handle the Tiptap JSON structure. A basic draft looks something like:
{
"type": "doc",
"content": [
{
"type": "paragraph",
"content": [
{
"type": "text",
"text": "Lately I’m trying to write & post a lot — so now I have lots of Substack drafts. They range from to-do-lists and single-sentence ideas I want to remember for later, to half-written things in progress, to fully-written posts that just need some final review from my editors"
}
]
}
]
}
This extension focuses on backup and search functionality, but it should be fairly straightforward to convert the files to HTML or other formats. It would also be fun to build a local-only Tiptap front end to edit the files directly.
How to use it
I haven't (yet?) bothered to package this up for installation from the Chrome web store, but the extension is open source and available on GitHub.
To use it, just add the root directory of the codebase as a Chrome extension (chrome://extensions → “load unpacked”) and then open its “Extension options” screen to configure your Substack URL and desired download folder. From then on it’ll just keep your drafts up-to-date in the local folder every hour.
Feel free to install, customize, contribute improvements, and share feedback & ideas!
Sort of prompted by posts from Simon Willison and Jeff Triplett.
Partly because I think Substack is interesting and partly because I like reading a lot of Substacks, but also because the editing interface really works for me — it managed to unseat my previous-gold-standard “typing here and then reading what I typed is easier than anywhere else” platform (Gmail drafts)
Claude and Jacqueline.
As far as I can tell, anyway.
As long as we want to put them somewhere within the Chrome downloads directory.
For me at least Chrome is always open.
/api/v1/post_management/drafts?offset=${offset}&limit=${limit}&order_by=draft_updated_at&order_direction=desc
/api/v1/drafts/${draft.id}