For the complete documentation index, see llms.txt. This page is also available as Markdown.

Custom Javascript Guide

This guide explains how to use the Custom JavaScript feature in NotionApps to add client-side behavior to a published app.

Custom JavaScript lets app builders run JavaScript in the end-user app after the app has loaded in the browser. You can use it for lightweight enhancements such as adding helper UI, sending analytics events, reading URL parameters, attaching event listeners, or applying small DOM-based customizations.

Who This Guide Is For

This guide is intended for:

  • App builders who want to add lightweight client-side behavior.

  • Technical writers documenting NotionApps customization workflows.

  • Customer success teams helping users validate custom scripts.

  • Developers or advanced users who understand browser JavaScript.

What Custom JavaScript Can Do

Custom JavaScript can be used to:

  • Log a confirmation message when the end-user app loads.

  • Add small UI elements, such as a footer note or help banner.

  • Read query parameters from the page URL.

  • Add event listeners to buttons, links, or forms.

  • Send client-side analytics events.

  • Modify the DOM after the app renders.

  • Observe dynamic page changes with MutationObserver.

  • Add small accessibility or usability enhancements.

Custom JavaScript should be used for small enhancements. It should not replace core app configuration, permissions, backend validation, or NotionApps product features.

What Custom JavaScript Should Not Do

Do not use Custom JavaScript to:

  • Store secrets, API keys, tokens, or private credentials.

  • Hide sensitive data that should not be shown to users.

  • Bypass permissions, authentication, or access controls.

  • Depend on private internal class names that may change.

  • Load untrusted third-party scripts.

  • Collect personal data without user consent.

  • Perform heavy computations in the browser.

  • Make the app unusable if the script fails.

Before You Begin

Before adding Custom JavaScript, make sure:

  • You know the exact behavior you want to add.

  • The app works correctly without the custom script.

  • The script does not require private backend credentials.

  • You have tested the script in a safe environment.

  • You understand how to open browser DevTools and check the Console.

Add Custom JavaScript in the Builder

To add Custom JavaScript:

  1. Open the app in the NotionApps builder.

  2. Go to the app settings area.

  3. Find the Custom JavaScript section.

  4. Enable Custom JavaScript.

  1. Paste your JavaScript into the editor.

  2. Save the app.

  3. Publish or re-publish the app.

  4. Open the published end-user app.

  5. Verify the script in browser DevTools.

Custom JavaScript applies to the published end-user app, not to the NotionApps builder interface.

Understand Where the Script Runs

Custom JavaScript runs in the browser for the end-user app.

This means:

  • It can access browser APIs such as document, window, and localStorage.

  • It can read the current URL.

  • It can modify visible page elements.

  • It can attach event listeners.

  • It can send network requests if allowed by browser security rules and CORS.

  • It cannot directly access server-only code.

  • It cannot safely store secrets because users can inspect browser code.

The supported run location is:

This means the script is intended to run only in the rendered end-user app.

Start with a Verification Script

Use a simple script first to confirm that Custom JavaScript is working.

After publishing, open the end-user app and check the browser Console. You should see:

You can also run this in the Console:

Expected result:

Use a Safe Script Wrapper

Wrap custom scripts in an immediately invoked function expression, also called an IIFE. This keeps variables out of the global scope and reduces the chance of naming conflicts.

For most scripts, use this pattern:

The try...catch block helps prevent one script error from breaking the rest of the page.

Target the End-User Root Element

The end-user app is wrapped in a root element with this attribute:

Use this selector to find the app root:

Always check whether the element exists before using it:

Wait for the App Root

The app may render dynamically. If your script runs before the app root is available, wait for it.

Use this helper:

This pattern is useful because many modern apps render or update content after the initial page load.

Avoid Depending Only on the Load Event

The browser load event may have already fired by the time your custom script runs.

This can fail:

Prefer code that runs immediately and checks the current document state:

For app elements that render later, use MutationObserver or a waitForElement helper.

This example adds a small footer note to the end-user app.

This script includes a guard:

The guard prevents duplicate footers if the script runs more than once.

Example: Add a Help Banner

This example adds a dismissible help banner above the app content.

Example: Read URL Parameters

This example reads a query parameter from the URL and logs it.

URL:

JavaScript:

You can use this pattern for analytics labels, user education flows, or non-sensitive display changes.

Example: Add a Class Based on a URL Parameter

This example adds a class to the app root when the URL contains theme=client.

You can pair this with Custom CSS:

Example: Send a Basic Analytics Event

This example sends a simple event to an analytics endpoint.

Only use endpoints that are approved by your organization.

Example: Track Button Clicks

This example listens for clicks on buttons inside the end-user app.

This uses event delegation. Event delegation is useful because buttons may be added or re-rendered after the script runs.

Example: Observe Dynamic Changes

Some app content changes after navigation, filtering, searching, or form interactions. Use MutationObserver to respond to those changes.

Use observers carefully. Avoid expensive work inside the observer callback.

Example: Add a Back-to-Top Button

This example adds a floating button that scrolls the user back to the top of the page.

Example: Load an External Script

Sometimes a customer may need to load an approved external script, such as an analytics script.

Error Handling

Always handle errors so a script does not break the page.

Recommended pattern:

For network requests:

Avoid Duplicate Elements

If your script adds DOM elements, add a marker attribute and check for it before creating another element.

This prevents duplicate banners, duplicate buttons, and duplicate event widgets.

Avoid Global Variables

Avoid creating global variables like this:

Prefer scoped variables inside a wrapper:

Avoid Fragile Selectors

Avoid selectors that depend on internal generated class names:

Generated class names can change after product updates or rebuilds.

Prefer stable selectors:

If you must target a specific element, inspect the page carefully and choose the least fragile selector available.

Security Best Practices

Follow these practices when authoring Custom JavaScript:

  • Review every script before publishing.

  • Do not paste code from unknown sources.

  • Do not include API keys, passwords, tokens, or secrets.

  • Do not collect personal data without approval.

  • Do not send data to unapproved third-party services.

  • Do not use Custom JavaScript to hide sensitive fields.

  • Do not change authentication or authorization behavior in the browser.

  • Keep scripts short and focused.

  • Test scripts in a non-production app first.

Performance Best Practices

Custom JavaScript runs in the user's browser, so performance matters.

Use these guidelines:

  • Keep scripts small.

  • Avoid long loops over large DOM trees.

  • Avoid frequent timers such as very short setInterval calls.

  • Avoid heavy work in MutationObserver callbacks.

  • Load external scripts only when needed.

  • Use event delegation instead of adding many individual event listeners.

  • Fail gracefully if an external service is unavailable.

Accessibility Best Practices

If your script adds UI elements:

  • Use semantic elements such as button for clickable actions.

  • Make sure buttons have visible text or accessible labels.

  • Ensure custom elements can be used with a keyboard.

  • Use sufficient color contrast.

  • Do not trap keyboard focus.

  • Do not remove focus outlines unless you replace them with an accessible focus style.

Example accessible button:

Verify That Custom JavaScript Worked

After saving and publishing, open the published app and use browser DevTools.

Check the Console

Add a log statement:

Then check the Console for:

Check a DOM Attribute

Add an attribute:

Then run this in the Console:

Expected result:

Check Added Elements

If your script adds an element, inspect the page and search for the marker attribute.

Example:

If the result is an element, the script added it successfully.

Troubleshooting

The Script Does Not Run

Check the following:

  • Custom JavaScript is enabled.

  • The app was saved after editing the script.

  • The app was re-published.

  • The published app was hard-refreshed.

  • The browser Console does not show a syntax error.

  • The script contains a console.log verification message.

The Script Runs but Cannot Find the App Root

The app root may not be available yet.

Use a wait helper:

The Script Adds Duplicate Elements

Add a marker attribute and check for it:

The Script Works Once but Breaks After Navigation

The app may re-render content after navigation or screen changes. Use event delegation or MutationObserver.

Event delegation example:

The Script Causes a Console Error

Wrap the script in try...catch:

An External Request Fails

Check:

  • The URL is correct.

  • The endpoint supports HTTPS.

  • The endpoint allows browser requests.

  • CORS is configured correctly.

  • The request does not require secret credentials.

  1. Start with a verification script.

  2. Publish and confirm the script runs.

  3. Add the smallest useful behavior.

  4. Wrap the script in an IIFE.

  5. Add error handling.

  6. Scope DOM changes to the end-user root.

  7. Add marker attributes for injected elements.

  8. Test on desktop and mobile.

  9. Test after navigation or screen changes.

  10. Re-publish and verify the final script.

JavaScript Pattern Reference

Goal
Pattern

Verify script runs

console.log("Custom JavaScript loaded")

Find app root

document.querySelector('[data-notionapps-end-user-root="true"]')

Avoid global scope

(function () { ... })();

Catch errors

try { ... } catch (error) { ... }

Read query params

new URLSearchParams(window.location.search)

Add an element

document.createElement("div")

Prevent duplicates

document.querySelector("[data-custom-element]")

Listen for clicks

root.addEventListener("click", callback)

Watch dynamic changes

new MutationObserver(callback)

Store non-sensitive preference

window.localStorage.setItem(key, value)

Starter Template

Use this as a starting point for most Custom JavaScript snippets:

Summary

Custom JavaScript in NotionApps is a flexible way to add small client-side enhancements to published apps. Use it carefully, keep scripts focused, and test thoroughly before publishing. For most use cases, start with a scoped script wrapper, target the end-user root, add error handling, and verify the result in browser DevTools.

Last updated