ServiceNow UI Builder
Custom Component Development

Building Custom UI Builder Components in ServiceNow: A Hands-On Guide
If you've ever wanted to extend ServiceNow's Next Experience with your own custom UI Builder components but found the official docs scattered across half a dozen pages, this guide walks you through the entire journey end-to-end. From installing the right Node version to deploying a working, interactive component on your personal developer instance (PDI), every step is here in order.
By the end, you'll have a fully functional counter card running inside UI Builder, complete with configurable properties and events that page authors can wire up without touching code.
TL;DR
Prerequisites: Node.js v22, npm, ServiceNow CLI, UI Component extension
One-time setup: about 15–20 minutes
Components are built using: ServiceNow's UI Framework (Snabbdom renderer) with JSX syntax
Deployment: pushes directly to your instance via the
sncCLIIn UI Builder: components appear in the component panel and can be dragged onto any page
Section 1 — Prerequisites & Installation
All tools must be installed before you can create or deploy components. Follow each step in order.
1.1 Node.js v22
The ServiceNow CLI requires Node.js version 22 specifically. Node v20 causes ESM compatibility errors during project scaffolding and deployment.
Step 1. Check if Node is already installed:
node -v
npm -v
Step 2. If Node is not installed or is the wrong version, install nvm (Node Version Manager) first:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
Step 3. Reload your terminal to activate nvm:
source ~/.zshrc
Step 4. Install and activate Node v22:
nvm install 22
nvm use 22
node -v # Should show v22.x.x
💡 Tip: nvm lets you switch between Node versions easily. If you ever open a new terminal and
sncstops working, runnvm use 22to re-activate the correct version.
⚠️ Note: Node v20 will not work with
sncCLI v29. You will seeERR_REQUIRE_ESMerrors. Always use Node v22.
1.2 npm Update
npm ships with Node but may need a permissions fix on macOS if it was previously installed by root.
sudo chown -R \((id -u):\)(id -g) ~/.npm
npm install -g npm@latest
npm -v # Should show 10.x.x or higher
1.3 ServiceNow CLI (snc)
The snc CLI is ServiceNow's command-line tool for deploying components to your instance.
npm install -g @servicenow/cli
snc help # Verify installation
Expected output from snc help shows available command groups including ui-component and configure.
⚠️ Note: The install produces many deprecation warnings — these are harmless. As long as you see
added X packages, the install succeeded.
1.4 UI Component Extension
The base CLI does not include UI component deployment. You must add this extension separately.
snc extension add --name ui-component
snc extension list # Verify it shows version 29.x.x
💡 Tip: If the extension is already installed, the command will say so — that is fine, no action needed.
1.5 Authorize CLI to Your PDI
Connect the CLI to your ServiceNow instance so it knows where to deploy components.
snc configure profile set
The CLI prompts you interactively. Enter the following values:
Host: Your PDI URL (e.g.,
https://dev12345.service-now.com)Login method: Basic
Username:
adminPassword: Your PDI admin password
Default output format: JSON
A successful connection shows:
Connection to https://your-instance.service-now.com successful.
⚠️ Note: The message "This instance does not support dynamic commands" is normal for PDIs and does not affect component deployment.
Section 2 — Create a Scoped App in Your PDI
Every UI component belongs to a scoped application. The CLI validates the scope against your instance, so you must create the scoped app first before scaffolding the project.
2.1 Why a Scoped App Is Required
ServiceNow components are namespaced under a scope (e.g., x_mycompany_myapp). The snc CLI sends the scope name to your instance during project creation to register it. If the scope does not exist, the CLI returns a 400 Bad Request error.
⚠️ Note: The scope prefix
x_sncis reserved by ServiceNow and cannot be used for custom development even on a PDI.
2.2 Find Your Vendor Prefix
Your PDI has a unique vendor prefix stored in a system property. This prefix is used automatically when you create scoped apps.
In your PDI, type
sys_properties.listin the navigation filter and press Enter.Search for the property
glide.appcreator.company.code.Note the Value field — this is your vendor prefix (e.g.,
snc,abc,demo).
Your scope names will follow the pattern x_[prefix]_[appname] — for example, x_snc_myapp.
2.3 Create the Scoped Application
Navigate to System Applications → Studio (or search "Application Creator" in the nav).
Click Create Application.
Enter a Name (e.g., "My Learning App").
Note the auto-generated Scope value — it will look like
x_snc_my_learning.Click Create.
Keep the exact scope value handy — you will need it in the next section.
Section 3 — Scaffold a New Component Project
The snc CLI generates all the boilerplate files needed for a UI component. Run this from a new, empty folder.
3.1 Create a Project Folder
The CLI will refuse to scaffold into a non-empty directory. Always create a fresh folder first.
mkdir my-first-component
cd my-first-component
3.2 Run the Project Generator
Replace x_snc_my_learning with your actual scope name from Section 2.
snc ui-component project \
--profile default \
--name x-snc-my-learning \
--scope x_snc_my_learning
The component name uses hyphens (x-snc-my-learning) while the scope uses underscores (x_snc_my_learning). This is a ServiceNow convention.
3.3 What Gets Generated
The CLI creates this project structure:
my-first-component/
├── src/
│ ├── snc-x-snc-my-learning/
│ │ ├── index.js ← Your component logic lives here
│ │ ├── styles.scss ← Component-scoped CSS
│ │ └── __tests__/ ← Unit tests
│ └── index.js ← Entry point (imports your component)
├── example/
│ └── element.js ← Local preview HTML
├── now-ui.json ← Component metadata for UI Builder
├── now-cli.json ← CLI config
└── package.json
3.4 Understanding now-ui.json
This file controls how your component appears and behaves in UI Builder. Key fields:
components— Registers the component tag name and its UI Builder configurationlabel— The display name shown in the UI Builder component panelactions— Events your component can fire (e.g., button clicks)properties— Input values that can be configured in UI BuilderscopeName— Must match your scoped app's scope name
Section 4 — Understanding & Modifying Your Component
UI Builder components use ServiceNow's UI Framework, which is based on the Snabbdom virtual DOM renderer. The syntax looks like React JSX but uses ServiceNow's own APIs.
4.1 Anatomy of index.js
The generated index.js contains three core parts:
// 1. IMPORTS — ServiceNow UI Framework libraries
import { createCustomElement } from '@servicenow/ui-core';
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';
// 2. VIEW FUNCTION — Returns the HTML to render
// 'state' holds your component's data
// 'updateState' is a function to change state values
const view = (state, { updateState }) => (
<div>
<h1>Hello World</h1>
</div>
);
// 3. REGISTRATION — Registers the component with ServiceNow
createCustomElement('snc-x-snc-my-learning', {
renderer: { type: snabbdom },
view,
initialState: { clicked: false }, // Starting data values
styles
});
4.2 How State Works
State is your component's data storage. Think of it like variables that, when changed, cause the UI to re-render automatically.
initialState— Sets default values when the component first loadsstate— Read current values inside the view functionupdateState— Call this to change a value and trigger a re-render
// initialState defines your data model
initialState: { count: 0, message: 'Hello' }
// In the view, read state values
const view = (state, { updateState }) => (
<div>
<p>Count: {state.count}</p>
<button onclick={() => updateState({ count: state.count + 1 })}>
Click me
</button>
</div>
);
4.3 Handling User Interactions
Event handlers in Snabbdom use lowercase event names (onclick, oninput, onchange) directly on the JSX element.
// Button click
<button onclick={() => updateState({ clicked: true })}>
Click Me
</button>
// Text input — captures typed value
<input
type="text"
oninput={(e) => updateState({ name: e.target.value })}
/>
// Conditional rendering using &&
{state.clicked && <p>You clicked the button!</p>}
4.4 Styling with SCSS
Add styles to the styles.scss file in your component folder. Styles are automatically scoped to your component and do not affect the rest of the page.
// styles.scss
.my-card {
background: #f0f4ff;
border-radius: 8px;
padding: 16px;
border-left: 4px solid #1c69d4;
}
.my-button {
background: #1c69d4;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
Then reference the class in your JSX:
<div className="my-card">
<button className="my-button">Save</button>
</div>
4.5 Adding Properties (Input from UI Builder)
Properties let UI Builder admins pass data into your component without modifying code. Define them in now-ui.json, then access them through state.
Step 1 — Add the property to now-ui.json:
"properties": [
{
"name": "greeting",
"label": "Greeting Text",
"fieldType": "string",
"default": "Hello!"
}
]
Step 2 — Access it in index.js via state.properties:
const view = (state, { updateState }) => (
<div>
<h1>{state.properties.greeting}</h1>
</div>
);
4.6 Firing Events to UI Builder
Components can fire events that UI Builder pages can listen to and react on (e.g., open a modal, navigate to a page).
Step 1 — Register the event in now-ui.json:
"actions": [
{
"name": "BUTTON_CLICKED",
"label": "On Button Click",
"payload": [{ "name": "itemId", "label": "Item ID" }]
}
]
Step 2 — Dispatch the event in index.js:
const view = (state, { updateState, dispatch }) => (
<button onclick={() => dispatch('BUTTON_CLICKED', { itemId: 42 })}>
Open Item
</button>
);
💡 Tip: After deploying, this event will appear in UI Builder's Events tab for your component, where page authors can wire it to actions like Open Modal or Navigate to URL.
Section 5 — Deploy to Your PDI
Once your component code is ready, deploy it to your ServiceNow instance with a single command.
5.1 Standard Deployment
Run from inside your project folder (the folder containing package.json):
snc ui-component deploy --profile default --force
The --force flag overwrites any existing version of the component on your instance. Always include it during development.
5.2 What Happens During Deployment
The CLI performs these steps automatically:
Compiles your JSX and SCSS using Webpack
Bundles all assets into optimized files
Connects to your instance using the stored profile credentials
Creates or updates records in
sys_ux_lib_componentandsys_ux_macroponenttablesUploads your component's tile icon to
db_image
A successful deployment ends with Finished deploying!
5.3 Clean Deployment (When Things Go Wrong)
If you encounter build errors, a clean install often resolves them by clearing stale cached dependencies.
rm -rf node_modules package-lock.json
npm install
snc ui-component deploy --profile default --force
5.4 Common Deployment Errors
| Error | Solution |
|---|---|
ERR_REQUIRE_ESM |
You are using Node v20. Switch to Node v22 with nvm use 22 |
| Cannot scaffold in non-empty folder | Create a new empty folder first: mkdir myfolder && cd myfolder |
| Scope name is invalid (400) | Create the scoped app in ServiceNow Studio first, then retry |
| Cannot read properties of undefined | Node version mismatch. Run nvm use 22 then retry |
| Connection failed / 401 | Re-run snc configure profile set and verify your credentials |
Section 6 — Use Your Component in UI Builder
After a successful deployment, your component is available in UI Builder on any page in your instance.
6.1 Open UI Builder
In your PDI, type
UI Builderin the navigation filter and press Enter.Select an existing Experience (e.g., Employee Center, Service Operations Workspace) or create a new one.
Open any Page within that experience.
6.2 Add Your Component to a Page
In the left panel, look for the Components section.
Search for the label you set in
now-ui.json(default: "My Component").Drag it onto the page canvas.
Your component renders immediately in the preview.
💡 Tip: If your component does not appear, wait 30 seconds and refresh the page. The deployment sometimes takes a moment to propagate.
6.3 Configure Properties in UI Builder
When your component is selected on the canvas, the right panel shows its configurable properties (the ones you defined in now-ui.json). UI Builder admins can change these values without touching code.
6.4 Wire Events in UI Builder
If your component fires events (Section 4.6), they appear in the Events tab of the right panel when the component is selected. From there, page authors can attach actions such as:
Open Modal
Navigate to URL or Page
Set a data binding value
Call a REST API via an action
Section 7 — Development Workflow
Once your project is set up, follow this cycle for every change.
7.1 The Edit → Deploy → Verify Loop
Edit — Open
src/[component]/index.jsorstyles.scssin VS Code and make your changesSave — Save the file (
Cmd+S). No build step needed before deployingDeploy — Run
snc ui-component deploy --profile default --forceVerify — Refresh UI Builder in your browser. The component updates immediately
7.2 Local Development Server
The CLI includes a local development server that lets you preview your component in a browser without deploying to your instance. This is faster for UI iteration.
snc ui-component develop
This starts a local server (usually at http://localhost:8081) where you can see your component in isolation. Hot reload is supported — changes reflect after saving the file.
⚠️ Note: Local develop mode only previews the component in isolation. To test it inside a real UI Builder page, you still need to deploy.
7.3 Keeping Node Version Active
Every time you open a new Terminal window, you may need to re-activate Node v22 if nvm does not set it automatically.
nvm use 22 # Re-activate Node v22
node -v # Confirm: v22.x.x
💡 Tip: Add
nvm use 22to your~/.zshrcfile to make it automatic on every new terminal session.
Section 8 — Complete Working Example
Below is a complete, ready-to-deploy component that demonstrates state, interaction, properties, and events together.
8.1 index.js — Interactive Counter Card
import { createCustomElement } from '@servicenow/ui-core';
import snabbdom from '@servicenow/ui-renderer-snabbdom';
import styles from './styles.scss';
const view = (state, { updateState, dispatch }) => (
<div className="card">
<h2>{state.properties.title || 'My Counter'}</h2>
<p className="count-display">Count: {state.count}</p>
<div className="button-row">
<button
className="btn-primary"
onclick={() => {
const newCount = state.count + 1;
updateState({ count: newCount });
dispatch('COUNT_CHANGED', { count: newCount });
}}
>
+ Increment
</button>
<button
className="btn-secondary"
onclick={() => updateState({ count: 0 })}
>
Reset
</button>
</div>
{state.count >= 10 && (
<p className="milestone">🎉 Reached 10!</p>
)}
</div>
);
createCustomElement('snc-x-snc-my-learning', {
renderer: { type: snabbdom },
view,
initialState: { count: 0 },
styles
});
8.2 styles.scss — Card Styles
.card {
background: white;
border-radius: 8px;
padding: 24px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
max-width: 320px;
}
.count-display {
font-size: 24px;
font-weight: bold;
color: #1c69d4;
}
.button-row {
display: flex;
gap: 8px;
margin-top: 16px;
}
.btn-primary {
background: #1c69d4;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.btn-secondary {
background: #f0f0f0;
color: #333;
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
}
.milestone {
color: green;
font-weight: bold;
}
8.3 now-ui.json additions
"properties": [
{
"name": "title",
"label": "Card Title",
"fieldType": "string",
"default": "My Counter"
}
],
"actions": [
{
"name": "COUNT_CHANGED",
"label": "On Count Changed",
"payload": [{ "name": "count", "label": "Current Count" }]
}
]
Section 9 — Quick Reference
9.1 All Commands at a Glance
| Command | Purpose |
|---|---|
nvm use 22 |
Activate Node v22 in current terminal |
snc configure profile set |
Connect CLI to a ServiceNow instance |
snc extension add --name ui-component |
Install the UI component extension |
mkdir myfolder && cd myfolder |
Create and enter a new project folder |
snc ui-component project --profile default --name x-snc-myapp --scope x_snc_myapp |
Scaffold a new component project |
snc ui-component develop |
Start local preview server |
snc ui-component deploy --profile default --force |
Deploy component to instance |
rm -rf node_modules package-lock.json && npm install |
Clean reinstall of dependencies |
9.2 Key Files Reference
src/[component]/index.js— Component logic, view function, and state managementsrc/[component]/styles.scss— Component-scoped CSS/SCSS stylesnow-ui.json— Properties and events exposed to UI Buildernow-cli.json— CLI configuration (scope, instance settings)package.json— npm dependencies and build scripts
9.3 Useful Links
Lastly
With these steps, I hope you can go from a clean machine to a deployed, configurable, event-firing UI Builder component running on your PDI. The Edit → Deploy → Verify loop in Section 7 is the rhythm you'll settle into — once the tooling is in place, iteration becomes fast.
If you run into issues that aren't covered in the error table, the most common culprits are a stale Node version after opening a new terminal and a scope mismatch between now-ui.json and the scoped app on the instance. Check those two first before going deeper.
