Skip to content

a2z2k26/notion-github-sync

Repository files navigation

 _   _  ___ _____ ___ ___  _   _    ____ ___ _____ _   _ _   _ ____
| \ | |/ _ \_   _|_ _/ _ \| \ | |  / ___|_ _|_   _| | | | | | | __ )
|  \| | | | || |  | | | | |  \| | | |  _ | |  | | | |_| | | | |  _ \
| |\  | |_| || |  | | |_| | |\  | | |_| || |  | | |  _  | |_| | |_) |
|_| \_|\___/ |_| |___\___/|_| \_|  \____|___| |_| |_| |_|\___/|____/

 ______   ___   _  ____
/ ___\ \ / / \ | |/ ___|
\___ \\ V /|  \| | |
 ___) || | | |\  | |___
|____/ |_| |_| \_|\____|

CI Node License: MIT


Sync issues both ways. GitHub ↔ Notion. Notion GitHub Sync is a Node.js library for using a Notion database as a project management surface for GitHub repositories. It includes bidirectional issue sync, rate-limited Notion API helpers, built-in PM database schemas, and a CLI for setup and common workflows.


Bidirectional sync, practical guardrails

  • GitHub → Notion: Open and recently-closed issues become Notion pages with status, labels, and a back-link to the issue.
  • Notion → GitHub: Tasks created in Notion get filed as GitHub issues. The Notion page is updated with the new issue number and URL.
  • Round-trip identity: Once linked, a row's GitHub Number is used as the stable identity for future syncs.
  • Three modes: bidirectional (default), github-to-notion, notion-to-github — switch per-run.

Built around the Notion API

  • Rate-limited HTTP client sized around Notion's documented request budget to reduce accidental 429s.
  • Exponential backoff with jitter on transient errors. Honors Retry-After when present.
  • Typed errors: AuthError, RateLimitError, NotFoundError, ValidationErrorinstanceof your way out of guesswork.
  • Familiar @notionhq/client shape — common SDK surfaces wrapped with rate limiting and retry behavior.

Lower-friction onboarding

  • Interactive setup wizard: npx notion-github-sync init walks through token, parent page, and connection verification.
  • Built-in PM schemas: Tasks, Sprints, Epics, and Projects database templates are available when you want a quick starting point.
  • Block factory: heading, paragraph, bulletList, todoList, callout, code — readable, composable, no manual JSON.
  • Single-command database creation: notion-github-sync create-db -k tasks -t "My Tasks".

Optional MCP routing

  • Bring your own MCP server: drop in a Notion MCP server and Bumba will auto-detect and route through it.
  • Detection across transports: HTTP, IPC socket, Claude Desktop config, or explicit env.
  • Graceful fallback: if MCP isn't configured or fails mid-call, the bridge can fall back to the direct Notion API. No MCP infrastructure is required to use the library.
  • Three modes: auto (default), mcp-only (strict), api-only (skip detection).

What's in the box

Module What it does
NotionClient Rate-limited HTTP client with retry and typed errors
NotionPublisher Page + database publisher with block factory and PM schemas
GitHubIssueBridge Bidirectional GitHub Issues ↔ Notion sync
NotionMCPBridge Optional MCP routing with API fallback
runWizard Interactive setup wizard
CLI init, verify, create-db, sync, mcp-status, config

Installation

(requires Node ≥ 14; tested on 18, 20, 22)

# Install from npm (recommended)
npm install notion-github-sync

# Or clone for local development
git clone https://github.com/a2z2k26/notion-github-sync
cd notion-github-sync
npm install

Setup

# 1. Configure your Notion integration
cp .env.example .env
# Add NOTION_API_KEY and NOTION_PARENT_PAGE_ID

# 2. Verify the connection
npx notion-github-sync verify

# 3. Or run the interactive wizard
npx notion-github-sync init

You'll need a Notion integration token and a parent page in your workspace shared with the integration. Full walkthrough: docs/notion-setup.md.


Environment

Create .env in your project root:

# Required
NOTION_API_KEY=<notion-token>                     # or ntn_...
NOTION_PARENT_PAGE_ID=<32-char-hex>

# For GitHub sync
GITHUB_TOKEN=<github-token>                          # PAT with `repo` scope
GITHUB_REPO=owner/name
NOTION_TASKS_DATABASE_ID=<32-char-hex>

# Optional
NOTION_DEBUG=true                             # verbose logging
NOTION_API_VERSION=2022-06-28                 # default Notion API version



Sync GitHub Issues into Notion

const { GitHubIssueBridge } = require('notion-github-sync');

const bridge = new GitHubIssueBridge({
  repo: 'owner/name',
  databaseId: process.env.NOTION_TASKS_DATABASE_ID,
  direction: 'bidirectional'
});

const result = await bridge.syncAll();
// {
//   fromGitHub: { created: 12, updated: 3, skipped: 0, errors: 0 },
//   toGitHub:   { created: 2,  updated: 5, skipped: 0, errors: 0 }
// }

Or just from the CLI

npx notion-github-sync sync -r owner/name -d <database-id>

Create a tasks database in one call

npx notion-github-sync create-db -k tasks -t "Engineering Tasks"

The database ships with Status, Priority, Assignee, Due Date, GitHub Issue, GitHub Number, Labels, and Last Synced — the core columns the bridge expects.


Build pages programmatically

const { NotionPublisher } = require('notion-github-sync');

const publisher = new NotionPublisher();
const { blocks } = publisher;

const page = await publisher.publishPage({
  title: 'Q1 Roadmap',
  blocks: [
    blocks.heading('Goals', 1),
    blocks.bulletList(['Ship v1', 'Onboard 10 customers', 'Launch docs']),
    blocks.callout('Target: end of March'),
    blocks.divider(),
    blocks.heading('Risks', 2),
    blocks.todoList([
      { text: 'API rate limits', checked: true },
      { text: 'Notion permissions', checked: false }
    ])
  ]
});

console.log(page.url);



Rate-limited Notion calls

const { NotionClient } = require('notion-github-sync');

const client = new NotionClient();  // reads NOTION_API_KEY

// Familiar @notionhq/client-style calls. Requests go through the limiter.
await client.databases.query({ database_id: '...' });
await client.pages.create({ parent: { page_id: '...' }, properties: { ... } });
await client.blocks.children.append({ block_id: '...', children: [...] });

The client retries 429 and 5xx automatically with exponential backoff and jitter. When it gives up, you get a typed error with a code and status:

const { errors } = require('notion-github-sync');

try {
  await publisher.publishPage({ ... });
} catch (err) {
  if (err instanceof errors.AuthError)        return rotateToken();
  if (err instanceof errors.RateLimitError)   return queueForLater(err.retryAfterMs);
  if (err instanceof errors.NotFoundError)    return reportMissing();
  throw err;
}

Validate config at startup

const { assertConfig } = require('notion-github-sync');
const config = assertConfig();  // throws ConfigError on bad/missing env vars



CLI reference

$ npx notion-github-sync --help

Commands:
  init                 Run interactive setup wizard
  verify               Verify Notion API key and parent page access
  create-db [options]  Create a built-in PM database (tasks/sprints/epics/projects)
  sync [options]       Run GitHub↔Notion issue sync
  config               Show current configuration (API key masked)

Examples

Runnable scripts in examples/:

node examples/01-verify-connection.js   # verify NOTION_API_KEY
node examples/02-create-page.js         # create a page with mixed blocks
node examples/03-create-database.js     # create a Tasks database
node examples/04-github-sync.js         # pull GitHub issues into Notion
node examples/05-mcp-bridge.js          # use MCP routing (with API fallback)



Architecture

┌────────────────────────────────────────────┐
│              Your code / CLI               │
└────────────────────────────────────────────┘
              │                  │
              ▼                  ▼
   ┌──────────────────┐  ┌──────────────────┐
   │ NotionPublisher  │  │ GitHubIssueBridge│
   └──────────────────┘  └──────────────────┘
              │                  │
              └─────────┬────────┘
                        ▼
              ┌──────────────────┐
              │  NotionClient    │
              │ rate-limit + retry│
              │  + typed errors  │
              └──────────────────┘
                        │
                        ▼
              ┌──────────────────┐
              │ @notionhq/client │
              └──────────────────┘

Performance

Operation Behavior
Burst calls Auto-throttled to ~3 req/s — well inside Notion's limit
Transient failure Retried with exp. backoff (default 4 attempts, 500ms→8s)
Rate limit Honors Retry-After header if present, jittered fallback otherwise
Typed errors Surface AuthError/RateLimitError/NotFoundError instead of raw Error



Notion-side requirements

For the GitHub sync, your Notion database needs these properties (the built-in tasks schema includes them all):

Property Type Purpose
Name (or any title) title Issue title
GitHub Issue url Link back to the issue
GitHub Number number Stable sync identity
Status select Maps to GitHub state
Last Synced date Set on every sync
Labels multi_select Mirrors GitHub labels

Generate a compatible database in one command:

npx notion-github-sync create-db -k tasks -t "Tasks"



Documentation


License

MIT License — see LICENSE for details.


Acknowledgement

Built on @notionhq/client and @octokit/rest.



BUMBA Multi-Agent Orchestration Framework

About

Bidirectional GitHub Issues and Notion project-management sync with CLI and API helpers.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors