DEV Community


Posted on • Originally published at


How to add Sign In with Notion to your webapp

Hey everyone! This is kind of a continuation of this tutorial where we create a simple React/Express.js app that saves form responses directly to your Notion database. For this part, we’re picking up where we left off. How can we allow users to connect their Notion accounts so we can programmatically do things like fetch their data or saves responses to a Notion page in their workspace? It’s actually pretty easy!

1. Make your integration into a public integration

First, go to and select the integration you created in the last tutorial, Basic Form, or just create a new one.

Image description

Scroll down a bit and change your integration to a “Public integration.” This means this integration will allow other users to connect their Notion accounts to your integration rather than only your own account.

Image description

In order to create a “Public integration”, you’ll need to fill in some info about your company. If you haven’t set up your own website, you can use your Notion page urls for things like your homepage or privacy policy site! Just whip up a Notion page, write some text, and plug it in here. Also, MAKE SURE YOU ADD “http://localhost:3000” TO REDIRECT URIs. This is very necessary.

Image description

Once you’ve submitted everything, scroll down to hit the save button. Once you do that, if you scroll to the top, you’ll now get an “OAuth client ID” and “OAuth client secret,” which you’ll need to use in your project.

Image description

2. Add a sign in link to your React app

Alright, now that we got these, let’s start building. Let’s go back to the “form-tool” app that we created in the last tutorial (you can also just create a new react app with npx create-react-app form-tool ) and go to the App.js file and paste into it the below code. I’ll explain how this works in a bit.

// form-tool/src/App.js

import { useEffect, useState } from "react";

// The OAuth client ID from the integration page!
const oauth_client_id = "02e1f9d8-...";

function App() {
  const [dbs, setdbs] = useState([]);

  // When you open the app, this doesn't do anything, but after you sign into Notion, you'll be redirected back with a code at which point we call our backend.
  useEffect(() => {
    const params = new URL(window.document.location).searchParams;
    const code = params.get("code");
    if (!code) return;
    fetch(`http://localhost:3002/login/${code}`).then(async (resp) => {
      setdbs(await resp.json());
  }, []);

  return (
        style={{ display: "block" }}
        Connect to Notion
      { => (
            display: "inline-flex",
            whiteSpace: "pre",
            border: "1px solid black",
            marginBottom: 10,
          {JSON.stringify(db, null, 2)}

export default App;
Enter fullscreen mode Exit fullscreen mode

When you run npm run start, we get the following plain website.

Image description

When you click “Connect to Notion”, you should be brought over here to this sign in page.

Image description

Once you fill everything out, we’re redirected back to our site and… nothing happens. That’s cause we need to update our backend as well. Before that, let’s explain what’s happening.

Essentially, we created a link to Notion’s site with the OAuth Client ID that allows you to sign into your Notion account. Once you select your workspace and the pages you want to give access to, you’ll be redirected to the url http://localhost:3000, which you should have put into the Redirect URI field in the integrations page. The caveat is now you’re given a secret code in the query parameter so the full URL is http://localhost:3000?code=SECRET_CODE… . With this secret code, you now can access the Notion user’s workspace.

So the flow goes: you open http://localhost:3000, you click on the click and go to…, and once you fill everything out, you’ll be sent to http://localhost:3000?code=CODE_VALUE…, with the code in hand. With this code, we’ll call our backend to start the real magic.

3. Generate an access token and fetch user’s information with the Notion API

Okay, now that we’ve logged in and have the code, now what do we with it? Well, we need to create a new endpoint in our backend. Let’s take the code that we just got from the frontend and convert it into an “access token” that we can actually use. With the token, we’ll return the user’s databases, but theoretically we can do anything we like with the Notion API. Go to your “form-tool-backend” Express.js project (you can also just create a new Express.js project), and go to the file app.js and paste in the code below. Make sure to update the variables with values in the integrations page that we retrieved earlier.

// form-tool-backend/app.js

const express = require("express");
const axios = require("axios");
const cors = require("cors");
const app = express();
const port = 3002;

// The OAuth client ID from the integration page!
const notionClientId = "02e1f9d8-...";

// The OAuth client secret from the integration page!
const notionClientSecret = "secret_...";

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);

app.get("/login/:code", async (req, res) => {
  const { code } = req.params;

  // Generate an access token with the code we got earlier and the client_id and client_secret we retrived earlier
  const resp = await axios({
    method: "POST",
    url: "",
    auth: { username: notionClientId, password: notionClientSecret },
    headers: { "Content-Type": "application/json" },
    data: { code, grant_type: "authorization_code" },

  // You want to save and if you want to make requests later with this Notion account (otherwise they'll need to reauthenticate)

  // Use the access token we just got to search the user's workspace for databases
  const { data } = await axios({
    method: "POST",
    url: "",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${}`,
      "Notion-Version": "2021-08-16",
    data: { filter: { property: "object", value: "database" } },

Enter fullscreen mode Exit fullscreen mode

What’s happening? Well, the code we just got from the frontend, we send it to our backend, and with the code as well as our OAuth client ID and OAuth client secret, we can generate an “access_token” which is the real important thing. So we just used a code to then create our beautiful “access_token” which we can use with the Notion API to interact with the user’s workspace. The “access_token” is very powerful and thus should be hidden away in your backend only and should never be leaked. Save the “access_token” and “workspace_id” from the response we get from our first API call to the userID of the person that just signed in. Whenever you want to call the Notion API for them, retrieve the “access_token” so you don’t need them to sign in again to Notion.

With the “access_token”, we retrieve the user’s databases in the Notion workspace and return it to the frontend. We can do anything we like using this “access_token” that is outlined in the Notion API docs.

Once we add this endpoint to our backend, if we go back to our website and Connect to Notion, it will now fetch the database information and display it on your site.

Image description

Amazing! So what did we just do? Well, users can connect their Notion accounts, and then we can do things like fetch their data as well as make changes to their Notion workspace. So how could we use this to create a form app like Commotion? A user can connect their Notion account, and we’ll fetch their Notion database. With the database, we’ll generate a form, and when someone submits a response, we’ll take that data and add it to the user’s Notion database, all with the API. All we do is fetch the database here, but with the “access_token”, we can do much more.

We hope that this was a helpful tutorial! If you want forms for your Notion workspace but don’t have the time, definitely check us out at!

Top comments (2)

digital_hub profile image

Hi there i love this thread.

btw - a new tool is here.

AFFiNE is the Next-Gen Knowledge Base to Replace Notion & Miro. Open-source, privacy-first, and always free. Built with Typescript/React/Rust

AFFiNE live demo has been released, please click and have a try:
[quote]Before we tell you how to get started with AFFiNE, we'd like to shamelessly plug our awesome user and developer communities across official social platforms! Once you’re familiar with using the software, maybe you will share your wisdom with others and even consider joining the AFFiNE Ambassador program to help spread AFFiNE to the world.
Privacy focussed — AFFiNE is built with your privacy in mind and is one of our key concerns. We want you to keep control of your data, allowing you to store it as you like, where you like while still being able to freely edit and view your data on-demand.
Offline-first - With your privacy in mind we also decided to go offline-first. This means that AFFiNE can be used offline, whether you want to view or edit, with support for conflict-free merging when you are back online.
Clean, intuitive design — With AFFiNE you can concentrate on editing with a clean and modern interface. Which is responsive, so it looks great on tablets too, and mobile support is coming in the future.
Seamless transitions — However you want your data displayed, whichever viewing mode you use, AFFiNE supports easy transitions to allow you to quickly and effortlessly view your data in the way you want.
Markdown support — When you write in AFFiNE you can use Markdown syntax which helps create an easier editing experience, that can be experienced with just a keyboard. And this allows you to export your data cleanly into Markdown.
Choice of multiple languages — Thanks to community contributions AFFiNE offers support for multiple languages. If you don't find your language or would like to suggest some changes we welcome your contributions.[/quote]

johnhuang14231 profile image
John Huang • Edited

Hi Ryan,
Thanks for putting it all together and this is what I am looking for.
But I have a question on the third step to fetch the data from notion API.
Does the response we get in step two contain the field for page id?
I wanna fetch the page content and show it on the page after user authorization.
When the user is directed to the site for signing into notion account, the page in notion for access can be selected. I wonder where is the page id stored in the response body.

Visualizing Promises and Async/Await 🤯

async await

☝️ Check out this all-time classic DEV post