Skip to main content

Command Palette

Search for a command to run...

ServiceNow UI Builder

Custom Component Development

Updated
15 min read
ServiceNow UI Builder

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 snc CLI

  • In 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 snc stops working, run nvm use 22 to re-activate the correct version.

⚠️ Note: Node v20 will not work with snc CLI v29. You will see ERR_REQUIRE_ESM errors. 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: admin

  • Password: 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_snc is 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.

  1. In your PDI, type sys_properties.list in the navigation filter and press Enter.

  2. Search for the property glide.appcreator.company.code.

  3. 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

  1. Navigate to System Applications → Studio (or search "Application Creator" in the nav).

  2. Click Create Application.

  3. Enter a Name (e.g., "My Learning App").

  4. Note the auto-generated Scope value — it will look like x_snc_my_learning.

  5. 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 configuration

  • label — The display name shown in the UI Builder component panel

  • actions — Events your component can fire (e.g., button clicks)

  • properties — Input values that can be configured in UI Builder

  • scopeName — 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 loads

  • state — Read current values inside the view function

  • updateState — 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:

  1. Compiles your JSX and SCSS using Webpack

  2. Bundles all assets into optimized files

  3. Connects to your instance using the stored profile credentials

  4. Creates or updates records in sys_ux_lib_component and sys_ux_macroponent tables

  5. Uploads 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

  1. In your PDI, type UI Builder in the navigation filter and press Enter.

  2. Select an existing Experience (e.g., Employee Center, Service Operations Workspace) or create a new one.

  3. Open any Page within that experience.

6.2 Add Your Component to a Page

  1. In the left panel, look for the Components section.

  2. Search for the label you set in now-ui.json (default: "My Component").

  3. Drag it onto the page canvas.

  4. 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

  1. Edit — Open src/[component]/index.js or styles.scss in VS Code and make your changes

  2. Save — Save the file (Cmd+S). No build step needed before deploying

  3. Deploy — Run snc ui-component deploy --profile default --force

  4. Verify — 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 22 to your ~/.zshrc file 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 management

  • src/[component]/styles.scss — Component-scoped CSS/SCSS styles

  • now-ui.json — Properties and events exposed to UI Builder

  • now-cli.json — CLI configuration (scope, instance settings)

  • package.json — npm dependencies and build scripts


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.

26 views

UI Builder Bytes

Part 1 of 1