Contents

    Guides

    Build a Chrome Extension in Next.js and Notion API

    Ravgeet Dhillon

    Ravgeet is a Full Stack Developer/Technical Content Writer based in India who codes and writes about React, Vue, Flutter, Node, Strapi, and Python.

    Published on

    April 14, 2022
    Build a Chrome Extension in Next.js and Notion API

    Chrome extensions are a great way to customize your browsing experience. Most of the time, Chrome extensions need to be reactive and this is where building the extension in vanilla JavaScript can be a painful experience. To overcome this shortcoming, you can use a JavaScript-based front-end framework like Next.js or Nuxt.js to build your Chrome extensions.

    In this tutorial, you’ll learn to build a Chrome extension using Next.js. For this tutorial, you’ll build a Chrome extension that allows you to tag and save web links to a Notion database.

    What is Next.js

    Next.js is a React.js framework for building server-side rendered React.js applications. It has a bunch of features with a good developer experience and supports TypeScript out of the box. This is tutorial will be helpful even if you want to use React.js as it is instead of using Next.js.

    What is Notion

    According to Notion, it is a way of organizing docs, wikis, tasks in a single place. Basically, Notion is a sort of database that allows you to view your data in multiple views like a kanban board, table, timeline, calendar, gallery, list to showcase your data but with a nice visual interface.

    For this tutorial, you’ll set up a database table in Notion to store your web links. To connect with Notion, you can use their API for public use.

    Prerequisites

    To follow along with this tutorial, you’ll need the following things:

    The entire source code for this tutorial is available in this GitHub repository.

    Creating database in Notion

    The first thing you need to do is to create a Notion database.

    First, in your Notion workspace, create a new page and give it the title - Bookmarks.

    Next, click inside the text area of the new page and type /table full. Select Table - Full page in the modal that appears.

    Image of popup modal to create a table in the Notion workspace page

    Next, give the table the same title as you gave to the page (”Bookmarks”).

    Every database table in Notion has fields, which are shown as columns. For the Bookmarks table, add the following columns by clicking the plus sign (+) icon in the header row of the table:

    1. Title (property type: Text) - for storing the title of the weblink
    2. URL (property type: URL) - for storing the weblink’s URL
    3. Tags (property type: Select) - for storing the tags related to weblink
    4. Notes (property type: Text) - for storing any additional notes
    5. Added On (property type: Created time ) - for an auto fillable date when you add a new entry
    Image of the bookmark table in the Notion workspace

    Getting Notion API token

    To use the Notion API, you’ll need to create a Notion integration in order to obtain a Notion API token.

    To do so, first, visit your integrations page and click on the New integration button in the left sidebar.

    View of your integrations page in your Notion workspace

    Next, configure the basic information about the integration and name it “Notion Bookmark”. Select the appropriate workspace and click Submit.

    screenshot of the Notion workspace view when filling out basic information about the integration you just created

    Once the integration is complete, you will be presented with a secret Internal Integration API token. Copy it somewhere safe as you’ll need it later while writing the code.

    View of the secret Internal Integration API token

    Sharing database with integration

    By default, Notion integrations don't have access to pages or databases in the workspace. You need to share the specific page or database that you want to connect to the Notion integration.

    To do so, first, open the Bookmarks database in your Notion workspace and click on the Share link in the upper right corner. A modal will appear. Use the search field to the left of the Invite button to find the “Notion Bookmark” integration, and click Invite.

    The "Notion Bookmark” integration in your Notion database

    That’s it. You have set up your Notion database and you can now add data to it with the help of the Notion API.

    Obtaining Notion database ID

    To add entries to your Bookmarks database, you need its database ID.

    If your Notion database is within a workspace, the URL structure of the database page may look like this: *https://www.notion.so/{workspace_name}/{database_id}?v={view_id}*

    If your Notion database is not within a workspace, or if it simply doesn’t match the URL shown above, it probably looks like this: *https://www.notion.so/{database_id}?v={view_id}*

    Depending upon your URL structure, extract the 36-character-long {database_id} portion from the URL. Keep it somewhere safe as you will need it to connect with this particular database through Notion API.

    In case you get stuck somewhere, make sure to check out Notion’s documentation page for working with databases for more information.

    Setting up Next.js project

    Now that you have completely set up your Notion, it's time to build the Next.js frontend app.

    First, open up your terminal, navigate to a path of your choice, and execute the following command to create a Next.js project:

    npx create-next-app@latest

    On the terminal, when you are asked about the project‘s name, set it to notion-bookmark. Next, it will install all the NPM dependencies.

    Finally, after the installation is complete, navigate into the notion-bookmark directory and start the Next.js development server by running the following commands in your terminal:

    cd notion-bookmark
    npm run dev

    This will start the development server on port 3000 and take you to localhost:3000. The first view of the Next.js website will look like this:

    First view of Next.js website

    Setting up development environment

    There are a couple of things you need to configure for building Chrome extension in Next.js,

    First, you need to install the following packages:

    Shut down the Next.js development server by pressing Control-C in your terminal and then run the following commands to install the NPM dependencies:

    npm install @notionhq/client
    npm install --save-dev npm-watch

    Once the installation is complete, next, open the package.json and add the following scripts to the scripts property:

    "scripts": {
      ...
      "dev": "npm-watch",
      "lint:build": "mv out/_next out/assets && sed -i 's/\\\\/_next/\\\\/assets/g' out/**.html",
      "build": "next build && next export && npm run lint:build",
    }

    In the above configuration:
    1. dev-extension is used to run the npm-watch.
    2. Since for some reason, Chrome extensions can’t have an underscore in their paths, the lint:build script renames the paths in your build files.
    3. The build script is the final script you need to run to build the Chrome extension for production. This script is run by the npm-watch. This prevents you from running the build script, again and again, and makes sure that the build script runs automatically when the files change.

    Next, add the following configuration for npm-watch to the package.json:

    {
      ...
      "watch": {
        "build": {
          "patterns": [
            "styles/**",
            "pages/**",
            "public/**",
            "helpers/**",
            "next.config.js"
          ],
          "ignore": [
            "out",
            "node_modules",
            ".next",
            ".vscode"
          ],
          "extensions": [
            "js",
            "json"
          ]
        }
      }
    }

    The above configuration tells npm-watch to run the build script whenever the files matching with the patterns and extensions array change.

    Configuring Chrome extension settings

    In the public directory, create an inject.js and a manifest.json file. Leave the inject.js file empty and add the following configuration to manifest.json file:

    {
      "manifest_version": 3,
      "name": "Notion Bookmarks",
      "short_name": "notion-bookmark",
      "version": "1.0.0",
      "description": "Store Links to Notion",
      "icons": {
        "16": "/icons/icon-16.png",
        "32": "/icons/icon-32.png",
        "64": "/icons/icon-64.png",
        "128": "/icons/icon-128.png"
      },
      "permissions": ["activeTab"],
      "content_scripts": [
        {
          "matches": ["https://*/*", "http://*/*"],
          "js": ["inject.js"]
        }
      ],
      "host_permissions": ["<all_urls>"],
      "action": {
        "default_popup": "index.html"
      }
    }

    To know more about the above properties and keys, make sure to check out Google’s official documentation for Chrome extensions.

    Finally, run the following command to build and export your Chrome extension for the first time:

    npm run build

    Next, you need to register your extension within Google Chrome.

    Registering Chrome extension

    First, visit Chrome Extensions by visiting chrome://extensions in the Chrome browser.

    Next, turn on the Developer mode.

    Developer mode on the chrome browser

    Next, click on Load unpacked and select the path that references to the out directory in your project directory (notion-bookmark).

    If your extension loads successfully, it’ll be added under the Chrome extensions and in the chrome extensions bar.

    chrome extension bar view

    Finally, click the extension icon in the extension bar and you’ll get the Next.js default home page in your extension.

    Image of the Next.js default home page in your extension

    Saving weblinks to Notion

    Now that your extension is set up, next, you need to write code to collect and save web links to the Bookmarks database.

    First, at the project’s root, create an .env file and the following environment variables to it:

    NOTION_DATABASE_ID=<your-notion-database-id>
    NOTION_API_TOKEN=<your-notion-api-token>

    Replace the <your-notion-database-id> and <your-notion-api-token> with your respective values.

    Next, open the next.config.js file and add the following two environment variables to the env property:

    module.exports = {
      ...
      env: {
        NEXT_PUBLIC_NOTION_API_TOKEN: process.env.NOTION_API_TOKEN,
        NEXT_PUBLIC_NOTION_DATABASE_ID: process.env.NOTION_DATABASE_ID,
      },
    };

    In the above config, you have specified the NEXT_PUBLIC_NOTION_API_TOKEN and NEXT_PUBLIC_NOTION_DATABASE_ID. It is important to prefix NEXT_PUBLIC to your environment variables so that they can be used on the client-side.

    Building Chrome extension’s UI

    The UI for your Chrome extension basically needs a form to collect the data from the user and a button to save the form data to the Notion database.

    First, in the pages directory, replace the code in index.js file with the following code:

    // 1
    import { Client } from '@notionhq/client'
    import { useEffect, useState } from 'react'
    
    // 2
    export default function Home() {
      // 3
      const [pageData, setPageData] = useState({})
      const [isSaving, setIsSaving] = useState(false)
      const [isSaved, setIsSaved] = useState(false)
    
      async function saveBookmarkToNotion(bookmark) {
        // todo
      }
    
      async function handleSubmit(e) {
        // todo
      }
    
      // 4
      return (
        <div>
          <div>
            <h1>Save to Notion Bookmarks</h1>
          </div>
          <div>
            {isSaved ? (
              <span>Saved</span>
            ) : (
              <form onSubmit={handleSubmit}>
                <div>
                  <label>Title</label>
                  <input name="title" type="text" defaultValue={pageData.title} title={pageData.title} required />
                </div>
                <div>
                  <label>URL</label>
                  <input name="url" type="url" defaultValue={pageData.url} title={pageData.url} required />
                </div>
                <div>
                  <label>Languages</label>
                  <input name="languages" type="text" />
                  <small>Separate Languages with Commas</small>
                </div>
                <div>
                  <label>Tags</label>
                  <input name="tags" type="text" />
                  <small>Separate Tags with Commas</small>
                </div>
                <div>
                  <label>Notes</label>
                  <input name="notes" as="textarea" rows={3} />
                </div>
                <div>
                  <button type="submit" disabled={isSaving}>
                    {isSaving ? <span>Saving</span> : <span>Save</span>}
                  </button>
                </div>
              </form>
            )}
          </div>
        </div>
      )
    }

    In the above code:

    1. You import the required NPM packages.
    2. You export a React functional component, Home.
    3. You initialize the state variables for the components using the useState hooks.
    4. You return the UI for the Home component.


    Note: We have skipped the styling part of the Chrome extension because we believe that it is important for us to help you understand the functionality as you can style the extension per your own preference.

    Next, add the following code just before the return method of the Home component:

    useEffect(() => {
      // 1
      chrome.tabs &&
        chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
          // 2
          const url = tabs[0].url
          const title = tabs[0].title
          // 3
          setPageData({ url, title })
        })
    }, [])

    In the above code:

    1. You use the Chrome API to query the currently opened tab.
    2. You get the active tab’s URL (url) and title (title).
    3. You set the pageData state variable.

    Next, add the following code for the saveBookmarkToNotion method:

    async function saveBookmarkToNotion(bookmark) {
      // 1
      const notion = new Client({
        auth: process.env.NEXT_PUBLIC_NOTION_API_TOKEN,
      })
    
      try {
        // 2
        await notion.pages.create({
          parent: {
            database_id: process.env.NEXT_PUBLIC_NOTION_DATABASE_ID,
          },
          properties: {
            Title: {
              title: [
                {
                  text: {
                    content: bookmark.title,
                  },
                },
              ],
            },
            URL: {
              url: bookmark.url,
            },
            Tags: {
              multi_select: bookmark.tags,
            },
            Notes: {
              rich_text: [
                {
                  text: {
                    content: bookmark.notes || '-',
                  },
                },
              ],
            },
          },
        })
        return true
      } catch (error) {
        return false
      }
    }

    In the above code:

    1. You create a new client for authenticating with the Notion API by providing the Notion API token (NEXT_PUBLIC_NOTION_API_TOKEN).
    2. In a Notion database, each entry is known as a page. So, you create a new page (notion.pages.create) and define the parent and properties for the page. You can get information about all the properties in the Notion API docs.

    Next, add the following code for the handleSubmit method:

    async function handleSubmit(e) {
      e.preventDefault()
      setIsSaving(true)
    
      // 1
      const data = new FormData(e.target)
      const bookmark = Object.fromEntries(data.entries())
    
      // 2
      bookmark.tags = bookmark.tags
        .split(',')
        .filter((tag) => tag.trim().length !== 0)
        .map((tag) => ({
          name: tag.trim(),
        }))
    
      // 3
      const result = await saveBookmarkToNotion(bookmark)
    
      // 4
      if (result) {
        setIsSaved(true)
      } else {
        setIsSaving(false)
      }
    }

    In the above code:

    1. You get the form’s data (e.target) and convert it to a JSON object (Object.fromEntries).
    2. Since tags are a comma-separated string, you convert the tags into an array of objects where each object is of {name: tag} structure.
    3. You pass the modified bookmark object to the saveBookmarkToNotion function.
    4. Based on the result, you update the state variables isSaved and isSaving.

    Next, save your progress and start the development server by running the following command:

    npm run dev

    The above command will build your extension and make it ready for being a Chrome extension. The coding part is complete and finally, you need to test the Chrome extension.

    Testing Chrome extension

    To verify that everything is working as expected, visit your favorite web page and try saving it to your Bookmarks database with the help of your Chrome extension:

    GIF showing how you can bookmark your favorite web page by saving it to your Bookmarks database

    And it works flawlessly!

    Conclusion

    That’s it. Today, you learned to build a Chrome extension using Next.js. You also learned to interact with Notion API and save your data to a Notion database. You can add/remove fields according to your needs and customize the extension according to your needs.

    Since Notion API is still in beta, the above code may/may not work in the future. To make sure that you are able to track the production issues, you can try Bird.

    The entire source code for this tutorial is available in this GitHub repository.

    Data-rich bug reports loved by everyone

    Get visual proof, steps to reproduce and technical logs with one click

    Make bug reporting 50% faster and 100% less painful

    Rating LogosStars
    4.6
    |
    Category leader

    Liked the article? Spread the word

    Put your knowledge to practice

    Try Bird on your next bug - you’ll love it

    “Game changer”

    Julie, Head of QA

    star-ratingstar-ratingstar-ratingstar-ratingstar-rating

    Overall rating: 4.7/5

    Try Bird later, from your desktop