Trigger a webhook that activates on a POST request directly from a Notion table.
Below is a demo where I can just click a button on a Notion table and trigger Netlify’s build webhook to update my blog built with Notablog.
How to Use
Download this repository ↓ and follow the steps below.
This is a proof-of-concept, so it requires a few changes in code to adapt to your need. They are simple, and I’ll explain as clear as possible.
- Open
src/extension.js
, replace the dummybuildHookUrl
with your webhook URL.Any webhook should work, as long as the webhook can be triggered by an empty
POST
request (for example, Netlify’s).const buildHookUrl = 'https://Replace.this.string.with.your.webhook.URL'
- (Optional) If you want the trigger button appear only on one table, open
manifest.json
, incontent_scripts.matches
, changehttps://www.notion.so/*
to a table’s URL.If you skip this step, you will see the trigger button on every table.
"content_scripts": [ { "matches": [ "https://www.notion.so/*" // Change this ], // ... } ]
- Load this extension into your browser. Since this is an unpacked extension, it needs to be loaded in developer mode or debug mode.
For Google Chrome or Chromium-based, follow the tutorial: https://developer.chrome.com/extensions/getstarted.
For Firefox, follow the tutorial: https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Your_first_WebExtension#Installing.
- Open a table in Notion to test it out!
How it works
Motivation
In my case, I want to trigger Netlify’s webhook to rebuild my static blog when I update posts on Notion with a single click. The webhook needs to be triggered by an empty POST
request, but I couldn’t find a block in Notion that can do a POST
request. In addition, I use a full-page table to manage my posts, so there’s actually no way to insert other type of blocks unless I change the Notablog’s logic.
The Extension
Prepare a trigger button
I think of HTML’s <form>
, where I can specify the HTTP method and in my case I just need to POST
with an empty form. This is attractive to me because it doesn’t need AJAX, which means easier to implement !
So, I write the following
/** Prepare a button */
const button = document.createElement('DIV')
/** Give the button an id so we can check if it exists later */
button.setAttribute('id', BUTTON_ID)
button.setAttribute('style', 'margin-left: 10px; display: inline-flex')
/** * Use a dummy iframe to prevent redirect * @see https://stackoverflow.com/a/28060195 */
button.innerHTML = `\
<iframe width="0" height="0" border="0" name="dummyframe" id="dummyframe" style="display: none;"></iframe>
<form action="${buildHookUrl}" method="post" target="dummyframe">
<button style="font-size: 14px; color: white; height: 24px; background: rgb(46, 170, 220); border: none; border-radius: 3px;">Trigger Site Update</button>
</form>`
Create a <div>
, inside the <div>
we have a <form>
, then we have a <button>
in the <form>
. The <form>
has action
attribute set to the buildHookUrl
, which is the webhook URL, and method
attribute set to post
.
This translates to: When we click the button, an empty form will be submit to the URL in action
with post
method. This is exactly what I want.
Wait! What’s the <iframe>
doing there?
Actually, the script works without the <iframe>
, but in that case after we click the button, we will be redirected to the webhook URL. This behavior is a bit annoying, so I find a way to prevent it, see https://stackoverflow.com/a/28060195.
The <iframe>
is not rendered, and the I set target
attribute of <form>
to the <iframe>
’s id
, so the redirect happens in the <iframe>
, without effecting the main page.
Inject the button to the page
By default, the browser loads content scripts of an extension after a page is loaded. The “loaded” means HTML is loaded. This is OK for static web pages, but since Notion is an React app, the parent element we want to inject to may not exist when HTML is loaded, so we need additional techniques to handle it.
In the past, I used to use DOMSubtreeModified
event to trigger my injection logic (like this), but now this event seems to be completely removed from Firefox 68, so I have to find something new, which is the MutationObserver
.
/**
* Use MutationObserver API, DOMSubtreeModified event is deprecated.
* @see https://developer.mozilla.org/zh-TW/docs/Web/API/MutationObserver
*/
const observer = new MutationObserver(function (mutations) {
/** Try to hook up the button if not exist */
let toolbar = document.querySelector('div.notion-scroller:nth-child(2) > div:nth-child(2) > div:nth-child(1) > div:nth-child(1) > div:nth-child(2)')
let btn = document.getElementById(BUTTON_ID)
/** btn doesn't exists && toolbar exists */
if (!btn && toolbar) toolbar.appendChild(button)
});
const observerConfig = {
attributes: true,
childList: true,
characterData: true,
subtree: true
}
observer.observe(document, observerConfig)
The MutationObserver
behaves the same as DOMSubtreeModified
event, if I set subtree: true
in the config object.
Note that here I target document
in observe()
, which seems to be inefficient. But since Notion’s React app is bundled as an IIFE, which does not expose any variable that can be accessed from outside bundle, also we don’t know how does the app execute. So listening to all mutations in document
makes sure we don’t lose any chance to detect if the page is ready for injection.