DEV Community

Mamoth Surfing
Mamoth Surfing

Posted on

Managing Notion Databases as Code with notionctl

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:

  1. Creating each database with raw JSON payloads
  2. Storing UUIDs to wire up relations
  3. Handling the chicken-and-egg problem (you need Database B's ID to create Database A's relation, but B doesn't exist yet)
  4. 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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Then pull the changes back:

$ notionctl sync
✓ Synced 2 database(s) from Notion → notionctl.yaml

Review changes with 'git diff' before committing.
Enter fullscreen mode Exit fullscreen mode

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 }}
Enter fullscreen mode Exit fullscreen mode

Templates

Start fast with pre-built templates:

cp templates/project-tracker.yaml notionctl.yaml
# Edit parent_page_id, then:
notionctl plan && notionctl apply
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Multi-Environment Support

Use environment variables in your config for staging/production workflows:

databases:
  - name: Projects
    parent_page_id: "${NOTION_PAGE_ID}"
Enter fullscreen mode Exit fullscreen mode
NOTION_PAGE_ID=abc123 notionctl apply   # staging
NOTION_PAGE_ID=def456 notionctl apply   # production
Enter fullscreen mode Exit fullscreen mode

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


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)