{"id":43806805,"url":"https://github.com/ran-codes/notion-sync","last_synced_at":"2026-04-25T00:01:39.470Z","repository":{"id":336692576,"uuid":"1148116964","full_name":"ran-codes/notion-sync","owner":"ran-codes","description":null,"archived":false,"fork":false,"pushed_at":"2026-02-23T22:25:48.000Z","size":5625,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-02-24T05:22:48.726Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ran-codes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-02-02T15:44:57.000Z","updated_at":"2026-02-23T22:25:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ran-codes/notion-sync","commit_stats":null,"previous_names":["ran-codes/notion-sync"],"tags_count":7,"template":false,"template_full_name":null,"purl":"pkg:github/ran-codes/notion-sync","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ran-codes%2Fnotion-sync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ran-codes%2Fnotion-sync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ran-codes%2Fnotion-sync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ran-codes%2Fnotion-sync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ran-codes","download_url":"https://codeload.github.com/ran-codes/notion-sync/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ran-codes%2Fnotion-sync/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32245151,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-24T13:21:15.438Z","status":"ssl_error","status_checked_at":"2026-04-24T13:21:15.005Z","response_time":64,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-02-05T23:02:28.535Z","updated_at":"2026-04-25T00:01:39.457Z","avatar_url":"https://github.com/ran-codes.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# notion-sync\n\nCLI tool to sync Notion databases to local Markdown files with YAML frontmatter.\n\nGiven a Notion database ID, notion-sync fetches all entries via the Notion API and writes them to `.md` files on disk. Each file gets YAML frontmatter containing the Notion ID, URL, edit timestamp, and all property values. On subsequent runs it compares `last_edited_time` and only re-syncs entries that changed.\n\n## Install\n\n### Install script (macOS / Linux)\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/ran-codes/notion-sync/main/scripts/install.sh | bash\n```\n\n### Scoop (Windows)\n\n```powershell\n## Install Scoop if you don't have it: https://scoop.sh\n# irm get.scoop.sh | iex\nscoop bucket add notion-sync https://github.com/ran-codes/notion-sync\nscoop install notion-sync\n```\n\n### Manual download\n\nDownload the binary for your platform from [GitHub Releases](https://github.com/ran-codes/notion-sync/releases), rename it to `notion-sync` (or `notion-sync.exe` on Windows), and add it to your PATH.\n\n## Update\n\n### Install script (macOS / Linux)\n\nRe-run the install script — it always fetches the latest release:\n\n```sh\ncurl -fsSL https://raw.githubusercontent.com/ran-codes/notion-sync/main/scripts/install.sh | bash\n```\n\n### Scoop (Windows)\n\n```powershell\nscoop update\nscoop update notion-sync\n```\n\n### Manual\n\nDownload the latest binary from [GitHub Releases](https://github.com/ran-codes/notion-sync/releases) and replace the existing one.\n\n## Usage\n\n```sh\n# Store your API key (saved in OS keychain)\nnotion-sync config set apiKey \u003cyour-notion-api-key\u003e\n\n# Import databases into an output folder\nnotion-sync import \u003cdatabase-id-A\u003e --output ./my-notes\nnotion-sync import \u003cdatabase-id-B\u003e --output ./my-notes\n\n# Refresh (incremental — only changed entries)\nnotion-sync refresh ./my-notes/Database\\ A\n\n# Force refresh (resync everything)\nnotion-sync refresh ./my-notes/Database\\ A --force\n\n# List synced databases\nnotion-sync list ./my-notes\n```\n\nThe `--output` folder is a **workspace**. Each database gets a subfolder:\n\n```\nmy-notes/                        ← workspace (--output target)\n├── Database A/\n│   ├── _database.json\n│   ├── Page One.md\n│   └── Page Two.md\n└── Database B/\n    ├── _database.json\n    ├── Entry Alpha.md\n    └── Entry Beta.md\n```\n\n## Prerequisites\n\n- A **Notion integration** with access to the databases you want to sync\n\n### Creating a Notion integration\n\n1. Go to [notion.so/my-integrations](https://www.notion.so/my-integrations)\n2. Click \"New integration\"\n3. Give it a name (e.g. \"notion-sync\") and select a workspace\n4. Copy the **Internal Integration Secret** (starts with `ntn_`)\n5. In Notion, open the database you want to sync\n6. Click the `...` menu \u003e \"Connections\" \u003e add your integration\n\n### Finding your database ID\n\nOpen the database as a **full page** in Notion. The URL will look like:\n\n```\nhttps://www.notion.so/yourworkspace/abc123def4567890abcdef1234567890?v=...\n```\n\nThe database ID is the 32-character hex string after your workspace name — in this example, `abc123def4567890abcdef1234567890`. You can pass it with or without dashes; notion-sync accepts both formats as well as the full URL.\n\n## Commands\n\n```sh\nnotion-sync import \u003cdatabase-id\u003e [--out \u003cfolder\u003e] [--api-key \u003ckey\u003e]\nnotion-sync refresh \u003cfolder\u003e [--force] [--api-key \u003ckey\u003e]\nnotion-sync list [\u003cfolder\u003e]\nnotion-sync config set \u003ckey\u003e \u003cvalue\u003e\n```\n\n| Command                   | Description                               |\n| ------------------------- | ----------------------------------------- |\n| `import`                  | First-time import of a Notion database    |\n| `refresh`                 | Incremental update (only changed entries) |\n| `refresh --force`         | Full resync ignoring timestamps           |\n| `list`                    | Show all synced databases in a folder     |\n| `config set apiKey \u003ckey\u003e` | Store API key in OS keychain              |\n\n## Architecture\n\n```\ncmd/notion-sync/         # CLI entry point\ninternal/\n├── notion/              # API client (rate limit, retry)\n├── sync/                # Core sync logic\n├── markdown/            # Block → Markdown conversion\n├── frontmatter/         # YAML parse/write\n└── config/              # Keyring + config file\n```\n\n## Development\n\n```sh\ngo build ./cmd/notion-sync   # Build binary\n```\n\n### Testing\n\n```sh\n# Unit + integration tests (mock client, no API needed)\ngo test ./...\n\n# System tests (hit real Notion API, require API key)\n/test-single-datasource-db        # single data source lifecycle\n/test-double-datasource-db        # multi-source layout + edge cases\n\n# Everything together (unit → single → double, sequential)\n/test\n```\n\n## Documentation\n\nSee [CLAUDE.md](CLAUDE.md) for implementation details, how to add block/property types.\n\n## Key design decisions\n\n- **Incremental sync** -- compares `last_edited_time` from frontmatter and skips unchanged entries\n- **Force refresh** -- `--force` flag bypasses timestamp checks to resync all entries (useful when database schema changes)\n- **Soft deletes** -- entries removed from a Notion database get `notion-deleted: true` in their frontmatter rather than being deleted from disk\n- **Two orchestration functions** -- `freshDatabaseImport()` for first-time imports, `refreshDatabase()` for incremental updates with diff-based optimization\n- **Database metadata file** -- each synced database folder contains `_database.json` with metadata (database ID, title, URL, last sync time, entry count), enabling `refreshDatabase()` to work from just a folder path\n- **Manual YAML serialization** -- frontmatter is written with hand-rolled code for precise formatting; the `yaml` package is used only for parsing\n- **Newer Notion API** -- database entries are queried via `client.dataSources.query()` (not `databases.query()`) to get full property data\n\n## Dependencies\n\n| Package                         | Used for                       |\n| ------------------------------- | ------------------------------ |\n| `github.com/zalando/go-keyring` | OS keychain access             |\n| `gopkg.in/yaml.v3`              | YAML parsing                   |\n\nNo third-party Notion client — uses a thin REST wrapper for full control over rate limiting.\n\n## Origin\n\nExtracted from [obsidian-notion-database-sync](https://github.com/ran-codes/obsidian-notion-database-sync), an Obsidian plugin.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fran-codes%2Fnotion-sync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fran-codes%2Fnotion-sync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fran-codes%2Fnotion-sync/lists"}