substack-copy
A CLI tool that converts Jekyll blog posts (Markdown) to Substack-compatible clipboard format. Paste directly into Substack’s editor with formatting, images, and footnotes preserved.
Features
- Markdown to HTML conversion with GitHub Flavored Markdown support
- Footnotes converted to Substack-compatible format
- Local images embedded as base64 data URIs (no broken images!)
- YAML frontmatter automatically stripped
- Multi-format clipboard - copies both HTML and plain text for reliable pasting
- Customizable header - prepend a note linking to your website
Requirements
- macOS (uses native Swift clipboard helper for multi-format paste)
- Node.js 18+
Installation
git clone https://github.com/YOUR_USERNAME/substack-copy.git
cd substack-copy
npm install
npm link # Makes 'substack-copy' available globally
Usage
# Convert and copy to clipboard
substack-copy path/to/_posts/2024-01-01-my-post.md
# Preview output without copying
substack-copy path/to/_posts/2024-01-01-my-post.md --dry-run
# Specify Jekyll site root manually (usually auto-detected)
substack-copy post.md --root ~/myblog
Then paste into Substack’s editor (Cmd+V).
How It Works
- Reads your Jekyll post (Markdown with YAML frontmatter)
- Strips frontmatter, converts Markdown to HTML
- Embeds local images as base64 data URIs
- Converts footnotes to simple superscript format
- Prepends your custom header note
- Copies both HTML and plain text to clipboard
Customization
Header Note
Edit src/html-processor.ts to customize the header prepended to each post:
function getHeaderNote(postUrl: string): string {
return `<p><strong><em>Note: You can read <a href="${postUrl}">this post on my website</a>...</em></strong></p>`;
}
Blog URL
Edit src/pipeline.ts to change the base URL for your blog:
const postUrl = `https://YOUR-SITE.com/${postSlug}/`;
Disable Header
To disable the header entirely, comment out this line in src/html-processor.ts:
// $('body').prepend(getHeaderNote(postUrl));
After any changes, rebuild with:
npm run build
Project Structure
substack-copy/
├── src/
│ ├── index.ts # CLI entry point
│ ├── pipeline.ts # Main conversion orchestration
│ ├── markdown.ts # Markdown → HTML conversion
│ ├── html-processor.ts # Image embedding, footnotes, header
│ └── clipboard.ts # macOS clipboard integration
├── native/
│ └── clipboard-helper.swift # Native clipboard helper
├── bin/
│ ├── substack-copy # npm bin script
│ └── clipboard-helper # Compiled Swift binary
└── dist/ # Built JavaScript
Limitations
- macOS only - The clipboard helper uses NSPasteboard
- No clickable footnotes - Substack strips anchor links during paste; footnotes appear as superscript numbers with definitions at the bottom
- Images must be local - Remote URLs are preserved as-is
License
MIT