Notion
Tag Notion pages inside Cycle docs
Mentioning documents
Many modern teams use Notion as their internal knowledge base to document market analysis, competitive research, and more.
Here's how to include your company's knowledge base in your product information.
Installation
Go to “Workspace settings” (or type "g" then "s")
Look for the “integrations” tab
Click on “Add new”, next to "Collaboration," and select Notion
Login to your Notion workspace & select the page hosting the Notion pages you will want to embed in your Cycle docs.

How it works
Product teams can seamlessly mention relevant Notion docs directly inside PRDs living in Cycle next to customer feedback.
Use the "/" command and search for the Notion integration.
Click it or press "enter" and type the three first letters of the Notion page you want to mention in your Cycle doc.
Once you mention a Notion page in Cycle, we'll add a backlink to the current Cycle doc at the bottom of the mentioned Notion page. This way, you'll always know which Cycle docs are related to your Notion pages.
Clicking a mentioned Notion page in Cycle will bring you straight to it.

Oh...almost forgot, it works both ways 😎.
Mentioning a Cycle doc in a Notion page will create a backlink to that Notion page inside the mentioned Cycle doc.
You can also embed a Cycle view inside a Notion page to share your product roadmap or a customer-specific board with other stakeholders.

FAQ
Synchronize your Notion roadmap with Cycle
Keep your Cycle docs and Notion pages in perfect harmony—status updates and links flow both ways!
Link any Cycle doc to a Notion page and pull in its current Status
Listen for Status changes in Notion → automatically update the matching Cycle doc
Listen for Status updates in Cycle → mirror them back into Notion
Prerequisites
Cycle
A custom Notion URL property on your Cycle roadmap items
A Cycle API token with rights to read/write docs
Notion
A “Roadmap” database with at least two properties:
Status (type: Status)
Cycle URL (type: URL)
A Notion integration with a token, and the DB shared to it.
Hosting
A publicly reachable HTTP endpoint (we use Google Cloud Functions in the example).
Installation & Config
Configure the Notion integration
Go to [Notion → Settings & members → Integrations]. (Or here)
Click “+ New integration”, give it a name (e.g. “Cycle Sync”), and copy the Internal Integration Token.
Share your Roadmap database
Open the database in Notion.
Click 3 dots menu → Connection and select your new integration.
Identify the database & property IDs
Note down:
Database ID - Present in the page URL
“Status” - property name (e.g.
Status
)“Cycle URL” property name and its property ID (you’ll need that UUID).
Configure Cycle
Create a Cycle API token
In Cycle, go to Settings → API and generate a new personal API key
Configure Cycle Webhooks
In Settings → API → Webhooks, add a new webhook pointing to your HTTP endpoint for:
Event:
Value changed
Event:
Status changed
Define Notion URL attribute
Go to Settings → Properties
Add a new URL attribute named Notion URL.
Make sure it’s link to the request type (Feature, Opportunity,... you name it) you want to sync, this will hold the link back to the Notion page.
Ensure Status property exists
Go to Settings → Request
Select the desired type (From above when you link the property)
Check you have the Status that you’d like
Deploy your webhook handler
We’ll deploy a function that:
Listens for webhooks from Notion (
page.properties_updated
).Listens for webhooks from Cycle (
value.change
&status.change
).Uses the status–ID mappings to keep them in sync.
Start by mapping the statuses
Get your Cycle statuses view this query, you need the IDs
Get your Notion status names: e.g. "To do", "Doing", "Done" (The exact match)
Map the statuses in your favourite language
const STATUS_MAP_NOTION_TO_CYCLE = {
'Not started': 'U3RhdHVzXzNkNGRmMzJlLWQwM2YtNDU1OC1iYjk2LTdlYzJhMWFjYTRhNg==', // To do in Cycle
'In progress': 'U3RhdHVzXzM4YTI4ZjkxLTc3MTctNDQ3NS1hYjdiLWFjZTYwODIzNTE2ZA==', // In progress in Cycle
Done: 'U3RhdHVzXzY3N2Y0Y2Y4LTdkNzEtNDgxNy04YmQwLTM5NWVlNTk4MWUyYg==', // Shipped in Cycle
};
const STATUS_MAP_CYCLE_TO_NOTION = {
'U3RhdHVzXzNkNGRmMzJlLWQwM2YtNDU1OC1iYjk2LTdlYzJhMWFjYTRhNg==': 'Not started',
'U3RhdHVzXzM4YTI4ZjkxLTc3MTctNDQ3NS1hYjdiLWFjZTYwODIzNTE2ZA==': 'In progress',
'U3RhdHVzXzY3N2Y0Y2Y4LTdkNzEtNDgxNy04YmQwLTM5NWVlNTk4MWUyYg==': 'Done',
};
Setup the code on your favourite provider
It could be AWS Lambda, GCP Cloud Run Functions, no code tool like Zapier, Make or else.
const functions = require('@google-cloud/functions-framework');
const axios = require('axios');
const notionHeaders = {
Authorization: `Bearer ${NOTION_TOKEN}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json',
};
const cycleHeaders = {
Authorization: `Bearer ${CYCLE_TOKEN}`,
'Content-Type': 'application/json',
};
const NOTION_STATUS_NAME = "Status"
const NOTION_PROPERTY_CYCLE_URL_NAME = "Cycle URL"
const STATUS_MAP_NOTION_TO_CYCLE = {
'Backlog': 'U3RhdHVzXzdiYmE5…',
'Discovery': 'U3RhdHVzXzFmM2E3…',
// …
};
functions.http('syncNotionToCycle', async (req, res) => {
try {
const { type, entity, data } = req.body;
// 1️⃣ Only react to Status changes
if (type !== 'page.properties_updated') {
return res.status(200).send('Ignored event');
}
const notionPageId = entity.id;
// 2️⃣ Fetch the Notion page
const { data: notionPage } = await axios.get(
`https://api.notion.com/v1/pages/${notionPageId}`,
{ headers: notionHeaders }
);
const statusName = notionPage.properties[NOTION_STATUS_NAME].status.name;
const cycleUrl = notionPage.properties['NOTION_PROPERTY_CYCLE_URL_NAME].url;
if (!cycleUrl) return res.status(200).send('No Cycle URL set');
// 3️⃣ Map status
const cycleStatusId = STATUS_MAP_NOTION_TO_CYCLE[statusName];
if (!cycleStatusId) throw new Error(`Unmapped status: ${statusName}`);
// 4️⃣ Extract Cycle docId from URL
const docId = cycleUrl.split('-').pop();
// 5️⃣ Perform GraphQL mutation
const mutation = `
mutation updateDocStatus($docId: ID!, $statusId: ID!) {
updateDocStatus(docId: $docId, statusId: $statusId) {
id title status { value }
}
}
`;
const gqlRes = await axios.post(
'https://api.product.cycle.app/graphql',
{ query: mutation, variables: { docId, statusId: cycleStatusId } },
{ headers: cycleHeaders }
);
console.log('✅ Cycle updated:', gqlRes.data.data.updateDocStatus);
res.status(200).send('Cycle status synced');
} catch (err) {
console.error('❌ Sync error:', err.message);
res.status(500).send(err.message);
}
});
Last updated
Was this helpful?