Upstack
pendingby Michael Layug
A 'copy for Substack' plugin. Exports your notes to Substack-compatible HTML with one click. Automatically converts Markdown, embeds images as base64, converts Mermaid charts, and handles callouts, footnotes, and more.
Upstack
Export your Obsidian notes to Substack-compatible HTML with one click. Automatically converts Markdown to clean, semantic HTML optimized for Substack's editor, including embedded images, Mermaid charts, callouts, and more.
Features
Core Functionality
- One-Click Export: Copy your note as Substack-ready HTML with a single click
- Automatic Image Embedding: Local images are automatically converted to base64 data URIs and embedded directly in the HTML
- Substack-Optimized: Output is clean, semantic HTML that works perfectly in Substack's editor
Markdown Support
Images
- Obsidian-style images:
![[image.png|caption]]→ Automatically converted to base64 and embedded - Standard Markdown images:
→ Converted to<figure>tags with captions - Aspect ratio containers: Images use responsive containers to prevent layout shift
- Excalidraw support: Excalidraw files are detected and converted (manual upload required)
Diagrams & Charts
- Mermaid charts: Automatically converted to PNG images via mermaid.ink API
- Supports flowcharts, sequence diagrams, and all Mermaid diagram types
Content Formatting
- Callouts/Admonitions: All 8 types (note, tip, important, warning, error, success, question, info)
- Highlights:
==text==→ Styled<mark>tags - Footnotes: Clickable references with footnotes section at bottom
- Tables: Converted to text format with separators (Substack requires Datawrapper for HTML tables)
- Code blocks: Styled with syntax highlighting support
- Task lists: Checkboxes (☐/☑) for todo items
- Wikilinks:
[[link]]→ Standard markdown links
Typography
- Headings: All 6 levels (H1-H6) with Substack-optimized styling
- Text formatting: Bold, italic, strikethrough, inline code
- Blockquotes: Styled quote blocks
- Lists: Ordered and unordered, with nested support
- Horizontal rules: Clean divider lines
Installation
From Obsidian (Recommended)
- Open Obsidian Settings → Community Plugins
- Disable Safe Mode
- Click "Browse" and search for "Upstack"
- Click "Install" then "Enable"
Manual Installation
- Download the latest release from GitHub Releases
- Extract the files to your vault's
.obsidian/plugins/upstack/folder:main.jsmanifest.json
- Reload Obsidian
- Enable the plugin in Settings → Community Plugins
Usage
Quick Start
- Open any Markdown note in Obsidian
- Desktop: Click the Upstack icon (stylized "U") in the ribbon, OR
- All platforms: Use the command palette: "Copy current note as Substack-compatible HTML"
- The HTML is automatically copied to your clipboard
- Paste directly into Substack's editor
Note:
- Ribbon icon is only available on desktop. On mobile, use the command palette.
- On mobile, if HTML clipboard is not supported, the content will be copied as plain text (still functional, but formatting may be lost).
Example
# My Article Title
Here's an image:
![[my-image.png|This is a caption]]
> [!tip]
> This is a helpful tip!
Here's a Mermaid chart:
```mermaid
graph TD
A[Start] --> B[End]
```
When you copy this note, it will be converted to clean HTML with:
- The image embedded as base64
- The callout styled as a tip box
- The Mermaid chart converted to a PNG image
Supported Features
Fully Supported
| Feature | Obsidian Syntax | Substack Output |
|---|---|---|
| Images | ![[image.png|caption]] | Base64 embedded <figure> |
| Mermaid Charts | ```mermaid ... ``` | PNG image via mermaid.ink |
| Callouts | > [!note] | Styled blockquote |
| Highlights | ==text== | <mark> tag |
| Footnotes | [^1] | Clickable superscript links |
| Tables | | Col1 | Col2 | | Text format with separators |
| Code Blocks | ```code``` | Styled <pre><code> |
| Task Lists | - [ ] / - [x] | Checkboxes (☐/☑) |
| Wikilinks | [[link]] | Standard markdown links |
| Headings | # H1 to ###### H6 | Semantic HTML headings |
| Text Formatting | **bold**, *italic* | Styled HTML |
| Lists | - item / 1. item | HTML lists |
| Blockquotes | > quote | Styled blockquote |
| Horizontal Rules | --- | <hr> tag |
Limitations
Current Limitations
Tables
- Issue: Substack doesn't support HTML tables directly. Tables are converted to a simple text format with vertical bar separators (
|), which may not be ideal for complex data. - Workaround: For complex tables, use Datawrapper to create interactive charts/tables and embed them manually in Substack.
- Impact: Readability of complex tables is reduced, but basic tables remain functional.
Excalidraw Images
- Issue: Excalidraw files (
.excalidraw) are detected but cannot be automatically converted. The plugin creates a placeholder with the caption, but the image source is empty. - Workaround: Manually export Excalidraw drawings as PNG/SVG and upload them to Substack, then replace the placeholder.
- Impact: Requires manual intervention for Excalidraw content.
Large Images
- Issue: Very large images (>1MB) converted to base64 can create extremely large HTML output (potentially 10MB+), which may:
- Slow down Substack's editor
- Cause browser performance issues
- Exceed clipboard size limits
- Workaround: Compress images before embedding or use external image hosting.
- Impact: Large images may cause performance degradation or fail to copy.
Mermaid Charts
- Issue: Mermaid charts require an internet connection to convert via the
mermaid.inkAPI. Charts won't render if:- You're offline
- The API is down
- Network requests are blocked
- Workaround: None currently - requires internet connection.
- Impact: Offline users cannot use Mermaid charts.
Base64 Image Embedding
- Issue: All local images are embedded as base64 data URIs, which:
- Increases HTML size significantly (base64 is ~33% larger than binary)
- May cause Substack's editor to lag with many images
- Cannot be cached by browsers
- Workaround: Use external image hosting for better performance.
- Impact: Larger HTML output and potential editor performance issues.
Substack Editor Limitations
- Issue: Substack's editor may strip or modify certain HTML structures:
- Complex nested elements
- Custom attributes
- Some inline styles
- Workaround: The plugin outputs clean, semantic HTML, but some advanced formatting may not be preserved.
- Impact: Some custom styling may be lost in Substack.
Unsupported Obsidian Features
- Not supported: Obsidian Canvas, embedded PDFs, embedded videos, Dataview queries (only the rendered output), and other advanced Obsidian features.
- Impact: These features will not be converted and may appear as broken references.
Performance
- Issue: Processing very large notes with many images can take several seconds.
- Impact: Brief delay when copying large notes.
Development
Prerequisites
- Node.js 18+
- npm or yarn
Setup
# Clone the repository
git clone git@github.com:memmmmike/upstack.git
cd upstack
# Install dependencies
npm install
# Build for production
npm run build
# Build in watch mode (for development)
npm run dev
Project Structure
upstack/
├── main.ts # Main plugin code
├── manifest.json # Plugin manifest
├── package.json # Dependencies
├── tsconfig.json # TypeScript config
├── esbuild.config.mjs # Build configuration
└── versions.json # Version tracking
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Acknowledgments
- Built with Obsidian API
- Uses marked for Markdown parsing
- Mermaid charts powered by mermaid.ink
Support
- Issues: GitHub Issues
- Discussions: GitHub Discussions
Roadmap
Short-term (v1.1 - v1.2)
- Image compression: Automatically compress images before base64 conversion to reduce HTML size
- Image size limits: Add configurable size limits with warnings for large images
- Performance optimization: Optimize base64 conversion for large images (chunking improvements)
- Better error handling: More graceful fallbacks for failed image conversions
Medium-term (v1.3 - v1.5)
- Excalidraw auto-export: Automatically export Excalidraw files to PNG before embedding
- Offline Mermaid support: Local Mermaid rendering using a headless browser or WASM
- Table improvements: Better text-based table formatting or Datawrapper integration
- Custom styling presets: Allow users to customize output styling (fonts, colors, spacing)
- Progress indicators: Show progress for large note processing
Long-term (v2.0+)
- Batch export: Export multiple notes at once
- Multi-platform support: Export to Medium, Ghost, WordPress, and other platforms
- Image hosting integration: Optional integration with image hosting services (Imgur, Cloudinary, etc.)
- Canvas support: Convert Obsidian Canvas to images or interactive HTML
- PDF embedding: Support for embedded PDFs
- Video embedding: Support for embedded videos
- Dataview integration: Better support for Dataview query results
- Template system: Customizable HTML templates for different output styles
Made with love for the Obsidian and Substack communities
For plugin developers
Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.