Substack Publisher
Publish markdown posts to Substack as drafts or newsletter articles. Converts markdown to Substack's ProseMirror JSON format.
Requirements
Set these environment variables (add to .env):
SUBSTACK_SID- Yoursubstack.sidsession cookie valueSUBSTACK_SUBDOMAIN- Your Substack subdomain (e.g.,adrianpetcu)SUBSTACK_USER_ID- Your Substack user ID (e.g.,106993810)
Quick Start
python scripts/publish_to_substack.py posts/daily-thoughts/03-context-driven-bug-fixing.md --dry-run
Script Usage
python scripts/publish_to_substack.py <file.md> [options]
Required:
file- Path to the markdown post file
Options:
--title- Override post title (default: extracted from# heading)--subtitle- Override subtitle (default: extracted from## Hooksection)--publish- Publish immediately instead of creating a draft--audience- Post audience:everyone(default) orpaid--dry-run- Output ProseMirror JSON to stdout without making API calls
Workflow
- Select the post file - Choose a markdown post from the repository
- Preview with dry-run - Always run
--dry-runfirst to verify the conversion - Create draft - Run without
--publishto create a draft on Substack - Review in Substack - Open the draft URL and verify formatting
- Publish - Either publish from Substack's editor or re-run with
--publish
Markdown Format Support
The script handles the repository's post template format:
- Title: Extracted from the first
# heading - Subtitle: Extracted from the
## Hooksection content - Body: All remaining sections converted to ProseMirror nodes
Included sections: Key Points, Body Outline, Call to Action, Visual Walkthrough, and any custom sections.
Excluded sections (metadata): Status, Hashtags, Notes, Verdict, LinkedIn Assessment.
Block elements: Paragraphs, headings (H1-H3), bullet lists, ordered lists, code blocks (with language), blockquotes, horizontal rules, images (HTTP URLs only).
Inline marks: bold, italic, code, links.
Special handling: Bold labels on standalone lines (e.g., **Section Name:**) become H3 headings. Local image paths are skipped with a warning.
Examples
Preview conversion:
python scripts/publish_to_substack.py posts/daily-thoughts/03-context-driven-bug-fixing.md --dry-run
Create a draft:
python scripts/publish_to_substack.py posts/daily-thoughts/03-context-driven-bug-fixing.md
Publish with custom title:
python scripts/publish_to_substack.py posts/daily-thoughts/03-context-driven-bug-fixing.md \
--title "How AI Turns Vague Bugs Into Actionable Tickets" --publish
Paid subscribers only:
python scripts/publish_to_substack.py post.md --audience paid
How to Get SUBSTACK_SID
- Log into Substack in your browser
- Open DevTools (F12) → Application → Cookies
- Find
substack.sidcookie for your subdomain - Copy the value and set it:
export SUBSTACK_SID="your-cookie-value"
Alternatively, inspect any network request in DevTools → Headers → Cookie and copy the substack.sid=... value.
Note: This cookie expires periodically. If you get a 401/403 error, refresh the cookie.