Agent Skills: Notion Local Dev Loop

|

UncategorizedID: jeremylongshore/claude-code-plugins-plus-skills/notion-local-dev-loop

Install this agent skill to your local

pnpm dlx add-skill https://github.com/jeremylongshore/claude-code-plugins-plus-skills/tree/HEAD/plugins/saas-packs/notion-pack/skills/notion-local-dev-loop

Skill Files

Browse the full folder contents for notion-local-dev-loop.

Download Skill

Loading file tree…

plugins/saas-packs/notion-pack/skills/notion-local-dev-loop/SKILL.md

Skill Metadata

Name
notion-local-dev-loop
Description
|

Notion Local Dev Loop

Overview

Set up a fast, reproducible local development workflow for Notion integrations. This skill creates a dedicated dev integration with its own token, structures the project for testability, mocks the Notion SDK in unit tests, and runs gated integration tests against a sandboxed dev workspace — so production data stays safe while you iterate quickly.

Prerequisites

  • Completed notion-install-auth setup (you have a working Notion integration)
  • Node.js 18+ with npm/pnpm, or Python 3.10+
  • A Notion workspace where you can create test pages and databases

Authentication

This workflow uses a separate dev integration token, never the production token. The Notion SDK reads NOTION_TOKEN from the environment automatically. Store the dev token (prefix ntn_) in a git-ignored .env.development and commit a .env.example template so teammates know which variables to fill in. Token creation is covered by the notion-install-auth skill; Step 1 below wires it into the dev sandbox.

Instructions

Step 1: Create a Dev Integration and Workspace Sandbox

Create a separate integration exclusively for development so writes can never touch production data.

  1. Go to Settings & Members > Connections > Develop or manage integrations (or visit developers.notion.com)
  2. Click New integration and name it My App — Dev
  3. Copy the token (starts with ntn_) into .env.development
  4. Create a dedicated Dev Workspace page (or a top-level "Dev Testing" page) and share it with the dev integration
  5. Inside that page, create test databases that mirror your production schema
# .env.development — git-ignored, dev only
NOTION_TOKEN=ntn_dev_xxxxxxxxxxxxxxxxxxxx
NOTION_TEST_DATABASE_ID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
NOTION_TEST_PAGE_ID=ffffffff-0000-1111-2222-333333333333

# .env.example — commit this as a template
NOTION_TOKEN=ntn_your_dev_token_here
NOTION_TEST_DATABASE_ID=your_test_db_id
NOTION_TEST_PAGE_ID=your_test_page_id

Recommended project layout — a notion/ module the app imports, and split unit/integration tests:

my-notion-project/
├── src/
│   ├── notion/
│   │   ├── client.ts          # Singleton with retry + rate-limit awareness
│   │   ├── queries.ts         # Database query wrappers
│   │   └── helpers.ts         # Property extractors, rich text builders
│   └── index.ts
├── tests/
│   ├── unit/
│   │   └── notion.test.ts     # Mocked SDK tests
│   └── integration/
│       └── notion.test.ts     # Live API tests (gated)
├── .env.development            # Dev token (git-ignored)
├── .env.example                # Template for team
├── .gitignore
├── package.json
├── tsconfig.json
└── vitest.config.ts

Step 2: Configure the Client with Retry and Rate-Limit Handling

The Notion API enforces a hard limit of 3 requests per second across all pricing tiers, so build retry logic into a shared singleton client from day one. The essential shape:

// src/notion/client.ts — singleton + exponential-backoff retry on HTTP 429
export function getNotionClient(): Client { /* cache one Client instance */ }
export async function withRetry<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> { /* backoff */ }

For the complete client.ts (backoff math, retry-after header parsing, debug logging) plus the package.json scripts (dev hot reload, test, test:integration, typecheck) and dev dependencies, see client and config reference.

Step 3: Write Unit Tests with a Mocked SDK, plus Gated Integration Tests

Unit tests mock the entire @notionhq/client module so they run instantly with no network calls. Integration tests hit the real API but are gated behind an environment variable and target only the dev workspace:

// tests/unit/notion.test.ts — mock the SDK so tests run offline
vi.mock('@notionhq/client', () => ({ Client: vi.fn().mockImplementation(() => ({ /* stubbed API */ })) }));

// tests/integration/notion.test.ts — only run when INTEGRATION=true, against the dev workspace
describe.skipIf(!process.env.INTEGRATION)('Notion Integration (live API)', () => { /* live calls */ });

Run units with npm test (or pnpm test) and the gated live suite with npm run test:integration. For the full mocked query/pagination tests, the live connect/query/create-and-archive cleanup tests, and the vitest.config.ts, see testing reference.

Output

After completing these steps you will have:

  • A dedicated dev integration with its own token, isolated from production
  • A singleton client with built-in retry logic for the 3 req/s rate limit
  • Unit tests that run instantly using mocked @notionhq/client
  • Integration tests gated behind INTEGRATION=true, targeting dev-only pages
  • Hot reload via tsx watch for rapid iteration
  • Type checking via tsc --noEmit

Error Handling

| Error | Cause | Solution | | ------- | ------- | ---------- | | NOTION_TOKEN undefined | Missing .env.development or not loaded | Run cp .env.example .env.development and fill in dev token | | 401 Unauthorized | Token invalid or integration not connected to page | Re-share the dev page with the dev integration | | 404 Not found (database/page) | Test DB not shared with dev integration | Open DB in Notion > ... > Connections > add your dev integration | | Mock not intercepting calls | vi.mock() not at file top level | Move vi.mock('@notionhq/client', ...) above all imports | | 429 Rate Limited | Exceeded 3 req/s | Use withRetry wrapper; add delay between batch operations | | Integration tests timeout | Slow API under rate limits | Increase testTimeout in vitest config; reduce test data volume | | baseUrl connection refused | Proxy or mock server not running | Verify proxy is up; remove baseUrl override for direct API access |

Examples

Minimal TypeScript smoke test to confirm the dev token and database access are wired correctly:

import { Client } from '@notionhq/client';

const notion = new Client({ auth: process.env.NOTION_TOKEN });
const { results } = await notion.users.list({});
console.log(`Connected. ${results.length} user(s) in workspace.`);

For the full TypeScript smoke test (with dev-database verification) and the Python equivalent using notion-client plus a pytest mocking example, see examples reference.

Resources

Next Steps

Once the dev loop is green, see the notion-sdk-patterns skill for production-ready query helpers, pagination utilities, and property extraction functions to build on this foundation.