idanbeck/claude-skills

substack-skill

Create, edit, publish, schedule, and manage Substack posts. Use when the user asks to draft or publish a Substack post, list/edit their Substack drafts, schedule a post, or upload images to Substack. Supports multiple accounts/publications.

First seen Jul 5, 2026

Installation

$ npx skills add idanbeck/claude-skills --skill substack-skill

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from idanbeck/claude-skills · top by installs.

npx skills add idanbeck/claude-skills

Browse all from idanbeck/claude-skills

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 13
Default branch main
Open issues 0
Status Active

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsBash, Read
Declared agents claude-code

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 6,621 B
  • docs SUMMARY.md 262 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 8 installs

SKILL.md

Substack Skill - Create, Edit, Publish, and Manage Posts

Manage Substack publications from the CLI via Substack's internal JSON API using your browser session cookie. Write posts in plain markdown — the skill converts to Substack's ProseMirror editor format (and back when fetching drafts).

CRITICAL: Publishing Confirmation Required

Before running publish or schedule, Claude MUST get explicit user confirmation:

  1. Show the user the draft title + a summary of the body.
  2. State clearly whether it will email all subscribers (--send-email) or

publish to web only (--no-email). The email blast cannot be unsent.

  1. Wait for an explicit yes.

Always confirm first. No exceptions. create-draft / update-draft are safe (drafts are private) and do not require confirmation.

First-Time Setup (One-Time)

  1. Log in to Substack in your browser.
  2. Open DevTools → Application → Cookies → https://substack.com and copy the

value of the substack.sid cookie (long string starting with s%3A).

  1. Run setup:
python3 ~/.claude/skills/substack-skill/substack_skill.py setup

It prompts for an account label, your publication URL (e.g. https://yourname.substack.com or your custom domain), and the cookie, then verifies auth and saves config.json. Sessions last ~3 months; when calls start returning auth_failed, grab a fresh cookie and re-run setup with the same label.

Multi-Account Convention

config.json (gitignored) mirrors notion-skill/zergscholar:

{
  "default_account": "personal",
  "accounts": {
    "personal": {
      "publication_url": "https://yourname.substack.com",
      "cookie": "substack.sid=s%3A...",
      "user_id": 123456,
      "user_name": "Your Name"
    },
    "company": { "publication_url": "https://blog.company.com", "cookie": "..." }
  }
}

Every command takes -a ACCOUNT to override the default.

Commands

Account

# Verify auth; show user, publications, subscriber count
python3 ~/.claude/skills/substack-skill/substack_skill.py whoami [-a ACCOUNT]

# List sections (for --section-id)
python3 ~/.claude/skills/substack-skill/substack_skill.py sections [-a ACCOUNT]

Reading

# List posts: --status published (default) | draft | scheduled
python3 ~/.claude/skills/substack-skill/substack_skill.py list-posts --status draft --limit 15

# Fetch a draft with its body converted back to markdown (--raw for full JSON)
python3 ~/.claude/skills/substack-skill/substack_skill.py get-draft DRAFT_ID

# Fetch a published post by slug
python3 ~/.claude/skills/substack-skill/substack_skill.py get-post my-post-slug

Writing

# Create a draft from a markdown file (local images auto-upload to Substack CDN)
python3 ~/.claude/skills/substack-skill/substack_skill.py create-draft \
  --title "Post Title" --subtitle "Optional subtitle" \
  --body-md ~/path/to/post.md [--audience everyone|only_paid|only_free|founding] [--section-id N]

# Or with inline markdown
python3 ~/.claude/skills/substack-skill/substack_skill.py create-draft \
  --title "Quick note" --body-text "Some **markdown** here."

# Update a draft (any subset of fields; body replace is full-overwrite)
python3 ~/.claude/skills/substack-skill/substack_skill.py update-draft DRAFT_ID \
  [--title ...] [--subtitle ...] [--body-md file.md] [--audience ...]

# Upload an image by itself, get the CDN URL
python3 ~/.claude/skills/substack-skill/substack_skill.py upload-image ~/pic.png

Publishing (confirm with user first)

# Publish AND email every subscriber (irreversible email blast)
python3 ~/.claude/skills/substack-skill/substack_skill.py publish DRAFT_ID --send-email [--share]

# Publish to web only, no email
python3 ~/.claude/skills/substack-skill/substack_skill.py publish DRAFT_ID --no-email

# Schedule / unschedule
python3 ~/.claude/skills/substack-skill/substack_skill.py schedule DRAFT_ID --at "2026-07-08T09:00:00-07:00"
python3 ~/.claude/skills/substack-skill/substack_skill.py unschedule DRAFT_ID

# Delete a draft (requires --force)
python3 ~/.claude/skills/substack-skill/substack_skill.py delete-draft DRAFT_ID --force

Examples

# Draft a post from a vault thought piece, review it, then publish with email
python3 ~/.claude/skills/substack-skill/substack_skill.py create-draft \
  --title "Nobody Reads Code Anymore" --body-md "$VAULT/Writing/nobody-reads-code.md"
python3 ~/.claude/skills/substack-skill/substack_skill.py get-draft 161803398
# ...user confirms...
python3 ~/.claude/skills/substack-skill/substack_skill.py publish 161803398 --send-email

Markdown Support

Headings, paragraphs, bold, italic, code, strike, links, bullet/ordered lists (one nesting level), blockquotes, fenced code blocks (with language), horizontal rules, and images. Standalone-line images become Substack image blocks (local file paths are uploaded automatically); inline images degrade to links. Not supported by Substack's format: tables, LaTeX — multi-paragraph list items are not supported by this converter.

Output

All commands print JSON to stdout and exit non-zero on error ({"error": code, "message": ...}; exit 2 = missing config).

Requirements

  • Python 3.9+ (stdlib only — no pip installs)

API Limitations

  • Unofficial API. Substack has no official post-management API; this uses the

same internal endpoints as their web editor (as does the python-substack ecosystem). Endpoints can change without notice; the official "Developer API" is profile-lookup only and not a substitute.

  • Session cookies last ~3 months, then re-run setup.
  • update-draft --body-md replaces the whole body: editor-only widgets

(buttons, polls, paywalls) in the existing draft are destroyed.

  • No documented rate limits; the skill retries 429/5xx with backoff. Keep bulk

operations gentle.

Security Notes

  • Publishing confirmation required — Claude must always confirm with the user

before publish or schedule (the --send-email blast is irreversible).

  • The session cookie in config.json is equivalent to your logged-in browser

session. It is stored locally and gitignored (blanket **/config.json rule).

  • Revoke by logging out of that browser session or changing your password.