Mastering UI Page Popups in ServiceNow: A Complete Guide for Native UI
- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
yesterday
In ServiceNow, UI Page popups are one of the most flexible and powerful ways to collect user input without navigating away from the current form. Even though most modern implementations are moving toward Workspaces, UI Page-based popups still play an important role in Native UI, especially when you need:
- Custom form inputs
- Complex UI interactions
- Small, focused data collection windows
- User confirmation dialogs
- Actions that update a record instantly
This article walks through how to build a clean, responsive, and user-friendly popup using a UI Page. We will build it step-by-step and gradually add different field types.
This tutorial is focused on ServiceNow’s Native UI, because UI Pages are not natively supported inside Workspaces.
Workspaces use Components and UI Builder, which are a completely different architecture. So if you are customizing Workspace, UI Pages will not appear inside modals — but for Native UI, UI Pages remain extremely powerful.
1.Simple Text area Popup :
To begin exploring UI Page popups, I started with a very simple example: a popup that asks the user to enter a short note. This helped me understand how UI Pages work, how the popup is displayed, and how the values entered by the user can be passed back to the server and saved on the record.
For this, I created a UI Page that contains only a label and a text area. The UI Page holds the complete HTML structure of the popup along with the styling. Inside the same page, I added a small client script that enables the Submit button only when the user types something. This gives the popup a cleaner user experience, because the user immediately knows that the input is required.
UI Page :
I created the UI Page inside the Customer Service application scope because the popup will be opened from the Case form.
The UI Page contains a single text area where the user can type a note. The Submit button is disabled by default and becomes active only when the user enters text. This prevents empty submissions and keeps the popup simple and user-friendly. After submitting, the popup closes automatically and the form refreshes so the updated Work Notes are visible immediately.
HTML :
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
<html>
<head>
<style>
div[class*="container"] {
padding-top: 6px !important;
margin-top: 0 !important;
}
.modal-body {
padding: 6px 16px 6px 16px !important;
}
label {
display: block;
margin: 0 0 6px 0 !important;
font-weight: normal;
}
textarea {
width: 100%;
height: 120px;
border: 1px solid #ccc;
border-radius: 4px;
resize: vertical;
padding: 8px;
font-size: 13px;
margin-bottom: 6px !important;
}
.modal-footer {
text-align: right;
padding: 6px 16px !important;
}
.btn {
padding: 6px 14px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
border: 1px solid #5052B7;
margin-left: 6px;
}
.btn-close {
background-color: #ffffff;
color: #5052B7;
}
.btn-submit {
background-color: #5052B7;
color: #ffffff;
}
.btn-submit:disabled {
background-color: #c9c9e2;
color: white;
cursor: not-allowed;
border-color: #c9c9e2;
}
.btn-close:hover {
background-color: #f3f3ff;
color: #5052B7;
}
.btn-submit:hover:enabled {
background-color: #4647a5;
color: #ffffff;
}
</style>
</head>
<body>
<div class="modal-body">
<label>Enter Notes:<span style="color:red;">*</span></label>
<textarea id="demo_comments" placeholder="Type something..." onkeyup="toggleSubmit();"></textarea>
</div>
<div class="modal-footer">
<button class="btn btn-close" onclick="GlideModal.get().destroy();">Close</button>
<button id="submitBtn" class="btn btn-submit" disabled="disabled" onclick="submitDemoNotes()">Submit</button>
</div>
</body>
</html>
</j:jelly>
Client Script :
Behind the scenes, the client script inside the UI Page sends the entered text to a Script Include using Glide Ajax. The Script Include then updates the Case record’s Work Notes field. Once the update is completed, the main form reloads to show the new entry. The popup itself is opened through a UI Action on the Case form, making it easy for the user to use this interaction whenever needed.
function toggleSubmit() {
var comments = document.getElementById('demo_comments').value.trim();
var submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = (comments === "");
}
function submitDemoNotes() {
var comments = gel('demo_comments').value.trim();
var ga = new GlideAjax('global.KAP_DemoPopupProcessor');
ga.addParam('sysparm_name', 'addNotes');
ga.addParam('sysparm_sys_id', g_form.getUniqueValue());
ga.addParam('sysparm_comments', comments);
ga.getXMLAnswer(function(response) {
if (response === 'success') {
GlideModal.get().destroy();
if (window.parent && window.parent.location) {
window.parent.location.reload();
} else {
window.location.reload();
}
}
});
}
UI Action :
The pop up appears like this in the Native UI on the form
Finally, when the user clicks Submit, the entered reason is passed to the Script Include, which updates the value in the Comments field of the record.
2.Dropdown Field Popup :
In this popup, the user selects a value for the Category field of the Case record. Instead of typing free text, the dropdown displays all the valid choices defined in the system for that field. To keep the UI Page flexible and reusable, I did not hardcode the options. Instead, the choices are loaded dynamically from the server using a Script Include and populated into the dropdown when the popup opens.
The UI Page contains a simple <select> element, and the client script calls the Script Include using GlideAjax to fetch the choice list for the category field. When a user selects a value, the Submit button becomes active. After submitting, the selected category is updated on the Case record, the popup closes, and the form refreshes automatically to show the updated value on the form.
HTML :
<?xml version="1.0" encoding="utf-8"?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
<html>
<head>
<style>
div[class*="container"] {
padding-top: 6px !important;
margin-top: 0 !important;
}
.modal-body {
padding: 6px 16px !important;
}
label {
display: block;
margin: 0 0 6px !important;
font-weight: normal;
}
select {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
margin-bottom: 10px;
font-size: 13px;
}
.modal-footer {
text-align: right;
padding: 6px 16px !important;
}
.btn {
padding: 6px 14px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
border: 1px solid #5052B7;
margin-left: 6px;
}
.btn-close {
background-color: #ffffff;
color: #5052B7;
}
.btn-submit {
background-color: #5052B7;
color: #ffffff;
}
.btn-submit:disabled {
background-color: #c9c9e2;
color: white;
cursor: not-allowed;
border-color: #c9c9e2;
}
.btn-close:hover {
background-color: #f3f3ff;
color: #5052B7;
}
.btn-submit:hover:enabled {
background-color: #4647a5;
color: #ffffff;
}
</style>
</head>
<body>
<div class="modal-body">
<label>Select Category:<span style="color:red;">*</span></label>
<select id="category" onchange="toggleSubmit();"></select>
</div>
<div class="modal-footer">
<button class="btn btn-close" onclick="GlideModal.get().destroy();">Close</button>
<button id="submitBtn" class="btn btn-submit" disabled="disabled" onclick="submitCategory()">Submit</button> </div>
</body>
</html>
</j:jelly>
Client Script :
function loadCategoryChoices() {
var ga = new GlideAjax('global.KAP_DemoPopupProcessor');
ga.addParam('sysparm_name', 'getFieldChoices');
ga.addParam('sysparm_table', 'sn_customerservice_case');
ga.addParam('sysparm_field', 'category');
ga.getXMLAnswer(function(response) {
var choices = JSON.parse(response);
var dropdown = document.getElementById('category');
dropdown.innerHTML = "<option value=''>-- Select --</option>";
choices.forEach(function(c) {
var opt = document.createElement("option");
opt.value = c.value;
opt.text = c.label;
dropdown.appendChild(opt);
});
});
}
loadCategoryChoices();
function toggleSubmit() {
var selected = document.getElementById('category').value;
document.getElementById('submitBtn').disabled = (selected === "");
}
function submitCategory() {
var category = gel('category').value;
var ga = new GlideAjax('global.KAP_DemoPopupProcessor');
ga.addParam('sysparm_name', 'updateCategory');
ga.addParam('sysparm_sys_id', g_form.getUniqueValue());
ga.addParam('sysparm_category', category);
ga.getXMLAnswer(function(response) {
if (response === 'success') {
GlideModal.get().destroy();
window.parent.location.reload();
}
});
}
UI Action :
The pop up appears like this in the Native UI on the form
3.Checkbox Popup :
I explored how to show or hide additional input fields based on the user’s selection. In this example, the popup contains a checkbox that allows the user to mark a Case as Urgent. When the checkbox is checked, a text area automatically appears where the user must provide a justification for marking the case as urgent. The Submit button becomes active only when both conditions are satisfied—checkbox selected and a reason entered.
When the user clicks Submit, the popup sends the urgency reason to a Script Include, which updates the Case by setting Urgency = High, Impact = High, and Priority = 1. The provided justification is added to the Work Notes to maintain proper audit tracking. After updating the record, the popup closes and the form reloads to show the new values immediately. This popup demonstrates how UI Pages can create simple conditional interactions that feel dynamic and user-friendly inside the Native UI.
UI Page:
HTML :
<?xml version="1.0" encoding="utf-8"?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
<html>
<head>
<style>
div[class*="container"] {
padding-top: 6px !important;
margin-top: 0 !important;
}
.modal-body {
padding: 6px 16px !important;
}
label {
display: block;
margin: 0 0 8px !important; /* increased gap */
font-weight: normal;
}
input[type="checkbox"] {
margin-right: 6px;
}
#reasonLabel {
margin-top: 12px !important; /* extra gap above textarea */
}
textarea {
width: 100%;
height: 100px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 8px;
font-size: 13px;
display: none;
margin-top: 10px !important; /* increased gap */
}
.modal-footer {
text-align: right;
padding: 6px 16px !important;
}
.btn {
padding: 6px 14px;
border-radius: 4px;
font-weight: 600;
border: 1px solid #5052B7;
margin-left: 6px;
cursor: pointer;
}
.btn-close:hover {
background-color: #f3f3ff;
}
.btn-submit {
background-color: #5052B7;
color: #fff;
}
.btn-submit:disabled {
background-color: #c9c9e2;
border-color: #c9c9e2;
cursor: not-allowed;
}
.btn-submit:hover:enabled {
background-color: #4647a5;
}
</style>
</head>
<body>
<div class="modal-body">
<label>
<input type="checkbox" id="urgentCheck" onchange="toggleUrgentFields();" />
Mark this Case as Urgent
</label>
<label id="reasonLabel" style="display:none;">Reason for marking urgent:<span style="color:red;">*</span></label>
<textarea id="urgent_reason" placeholder="Enter reason..." onkeyup="toggleSubmit();"></textarea>
</div>
<div class="modal-footer">
<button class="btn btn-close" onclick="GlideModal.get().destroy();">Close</button>
<button id="submitBtn" class="btn btn-submit" disabled="disabled" onclick="submitUrgentUpdate()">Submit</button>
</div>
</body>
</html>
</j:jelly>
Client Script :
function toggleUrgentFields() {
var checked = document.getElementById("urgentCheck").checked;
var textarea = document.getElementById("urgent_reason");
var label = document.getElementById("reasonLabel");
if (checked) {
textarea.style.display = "block";
label.style.display = "block";
} else {
textarea.style.display = "none";
label.style.display = "none";
textarea.value = "";
}
toggleSubmit();
}
function toggleSubmit() {
var isChecked = document.getElementById("urgentCheck").checked;
var reason = document.getElementById("urgent_reason").value.trim();
document.getElementById("submitBtn").disabled = !(isChecked && reason !== "");
}
function submitUrgentUpdate() {
var reason = document.getElementById('urgent_reason').value.trim();
var ga = new GlideAjax('global.KAP_DemoPopupProcessor');
ga.addParam('sysparm_name', 'markUrgent');
ga.addParam('sysparm_sys_id', g_form.getUniqueValue());
ga.addParam('sysparm_reason', reason);
ga.getXMLAnswer(function(response) {
if (response === 'success') {
GlideModal.get().destroy();
window.parent.location.reload();
}
});
}
UI Action :
The pop up appears like this in the Native UI on the form
After submitting, the Case is marked High priority and the entered reason is added to Work Notes.
4.Date Filed Popup :
In this example, the popup is used to capture a Follow-Up Date for the Case. Instead of updating the date directly on the form, a popup provides a focused and guided way for the user to select the required date before submitting.
The UI Page contains a single date field that allows the user to choose a follow-up date using the standard date picker. The Submit button remains disabled until a valid date is selected, ensuring that empty values are not submitted.
When the user clicks Submit, the selected date is sent to a Script Include using GlideAjax. The Script Include updates the Follow-Up Date field on the Case record. After the update, the popup closes automatically and the form reloads so the updated date is immediately visible to the user.
This approach is useful when follow-up dates are mandatory for certain actions and need to be captured with clarity and validation, without interrupting the user’s workflow.
UI Page:
HTML :
<?xml version="1.0" encoding="utf-8"?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
<html>
<head>
<style>
div[class*="container"] {
padding-top: 6px !important;
margin-top: 0 !important;
}
.modal-body {
padding: 6px 16px !important;
}
label {
display: block;
margin-bottom: 6px;
font-weight: normal;
}
.modal-footer {
text-align: right;
padding: 6px 16px !important;
}
.btn {
padding: 6px 14px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
border: 1px solid #5052B7;
margin-left: 6px;
}
.btn-close {
background-color: #ffffff;
color: #5052B7;
}
.btn-submit {
background-color: #5052B7;
color: #ffffff;
}
.btn-submit:disabled {
background-color: #c9c9e2;
cursor: not-allowed;
border-color: #c9c9e2;
}
.btn-close:hover {
background-color: #f3f3ff;
}
.btn-submit:hover:enabled {
background-color: #4647a5;
}
</style>
</head>
<body>
<div class="modal-body">
<label>Select Date:<span style="color:red;">*</span></label>
<!-- Native ServiceNow Date Picker -->
<g:ui_date_time
id="follow_up_date"
name="follow_up_date"
value=""
onchange="toggleSubmit();" />
</div>
<div class="modal-footer">
<button class="btn btn-close" onclick="GlideModal.get().destroy();">Close</button>
<button id="submitBtn" class="btn btn-submit" disabled="disabled" onclick="submitFollowUp()">Submit</button>
</div>
</body>
</html>
</j:jelly>
Client Script :
function toggleSubmit() {
var dateVal = gel('follow_up_date').value;
document.getElementById('submitBtn').disabled = (dateVal === '');
}
function submitFollowUp() {
var followUpDate = gel('follow_up_date').value;
var ga = new GlideAjax('global.KAP_DemoPopupProcessor');
ga.addParam('sysparm_name', 'updateFollowUpDate');
ga.addParam('sysparm_sys_id', g_form.getUniqueValue());
ga.addParam('sysparm_followup_date', followUpDate);
ga.getXMLAnswer(function(response) {
if (response === 'success') {
GlideModal.get().destroy();
window.parent.location.reload();
}
});
}
UI Action :
The pop up appears like this in the Native UI on the form
After submitting the popup, the selected date is passed to the Script Include and updated in the Case’s Follow-up Date field.
5.Reference Filed Popup :
In this use case, a popup is used to select a user using a sys_user reference field. The popup provides a simple and focused interface for choosing the required user instead of selecting directly from the form.
The popup contains a single reference field that opens the standard ServiceNow user lookup. The Submit button is enabled only after a user is selected, ensuring that an empty value is not submitted.
On submission, the selected user is sent to a Script Include using Glide Ajax, where the corresponding field on the Case record is updated. The popup then closes automatically and the form refreshes to display the updated value.
UI Page:
HTML :
<?xml version="1.0" encoding="utf-8"?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
<html>
<head>
<style>
div[class*="container"] {
padding-top: 6px !important;
margin-top: 0 !important;
}
.modal-body {
padding: 6px 16px !important;
}
label {
display: block;
margin: 0 0 6px !important;
font-weight: normal;
}
/* Keep reference field clean */
.reference-container {
margin-bottom: 10px;
}
.modal-footer {
text-align: right;
padding: 6px 16px !important;
}
/* Buttons — SAME AS YOUR ORIGINAL UI */
.btn {
padding: 6px 14px;
border-radius: 4px;
cursor: pointer;
font-weight: 600;
border: 1px solid #5052B7;
margin-left: 6px;
}
.btn-close {
background-color: #ffffff;
color: #5052B7;
}
.btn-submit {
background-color: #5052B7;
color: #ffffff;
}
.btn-submit:disabled {
background-color: #c9c9e2;
color: white;
cursor: not-allowed;
border-color: #c9c9e2;
}
.btn-close:hover {
background-color: #f3f3ff;
color: #5052B7;
}
.btn-submit:hover:enabled {
background-color: #4647a5;
color: #ffffff;
}
</style>
</head>
<body>
<div class="modal-body">
<label>Select User:<span style="color:red;">*</span></label>
<div class="reference-container">
<g:ui_reference id="assigned_to" name="assigned_to" table="sys_user" onchange="toggleSubmit();" />
</div>
</div>
<div class="modal-footer">
<button class="btn btn-close" onclick="GlideModal.get().destroy();">Close</button>
<button id="submitBtn" class="btn btn-submit" disabled="disabled" onclick="submitAssignment()">Submit</button>
</div>
</body>
</html>
</j:jelly>
Client Script :
function toggleSubmit() {
document.getElementById('submitBtn').disabled = !gel('assigned_to').value;
}
function submitAssignment() {
var assignedTo = gel('assigned_to').value;
if (!assignedTo)
return;
var ga = new GlideAjax('global.KAP_DemoPopupProcessor');
ga.addParam('sysparm_name', 'updateAssignedTo');
ga.addParam('sysparm_sys_id', g_form.getUniqueValue());
ga.addParam('sysparm_assigned_to', assignedTo);
ga.getXMLAnswer(function(resp) {
if (resp === 'success') {
GlideModal.get().destroy();
window.parent.location.reload();
}
});
}
UI Action:
The pop up appears like this in the Native UI on the form
After submitting the popup, the selected user is passed to the Script Include and updated in the Case’s Assigned to field.
6.Read only fields Popup :
In some scenarios, a popup is not only used to collect input, but also to show important information that the user should be aware of before taking an action. For example, before updating a Case, it can be helpful to display certain values like the Case Number, Short Description, or Current State in a read-only format.
In this popup, the field is displayed as read-only, meaning the user can see the value but cannot modify it. This ensures clarity and avoids accidental changes while still providing context to the user.
The UI Page displays the field value as disabled or read-only, fetched from the current record when the popup opens. Since no user input is required in this case, the Submit button can remain enabled or can be combined with another editable field if needed. After submission, the popup simply closes without modifying the displayed value.
This approach is useful when the popup acts as a confirmation or information screen, rather than a data entry form.
UI Page :
HTML :
<?xml version="1.0" encoding="utf-8"?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
<html>
<head>
<style>
.modal-body {
padding: 6px 16px !important;
}
label {
display: block;
margin-bottom: 4px;
font-weight: normal;
}
input[readonly] {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
background-color: #f7f7f7;
margin-bottom: 10px;
font-size: 13px;
}
.modal-footer {
text-align: right;
padding: 6px 16px !important;
}
.btn {
padding: 6px 14px;
border-radius: 4px;
border: 1px solid #5052B7;
background: #ffffff;
color: #5052B7;
cursor: pointer;
}
</style>
</head>
<body>
<div class="modal-body">
<label>Case Number</label>
<input type="text"
readonly="readonly"
value="${sysparm_number}" />
<label>Short Description</label>
<input type="text"
readonly="readonly"
value="${sysparm_short_desc}" />
</div>
<div class="modal-footer">
<button class="btn" onclick="GlideModal.get().destroy();">Close</button>
</div>
</body>
</html>
</j:jelly>
UI Action :
The pop up appears like this in the Native UI on the form