The internal integration token from Notion. It starts with ntn_.
Notion Integration
Connect a Notion database and every vote from the "Was this helpful?" widget lands in it as a row: the page path, a thumbs up or down, the time it came in, and an optional comment when a reader explains why a page fell short. You read feedback right in Notion, filter it, and share it with your team. This page covers creating the integration and the database, then wiring both into OwnDocs.
Why an integration at all? By default, OwnDocs writes votes to a JSONL file on your server. That works on any normal Node host. It doesn't work on Vercel, where the filesystem is read-only, so the write fails and your reader sees an error. Notion is the fix for those hosts.
Prerequisites
A Notion account. The free plan covers everything here. You also need two values, and the steps below produce both:
NOTION_API_KEYstringrequiredNOTION_DATABASE_IDstringrequiredThe database that receives the votes. A 32-character ID, copied from the database URL.
Pick one of three ways to get them. The third is optional and uses the bundled script.
Steps
Option 1: Click through Notion
No terminal needed. About five minutes.
-
Create the integration
Open notion.so/profile/integrations and choose New integration. Name it something like
docs-feedback, pick your workspace, and save. Copy the Internal Integration Secret: it starts withntn_. That token is yourNOTION_API_KEY. If that link ever moves, Notion's developer portal at notion.so/developers/tokens is the fallback from their official quick-start guide. -
Create the database
Add a new page in Notion and choose Table. Name it
Docs Feedback(any name works). Then give it exactly these four properties:PathtitlerequiredThe page's URL path, like
/integrations/notion. Notion adds a title property for you: rename it toPathif it says something else.HelpfulcheckboxrequiredChecked for a thumbs up, empty for a thumbs down.
SubmitteddaterequiredWhen the vote came in.
Commentrich_textThe optional text a reader leaves after a thumbs down. Without it, votes still save; only comments are skipped.
Match the names exactly, capital first letter included. OwnDocs looks them up by name.
-
Share the database with the integration
Open the database, click the ⋯ menu at the top right, choose Connections, and pick the integration from step 1. This screen has no direct URL of its own: the Connections menu lives on each page and database, two clicks from the database you just made. Until you do this, the token sees nothing. Notion keeps bots out of your content by default.
-
Copy the database ID
Open the database as a full page and look at the URL. The 32 characters between the last dash and the
?v=part are the ID, highlighted here:Plain TextPlain Texthttps://www.notion.so/myworkspace/Docs-Feedback-218fb8926f02e83e52a542a8869bb279?v=594fa6dddfef38df9e92acffe2f1f1e1That's your
NOTION_DATABASE_ID.
Option 2: One API call
Faster if you live in a terminal. A single call to Notion's application programming interface (API) creates the database, but a bot can only create one inside a page that's already shared with it. Share any page with your integration (step 3 above), then run the command below.
Export your token and the parent page's ID, the 32 characters at the end of its URL:
export NOTION_API_KEY="ntn_your-token"
export PARENT_PAGE_ID="ec55e31f5861e743bbd21fbff15d80f6"Now create the database with the exact schema OwnDocs expects:
curl -X POST https://api.notion.com/v1/databases \
-H "Authorization: Bearer $NOTION_API_KEY" \
-H "Content-Type: application/json" \
-H "Notion-Version: 2026-03-11" \
-d '{
"parent": { "type": "page_id", "page_id": "'"$PARENT_PAGE_ID"'" },
"title": [{ "type": "text", "text": { "content": "Docs Feedback" } }],
"initial_data_source": {
"properties": {
"Path": { "title": {} },
"Helpful": { "checkbox": {} },
"Submitted": { "date": {} },
"Comment": { "rich_text": {} }
}
}
}'The id in the response is your NOTION_DATABASE_ID:
{ "object": "database", "id": "218fb892-6f02-e83e-52a5-42a8869bb279" }NOTE Skip the sharing step and Notion answers with a
validation_erroraboutparent.page_id. Bots can't create anything at the workspace root. Share the page, run the same command again, and it goes through.
Option 3: Let the script do it (optional)
OwnDocs ships a small Node script that talks to the Notion API for you. It is optional; Options 1 and 2 already cover everything. Reach for it when you would rather not hand-build the request, or when you want to check an existing database instead of creating a new one.
It reads NOTION_API_KEY and NOTION_DATABASE_ID from your environment or
.env.local. It never stores a token and never prints one.
Check that a database you already have carries every field OwnDocs writes:
npm run notion:verifyAdd any field that is missing, leaving everything else untouched:
npm run notion:fixCreate a new database from scratch. It asks for the name, lists the pages your integration can write to, and confirms before it writes anything:
npm run notion:createWhen it finishes it prints the NOTION_DATABASE_ID line to paste into
.env.local.
NOTE
notion:fixonly adds fields that are absent. If a field exists with the wrong type, the script reports it and stops rather than rewriting your data. Rename or delete that field in Notion, then run it again.
Extra flags:
npm run notion:verify -- --jsonprints a machine-readable result, so CI can gate on the schema.npm run notion:verify -- --database <id>checks a database other than the one in your environment.
Wire it into OwnDocs
For local development, put both values in .env.local:
NOTION_API_KEY="ntn_your-token"
NOTION_DATABASE_ID="218fb8926f02e83e52a542a8869bb279"For Vercel, add the same two under Project → Settings → Environment Variables, then redeploy.
Result
Open any page and vote. A row lands in the database within a second or two, carrying the page path, the thumbs up or down, the timestamp, and the comment when the reader left one.