|Build Status| |PyPI|

Notion Builder for Sphinx
=========================

Builder for Sphinx which enables publishing documentation to Notion.

See a `sample document source`_ and the `published Notion page`_ for an example of what it can do.

.. contents::

Installation
------------

``sphinx-notionbuilder`` is compatible with Python |minimum-python-version|\+.

.. code-block:: console

   $ pip install sphinx-notionbuilder

Add the following to ``conf.py`` to enable the extension:

.. code-block:: python

   """Configuration for Sphinx."""

   extensions = ["sphinx_notion"]

``sphinx-notionbuilder`` also works with a variety of Sphinx extensions:

* `sphinx-toolbox collapse`_
* `sphinx-toolbox rest_example`_
* `sphinxcontrib-video`_
* `sphinxnotes-strike`_
* `atsphinx-audioplayer`_
* `sphinx-immaterial task_lists`_
* `sphinx.ext.mathjax`_
* `sphinx-simplepdf`_
* `sphinx-iframes`_
* `sphinxcontrib-text-styles`_
* `sphinxcontrib-mermaid`_

See a `sample document source`_ and the `published Notion page`_ for an example of each of these.

To set these up, install the extensions you want to use and add them to your ``conf.py``, before ``sphinx_notion``:

.. code-block:: python

   """Configuration for Sphinx."""

   extensions = [
       "atsphinx.audioplayer",
       "sphinx.ext.mathjax",
       "sphinx_iframes",
       "sphinx_immaterial.task_lists",
       "sphinx_simplepdf",
       "sphinx_toolbox.collapse",
       "sphinx_toolbox.rest_example",
       "sphinxcontrib.mermaid",
       "sphinxcontrib.video",
       "sphinxcontrib_text_styles",
       "sphinxnotes.strike",
       "sphinx_notion",
   ]

Supported Notion Block Types
----------------------------

The following syntax is supported:

- Headers
- Bulleted lists
- TODO lists (with checkboxes)
- Code blocks
- Table of contents
- Block quotes
- Callouts
- Collapsible sections (using the ``collapse`` directive from `sphinx-toolbox`_ )
- Rest-example blocks (using the ``rest-example`` directive from `sphinx-toolbox`_ )
- Images (with URLs or local paths)
- Videos (with URLs or local paths)
- Audio (with URLs or local paths)
- PDFs (with URLs or local paths)
- Files (with URLs or local paths)
- Embed blocks (using the ``iframe`` directive from `sphinx-iframes`_ )
- Tables
- Dividers (horizontal rules / transitions)
- Strikethrough text (using the ``strike`` role from `sphinxnotes-strike`_ )
- Colored text and text styles (bold, italic, monospace) (using various roles from `sphinxcontrib-text-styles`_ )
- Mermaid diagrams (using the ``mermaid`` directive from `sphinxcontrib-mermaid`_ )
- Mathematical equations (inline and block-level, using the ``math`` role and directive from `sphinx.ext.mathjax`_ )
- Link to page blocks (using the ``notion-link-to-page`` directive)
- Mentions (users, pages, databases, dates) (using the ``notion-mention-user``, ``notion-mention-page``, ``notion-mention-database``, and ``notion-mention-date`` roles)
- Describe blocks (using the ``describe`` directive)
- Definition lists
- Glossary definitions (using the ``glossary`` directive)
- Rubrics (informal headings that do not appear in the table of contents)

See a `sample document source`_ and the `published Notion page`_.

All of these can be used in a way which means your documentation can still be rendered to HTML.

Directives
----------

``sphinx-notionbuilder`` provides custom directives for Notion-specific features:

``notion-link-to-page``
~~~~~~~~~~~~~~~~~~~~~~~

Creates a Notion "link to page" block that references another page in your Notion workspace.

**Usage:**

.. code-block:: rst

   .. notion-link-to-page:: PAGE_ID

**Parameters:**

- ``PAGE_ID``: The UUID of the Notion page you want to link to (without hyphens or with hyphens, both formats are accepted)

**Example:**

.. code-block:: rst

   .. notion-link-to-page:: 12345678-1234-1234-1234-123456789abc

This creates a clickable link block in Notion that navigates to the specified page when clicked.

``notion-file``
~~~~~~~~~~~~~~~

Creates a Notion File block that links to an external file by URL, or uploads a local file.

**Usage:**

.. code-block:: rst

   .. notion-file:: FILE_URL_OR_PATH

**Parameters:**

- ``FILE_URL_OR_PATH``: A URL to an external file, or a local file path relative to the source directory

**Options:**

- ``:name:``: (Optional) Display name for the file in Notion
- ``:caption:``: (Optional) Caption text displayed below the file block

**Examples:**

.. code-block:: rst

   .. notion-file:: https://example.com/document.zip

   .. notion-file:: _static/data.csv
      :name: Project Data
      :caption: CSV export of the project data

When not using the Notion builder (e.g. HTML), the directive renders as a link to the file.

Roles
-----

``sphinx-notionbuilder`` provides custom roles for inline Notion-specific features:

``notion-mention-user``
~~~~~~~~~~~~~~~~~~~~~~~

Creates a Notion user mention inline.

**Usage:**

.. code-block:: rst

   :notion-mention-user:`USER_ID`

**Parameters:**

- ``USER_ID``: The UUID of the Notion user you want to mention

**Example:**

.. code-block:: rst

   Hello :notion-mention-user:`12345678-1234-1234-1234-123456789abc` there!


``notion-mention-page``
~~~~~~~~~~~~~~~~~~~~~~~

Creates a Notion page mention inline.

**Usage:**

.. code-block:: rst

   :notion-mention-page:`PAGE_ID`

**Parameters:**

- ``PAGE_ID``: The UUID of the Notion page you want to mention

**Example:**

.. code-block:: rst

   See :notion-mention-page:`87654321-4321-4321-4321-cba987654321` for details.

``notion-mention-database``
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Creates a Notion database mention inline.

**Usage:**

.. code-block:: rst

   :notion-mention-database:`DATABASE_ID`

**Parameters:**

- ``DATABASE_ID``: The UUID of the Notion database you want to mention

**Example:**

.. code-block:: rst

   Check the :notion-mention-database:`abcdef12-3456-7890-abcd-ef1234567890` database.

``notion-mention-date``
~~~~~~~~~~~~~~~~~~~~~~~

Creates a Notion date mention inline.

**Usage:**

.. code-block:: rst

   :notion-mention-date:`DATE_STRING`

**Parameters:**

- ``DATE_STRING``: A date string in ISO format (e.g., ``2025-11-09``)

**Example:**

.. code-block:: rst

   The meeting is on :notion-mention-date:`2025-11-09`.

Cross-references
----------------

Sphinx cross-reference roles are not fully supported by the Notion builder because there is no way to determine the URL of the target page in Notion.
Cross-references that resolve to internal links are rendered as plain text and a build warning is emitted.

The affected roles include ``:doc:``, ``:ref:``, ``:term:``, ``:any:``, ``:numref:``, ``:keyword:``, ``:option:``, ``:envvar:``, ``:confval:``, and ``:token:``.

To suppress these warnings, add the following to your ``conf.py``:

.. code-block:: python

   """Configuration for Sphinx."""

   suppress_warnings = ["ref.notion"]

Unsupported Notion Block Types
------------------------------

- Bookmark
- Breadcrumb
- Child database
- Child page
- Column and column list
- Link preview
- Synced block
- Template
- Heading with ``is_toggleable`` set to ``True``

Uploading Documentation to Notion
----------------------------------

Build documentation with the ``notion`` builder.
For eaxmple:

.. code-block:: console

   $ sphinx-build -W -b notion source build/notion

After building your documentation with the Notion builder, you can upload it to Notion using the included command-line tool.

Prerequisites
~~~~~~~~~~~~~

#. Create a Notion integration at `notion-integrations`_

The integration token must have the following "Capabilities" set within the "Configuration" tab:

- **Content Capabilities**: Insert content, Update content, Read content
- **Comment Capabilities**: Read comments (required for checking if blocks have discussion threads for the ``--cancel-on-discussion`` option)
- **User Capabilities**: Read user information without email addresses (for bot identification)

In the "Access" tab, choose the pages and databases your integration can access.

#. Get your integration token and set it as an environment variable:

.. code-block:: console

   $ export NOTION_TOKEN="your_integration_token_here"

Usage
~~~~~


.. code-block:: console

   # The JSON file will be in the build directory, e.g. ./build/notion/index.json
   $ notion-upload --file path/to/output.json --parent-page-id parent_page_id --title "Page Title"

Or with a database parent:

.. code-block:: console

   $ notion-upload --file path/to/output.json --parent-database-id parent_database_id --title "Page Title"

Arguments:

- ``--file``: Path to the JSON file generated by the Notion builder
- ``--parent-page-id``: The ID of the parent page in Notion (must be shared with your integration) - mutually exclusive with ``--parent-database-id``
- ``--parent-database-id``: The ID of the parent database in Notion (must be shared with your integration) - mutually exclusive with ``--parent-page-id``
- ``--title``: Title for the new page in Notion
- ``--icon``: (Optional) Icon for the page (emoji)
- ``--cover-path``: (Optional) Path to a cover image file for the page

The command will create a new page if one with the given title doesn't exist, or update the existing page if one with the given title already exists.

Automatic Publishing Configuration
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Instead of using the command-line tool, you can configure automatic publishing to Notion in your ``conf.py``.
When enabled, documentation will be uploaded to Notion automatically after a successful build with the ``notion`` builder.

Add the following configuration options to your ``conf.py``:

.. code-block:: python

   """Configuration for Sphinx."""

   # Enable automatic publishing to Notion
   notion_publish = True

   # Required: Parent page or database ID
   notion_parent_page_id = "your-page-id-here"
   # OR
   notion_parent_database_id = "your-database-id-here"

   # Required: Title for the Notion page
   notion_page_title = "My Documentation"

   # Optional: Icon emoji for the page
   notion_page_icon = "📚"

   # Optional: Cover image URL
   notion_page_cover_url = "https://example.com/cover.jpg"

   # Optional: Cancel upload if blocks to be deleted have discussion threads
   notion_cancel_on_discussion = True

**Configuration Options:**

``notion_publish``
   Enable automatic publishing to Notion after the build completes.
   When set to ``True``, the documentation will be uploaded to Notion automatically after a successful build with the ``notion`` builder.
   Default: ``False``

``notion_parent_page_id``
   The ID of the parent Notion page under which the documentation will be published.
   The page must be shared with your Notion integration.
   This option is mutually exclusive with ``notion_parent_database_id``.
   Default: ``None``

``notion_parent_database_id``
   The ID of the parent Notion database under which the documentation will be published.
   The database must be shared with your Notion integration.
   This option is mutually exclusive with ``notion_parent_page_id``.
   Default: ``None``

``notion_page_title``
   The title for the Notion page.
   This is required when ``notion_publish`` is ``True``.
   If a page with this title already exists under the parent, it will be updated.
   Otherwise, a new page will be created.
   Default: ``None``

``notion_page_icon``
   An optional emoji icon for the Notion page (e.g., ``"📚"``).
   Default: ``None``

``notion_page_cover_url``
   An optional URL for a cover image for the Notion page.
   Default: ``None``

``notion_cancel_on_discussion``
   When set to ``True``, the upload will be cancelled with an error if any blocks that would be deleted have discussion threads attached to them.
   This helps prevent accidentally losing discussion content.
   Default: ``False``

Publishing the Sample Document Locally
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

A convenience script is provided to build and publish the sample documentation to a test Notion page.

#. Create a ``.env`` file in the repository root with the following variables:

   .. code-block:: shell

      export NOTION_TOKEN=your_integration_token_here
      export NOTION_SAMPLE_DATABASE_ID=your_database_id_here

   This file is gitignored and will not be committed.

#. Run the script:

   .. code-block:: console

      $ ./publish-sample.sh

.. |Build Status| image:: https://github.com/adamtheturtle/sphinx-notionbuilder/actions/workflows/ci.yml/badge.svg?branch=main
   :target: https://github.com/adamtheturtle/sphinx-notionbuilder/actions
.. |PyPI| image:: https://badge.fury.io/py/Sphinx-Notion-Builder.svg
   :target: https://badge.fury.io/py/Sphinx-Notion-Builder
.. |minimum-python-version| replace:: 3.11

.. _atsphinx-audioplayer: https://github.com/atsphinx/atsphinx-audioplayer
.. _notion-integrations: https://www.notion.so/my-integrations
.. _published Notion page: https://www.notion.so/Sphinx-Notionbuilder-Sample-2579ce7b60a48142a556d816c657eb55
.. _sample document source: https://raw.githubusercontent.com/adamtheturtle/sphinx-notionbuilder/refs/heads/main/sample/index.rst
.. _sphinx-iframes: https://pypi.org/project/sphinx-iframes/
.. _sphinx-immaterial task_lists: https://github.com/jbms/sphinx-immaterial
.. _sphinx-simplepdf: https://sphinx-simplepdf.readthedocs.io/
.. _sphinx-toolbox collapse: https://sphinx-toolbox.readthedocs.io/en/stable/extensions/collapse.html
.. _sphinx-toolbox rest_example: https://sphinx-toolbox.readthedocs.io/en/stable/extensions/rest_example.html
.. _sphinx-toolbox: https://sphinx-toolbox.readthedocs.io/en/stable/extensions/
.. _sphinx.ext.mathjax: https://www.sphinx-doc.org/en/master/usage/extensions/math.html#module-sphinx.ext.mathjax
.. _sphinxcontrib-mermaid: https://github.com/mgaitan/sphinxcontrib-mermaid
.. _sphinxcontrib-text-styles: https://sphinxcontrib-text-styles.readthedocs.io/
.. _sphinxcontrib-video: https://sphinxcontrib-video.readthedocs.io
.. _sphinxnotes-strike: https://github.com/sphinx-toolbox/sphinxnotes-strike
