substack-copy

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

  1. Reads your Jekyll post (Markdown with YAML frontmatter)
  2. Strips frontmatter, converts Markdown to HTML
  3. Embeds local images as base64 data URIs
  4. Converts footnotes to simple superscript format
  5. Prepends your custom header note
  6. 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