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).
By sugarforever · 596 installs
npx skills add sugarforever/01coder-agent-skills --skill publish-substack-article
Source repository · Upstream listing
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 markdown package ( pip install markdown )
copy to clipboard.py script (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:
1. Skill argument — if the user invoked the skill with a publication subdomain, use it.
2. Environment variable — check SUBSTACK PUBLICATION (optional, for users who publish repeatedly to the same publication).
3. 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:
1. Convert Markdown to HTML using Python's markdown library
2. Copy HTML to system clipboard using copy to clipboard.py html
3. Focus the editor content area
4. Press Cmd+V (macOS) or Ctrl+V (Windows/Linux) to paste
Why HTML paste?
fill tool → 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:
1. Detect tables in the Markdown file (lines with forming table structure)
2. Convert each table to PNG using the diagram to image skill:
3. Note the position of each table in the article for later insertion (after which heading/paragraph)
4. 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 title field, or H1 header Title , or filename
Subtitle : from YAML frontmatter excerpt or description field
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:
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:
If not logged in, prompt user to log in:
Step 4: Create New Post
From the dashboard, create a new text post:
1. Click "Create new" in the sidebar
2. 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
1. Click the title textbox ( textbox "title" )
2. Type the article title
3. Click the subtitle textbox ( textbox "Add a subtitle…" )
4. Type the subtitle/excerpt
Step 6: Insert HTML Content (via Clipboard Paste)
CRITICAL: Do NOT use fill tool it inserts plain text without formatting.
1. Copy HTML to system clipboard:
2. Click the editor content area ( .ProseMirror or paragraph element inside it)
3. Press Cmd+V to paste:
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:
1. 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)
2. Click the Image toolbar button ( button "Image" ) — a dropdown menu appears with options: Image, Gallery, Stock photos, Generate image
3. Click "Image" menuitem from the dropdown — a file chooser dialog opens
4. Upload the image via file chooser:
Playwright MCP: browser file upload with the image path
Chrome DevTools MCP: upload file with the image path
Important notes:
File path restriction : Playwright MCP only allows file uploads from within allowed roots (project directories). If your image is in /tmp/ , copy it to the project directory first
Repeat for each table : Position cursor at the correct location, then upload each table image
Delete residual text : If table content was pasted as plain text (because it wasn't removed in pre processing), select it (triple click to select paragraph) and delete before inserting the image
Step 8: Verify Draft
After pasting:
1. Check the "Saved" status indicator (green dot + "Saved" text)
2. Take a snapshot to verify content structure
3. Optionally take a screenshot for visual verification
The editor auto saves, so no explicit save action is needed.
Step 9: Report Completion
Complete Example Flow
User: "把 /path/to/my article.md 发布到 Substack"
Critical Rules
1. NEVER click "Continue" This starts the publish flow. Only save as draft (auto save handles this)
2. Always convert to HTML first Plain Markdown will not be parsed by the Tiptap editor
3. Use clipboard paste The only reliable way to insert formatted content
4. Check login status Prompt user to login if needed
5. Preserve original file Never modify the source Markdown file
6. Report completion Tell user the draft is saved and needs manual review
7. No nl2br extension Causes double line breaks
8. Tables → images Pre process tables before pasting content; upload images after paste
9. Playwright file paths Playwright MCP restricts file uploads to allowed roots; copy temp files to project directory before uploading
10. Resolve publication first Never hardcode a publication subdomain. Resolve via skill argument, SUBSTACK PUBLICATION env var, or user prompt before any navigation (see Publication Resolution)
11. Prefer Playwright MCP Default to Playwright MCP; only use Chrome DevTools MCP when Playwright is unavailable
Troubleshooting
Content Shows as Plain Text (No Formatting)
If you see raw HTML tags or unformatted text:
Cause : Content was inserted using fill tool instead of clipboard paste
Solution : Use the copy to clipboard.py + Cmd+V method (see Step 6)
Tables Not Rendering (Shows Plain Text)
Substack's Tiptap editor does not support HTML tables. They collapse into inline plain text.
Solution : Convert tables to PNG via diagram to image skill → upload as images (see Step 0 and Step 7)
Alternative : Restructure simple tables as formatted lists
If plain text already pasted : Triple click the plain text paragraph to select it, press Backspace to delete, then insert the table image at that position
Login Required
If page shows login prompt:
Editor Not Loading
If editor elements are not visible:
1. Wait for page to fully load
2. Take a new snapshot
3. If still not loading, refresh the page
Clipboard Copy Fails
If copy to clipboard.py fails:
Ensure dependencies: pip install pyobjc framework Cocoa (macOS)
Check the HTML file exists and is readable
Try copying a smaller test string first
Element Reference
Element Selector/Identifier Description
Title input textbox "title" Post title
Subtitle input textbox "Add a subtitle…" Post subtitle
Content area .ProseMirror (Tiptap editor) Post content
Save status button "Saved" Auto save indicator
Preview button button "Preview" Preview post
Continue button button "Continue" DO NOT USE starts publish flow
Settings button button "Settings" Open settings sidebar
Exit button button "Exit" Exit editor
Image button button "Image" Opens image upload dropdown
Image menuitem menuitem "Image" Opens file chooser for image upload
Author button button "{PublicationName}" Author/publication selector
Technical Details
Editor Stack
Tiptap : A headless, framework agnostic rich text editor built on ProseMirror
ProseMirror : The underlying rich text editing framework
Paste handling : Tiptap natively parses HTML from clipboard and converts to its internal document model
Content Conversion Pipeline
Supported Formatting
The following Markdown elements are correctly rendered after HTML conversion and paste:
Markdown Element Substack Support Notes
Headings (H2 H6) Yes H1 not recommended (title is separate)
Bold / Italic Yes
Inline code Yes
Code blocks Yes Syntax highlighting may vary
Links Yes
Blockquotes Yes
Bullet lists Yes
Ordered lists Yes
Horizontal rules Yes
Tables No → Image Convert via diagram to image skill, upload as image
Images Manual Upload via Image toolbar button → file chooser