an enhancer/customiser for the all-in-one productivity workspace


This page gives an overview of the notion-enhancer's operations & interactions. If you have further questions after reading this, come talk to us in the Discord server or have a go at reading through the source code yourself.

If you plan on building a mod for the notion-enhancer, it is important to understand these concepts first.

How Notion Works

The Notion web client is, essentially, a complicated website. It is built with React, Next.js and TypeScript. These are packaged together through a build process that transpiles them into runnable code.

  • HTML is a markup language that describes the content of a page. Tags give structure to the page, attributes provide additional metadata about the tag, and content can be made up of text and/or child elements. Put together, these form a HTML element. The majority of Notion's HTML is generated by JavaScript.
    <tag attribute="value">content</tag>
  • CSS is a styling language that specifies how the browser should render HTML content. Notion uses stylesheets for dependency components (e.g. code highlight) and uses inline styles generated by React to style their own UI.
    /* stylesheet */ tag { property: value; }
    <!-- inline styles --> <tag style="property: value;"></tag>
  • JavaScript is a scripting language that brings interactivity to webpages. It can manipulate content on a page, follow logical instructions and perform system actions (e.g. copying something to the clipboard or sending a network request). The Notion client is powered by JavaScript, depending on it for almost everything e.g. rendering/generating page content, displaying interactive menus and saving user data.

The Notion backend handles Notion's hosting & provision. It is written with either Node.js or Ruby on Rails and hosted on AWS.

  • It serves the Notion client to the domain.
  • It handles user authorisation requests.
  • It accepts user-uploaded files to add to an authorisation-protected space on one of Notion's AWS servers (e.g. images).
  • It runs a PostgreSQL object-relational database to store user data/content. Database records are indexed by uuidv4 IDs and grouped into tables of different data types e.g. collection, notion_user, or block. The output from this is similar to the output from the official Notion API, but in a less processed/readable and more efficient/relational structure.

The Notion app is built with Electron, a tool used to package web technologies into cross-platform desktop apps. By default, the app:

  • Provides the notion:// URL scheme.
  • Performs some basic caching (not enough for true offline mode).
  • Contains some additional localisation services.
  • Loads the website with the Chromium browser engine in an apparently standalone/native app window.

The Notion app itself is not a client. It is a wrapper for the web client. The app is versioned & updated separately to the Notion web client, and receives new web client features without having to be updated itself.

How notion-repackaged Works

notion-enhancer/notion-repackaged provides executables containing both Notion and the notion-enhancer for dependency-free installation and brings the Notion app to Linux.

  1. The official Windows build of the Notion app is downloaded and its source code is extracted.

  2. The notion-enhancer CLI from notion-enhancer/desktop is run on the extracted app sources.

  3. The app icon is replaced with the notion-enhancer logo.

  4. The dependency cache (node_modules) is emptied & the correct dependencies for the target platform downloaded.

  5. The app is re-compiled into exectuable form by the electron-builder.

The app can then be directly installed or uninstalled with the notion-enhancer included as if it were any other normal app. The autoupdater is a little unreliable at this stage, working for some platforms but not others - reinstalling when a new release is available is recommended.

As this process essentially hacks the app, the re-compiled executables are unsigned and may be detected as malware by antivirus software. This warning can be safely ignored - the entire build process is done publicly through GitHub Actions and can be inspected in the notion-enhancer/notion-repackaged repository.

How The notion-enhancer Works

  1. The notion-enhancer's loader is injected into Notion. This is done differently depending on the environment.

    • In browsers, the loader is registered as a content script.

      notion-enhancer scripts and other assets are loaded over the brower-provided URL scheme e.g. extension://.

    • In the app, JavaScript sources are extracted from the app's .asar and appended with a require() call to the loader.

      The notion-enhancer is copied into the Notion app's node_modules directory and scripts and other assets are loaded by modifying the Notion app's custom URL scheme to provide the notion-enhancer to URLs prefixed with notion:// This lets the notion-enhancer bypass Notion's Content Security Policy.

  2. The repo/registry.json file is read as a list of available mod directories to load and validate mod.json files from.

  3. Depending on the runtime context the loader is in (e.g. an Electron source file, the Notion client, the notion-enhancer menu or the app frame), paths specified in enabled mods' mod.json files are loaded (by require() for a .cjs Electron source insert, import() for a .mjs client script, or a <link> tag appended to the document head for a .css file).

  4. The default exports of mod scripts are called with the notion-enhancer API and the mod database provided as arguments. Electron source inserts are also provided with the exports of their target files and an eval() function to access/override variables and functions in their target files' scopes.

    At this point, the Notion client is usually mid-way through loading. Due to the obfuscation, complexity and provision of the Notion client's source code, the notion-enhancer cannot directly update the Notion client or hook into its instance of React. All enhancements are done as overrides and additions to the client after it has been loaded.

    This does unfortunately come with some unavoidable lag. To deal with this:

    • The API unifies observation of user keypresses and changes to the Notion document into simple interfaces for more efficient reactive enhancement through minimal event listeners.

    • Notion themes are not imitated with 100% accuracy. The complex selectors and colour variations that would be needed to do so would bring heavy performance drops, particularly when opening large pages or zooming in/out of pages. Instead similar colours are combined into a single CSS variable (e.g. --theme--ui_divider applies to all the various outlines and borders).

Edit this page