If you've ever tried to automate Notion databases — setting up relations, managing IDs across workspaces, or keeping databases consistent across environments — you know the pain. Database IDs change when you copy, differ between workspaces, and are impossible to read in scripts.
I built notionctl to fix this: declare your databases in YAML, reference relations by name, and sync everything with one command.
The Problem
Let's say you have a project tracker with Projects, Milestones, and Tasks databases — all linked by relations. Setting this up via the Notion API means:
- Creating each database with raw JSON payloads
- Storing UUIDs to wire up relations
- Handling the chicken-and-egg problem (you need Database B's ID to create Database A's relation, but B doesn't exist yet)
- Manually tracking what's deployed vs. what's changed
It's painful, error-prone, and impossible to version control.
The Solution: Declare, Don't Script
With notionctl, you write YAML:
version: "1"
databases:
- name: Projects
parent_page_id: "abc123..."
properties:
Name:
type: title
Status:
type: status
options:
- name: Not Started
color: default
- name: In Progress
color: blue
- name: Done
color: green
tasks:
type: relation
relation: Tasks # ← by name, not UUID
total_estimate:
type: rollup
relation: tasks
rollup_property: estimate
function: sum
- name: Tasks
parent_page_id: "abc123..."
properties:
Name:
type: title
project:
type: relation
relation: Projects # ← two-way relation
estimate:
type: number
format: number
Then run:
$ notionctl plan
Plan: 2 action(s)
+ create "Projects"
+ property "Name" (title)
+ property "Status" (status)
+ property "tasks" (relation)
+ property "total_estimate" (rollup)
+ create "Tasks"
+ property "Name" (title)
+ property "project" (relation)
+ property "estimate" (number)
$ notionctl apply
✓ create "Projects"
→ created with ID 1a2b3c
→ relations linked
✓ create "Tasks"
→ created with ID 4d5e6f
→ relations linked
Done. State saved to .notionctl/state.json
notionctl handles the two-pass relation resolution automatically — no UUID juggling required.
Key Features
19 Property Types (100% Coverage)
Every Notion property type is supported: title, rich_text, number, select, multi_select, status, relation, rollup, formula, checkbox, date, url, email, phone_number, created_time, last_edited_time, people, files, and unique_id.
Drift Detection
Someone edited a database directly in Notion? Detect it:
$ notionctl diff
✓ "Projects" — in sync
⚠ "Tasks" — drifted
+ property "Priority" (select): in config but not in Notion
- property "OldField" (rich_text): in Notion but not in config
Then pull the changes back:
$ notionctl sync
✓ Synced 2 database(s) from Notion → notionctl.yaml
Review changes with 'git diff' before committing.
CI/CD Ready
Automate with GitHub Actions:
on:
push:
branches: [main]
paths: ['notionctl.yaml']
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: brew install radityajay/tap/notionctl
- run: notionctl apply --auto-approve
env:
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }}
Templates
Start fast with pre-built templates:
cp templates/project-tracker.yaml notionctl.yaml
# Edit parent_page_id, then:
notionctl plan && notionctl apply
Available: CRM, inventory, project tracker, bug tracker. Or contribute your own — no Go required!
Install
# Homebrew (macOS/Linux)
brew install radityajay/tap/notionctl
# Go
go install github.com/radityajay/notionctl@latest
# Binary — download from GitHub Releases
# https://github.com/radityajay/notionctl/releases
Multi-Environment Support
Use environment variables in your config for staging/production workflows:
databases:
- name: Projects
parent_page_id: "${NOTION_PAGE_ID}"
NOTION_PAGE_ID=abc123 notionctl apply # staging
NOTION_PAGE_ID=def456 notionctl apply # production
All Commands
| Command | Description |
|---|---|
notionctl init |
Generate config (interactive template picker) |
notionctl import |
Import existing databases from Notion |
notionctl plan |
Preview changes |
notionctl apply |
Create/update/destroy databases |
notionctl diff |
Detect drift from manual Notion edits |
notionctl sync |
Pull Notion state back into YAML |
notionctl validate |
Validate config offline |
notionctl fmt |
Auto-format config |
notionctl list |
Show managed databases and IDs |
Links
- GitHub: github.com/radityajay/notionctl
- Releases: github.com/radityajay/notionctl/releases
notionctl is MIT-licensed and open for contributions. If you have a Notion database setup you use often, consider adding it as a template — it's a single YAML file, no Go required.
Top comments (0)