Service Portal widgets let you build custom UI components that display data or capture user input without the limitations of stock portal pages. This guide walks you through creating a widget from scratch using the Widget Editor.
Why custom widgets beat stock portal pages
Stock Service Portal pages work for basic needs, but fall short when you need custom data displays, specific user interactions, or UI components that don't exist out of box. Before widgets, portal customization meant either hacking existing pages (fragile) or building entire custom pages (overkill). Widget development affects two groups: platform developers who build them and end users who interact with them through portal pages.
How Service Portal widgets work
A widget has four components: HTML template for structure, CSS for styling, client script (AngularJS 1.x controller) for user interactions, and server script for data queries. The server script runs first and populates a data object that gets passed to the client controller. Most widgets start simple — server script queries a table, client script displays the results. Production widgets add error handling, loading states, user input validation, and spUtil service calls for portal-specific operations like navigation and user context.
From working widget to production-ready component
Once your basic widget works, the improvements that matter are: proper error handling for failed queries, loading indicators while data fetches, input validation before server calls, and mobile-responsive CSS. Advanced widgets integrate with portal theming, handle real-time updates via server-sent events, and use widget dependencies to share code across multiple widgets. The difference between a demo widget and a production one is graceful failure handling and user experience polish.
Before you start
- •sp_admin or admin role
- •Service Portal instance configured and accessible
Sourdough: ServiceNow Monitoring and Analytics
A Chrome extension for ServiceNow Admins and Developers with essential tools, analytics, graphs and monitoring features.
Free to install. Pro $5/month after a 14-day no-card trial.
Pro requires the ServiceNow admin role. Upgrade inside the extension.
Step by step
Open the Widget Editor
Navigate to Service Portal > Widgets and click New. The Widget Editor opens with four tabs: HTML Template, CSS - SCSS, Client Script, and Server Script. Name your widget something descriptive — you'll reference this in portal page configurations later. The ID field auto-populates from the name and can't be changed after save.
Widget IDs become part of the DOM, so avoid spaces and special characters even though ServiceNow allows them.
Write the server script
Click the Server Script tab and write the GlideRecord query or other server-side logic. Populate the data object with whatever the client needs — data.records = results.getTableRecords(), data.userRole = gs.hasRole('admin'), etc. The data object gets serialized and passed to the client controller automatically. Keep queries efficient — this runs every time the widget loads.
Use data.loading = true initially, then set data.loading = false after queries complete to show loading states on the client.
Build the HTML template
Click HTML Template and write AngularJS 1.x template markup. Reference server data with expressions like {{data.records.length}} or ng-repeat="item in data.records". Use Bootstrap classes for styling — Service Portal includes Bootstrap 3 by default. Add ng-click handlers that call client controller functions: ng-click="c.doSomething(item)".
Wrap your template in a single div with a widget-specific class for CSS scoping.
Implement the client controller
Click Client Script to write the AngularJS controller. Access server data via data, call server functions with c.server.get() or c.server.update(), and use spUtil service for portal operations. Common spUtil methods: spUtil.recordWatch for real-time updates, spUtil.getURL for navigation, spUtil.addErrorMessage for user feedback. Controller functions get bound automatically — c.functionName becomes available in the HTML template.
Add CSS styling
Click CSS - SCSS and write styles scoped to your widget. ServiceNow compiles SCSS to CSS automatically, so you can use variables and nesting. Scope styles to your widget class to avoid conflicts: .my-widget-class { .some-element { color: blue; } }. Portal pages inherit theme CSS, so your widget styling builds on top of existing styles.
Test the widget
Save the widget, then navigate to Service Portal > Pages and either create a test page or edit an existing one. Drag your widget from the widget library onto the page. Click the portal URL to view the live widget. Use browser developer tools to debug AngularJS scope issues — look for the ng-scope elements and inspect $scope.data.
Handle errors and edge cases
Add server-side error handling with try/catch blocks and populate data.error with user-friendly messages. On the client, check for data.error before rendering content and display appropriate messages. Handle empty result sets gracefully — show "No results found" instead of broken templates. Add loading indicators while server calls execute using data.loading flags.
Use c.server.refresh() to re-run the server script without a full page reload when users take actions that change data.
Best practices
Always validate user input in both client and server scripts — client validation for UX, server validation for security.
Keep server scripts fast by limiting GlideRecord queries and avoiding unnecessary table joins — widgets run on every page load.
Use spUtil.recordWatch() instead of polling when you need real-time updates — it's more efficient and provides better user experience.
Scope CSS classes to your specific widget to avoid style conflicts when multiple widgets exist on the same page.
Handle mobile breakpoints in your CSS — portal users frequently access widgets on phones and tablets.
Test Your Knowledge
Quick 3-question quiz — see how your ServiceNow skills stack up.
A list view on a table with millions of records is slow. Best fix?
Select an answer to continue