notion-multi-env-setup
Configure Notion integrations across development, staging, and production environments. Use when setting up multi-environment deployments, managing per-environment tokens, or implementing environment-specific Notion configurations. Trigger with phrases like "notion environments", "notion staging", "notion dev prod", "notion environment setup", "notion config by env".
What this skill does
# Notion Multi-Environment Setup
## Overview
Configure separate Notion integrations for development, staging, and production. Each environment uses its own integration token, targets different databases, and applies environment-appropriate log levels and timeouts. This prevents dev data leaking into prod, isolates testing, and enforces least-privilege per tier.
## Prerequisites
- Notion workspace(s) per environment (one workspace can serve dev/staging via separate integrations)
- `@notionhq/client` v2+ installed (`npm install @notionhq/client`)
- Python alternative: `notion-client` (`pip install notion-client`)
- Secret management platform (AWS Secrets Manager, GCP Secret Manager, or HashiCorp Vault)
- CI/CD pipeline with per-environment variable injection
## Instructions
### Step 1: Create Per-Environment Integrations and Env-Aware Client
Create separate integrations at https://www.notion.so/my-integrations with scoped capabilities:
| Environment | Integration Name | Capabilities | Timeout | Log Level |
|-------------|-----------------|--------------|---------|-----------|
| Development | `my-app-dev` | All (read+update+insert+delete) | 60s | DEBUG |
| Staging | `my-app-staging` | Read + Update + Insert | 30s | WARN |
| Production | `my-app-prod` | Minimum required only | 30s | ERROR |
**TypeScript — environment-aware client factory:**
```typescript
import { Client, LogLevel } from '@notionhq/client';
interface NotionEnvConfig {
token: string;
databaseIds: Record<string, string>;
logLevel: LogLevel;
timeoutMs: number;
maxRetries: number;
}
const ENV_DEFAULTS: Record<string, Omit<NotionEnvConfig, 'token' | 'databaseIds'>> = {
development: { logLevel: LogLevel.DEBUG, timeoutMs: 60_000, maxRetries: 0 },
staging: { logLevel: LogLevel.WARN, timeoutMs: 30_000, maxRetries: 2 },
production: { logLevel: LogLevel.ERROR, timeoutMs: 30_000, maxRetries: 3 },
};
function getConfig(): NotionEnvConfig {
const env = process.env.NODE_ENV || 'development';
const defaults = ENV_DEFAULTS[env] ?? ENV_DEFAULTS.development;
const token = process.env.NOTION_TOKEN;
if (!token) {
throw new Error(
`NOTION_TOKEN not set for "${env}". ` +
`Set it in .env.${env} or your secret manager.`
);
}
return {
token,
databaseIds: {
tasks: process.env.NOTION_TASKS_DB_ID!,
users: process.env.NOTION_USERS_DB_ID!,
logs: process.env.NOTION_LOGS_DB_ID!,
},
...defaults,
};
}
export function createNotionClient(): Client {
const config = getConfig();
return new Client({
auth: config.token,
logLevel: config.logLevel,
timeoutMs: config.timeoutMs,
});
}
export function getDatabaseId(name: string): string {
const config = getConfig();
const id = config.databaseIds[name];
if (!id) {
throw new Error(
`Database ID not configured for "${name}". ` +
`Set NOTION_${name.toUpperCase()}_DB_ID in your environment.`
);
}
return id;
}
```
**Python — environment-aware client factory:**
```python
import os
from notion_client import Client
ENV_CONFIGS = {
"development": {"timeout_ms": 60_000, "log_level": "DEBUG"},
"staging": {"timeout_ms": 30_000, "log_level": "WARNING"},
"production": {"timeout_ms": 30_000, "log_level": "ERROR"},
}
def create_notion_client() -> Client:
env = os.getenv("APP_ENV", "development")
token = os.getenv("NOTION_TOKEN")
if not token:
raise ValueError(f"NOTION_TOKEN not set for '{env}'")
cfg = ENV_CONFIGS.get(env, ENV_CONFIGS["development"])
return Client(auth=token, timeout_ms=cfg["timeout_ms"])
def get_database_id(name: str) -> str:
env_var = f"NOTION_{name.upper()}_DB_ID"
db_id = os.getenv(env_var)
if not db_id:
raise ValueError(f"{env_var} not set")
return db_id
```
### Step 2: Secret Management and Environment Files
**Local development — `.env` files (git-ignored):**
```bash
# .env.development
NOTION_TOKEN=ntn_dev_xxxxxxxxxxxxxxxxxxxxx
NOTION_TASKS_DB_ID=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
NOTION_USERS_DB_ID=ffffffff-gggg-hhhh-iiii-jjjjjjjjjjjj
NOTION_LOGS_DB_ID=11111111-2222-3333-4444-555555555555
# .env.staging
NOTION_TOKEN=ntn_staging_xxxxxxxxxxxxxxxxx
NOTION_TASKS_DB_ID=66666666-7777-8888-9999-000000000000
NOTION_USERS_DB_ID=aaaaaaaa-1111-2222-3333-444444444444
# Production tokens NEVER stored as files — use secret manager
```
**AWS Secrets Manager:**
```bash
# Store production secrets
aws secretsmanager create-secret \
--name "notion/production" \
--secret-string '{
"token": "ntn_prod_xxxxxxxxxxxxxxxxx",
"tasks_db": "abcdefab-cdef-abcd-efab-cdefabcdefab",
"users_db": "12345678-abcd-efgh-ijkl-123456789012"
}'
# Retrieve in application
aws secretsmanager get-secret-value --secret-id notion/production --query SecretString --output text
```
**GCP Secret Manager:**
```bash
# Store each secret individually
echo -n "ntn_prod_xxxxxxxxxxxxxxxxx" | gcloud secrets create notion-token-prod --data-file=-
echo -n "abcdefab-cdef-abcd-efab-cdefabcdefab" | gcloud secrets create notion-tasks-db-prod --data-file=-
# Inject into Cloud Run service
gcloud run deploy my-service \
--set-secrets=NOTION_TOKEN=notion-token-prod:latest,NOTION_TASKS_DB_ID=notion-tasks-db-prod:latest
```
**HashiCorp Vault:**
```bash
vault kv put secret/notion/production \
token=ntn_prod_xxxxxxxxxxxxxxxxx \
tasks_db_id=abcdefab-cdef-abcd-efab-cdefabcdefab
```
### Step 3: Environment Guards and CI/CD
**Environment guards — prevent cross-environment mistakes:**
```typescript
function requireEnvironment(required: 'development' | 'staging' | 'production') {
const current = process.env.NODE_ENV || 'development';
if (current !== required) {
throw new Error(
`This operation requires "${required}" but running in "${current}". Aborting.`
);
}
}
// Block destructive operations in production
function requireNonProduction() {
if (process.env.NODE_ENV === 'production') {
throw new Error('Destructive operation blocked in production');
}
}
// Usage
async function seedTestData(notion: Client, dbId: string) {
requireNonProduction(); // Throws in production
await notion.pages.create({
parent: { database_id: dbId },
properties: {
Name: { title: [{ text: { content: 'Test Record' } }] },
Status: { select: { name: 'Test' } },
},
});
}
async function runMigration(notion: Client) {
requireEnvironment('production'); // Only runs in production
// ... migration logic
}
```
**Startup validation — fail fast on missing config:**
```typescript
function validateNotionConfig() {
const env = process.env.NODE_ENV || 'development';
const required = ['NOTION_TOKEN', 'NOTION_TASKS_DB_ID'];
const missing = required.filter(v => !process.env[v]);
if (missing.length > 0) {
throw new Error(
`Missing env vars for "${env}": ${missing.join(', ')}. ` +
`Check .env.${env} or your secret manager.`
);
}
// Validate token prefix matches environment
const token = process.env.NOTION_TOKEN!;
if (env === 'production' && token.includes('dev')) {
throw new Error('Production detected but NOTION_TOKEN contains "dev" — likely wrong token');
}
console.log(`Notion configured for ${env} (token: ${token.slice(0, 8)}...)`);
}
```
**CI/CD deployment with per-environment secrets:**
```yaml
# .github/workflows/deploy.yml
jobs:
deploy-staging:
if: github.ref == 'refs/heads/develop'
environment: staging
env:
NODE_ENV: staging
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN_STAGING }}
NOTION_TASKS_DB_ID: ${{ secrets.NOTION_TASKS_DB_ID_STAGING }}
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm test
- run: npm run deploy:staging
deploy-production:
if: github.ref == 'refs/heads/main'
environment: production
env:
NODE_ENV: production
NOTION_TOKEN: ${{ secrets.NOTION_TOKEN_PROD }}
NOTION_TASKS_DB_ID: ${{ secrets.NOTION_TASKS_DBRelated in Productivity
gitea-workflow
IncludedOrchestrate agile development workflows for Gitea repositories using the tea CLI. Use when working with Gitea-hosted repos and asking to 'run the workflow', 'continue working', 'what's next', 'complete the task cycle', 'start my day', 'end the sprint', 'implement the next task', or wanting guided step-by-step development assistance. Keywords: workflow, orchestrate, agile, task cycle, sprint, daily, implement, review, PR, standup, retrospective, gitea, tea.
microsoft-graph-gateway
IncludedRoute Microsoft Graph work in this workspace. Use when users want to read or write Outlook mail, calendar events, contacts, OneDrive or SharePoint files, Teams, Planner, To Do, users, groups, directory data, or arbitrary Microsoft Graph endpoints from VS Code. Prefer WorkIQ for common read scenarios. Use Microsoft Graph for write actions and gap-read scenarios that need exact Graph properties, filters, permissions, or endpoints.
copilotkit
IncludedUse when building with CopilotKit — setup, development, integrations, debugging, upgrading, or contributing. Routes to the appropriate specialized skill based on the task.
wordly-wisdom
IncludedProvides calibrated decision analysis using Charlie Munger-style multiple mental models, inversion, incentive mapping, circle-of-competence checks, misjudgment audits, second-order effects, and forecast updates. Use when the user asks for an oracle take, a hard call, a decision memo, a premortem, an outside view, a red-team, a sanity-check, what am I missing, think this through, or wants a strategy, hire, investment, plan, product, partnership, or major life choice analysed. Avoid for simple factual lookups or time-sensitive legal, medical, or market questions without fresh evidence.
swain-session
IncludedSession management and project status dashboard. Owns the full session lifecycle (start/work/close/resume), focus lane, bookmarks, worktree detection, and tab naming. Also serves as the project status dashboard — shows active epics, progress, actionable next steps, blocked items, tasks, GitHub issues, and recommendations. Worktree creation is deferred to swain-do task dispatch (SPEC-195). Triggers on: 'session', 'status', 'what's next', 'dashboard', 'overview', 'where are we', 'what should I work on', 'show me priorities', 'bookmark', 'focus on', 'session info'.
gandi
IncludedComprehensive Gandi domain registrar integration for domain and DNS management. Register and manage domains, create/update/delete DNS records (A, AAAA, CNAME, MX, TXT, SRV, and more), configure email forwarding and aliases, check SSL certificate status, create DNS snapshots for safe rollback, bulk update zone files, and monitor domain expiration. Supports multi-domain management, zone file import/export, and automated DNS backups. Includes both read-only and destructive operations with safety controls.