Publish Substack Article
Publish Markdown articles to Substack as drafts. Use when user wants to publish a Markdown file to Substack, or mentions "发布到 Substack", "Substack article", "publish to Substack". Handles Markdown-to-HTML conversion and saves as draft (never auto-publish).
- Skill ID
- sugarforever/01coder-agent-skills/publish-substack-article
- Publisher
- sugarforever
- Repository
- 01coder-agent-skills
- Installs
- 0
- Files
- 1
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
sugarforever/01coder-agent-skills/publish-substack-articleInstalls these files- SKILL.md
What this skill tells the agent
Publish Substack Article
Publish Markdown content to Substack post editor, converting Markdown to HTML and pasting as rich text. Saves as draft for user review before publishing.
Prerequisites
- Browser automation MCP (either one):
- Chrome DevTools MCP (
mcp__chrome-devtools__*) - Playwright MCP (
mcp__playwright__*) - User logged into Substack
- Python 3 with
markdownpackage (pip install markdown) copy_to_clipboard.pyscript (shared from publish-zsxq-article skill)
Browser MCP Tool Mapping
This skill works with both Chrome DevTools MCP and Playwright MCP:
| Action | Chrome DevTools MCP | Playwright MCP |
|---|---|---|
| Navigate | navigate_page | browser_navigate |
| Take snapshot | take_snapshot | browser_snapshot |
| Take screenshot | take_screenshot | browser_take_screenshot |
| Click element | click | browser_click |
| Fill text | fill | browser_type |
| Upload file | upload_file | browser_file_upload |
| Press key | press_key | browser_press_key |
| Evaluate JS | evaluate_script | browser_evaluate |
Priority: Default to Playwright MCP. Use Chrome DevTools MCP only when Playwright MCP is unavailable.
Detection: At runtime, prefer mcp__playwright__browser_navigate. Fall back to mcp__chrome-devtools__navigate_page only if Playwright tools are not available.
Key URLs
- Substack dashboard:
https://{publication}.substack.com/publish - Post editor:
https://{publication}.substack.com/publish/post/{postId}
Publication Resolution
The Substack publication subdomain (e.g., verysmallwoods in verysmallwoods.substack.com) is required to navigate to the dashboard. Do not hardcode a default.
Before any navigation, resolve the publication in this order:
- Skill argument — if the user invoked the skill with a publication subdomain, use it.
- Environment variable — check
SUBSTACK_PUBLICATION(optional, for users who publish repeatedly to the same publication). - Prompt the user if neither is available: > 请提供 Substack publication subdomain(例如 verysmallwoods.substack.com 里的
verysmallwoods)。
Do not proceed to Step 3 (Navigate) without a resolved publication subdomain.
Editor Interface
The Substack post editor uses Tiptap (ProseMirror-based WYSIWYG editor).
Key Elements
- Title input:
textbox "title"(placeholder: "Title") - Subtitle input:
textbox "Add a subtitle…" - Content area:
.ProseMirror(Tiptap editor, "Start writing...") - Save status:
button "Saved"(auto-saves) - Preview button:
button "Preview" - Continue button:
button "Continue"(publish flow - DO NOT USE) - Settings sidebar:
button "Settings"(title, description, thumbnail)
Settings Sidebar (left panel)
When "Settings" or "File Settings" is open:
- Title:
textbox "Add a title..." - Description:
textbox "Add a description..." - Thumbnail: Upload button (3:2 aspect ratio)
Toolbar
Bold, Italic, Strikethrough, Code, Link, Image, Audio, Video, Quote, Lists (bullet/ordered), Button, More (Code block, Divider, Footnote, LaTeX, etc.)
Content Insertion Method
CRITICAL: Use clipboard paste with HTML content, NOT direct fill or plain Markdown paste.
The Tiptap editor handles HTML paste natively and renders it as rich content. The workflow is:
- Convert Markdown to HTML using Python's
markdownlibrary - Copy HTML to system clipboard using
copy_to_clipboard.py html - Focus the editor content area
- Press Cmd+V (macOS) or Ctrl+V (Windows/Linux) to paste
Why HTML paste?
filltool → Content treated as plain text, no formatting- Plain Markdown paste → Tiptap does NOT parse Markdown on paste
- HTML paste → Tiptap renders HTML as rich content (headings, code blocks, links, bold, etc.)
Known limitation: Substack's editor does NOT support HTML tables. Tables will be collapsed into plain text. See Step 0: Pre-Processing for converting tables to images.
Main Workflow
Step 0: Pre-Processing — Convert Tables to Images
Substack does NOT render HTML tables. They collapse into plain text. Any Markdown table must be converted to a PNG image and uploaded separately.
Workflow:
- Detect tables in the Markdown file (lines with
|forming table structure)
- Convert each table to PNG using the diagram-to-image skill:
# Extract table to temp file
cat > /tmp/table1.md << 'TABLE_EOF'
| Column 1 | Column 2 | Column 3 |
|----------|----------|----------|
| Data 1 | Data 2 | Data 3 |
TABLE_EOF
# Convert via diagramless.xyz API (auto-detects as table)
node ~/.claude/skills/diagram-to-image/scripts/diagram-to-image.mjs /tmp/table1.md -o /tmp/table1.png- Note the position of each table in the article for later insertion (after which heading/paragraph)
- Remove table Markdown from the content before HTML conversion (so it won't appear as plain text in the pasted content)
Image upload happens after pasting the main content — see Step 7.
Step 1: Prepare Content
Read the Markdown file and extract:
- Title: from YAML frontmatter
titlefield, or H1 header# Title, or filename - Subtitle: from YAML frontmatter
excerptordescriptionfield - Content: full Markdown body (strip YAML frontmatter and any cross-reference links)
Step 2: Convert Markdown to HTML
Use Python's markdown library with tables and fenced_code extensions:
import markdown
import re
with open('/path/to/article.md', 'r') as f:
content = f.read()
# Strip YAML frontmatter
content = re.sub(r'^---\n.*?\n---\n', '', content, flags=re.DOTALL)
# Strip cross-reference links (e.g., English version link)
# Adjust pattern as needed for your articles
content = re.sub(r'^> .* available at.*\n\n?', '', content, flags=re.MULTILINE)
# Convert to HTML
html = markdown.markdown(content, extensions=['tables', 'fenced_code'])
# Write to temp file
with open('/tmp/substack_article.html', 'w') as f:
f.write(html)IMPORTANT: Do NOT use nl2br extension - it converts single newlines to <br> tags, causing extra line breaks in the editor.
Step 3: Navigate to Substack
Navigate to the Substack dashboard and create a new post:
# Navigate to Substack dashboard
navigate to: https://{publication}.substack.com/publishIf not logged in, prompt user to log in:
请先登录 Substack,登录完成后告诉我。
Please log in to Substack first, then let me know.Step 4: Create New Post
From the dashboard, create a new text post:
- Click "Create new" in the sidebar
- Select "Text post" (or navigate directly to a new post URL)
Alternatively, if the editor is already open with an empty post, proceed directly.
Step 5: Fill Title and Subtitle
- Click the title textbox (
textbox "title") - Type the article title
- Click the subtitle textbox (
textbox "Add a subtitle…") - Type the subtitle/excerpt
click: title textbox
fill/type: article title
click: subtitle textbox
fill/type: article subtitleStep 6: Insert HTML Content (via Clipboard Paste)
CRITICAL: Do NOT use `fill` tool - it inserts plain text without formatting.
- Copy HTML to system clipboard:
python3 /path/to/copy_to_clipboard.py html --file /tmp/substack_article.html- Click the editor content area (
.ProseMirroror paragraph element inside it)
- Press Cmd+V to paste:
press_key: Meta+v (macOS)
press_key: Control+v (Windows/Linux)This triggers Tiptap's HTML paste handler, which renders the content as rich text with proper formatting.
Step 7: Insert Table Images
If the article had tables converted to images in Step 0, insert them now:
- Navigate to the correct position in the editor — click on the paragraph or empty line where the table should appear (after the relevant heading/text)
More from sugarforever
Use this skill in a project
Open any RiverX project, search the Skills panel for sugarforever/01coder-agent-skills/publish-substack-article, and install it in one click.
