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

  1. Go to “Workspace settings” (or type "g" then "s")

  2. Look for the “integrations” tab

  3. Click on “Add new”, next to "Collaboration," and select Notion

  4. 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.

  1. Use the "/" command and search for the Notion integration.

  2. Click it or press "enter" and type the three first letters of the Notion page you want to mention in your Cycle doc.

  3. 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.

Mention Notion pages in your Cycle docs

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.

Embed Cycle docs in Notion

FAQ

I can't find my Notion page

Make sure you include the parent page of the Notion page you're trying to find when setting up the Notion integration. If you're unsure about this, remove the integration and add it back again.

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

  1. Go to [Notion → Settings & members → Integrations]. (Or here)

  2. Click “+ New integration”, give it a name (e.g. “Cycle Sync”), and copy the Internal Integration Token.

  3. Share your Roadmap database

    • Open the database in Notion.

    • Click 3 dots menu → Connection and select your new integration.

  4. 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

  1. Create a Cycle API token

    • In Cycle, go to Settings → API and generate a new personal API key

  2. Configure Cycle Webhooks

    • In Settings → API → Webhooks, add a new webhook pointing to your HTTP endpoint for:

      • Event: Value changed

      • Event: Status changed

  3. 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.

  4. 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

  1. Get your Cycle statuses view this query, you need the IDs

  2. Get your Notion status names: e.g. "To do", "Doing", "Done" (The exact match)

  3. 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?