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!
Make your integration into a public integration
First, go to https://notion.so/my-integrations and select the integration you created in the last tutorial, Basic Form, or just create a new one.
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.
We'll do it live!
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.
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.
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 (
<div>
<a
style={{ display: "block" }}
href={`https://api.notion.com/v1/oauth/authorize?client_id=${oauth_client_id}&response_type=code&owner=user`}
>
Connect to Notion
</a>
{dbs.map((db) => (
<div
style={{
display: "inline-flex",
whiteSpace: "pre",
border: "1px solid black",
marginBottom: 10,
}}
>
{JSON.stringify(db, null, 2)}
</div>
))}
</div>
);
}
export default App;
When you run npm run start
, we get the following plain website.
nifty
When you click âConnect to Notionâ, you should be brought over here to this sign in page.
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 https://www.notion.so/install-integration?âŚ
, 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.
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.use(cors());
app.use(express.json());
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: "https://api.notion.com/v1/oauth/token",
auth: { username: notionClientId, password: notionClientSecret },
headers: { "Content-Type": "application/json" },
data: { code, grant_type: "authorization_code" },
});
// You want to save resp.data.workspace_id and resp.data.access_token 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: "https://api.notion.com/v1/search",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${resp.data.access_token}`,
"Notion-Version": "2021-08-16",
},
data: { filter: { property: "object", value: "database" } },
});
res.json(data?.results);
});
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.
These tutorials just drip with great design
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 Commotion.page!
The Notion API officially came out of beta on May 13, 2021, enabling web developers to create cool apps that integrate with your Notion workspace, an example being ourselves, Commotion. For our form tool, we use the Notion API to save form responses to a connected Notion database. In this tutorial, weâre gonna show you how to create a simple form tool like ours that accepts emails with an html form and saves it to your Notion workspace. Letâs get started!
First, create a Notion account (obviously). Then create a Database table (typing in â/databaseâ) and add a column called Email
that is of type email, like below.
From humble beginnings
Open the database object as its own page by clicking the expand icon. Weâll come back to this in a second.
Then go to https://www.notion.so/my-integrations and select âCreate new integrationâ and give it the name âBasic Formâ or something.
Go back to your databaseâs Notion page and share the database with the app you just created by hitting Share
on the top right, clicking the Invite input in the dropdown, and selecting your app from the popup. This makes sure that the API actually has access to this database.
For this tutorial, we need to setup a backend, so weâre going with Express. You can read how to set it up here, but the gist of it is to go to a new folder and input the following commands in your terminal.
$ mkdir form-tool-backend
$ cd form-tool-backend
$ npm install express
$ npm install axios
$ npm install cors
Within the form-tool-backend
folder, create a file app.js
and paste the following into it just to get things going.
// form-tool-backend/app.js
const express = require('express')
const axios = require('axios')
const app = express()
const port = 3002
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Why do we need a backend? Because we need to use secret keys that we donât want to leak out. If someone were to get your secret key, that would be able to read and and write anything to your Notion workspace! Very scary.
Finally, letâs actually start building! Iâd open up https://developers.notion.com/reference cause weâll be referencing this page a lot. First, we need to get our secret key, so letâs go back to https://www.notion.so/my-integrations, select our app, then copy our Internal Integration Token. Make sure to not leak this!
Show and copy the Token
Paste the following code below into your app.js
and replace the secretKey variable with the token you just copied. Weâll then create a route that fetches the database data with axios.
// form-tool-backend/app.js
const express = require('express')
const axios = require('axios')
const app = express()
const port = 3002
// REPLACE! Retrieved from https://www.notion.so/my-integrations
const secretKey = 'secret_***************'
// What we'll pass into axios
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${secretKey}`,
'Notion-Version': '2021-08-16',
}
// Route to fetch database data
app.get('/:database_id', async (req, res) => {
const { database_id } = req.params;
const resp = await axios({
method: 'GET',
url: 'https://api.notion.com/v1/databases/' + database_id,
headers
});
return res.json(resp.data);
})
app.use(cors())
app.use(express.json())
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Now we have something to test! Within your form-tool-backend
folder, run:
$ node app.js
Go back to your Notion database in your browser and copy your database_id
from your Notion url, marked as the part here:
Copy the ID from the URL and ignore everything after "?"
Now if your run the following in a different terminal tab:
curl http://localhost:3002/REPLACE_THIS_WITH_DATABASE_ID
You should get the data from your database!
Next, we need to create our frontend. In this case, weâre using React. Go to your terminal, go to the folder you want to put all your code in, and input the following command to create a sample app.
$ npx create-react-app form-tool
$ cd form-tool
$ npm start
It's so beautiful
Letâs make some changes, shall we? Replace the contents of the App.js
with the below, and replace the database_id
value with the one you copied earlier from the Notion URL.
// form-tool/src/App.js
import { useEffect, useState } from 'react';
const database_id = REPLACE_WITH_YOUR_DATABASE_ID;
function App() {
const [db, setDB] = useState({});
useEffect(() => {
// We fetch the data from the local server that we're running
fetch('http://localhost:3002/' + database_id).then(async (resp) => {
setDB(await resp.json())
});
}, []);
return (
<div>
{db.title?.[0]?.plain_text}
</div>
);
}
export default App;
This just gets the data that our backend retrieves and brings it into our frontend. The schema for a database object is a little unique, so checkout the official documentation here. If we run npm start
, we should get the following.
It's plain, but it works!
Weâve officially fetched data from our Notion workspace! Hooray!
Finally, what weâve all been waiting for, letâs actually take some information and save it to our database shall we? Weâll start by adding a simple form in our frontend React project that sends the email + name to our backend to send to Notion.
import { useEffect, useState } from 'react';
const database_id = REPLACE_WITH_YOUR_DATABASE_ID;
function App() {
const [db, setDB] = useState({});
// NEW! This will send the form information to our backend we setup
const onSubmit = (e) => {
e.preventDefault();
fetch('http://localhost:3002/' + database_id, {
method: 'POST',
body: JSON.stringify({ email: e.target.email.value, name: e.target.name.value }),
headers: { "Content-Type": 'application/json' }
});
}
useEffect(() => {
fetch('http://localhost:3002/' + database_id).then(async (resp) => {
setDB(await resp.json());
});
}, []);
return (
<div>
{db.title?.[0]?.plain_text}
{/* NEW! This is an extremely simple form to collect the information */}
<form onSubmit={onSubmit}>
<input name="name" placeholder="Name" required />
<input name="email" placeholder="Email" type="email" required />
<button>Submit</button>
</form>
</div>
);
}
export default App;
It's a WIP
We now have an extremely simple form. Right now, nothing actually happens if we submit the form. Thatâs cause we need to build the backend function! If we switch back to our Express app, we can create a POST route that accepts our data, the name and email, and sends it to our Notion database. Entries in a database are equivalent to a Notion âpageâ, so we need to create a page where the database_id
is the parent. You can reference https://developers.notion.com/reference/post-page to get a better sense of what this means.
const express = require('express')
const axios = require('axios')
const cors = require('cors')
const app = express()
const port = 3002
// REPLACE! Retrieved from https://www.notion.so/my-integrations
const secretKey = 'secret_***************'
// What we'll pass into axios
const headers = {
'Content-Type': 'application/json',
Authorization: `Bearer ${secretKey}`,
'Notion-Version': '2021-08-16',
}
// NEW! This is a new route that will actually send the information to Notion
app.post('/:database_id', async (req, res) => {
const { database_id } = req.params
const { name, email } = req.body
await axios({
method: 'POST',
url: 'https://api.notion.com/v1/pages',
headers,
data: {
parent: { database_id },
properties: {
"Name": { "title": [{ text: { content: name } }] },
"Email": { "email": email }
}
}
})
})
// Route to fetch
app.get('/:database_id', async (req, res) => {
const { database_id } = req.params;
const resp = await axios({
method: 'GET',
url: 'https://api.notion.com/v1/databases/' + database_id,
headers
});
return res.json(resp.data)
})
app.use(cors())
app.use(express.json())
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
Once we get this up and running and we restart our backend by quitting and rerunning node app.js
, we can input a name and email into our form and it will save to our Notion database!
Voila! We did it!
So this has been a tutorial on how to save form responses to YOUR Notion database, but this doesnât explain how we at Commotion are able to save responses to ANYONEâs Notion database. This requires making your integration a public one and having your user connect their Notion account like how we do it.
Hit Connect
And your prompted to give us access to your Notion workspace
Weâll save this for another time, but itâs not actually too bad. Weâre 75% of the way there already. In the mean time, please check our Commotion if you need to create forms that work with your Notion workspace or want to send emails to contacts in a Notion database, mail merge style. We hope that this was informative!