# notion2pg_bulk
- Automatically discovers and migrates **all databases in a workspace** regardless of their views or position in the page structure
- Preserves **database descriptions and property descriptions**
- Supports **relation properties** using PostgreSQL arrays
- **Select field support**: Single and multi-select options stored in separate lookup tables with foreign key constraints
- Complies with Notion API limits (3 requests/second average)
## Installation
```bash
pip install notion2pg-bulk
```
## Setup and Usage
### Requirements
- a **Notion API key** connected to all the databased that you wish to migrate (note: Notion now allows to connect databases ditectly from the integration configuration page, under the 'Access' tab. For quickly connecting **all databases** in a workspace, just tick all top level pages.
- PostgreSQL database connection URL
### Interactive Mode
**On (Default):**
- Dispays detected databases and fields and asks for confirmation before running the migration
- Progress bar
- Shows post-migration notes
**Off:**
(`--quiet` flag in CLI or set `interactive_mode=False` in Python API)
- `.run()` runs migration directly and silently
### CLI
**Options:**
- `--notion-token`: Notion integration token (or set `NOTION_TOKEN` env var)
- `--database-url`: PostgreSQL connection string (or set `DATABASE_URL` env var)
- `--quiet`: Run in non-interactive mode (skip validation steps and progress bars)
- `--extract-page-content`: Extract free-form content from page bodies (slower migration)
**Examples:**
```bash
# Interactive mode (default)
notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..."
# Non-interactive mode (skip validation steps)
notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --quiet
# Extract page content (slower but includes free-form content)
notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --extract-page-content
# Non-interactive with page content extraction
notion2pg-bulk --notion-token "your_token" --database-url "postgresql://..." --quiet --extract-page-content
```
### Python API:
```python
from notion2pg_bulk import NotionMigrator
import sqlalchemy as sa
engine = sa.create_engine('postgresql://user:password@localhost/dbname')
migrator = NotionMigrator(
notion_token="your_notion_integration_token",
db_connection=engine,
interactive_mode=True,
extract_page_content=True,
)
migrator.run()
```
## Property Type Mapping
| Notion Property | PostgreSQL Type | Notes |
|----------------|-----------------|-------|
| Title | `TEXT` | Primary identifier |
| Rich Text | `TEXT` | Preserved as markdown with formatting |
| Number | `NUMERIC` | |
| Select | `TEXT` | Single option value + lookup table with foreign key |
| Multi-select | `TEXT[]` | Array of option values + lookup table with check constraints |
| Date | `TIMESTAMP` | |
| Checkbox | `BOOLEAN` | |
| URL | `TEXT` | |
| Email | `TEXT` | |
| Phone | `TEXT` | |
| Relation | `TEXT[]` | Array of related page IDs |
| People | `TEXT[]` | Array of user IDs |
| Files | `TEXT[]` | Array of file URLs |
| Created time | `TIMESTAMP` | |
| Created by | `TEXT` | User ID |
| Last edited time | `TIMESTAMP` | |
| Last edited by | `TEXT` | User ID |
| Formula | - | ⚠️ Skipped (not supported by Notion API) |
| Rollup | - | ⚠️ Skipped (not supported by Notion API) |
## Additional Page Content Mode
The migrator can optionally extract free-form content from database pages. Extracted content is stored in the `additional_page_content` column as plain text.
```python
migrator = NotionMigrator(
notion_token=token,
db_connection=engine,
extract_page_content=True # Default is false
)
```
**Supported blocks:**
- Paragraph text
- Headings (all levels)
- Bulleted and numbered lists
- Code blocks
- To-do items
- Callouts
- Toggle blocks
- Dividers
- Embedded databases (ID reference only)
**Unsupported blocks:**
- Images and media files
- Complex block types (equations, embeds, etc.)
- Block formatting and styling
- Page hierarchies and relationships
**Post-migration analysis:**
When page content extraction is enabled, the migrator provides a comprehensive summary of:
- Unsupported block types found and their locations (grouped by record)
- Embedded databases that were referenced in page content but not connected to the integration (with full database IDs)
Raw data
{
"_id": null,
"home_page": null,
"name": "notion2pg-bulk",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": null,
"keywords": "notion, postgresql, migration, database, export, workspace, notion export, notion migrate",
"author": "benvigano",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/6a/6b/9240816f67d4b673ce499534265d719f209a8266ed0707bc1bac3cd4696d/notion2pg_bulk-0.1.0.tar.gz",
"platform": null,
"description": "# notion2pg_bulk\r\n\r\n- Automatically discovers and migrates **all databases in a workspace** regardless of their views or position in the page structure\r\n- Preserves **database descriptions and property descriptions**\r\n- Supports **relation properties** using PostgreSQL arrays\r\n- **Select field support**: Single and multi-select options stored in separate lookup tables with foreign key constraints\r\n- Complies with Notion API limits (3 requests/second average)\r\n\r\n## Installation\r\n```bash\r\npip install notion2pg-bulk\r\n```\r\n\r\n## Setup and Usage\r\n\r\n### Requirements\r\n- a **Notion API key** connected to all the databased that you wish to migrate (note: Notion now allows to connect databases ditectly from the integration configuration page, under the 'Access' tab. For quickly connecting **all databases** in a workspace, just tick all top level pages.\r\n- PostgreSQL database connection URL\r\n\r\n### Interactive Mode\r\n**On (Default):**\r\n- Dispays detected databases and fields and asks for confirmation before running the migration\r\n- Progress bar\r\n- Shows post-migration notes\r\n\r\n**Off:**\r\n(`--quiet` flag in CLI or set `interactive_mode=False` in Python API)\r\n- `.run()` runs migration directly and silently\r\n\r\n### CLI\r\n**Options:**\r\n- `--notion-token`: Notion integration token (or set `NOTION_TOKEN` env var)\r\n- `--database-url`: PostgreSQL connection string (or set `DATABASE_URL` env var)\r\n- `--quiet`: Run in non-interactive mode (skip validation steps and progress bars)\r\n- `--extract-page-content`: Extract free-form content from page bodies (slower migration)\r\n\r\n**Examples:**\r\n```bash\r\n# Interactive mode (default)\r\nnotion2pg-bulk --notion-token \"your_token\" --database-url \"postgresql://...\"\r\n\r\n# Non-interactive mode (skip validation steps)\r\nnotion2pg-bulk --notion-token \"your_token\" --database-url \"postgresql://...\" --quiet\r\n\r\n# Extract page content (slower but includes free-form content)\r\nnotion2pg-bulk --notion-token \"your_token\" --database-url \"postgresql://...\" --extract-page-content\r\n\r\n# Non-interactive with page content extraction\r\nnotion2pg-bulk --notion-token \"your_token\" --database-url \"postgresql://...\" --quiet --extract-page-content\r\n```\r\n\r\n### Python API:\r\n```python\r\nfrom notion2pg_bulk import NotionMigrator\r\nimport sqlalchemy as sa\r\n\r\nengine = sa.create_engine('postgresql://user:password@localhost/dbname')\r\nmigrator = NotionMigrator(\r\n notion_token=\"your_notion_integration_token\",\r\n db_connection=engine,\r\n interactive_mode=True,\r\n extract_page_content=True,\r\n)\r\nmigrator.run()\r\n```\r\n\r\n## Property Type Mapping\r\n\r\n| Notion Property | PostgreSQL Type | Notes |\r\n|----------------|-----------------|-------|\r\n| Title | `TEXT` | Primary identifier |\r\n| Rich Text | `TEXT` | Preserved as markdown with formatting |\r\n| Number | `NUMERIC` | |\r\n| Select | `TEXT` | Single option value + lookup table with foreign key |\r\n| Multi-select | `TEXT[]` | Array of option values + lookup table with check constraints |\r\n| Date | `TIMESTAMP` | |\r\n| Checkbox | `BOOLEAN` | |\r\n| URL | `TEXT` | |\r\n| Email | `TEXT` | |\r\n| Phone | `TEXT` | |\r\n| Relation | `TEXT[]` | Array of related page IDs |\r\n| People | `TEXT[]` | Array of user IDs |\r\n| Files | `TEXT[]` | Array of file URLs |\r\n| Created time | `TIMESTAMP` | |\r\n| Created by | `TEXT` | User ID |\r\n| Last edited time | `TIMESTAMP` | |\r\n| Last edited by | `TEXT` | User ID |\r\n| Formula | - | \u26a0\ufe0f Skipped (not supported by Notion API) |\r\n| Rollup | - | \u26a0\ufe0f Skipped (not supported by Notion API) |\r\n\r\n## Additional Page Content Mode\r\n\r\nThe migrator can optionally extract free-form content from database pages. Extracted content is stored in the `additional_page_content` column as plain text.\r\n\r\n```python\r\nmigrator = NotionMigrator(\r\n notion_token=token,\r\n db_connection=engine,\r\n extract_page_content=True # Default is false\r\n)\r\n```\r\n\r\n**Supported blocks:**\r\n- Paragraph text\r\n- Headings (all levels)\r\n- Bulleted and numbered lists\r\n- Code blocks\r\n- To-do items\r\n- Callouts\r\n- Toggle blocks\r\n- Dividers\r\n- Embedded databases (ID reference only)\r\n\r\n**Unsupported blocks:**\r\n- Images and media files\r\n- Complex block types (equations, embeds, etc.)\r\n- Block formatting and styling\r\n- Page hierarchies and relationships\r\n\r\n**Post-migration analysis:**\r\nWhen page content extraction is enabled, the migrator provides a comprehensive summary of:\r\n- Unsupported block types found and their locations (grouped by record)\r\n- Embedded databases that were referenced in page content but not connected to the integration (with full database IDs)\r\n",
"bugtrack_url": null,
"license": "GPL-3.0-or-later",
"summary": "A Python package that automatically finds and migrates to PostgreSQL all databases in a Notion workspace.",
"version": "0.1.0",
"project_urls": {
"Homepage": "https://github.com/benvigano/notion2pg-bulk",
"Issues": "https://github.com/benvigano/notion2pg-bulk/issues",
"Repository": "https://github.com/benvigano/notion2pg-bulk"
},
"split_keywords": [
"notion",
" postgresql",
" migration",
" database",
" export",
" workspace",
" notion export",
" notion migrate"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "6a0fdef6d837160d39cd6ccc272ac4226ee9a9c1dcf1f5b795f9d20b02ac6216",
"md5": "9bca8672bbfec7fe4dd26449172b682a",
"sha256": "9ee328f707369ba6518e03219bd91010e35fc8a432e0fee628ec82c278d32b10"
},
"downloads": -1,
"filename": "notion2pg_bulk-0.1.0-py3-none-any.whl",
"has_sig": false,
"md5_digest": "9bca8672bbfec7fe4dd26449172b682a",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 16930,
"upload_time": "2025-08-22T21:33:21",
"upload_time_iso_8601": "2025-08-22T21:33:21.728897Z",
"url": "https://files.pythonhosted.org/packages/6a/0f/def6d837160d39cd6ccc272ac4226ee9a9c1dcf1f5b795f9d20b02ac6216/notion2pg_bulk-0.1.0-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "6a6b9240816f67d4b673ce499534265d719f209a8266ed0707bc1bac3cd4696d",
"md5": "14f07fff3f7eeb64ea3c4fc837a349f5",
"sha256": "67bdc104fb3cb0eb28e06f1ac1791a70ac9184496e8f817299d8b36f91f743d9"
},
"downloads": -1,
"filename": "notion2pg_bulk-0.1.0.tar.gz",
"has_sig": false,
"md5_digest": "14f07fff3f7eeb64ea3c4fc837a349f5",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 14234,
"upload_time": "2025-08-22T21:33:23",
"upload_time_iso_8601": "2025-08-22T21:33:23.085970Z",
"url": "https://files.pythonhosted.org/packages/6a/6b/9240816f67d4b673ce499534265d719f209a8266ed0707bc1bac3cd4696d/notion2pg_bulk-0.1.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-22 21:33:23",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "benvigano",
"github_project": "notion2pg-bulk",
"github_not_found": true,
"lcname": "notion2pg-bulk"
}