nikhilbadyal/notion-backup
nikhilbadyal/notion-backup has 26 GitHub stars and 10 forks. GitHot measured 0 stars in 24 hours. Last checked 2026-11-15 12:00 UTC. Last push 2026-11-13.
What you can do
- 🚀 Export entire Notion workspace - Complete backup of all your data
- 📦 Pluggable storage backends - Local, rclone (cloud storage), with more coming
- 📧 Rich notifications - Apprise integration for 70+ notification services
- 🔧 Highly configurable - Customize every aspect via environment variables
Measured star growth
- in 24h
- 0
- in 7 days
- Collecting history
- in 15 days
- Collecting history
- in 1 month
- Collecting history
Net star changes over overlapping periods, not daily totals. Missing measurements are left blank. Stars measure attention, not software quality.
About
A powerful, modular Python tool to automatically back up your Notion workspace with pluggable storage backends and notification systems.
ProductivityREADME.md
OriginalNotion Backup - Modular Version
A powerful, modular Python tool to automatically back up your Notion workspace with pluggable storage backends and notification systems.
✨ Features
- 🚀 Export entire Notion workspace - Complete backup of all your data
- 📦 Pluggable storage backends - Local, rclone (cloud storage), with more coming
- 📧 Rich notifications - Apprise integration for 70+ notification services
- 🔧 Highly configurable - Customize every aspect via environment variables
- 📝 Detailed logging - Comprehensive logging for debugging and monitoring
- 🔒 Secure - Token-based authentication with Notion
- ⚡ Async/await support - Fast, concurrent operations
- 🧹 Automatic cleanup - Keep only the most recent backups
- 📊 Multiple export formats - Markdown or HTML output
- 🔄 Retry logic - Robust error handling with exponential backoff
- 📬 Smart notification management - Automatically mark export notifications as read
- 🔄 Export Recovery - Optional Redis integration to recover from rare Notion notification failures
🏗️ Architecture
The tool is built with a modular, pluggable architecture:
src/notion_backup/
├── core/ # Core backup logic
│ ├── client.py # Notion API client
│ └── backup.py # Main backup orchestrator
├── storage/ # Storage backends
│ ├── local.py # Local file storage
│ └── rclone.py # Rclone cloud storage
├── notifiers/ # Notification backends
│ └── apprise.py # Apprise notifications
├── config/ # Configuration management
└── utils/ # Utility functions
└── redis_client.py # Optional Redis client for export recovery
🚀 Quick Start
1. Installation
# Clone the repository
git clone https://github.com/nikhilbadyal/notion.git
cd notion-backup
# Install dependencies
pip install -r requirements.txt
2. Configuration
Copy the example environment file:
cp .env.example .env
3. Get Notion Credentials
- Login to Notion
- Open the browser developer console (
F12→ Console) - Paste the snippet from docs/get-credentials-console-snippet.md and press Enter
- Copy the printed values into your
.envfile:spaceId→NOTION_SPACE_IDtoken_v2cookie →NOTION_TOKEN_V2file_tokencookie →NOTION_FILE_TOKEN
4. Basic Configuration
Edit your .env file with your credentials:
# Required
NOTION_SPACE_ID=your_actual_space_id_here
NOTION_TOKEN_V2=your_actual_token_v2_here
NOTION_FILE_TOKEN=your_file_token
# Optional - defaults shown
STORAGE_BACKEND=local
EXPORT_TYPE=markdown
LOCAL_PATH=./downloads
5. Run Backup
# Simple backup
python main.py backup
# With debug logging
python main.py --debug backup
# List available backups
python main.py list
# Cleanup old backups (keep 5 most recent)
python main.py cleanup --keep 5
6. Verify Credentials
Before running a non-dry-run backup, the tool verifies token_v2 and the space ID via a lightweight API call — no export is triggered. Dry-run mode skips the Notion API check. This check runs automatically as part of the backup pre-flight, so confirmed invalid credentials fail fast before any export or recovery work begins. An HTTP 429 rate limit is treated as inconclusive and allows the run to continue with a warning.
You can also run the check on its own:
# Verify Notion credentials without triggering an export
python main.py test
If token_v2 or the space ID is invalid, the command exits with a non-zero status and a clear error message.
The file_token cookie (used for downloads) is checked only with a best-effort probe: an HTTP 403 produces a warning but does not fail the credential check. If downloads fail with HTTP 403, refresh NOTION_FILE_TOKEN from your browser and rerun; the backup session is preserved, but interactive runs prompt before resuming while non-interactive runs start fresh. To resume directly, run python main.py backup --resume. Notion cookies are sent only to HTTPS Notion hosts. External signed storage URLs are downloaded without them. If a cookie-authenticated download returns 403, the tool retries once without cookies in case the signed URL is self-sufficient. If the 403 persists after refreshing the token, the old export link may be bound to a previous account/session — start a fresh export with python main.py backup --skip-resume.
🐳 Docker
This project is fully containerized, allowing you to run the backup tool in a consistent and isolated environment.
Prerequisites
Running with Docker Compose
Configure Environment: Create a
.envfile by copying the.env.exampleand filling in your Notion credentials and other settings.Build and Run: Use
docker-composeto build the image and run the backup service.# Build the Docker image docker-compose build # Run a one-off backup docker-compose run --rm notion-backup
Scheduling Backups
To run backups on a schedule, you can use a standard cron job on your host machine to execute the docker-compose run command.
Example Cron Job (daily at 2 AM):
0 2 * * * cd /path/to/notion-backup && /usr/local/bin/docker-compose run --rm notion-backup
📦 Storage Backends
Local Storage
Store backups on local filesystem:
STORAGE_BACKEND=local
LOCAL_PATH=./downloads
MAX_BACKUPS=10
Rclone Storage
Store backups on any cloud provider supported by rclone:
STORAGE_BACKEND=rclone
RCLONE_REMOTE=mycloud
RCLONE_PATH=notion-backups
RCLONE_CONFIG_PATH=/path/to/rclone.conf
KEEP_LOCAL_BACKUP=true
📧 Notifications
Get notified about backup status via 70+ services using Apprise:
ENABLE_NOTIFICATIONS=true
NOTIFICATION_LEVEL=all
APPRISE_URLS=discord://webhook_id/webhook_token,mailto://user:[email protected][email protected]
Popular notification services:
- Discord:
discord://webhook_id/webhook_token - Slack:
slack://TokenA/TokenB/TokenC/Channel - Email:
mailto://user:[email protected][email protected] - Telegram:
tgram://bottoken/ChatID - Microsoft Teams:
msteams://TokenA/TokenB/TokenC/ - PushBullet:
pbul://accesstoken
See full list of supported services
⚙️ Configuration Reference
Required Settings
| Variable | Description |
|---|---|
NOTION_SPACE_ID |
Your Notion workspace ID |
NOTION_TOKEN_V2 |
Notion authentication token |
NOTION_FILE_TOKEN |
Notion file download token |
Export Settings
| Variable | Default | Options | Description |
|---|---|---|---|
EXPORT_TYPE |
markdown |
markdown, html |
Export format |
FLATTEN_EXPORT_FILETREE |
false |
true, false |
Flatten nested pages |
EXPORT_COMMENTS |
true |
true, false |
Include comments |
TIME_ZONE |
UTC |
Any valid TZ name | Timezone for export |
Storage Settings
| Variable | Default | Description |
|---|---|---|
STORAGE_BACKEND |
local |
local, rclone |
LOCAL_PATH |
./downloads |
Local storage directory |
MAX_BACKUPS |
None |
Max local backups to keep |
Rclone Settings
| Variable | Default | Description |
|---|---|---|
RCLONE_REMOTE |
None |
Rclone remote name |
RCLONE_PATH |
notion-backups |
Path on remote |
RCLONE_CONFIG_PATH |
None |
Rclone config file path |
KEEP_LOCAL_BACKUP |
true |
Keep local copy after upload |
Notification Settings
| Variable | Default | Description |
|---|---|---|
ENABLE_NOTIFICATIONS |
false |
Enable notifications |
NOTIFICATION_LEVEL |
all |
success, error, all, none |
APPRISE_URLS |
"" |
Comma-separated notification URLs |
NOTIFICATION_TITLE |
Notion Backup |
Notification title prefix |
Advanced Settings
| Variable | Default | Description |
|---|---|---|
MAX_RETRIES |
3 |
Max retry attempts |
RETRY_DELAY |
5 |
Delay between retries (seconds) |
DOWNLOAD_TIMEOUT |
300 |
Download timeout (seconds) |
MAX_EXPORT_WAIT_TIME |
3600 |
Max time to wait for the export to be ready (seconds). Bounds both task completion and download-URL availability — raise this for large workspaces that take longer to finalize |
EXPORT_POLL_INTERVAL |
15 |
Interval between export readiness checks (seconds) |
MAX_RETRY_DELAY |
300 |
Maximum delay between retries (seconds) |
MARK_NOTIFICATIONS_AS_READ |
true |
Mark export notifications as read after download |
ARCHIVE_NOTIFICATION |
false |
Archive export notification after upload |
Export Recovery Settings (Optional)
This feature uses Redis to recover from rare cases where a Notion export succeeds but the completion notification isn't received. When enabled, failed notifications are queued in Redis and processed on subsequent backup runs.
| Variable | Default | Description |
|---|---|---|
REDIS_HOST |
None | Redis server hostname (enables feature) |
REDIS_PORT |
6379 | Redis port |
REDIS_DB |
0 | Redis database number (0-15) |
REDIS_USERNAME |
None | Redis username (for ACL, Redis 6+) |
REDIS_PASSWORD |
None | Redis password (optional) |
REDIS_SSL |
false | Enable SSL/TLS for Redis connection |
REDIS_SSL_CA_CERTS |
None | Path to Redis CA certificate file |
REDIS_SSL_CERT_REQS |
"required" | SSL certificate requirements ('required', 'optional', 'none') |
Note: Redis is completely optional. If REDIS_HOST is not set, the feature is disabled and backups proceed normally. Install Redis via your package manager or Docker for production use. For TLS-enabled Redis servers (common in cloud providers), set REDIS_SSL=true and provide certificate details if required.
🔧 Command Line Usage
# Run backup (default command)
python main.py
python main.py backup
# List available backups
python main.py list
# Clean up old backups
python main.py cleanup --keep 5
# Test configuration
python main.py test
# Enable debug logging
python main.py --debug backup
🔌 Extending the System
Adding New Storage Backends
- Create a new class inheriting from
AbstractStorage - Implement required methods:
store,list_backups,cleanup_old_backups,test_connection - Register in
BackupManager._create_storage_backend()
Adding New Notifiers
- Create a new class inheriting from
AbstractNotifier - Implement required methods:
send_notification,test_connection - Register in
BackupManager._create_notifier()
📋 Example Configurations
Personal Local Backup
# .env
NOTION_SPACE_ID=your_space_id
NOTION_TOKEN_V2=your_token
NOTION_FILE_TOKEN=your_file_token
STORAGE_BACKEND=local
LOCAL_PATH=./backups
MAX_BACKUPS=7
ENABLE_NOTIFICATIONS=true
APPRISE_URLS=mailto://user:[email protected][email protected]
MARK_NOTIFICATIONS_AS_READ=true
Cloud Backup with Discord Notifications
# .env
NOTION_SPACE_ID=your_space_id
NOTION_TOKEN_V2=your_token
NOTION_FILE_TOKEN=your_file_token
STORAGE_BACKEND=rclone
RCLONE_REMOTE=gdrive
RCLONE_PATH=backups/notion
KEEP_LOCAL_BACKUP=false
ENABLE_NOTIFICATIONS=true
APPRISE_URLS=discord://webhook_id/webhook_token
NOTIFICATION_LEVEL=all
Enterprise Setup
# .env
NOTION_SPACE_ID=your_space_id
NOTION_TOKEN_V2=your_token
NOTION_FILE_TOKEN=your_file_token
STORAGE_BACKEND=rclone
RCLONE_REMOTE=s3backup
RCLONE_PATH=company-notion-backups
RCLONE_ADDITIONAL_ARGS=--transfers=8,--checkers=16
ENABLE_NOTIFICATIONS=true
APPRISE_URLS=slack://TokenA/TokenB/TokenC/general,mailto://[email protected][email protected]
NOTIFICATION_LEVEL=all
MAX_RETRIES=5
RETRY_DELAY=10
🤖 Automation
Cron Job (Linux/macOS)
# Daily backup at 2 AM
0 2 * * * cd /path/to/notion-backup && python main.py backup
# Weekly cleanup (keep 30 backups)
0 3 * * 0 cd /path/to/notion-backup && python main.py cleanup --keep 30
Windows Task Scheduler
Create a task that runs:
Program: python
Arguments: /path/to/notion-backup/main.py backup
Start in: /path/to/notion-backup
🐛 Troubleshooting
Common Issues
"Configuration Error: Field required"
- Ensure all required environment variables are set
- Check
.envfile syntax
"Storage connection failed"
- For local: Check directory permissions
- For rclone: Test rclone config with
rclone lsd remote:
"Export task failed"
- Token may have expired - get new tokens from browser
- Check network connectivity
- Verify space ID is correct
"Failed to extract download URL" / "url_not_ready" on large workspaces
For large workspaces, Notion can report the export task as complete in
getTasks while the export-completed notification (which carries the
download link) is still being prepared. The tool now keeps polling for the
download URL for up to MAX_EXPORT_WAIT_TIME seconds (default 1 hour) within
the same run, so it should complete on its own. If you still see
url_not_ready, the export is taking longer than that window — raise
MAX_EXPORT_WAIT_TIME (and optionally lower EXPORT_POLL_INTERVAL for more
frequent checks). The backup session is preserved, so a subsequent run will
resume the same export rather than starting over.
"Notification failed"
- Test notification URLs independently
- Check URL format in Apprise documentation
Debug Mode
Enable debug logging for detailed troubleshooting:
python main.py --debug backup
Debug output includes:
- Environment info (Python version, platform,
requestsversion) - Export task IDs, task states, page counts, and retry progress
- HTTP status codes and download hosts/paths with query signatures redacted
- Rate-limit events when Notion throttles a request
Debug output never includes token characters, response bodies, or signed URL query parameters.
Testing Configuration
# Test all connections
python main.py test
# Test specific components
python -c "from src.notion_backup.config import Settings; s=Settings(); print('Config loaded successfully')"
🔒 Security Notes
- Store
.envfile securely, never commit to version control NOTION_TOKEN_V2provides full workspace access- Tokens expire after ~1 year or on logout
- Use environment variables in production instead of
.envfiles - Regularly rotate tokens and review access
📄 License
This project is licensed under the MIT License - see the LICENSE file for details.
🤝 Contributing
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request