We'll use two specialized scripts:
- Backup Script (
bulk-exporter.js): Downloads your Notion content as structured JSON - Converter Script (
json-to-markdown.js): Transforms JSON into beautifully formatted Markdown
Create and configure your export directory:
mkdir notion-backup
cd notion-backupCreate package.json:
{
"name": "notion-bulk-exporter",
"version": "1.0.0",
"description": "Bulk Notion workspace backup to JSON and Markdown",
"main": "bulk-exporter.js",
"type": "commonjs",
"scripts": {
"backup": "node bulk-exporter.js",
"convert": "node json-to-markdown.js",
"full-export": "node bulk-exporter.js && node json-to-markdown.js"
},
"dependencies": {
"@notionhq/client": "^2.3.0",
"dotenv": "^16.4.5"
}
}Install dependencies:
npm install- Go to Notion Integrations
- Click "+ New integration"
- Give it a name (e.g., "Workspace Backup Bot")
- Select the workspace you want to backup
- Click "Submit" to create the integration
- Copy the "Internal Integration Token" (starts with
ntn_orsecret_) - Repeat for each workspace you want to backup
Note: Each Notion workspace requires its own integration token. You cannot use one token across multiple workspaces.
Critical Step: Your integration can only access pages explicitly shared with it.
For each workspace:
- Open Notion and navigate to the top-level page you want to export
- Click "Share" in the top-right corner
- Click "Invite" and search for your integration name
- Select your integration from the dropdown
- The integration now has access to that page and all its children
Pro Tip: Share your workspace's root page to export everything, or share specific pages for selective backups.
Create a .env file in your project directory:
# .env file
NOTION_TOKENS="ntn_64xxx...,ntn_333xxx...,ntn_444xxx..."
NOTION_BACKUP_DIR="./notion-bulk-backup"
NOTION_MARKDOWN_DIR="./notion-markdown-export"Or export directly in your terminal:
export NOTION_TOKENS="ntn_64xxx...,ntn_333xxx...,ntn_444xxx..."
export NOTION_BACKUP_DIR="./notion-bulk-backup"
export NOTION_MARKDOWN_DIR="./notion-markdown-export"Token Format:
- Comma-separated list of tokens
- No spaces between tokens (unless inside quotes)
- Each token represents one workspace
Save this as bulk-exporter.js:
#!/usr/bin/env node
const { Client } = require("@notionhq/client");
const fs = require("fs");
const path = require("path");
const readline = require("readline");
require("dotenv").config();
// [Insert the complete bulk-exporter.js code here]Make it executable (optional):
chmod +x bulk-exporter.jsKey Features:
- β Multi-workspace support (unlimited workspaces)
- β Recursive page/database crawling
- β Preserves complete block hierarchies (nested blocks)
- β Handles all Notion block types
- β Creates manifest.json for tracking
- β Incremental updates (won't re-download existing pages)
- β Error recovery (continues if one workspace fails)
Execute the backup script:
node bulk-exporter.js
# Or if you made it executable:
# ./bulk-exporter.jsYou'll be prompted with export options:
How do you want to export from each workspace?
1) Export EVERYTHING each integration can see
2) Search and pick root pages interactively (per workspace)
3) Same root IDs for all workspaces (enter manually)
Recommendations:
- Option 1: Best for complete workspace backups (recommended for most users)
- Option 2: Best if you want to select specific pages per workspace interactively
- Option 3: Best if you have the same page structure across workspaces and know the page IDs
Expected Output:
======================================
WORKSPACE 1 of 3
======================================
β Successfully connected to workspace
[PAGE] My Important Notes (abc-123...)
[PAGE] Project Documentation (def-456...)
[DATABASE] Task List (ghi-789...)
β
Workspace export complete.
Time Estimate:
- Small workspace (< 100 pages): 2-5 minutes
- Medium workspace (100-500 pages): 10-30 minutes
- Large workspace (> 500 pages): 30+ minutes
Save this as json-to-markdown.js:
#!/usr/bin/env node
const fs = require("fs");
const path = require("path");
const https = require("https");
const http = require("http");
const readline = require("readline");
// [Insert the complete json-to-markdown.js code here]Conversion Capabilities:
- β All text formatting (bold, italic, strikethrough, code, underline)
- β Headings (H1, H2, H3)
- β Lists (bulleted, numbered, to-do with checkboxes)
- β Code blocks with language syntax
- β Images (with optional download)
- β Files and PDFs (with optional download)
- β Videos (with optional download)
- β Tables, quotes, callouts
- β Nested blocks (preserves hierarchy)
- β Links and bookmarks
- β Database schemas
- β YAML frontmatter with metadata
Run the converter:
node json-to-markdown.jsInteractive Prompts:
Enter path to backup directory: [./notion-bulk-backup]
Download files, images, PDFs, and videos? [y/N]
Asset Download Recommendation:
- Yes: Choose if you want a complete offline archive (recommended)
- No: Choose if you want to keep Notion's hosted URLs (smaller export)
What happens during conversion:
- Reads all JSON files from backup
- Converts each page to Markdown
- Downloads assets if requested (may take time for large workspaces)
- Creates INDEX.md with export summary
- Preserves folder structure
Your exported structure will look like:
notion-markdown-export/
βββ workspace_1/
β βββ INDEX.md # Summary of this workspace
β βββ pages/
β β βββ My_Page_abc123.md
β β βββ Another_Page_def456.md
β βββ databases/
β β βββ Tasks_ghi789.md
β βββ assets/ # Downloaded files (if enabled)
β β βββ image_123.png
β β βββ pdf_456.pdf
β β βββ video_789.mp4
β βββ manifest.json # Export metadata
β βββ workspace_info.json # Workspace details
βββ workspace_2/
β βββ [same structure]
βββ workspace_3/
βββ [same structure]
File Naming Convention:
- Pages:
Title_NotionID.md(e.g.,Project_Notes_abc123.md) - Assets:
type_NotionID.ext(e.g.,image_abc123.png) - Databases:
DatabaseName_NotionID.md
- Text: All rich text formatting (bold, italic, code, strikethrough, underline)
- Blocks: Paragraphs, headings, lists, to-dos, quotes, callouts
- Code: Code blocks with language syntax highlighting
- Media: Images, videos, files, PDFs (with download option)
- Embeds: Bookmarks, link previews
- Databases: Schema documentation with property types
- Nested Content: Toggles, nested lists, child pages/databases
- Metadata: Created/edited times, page properties, URLs
- Tables: Converted to Markdown tables (complex formatting may be simplified)
-
Equations: Preserved as LaTeX (
$$formula$$ ) - Synced Blocks: Marked as references, not duplicated
- Column Layouts: Marked but flattened to single column
- Database Relations: Links preserved as IDs, not expanded
- Advanced Properties: Formulas and rollups show values, not formulas
- Interactive Elements: Buttons, embedded apps
- Real-time Collaboration: Comments, mentions (metadata only)
- Database Views: Only default view captured
- Page History: Only current version exported
- Permissions: All content exported without access restrictions
Issue: "NOTION_TOKEN not set"
# Solution: Make sure .env file exists and tokens are set
echo 'NOTION_TOKENS="your_token_here"' > .envIssue: "Failed to talk to Notion API"
- Check that your token is valid (starts with
ntn_orsecret_) - Verify you've shared pages with your integration
- Ensure your integration has the correct permissions
Issue: "No pages found for this integration"
- You must explicitly share pages with your integration (see Step 2)
- Go to Notion β Share β Invite your integration
Issue: "Failed to download image/file"
- Notion's file URLs expire after ~1 hour
- Run the converter immediately after backup
- Or use Notion-hosted URLs (choose 'N' for asset download)
Issue: "Rate limit exceeded"
- Notion has API rate limits (~3 requests/second)
- The scripts include built-in delays
- For very large workspaces, the export may take several hours
Issue: "Memory issues with large workspaces"
# Increase Node.js memory limit
node --max-old-space-size=4096 bulk-exporter.jsAfter export, verify:
- All expected workspaces have folders
- Page count matches expectations (check INDEX.md)
- Images display correctly in Markdown viewers
- Assets folder contains downloaded files
- No error messages in console output
- manifest.json shows
exportCompletedAttimestamp
Create a cron job for automatic backups:
# Edit crontab
crontab -e
# Add this line: Run every Sunday at 2 AM
0 2 * * 0 cd /path/to/notion-backup && /usr/bin/node bulk-exporter.js && /usr/bin/node json-to-markdown.jsCreate backup.sh for easy execution:
#!/bin/bash
set -e
echo "Starting Notion backup..."
node bulk-exporter.js
echo "Converting to Markdown..."
node json-to-markdown.js
echo "Creating archive..."
tar -czf notion-backup-$(date +%Y%m%d).tar.gz notion-markdown-export/
echo "β
Backup complete!"Make it executable:
chmod +x backup.sh
./backup.shExport specific pages by ID:
# When prompted, choose option 3 and enter page IDs:
# https://notion.so/page-name-abc123def456...
# Just copy the URL and the script will extract the IDCreate Dockerfile:
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
CMD ["npm", "run", "full-export"]Build and run:
docker build -t notion-exporter .
docker run -e NOTION_TOKENS="token1,token2" -v $(pwd)/output:/app/notion-markdown-export notion-exporterAfter exporting, you can import your Markdown files to:
- Wiki.js - Best for team wikis (git-based, searchable)
- Obsidian - Best for personal knowledge management
- Docusaurus - Best for public documentation
- MkDocs - Simple static site generator
- BookStack - Good for structured documentation
See the separate "IMPORT_GUIDE.md" for detailed instructions.
Q: Can I export multiple workspaces at once? A: Yes! Just provide all tokens comma-separated in NOTION_TOKENS.
Q: How long do exports take? A: Depends on workspace size. Typical: 5-30 minutes. Large workspaces: 1-2 hours.
Q: Will this work with Notion personal accounts? A: Yes, integrations work with all Notion account types.
Q: Do I need to keep Notion open during export? A: No, the scripts use Notion's API independently.
Q: Can I resume a failed export? A: Yes, the backup script tracks progress in manifest.json and skips already-exported pages.
Q: Will this export private pages? A: Only pages shared with your integration. You must explicitly share them.
Q: What about database entries? A: All pages in databases are exported as individual markdown files.
Q: Can I import back to Notion? A: Yes, Notion supports Markdown import, but you'll lose some metadata and structure.
Q: Are there any costs? A: No, this uses Notion's free API. No rate limits for personal use.
Q: What if I have 1000+ pages? A: The scripts handle large workspaces. Just increase Node.js memory if needed.
- Issues: Report bugs on GitHub
- Features: Suggest improvements via Issues
- Pull Requests: Contributions welcome!
MIT License - Feel free to use, modify, and distribute.
Happy exporting! π
For import guides and platform-specific instructions, see IMPORT_GUIDE.md
bulk-exporter.js