Notion Uploader avatar

Notion Uploader

Pricing

Pay per usage

Go to Apify Store
Notion Uploader

Notion Uploader

Upload data into a specified Notion database. It dynamically maps data from any Actor / Dataset to your Notion properties, ensuring your database stays up-to-date with the latest information.

Pricing

Pay per usage

Rating

5.0

(1)

Developer

Filip Cicvárek

Filip Cicvárek

Maintained by Community

Actor stats

8

Bookmarked

82

Total users

6

Monthly active users

3 days ago

Last modified

Share

Upload any Apify dataset to Notion: one-click connection, self-configuring mapping, rerun-safe

Uploads items from an Apify dataset into Notion: an existing database, a database created for you with a schema inferred from the data, or a page. Connect Notion with one click via an Apify MCP connector (the Actor never sees your token) or with a classic Notion API key. Field mapping resolves itself by name matching, a plain-English AI prompt, or explicit JSON; upserts keep reruns duplicate-free, and AI can transform values or enrich items on the way in.

Quick start

MCP connector (recommended)

  1. In Apify Console, open Settings → Integrations → MCP connectors and add a Notion connector (one OAuth click).
  2. Run this Actor: pick the connector, pick a source dataset, and either paste a target database URL or enable Create a database for me.
  3. Done. Check the run's Upload report dataset and the created rows in Notion.

Notion API key (works everywhere)

  1. Create an internal integration at notion.so/profile/integrations and copy the ntn_… secret.
  2. In Notion, open the target database/page → •••Connections → add your integration.
  3. Run the Actor with notionApiKey and the database URL.

Turn on Dry run for the first attempt: it resolves and prints the field mapping, validates everything against the Notion schema, and writes nothing.

Destinations

ModeHow to selectWhat happens
Existing databasefill notionDatabaseIdone row per item; missing property values are skipped with per-item warnings
New databaseleave notionDatabaseId empty, set createDatabaseIfMissing: trueschema (column names and types) is inferred from your data; the created database URL is in the log and SUMMARY
Pagefill notionPageIditems are rendered as a markdown table, a bulleted digest, or raw text, and appended, replacing content, or placed on a new child page

Field mapping

Three layers, strongest wins. The result is always printed in the log and saved to the RESOLVED_MAPPING record:

  1. Explicit: dataMapping: {"Name": "title", "Score": "stats.score"}. Dot paths reach nested fields.
  2. AI prompt: mappingPrompt: "Put the listing headline into Name and the monthly rent into Price". One LLM call (needs anthropicApiKey); proposals are validated against the real schema and dataset fields.
  3. Automatic: properties are matched to fields by normalized name where sampled values are type-compatible.

Values are coerced per property type: numbers from strings, dates from ISO strings and epochs, checkboxes from yes/true/1, multi-select from arrays, JSON, and comma lists, plus URLs, emails, and files. Unparseable values become per-item warnings, never silent data corruption.

Duplicate handling (rerun-safe uploads)

Set dedupeMode to upsert or skip-existing with keyProperty (for example URL): existing rows are matched by that key and updated or skipped instead of duplicated. Lookups run via the Notion API key, or via the connector on Notion Business+ plans; otherwise the run falls back to create-only with a warning and dedupeDegraded: true in SUMMARY.

AI value transformation

transformPrompt: "append CZK to all price values" rewrites existing field values before upload. One planning call compiles the instruction into per-field rules: mechanical changes become templates applied locally to every item (no per-item LLM cost); fields that need judgment ("translate the description to English") get one call per item. The rule plan is saved to the TRANSFORM_PLAN record. Transforms run before type inference, so new-database column types adapt; in an existing database, transformed values must still fit the column type.

AI enrichment

enrichmentPrompt: "One-sentence summary: {{title}}, {{description}}" generates a new value per item (Claude Haiku by default) and writes it into enrichmentProperty (created automatically). {{field}} placeholders are filled from the item; the full item JSON is appended for context.

Input examples

Zero-config: dataset to a brand-new Notion database via connector:

{
"notionConnector": "<your connector id>",
"createDatabaseIfMissing": true,
"newDatabaseTitle": "Scraped listings",
"datasetId": "<dataset id>"
}

Existing database, rerun-safe, with an API key:

{
"notionApiKey": "ntn_...",
"notionDatabaseId": "https://www.notion.so/workspace/My-DB-82e8e449d87949898ff6e705ed2ecbbf",
"datasetId": "<dataset id>",
"dedupeMode": "upsert",
"keyProperty": "URL"
}

Digest appended to a page:

{
"notionConnector": "<your connector id>",
"notionPageId": "https://www.notion.so/workspace/Weekly-digest-391f39950a22819fa2b6e5283fdd08d3",
"pageWriteMode": "append",
"pageRenderMode": "table",
"datasetId": "<dataset id>"
}

AI mapping, value transformation, and per-item summaries:

{
"notionConnector": "<your connector id>",
"notionDatabaseId": "<database url>",
"datasetId": "<dataset id>",
"mappingPrompt": "Map the listing headline into Name, the monthly rent into Price, the link into URL.",
"transformPrompt": "Append CZK to all price values.",
"enrichmentPrompt": "One short English sentence describing this listing: {{title}}, district {{district}}.",
"enrichmentProperty": "Summary",
"anthropicApiKey": "sk-ant-..."
}

Calling from an agent or backend (the input schema is machine-readable; agents can fill it from the field descriptions):

curl -X POST "https://api.apify.com/v2/acts/<username>~notion-uploader/run-sync?token=$APIFY_TOKEN" \
-H 'Content-Type: application/json' \
-d '{"notionConnector": "...", "createDatabaseIfMissing": true, "datasetId": "..."}'

Output

  • Upload report (default dataset): one row per item with itemIndex, status (created | updated | skipped | failed), key, notionUrl, error, and warnings. Written incrementally, so partial results survive aborts.
  • SUMMARY (key-value store): counts, warnings, dedupeDegraded, createdDatabaseUrl, mappingSource.
  • RESOLVED_MAPPING: the final mapping table with each entry's source (explicit / llm / auto).
  • TRANSFORM_PLAN: the compiled value-transformation rules.
  • WOULD_WRITE (dry runs): a sample of exactly what would be sent to Notion.

Limits and behavior notes

TopicBehavior
Notion rate limitsAPI-key path is rate-limited to ~2.5 requests/s with Retry-After handling; connector path batches up to 100 pages per call
Property typestitle, rich text, number, URL, checkbox, select, multi-select, date, status, email, phone, files; other types (relation, formula, people) are reported and skipped
Files propertywritable on the API-key path only (external URLs); the connector path skips it with a warning
Page moderenders at most 500 items per run
Duplicate lookupsneed an API key or a Business+ Notion plan on the connector; otherwise the run degrades to create-only with a warning
New-database rerunseach run with createDatabaseIfMissing creates a new database; pass the created database URL back in for the next run
StatusesNotion does not auto-create status options; unknown status values are skipped with a warning

Development

npm install
apify run # local run against ./storage
npm test # vitest unit tests (fixtures in test/fixtures/)
apify push # deploy to the Apify platform

See AGENTS.md for architecture, MCP-protocol facts, and probe fixtures.