Skip to content

wiremind/markdown-to-notionapi

Repository files navigation

md2notion

A production-ready Go CLI tool that converts Markdown to Notion blocks and uploads them to Notion pages via the public REST API.

Features

  • Zero dependencies: Direct HTTP calls to Notion API, no external SDK required
  • CommonMark + GitHub flavored Markdown: Support for headings, paragraphs, lists, code blocks, quotes, images, and more
  • Flexible operations: Append to existing pages, replace content, or create new pages
  • Robust error handling: Rate limiting, retry logic with exponential backoff, and clear error messages
  • Developer-friendly: Dry-run mode, verbose logging, stdin support

Installation

go install github.com/wiremind/markdown-to-notionapi/cmd/notion-md@latest

Quick Start

  1. Create a Notion Integration:

  2. Share your page with the integration:

    • Open your Notion page
    • Click "Share" → "Invite" → Select your integration
  3. Set your token:

    export NOTION_TOKEN="secret_abc123..."
  4. Upload Markdown:

    md2notion --page-id YOUR_PAGE_ID --md document.md

Usage Examples

Append to existing page

md2notion --page-id abc123def456 --md notes.md

Read from stdin

cat README.md | md2notion --page-id abc123def456

Replace page content

md2notion --page-id abc123def456 --md document.md --replace

Create new page

md2notion --create --parent-id xyz789 --title "My Document" --md notes.md

Dry run (preview JSON)

md2notion --page-id abc123def456 --md notes.md --dry-run

Handle relative images

md2notion --page-id abc123def456 --md notes.md --image-base-url "https://example.com/assets/"

Supported Markdown

Markdown Notion Block
# ## ### heading_1/2/3
Paragraphs paragraph
bold, italic, code Rich text formatting
strikethrough Rich text formatting
links Rich text links
- bulleted lists bulleted_list_item
1. numbered lists numbered_list_item
Nested lists Nested list items
> blockquotes quote
```code blocks``` code
--- horizontal rules divider
![images](url) image (external URLs only)

Command Line Options

Usage: md2notion [options]

Options:
  --page-id string         Notion page ID to append blocks to (required unless --create is set)
  --parent-id string       Parent page ID for creating new page (required if --create is set)
  --title string           Title for new page (required if --create is set)
  --md string              Path to markdown file (default: read from stdin)
  --append                 Append blocks to the end of the page (default true)
  --replace                Replace existing page content
  --create                 Create a new page
  --image-base-url string  Base URL for relative image paths
  --dry-run                Print JSON that would be sent, don't call API
  --notion-version string  Notion API version (default "2022-06-28")
  -v, --verbose            Verbose output
  --timeout duration       HTTP request timeout (default 1000s)
  -h, --help              Show help

Environment Variables:
  NOTION_TOKEN    Notion integration token (required)

Finding Page IDs

From URL

If your page URL is https://notion.so/My-Page-abc123def456, the page ID is abc123def456.

From API

You can also use the Notion API to list pages in a database or workspace.

Error Handling

The tool provides clear, actionable error messages:

  • Authentication errors: Check your NOTION_TOKEN
  • Permission errors: Ensure the integration has access to the page
  • Rate limiting: Automatically handled with exponential backoff
  • Invalid page ID: Verify the page ID format and permissions

Limitations

  • Images: Only external URLs are supported (no binary upload)
  • Tables: Not supported (would require complex Notion table structure)
  • Advanced formatting: Some complex Markdown features may not translate perfectly

Development

Quick Setup

git clone https://github.com/wiremind/markdown-to-notionapi.git
cd markdown-to-notionapi
make dev-setup  # Install development tools
make build      # Build the binary

Available Commands

make help           # Show all available commands
make test           # Run tests
make coverage       # Run tests with coverage report
make lint           # Run golangci-lint
make security       # Run security scans
make check          # Run all quality checks
make build          # Build binary
make build-all      # Build for all platforms
make container-build # Build container image
make clean          # Clean build artifacts

Running Tests

# Unit tests
make test

# With coverage
make coverage

# All quality checks
make check

Local Testing

# Set up test environment
export NOTION_TOKEN="your-integration-token"
export TEST_PAGE_ID="your-test-page-id"

# Test locally
make run-local

# Test container
make container-test

GitHub Actions CI

name: CI
on: [push, pull_request]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: actions/setup-go@v4
      with:
        go-version: '1.25'
    - run: go test ./...
    - run: go vet ./...
    - run: go build ./cmd/notion-md

Docker Usage

Pull and Run

# Pull the latest image
docker pull ghcr.io/wiremind/markdown-to-notionapi:latest

# Run with a file
docker run --rm -v $(pwd):/data -e NOTION_TOKEN=$NOTION_TOKEN \
  ghcr.io/wiremind/markdown-to-notionapi:latest \
  -page-id=YOUR_PAGE_ID -md=/data/document.md

# Run with stdin
cat README.md | docker run --rm -i -e NOTION_TOKEN=$NOTION_TOKEN \
  ghcr.io/wiremind/markdown-to-notionapi:latest \
  -page-id=YOUR_PAGE_ID

Build Locally

# Using Docker
docker build -f Containerfile -t md2notion .

# Using Podman (also works with Containerfile)
podman build -f Containerfile -t md2notion .

# Run the container
docker run --rm -e NOTION_TOKEN=$NOTION_TOKEN md2notion -page-id=YOUR_PAGE_ID -dry-run

GitHub Actions Integration

Using Docker Image Directly

name: Upload to Notion
on: [push]

jobs:
  upload:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Upload README to Notion
      uses: docker://ghcr.io/wiremind/markdown-to-notionapi:latest
      env:
        NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
      with:
        args: -page-id=${{ secrets.NOTION_PAGE_ID }} -verbose README.md

Using Custom Action

name: Upload to Notion
on: [push]

jobs:
  upload:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    
    - name: Upload documentation
      uses: wiremind/markdown-to-notionapi@main
      with:
        page-id: ${{ secrets.NOTION_PAGE_ID }}
        markdown-file: 'docs/api.md'
        dry-run: 'false'
        verbose: 'true'
      env:
        NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
    
    - name: Upload inline content
      uses: wiremind/markdown-to-notionapi@main
      with:
        page-id: ${{ secrets.NOTION_PAGE_ID }}
        markdown-content: |
          # Build Report
          
          **Status**: ✅ Success
          **Commit**: ${{ github.sha }}
          **Branch**: ${{ github.ref_name }}
          **Time**: ${{ github.event.head_commit.timestamp }}
      env:
        NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}

Action Inputs

Input Description Required Default
page-id Notion page ID to upload to
markdown-file Path to markdown file
markdown-content Inline markdown content
notion-token Notion token (or use env var)
dry-run Only show what would be uploaded false
verbose Enable verbose output false
replace Replace page content instead of append false

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

License

MIT License - see LICENSE file for details.

Troubleshooting

"NOTION_TOKEN environment variable is required"

Set your integration token:

export NOTION_TOKEN="secret_your_token_here"

"Object not found" or "Unauthorized"

  • Verify the page ID is correct
  • Ensure the integration has access to the page (share the page with your integration)
  • Check that the token is valid and not expired

Images not appearing

  • Only external URLs (http/https) are supported
  • Use --image-base-url for relative paths
  • Verify image URLs are publicly accessible

Rate limiting

The tool automatically handles rate limiting with exponential backoff. If you hit limits frequently, consider:

  • Reducing concurrency
  • Adding delays between operations
  • Processing smaller batches

Large documents

For very large Markdown files:

  • The tool automatically chunks requests (100 blocks max per API call)
  • Use --verbose to monitor progress
  • Consider breaking large documents into smaller sections

About

A small tool to convert markdown to Notion format and have the possibility to upload to the API.

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages