Develop ServiceNow Service Portal widgets directly from VS Code — with live preview, hot reload, IntelliSense from your own instance, and safe push guards.
A complementary workflow that speeds up widget development: pull a widget into a Git-versionable workspace, edit with real autocomplete, and use an instant local browser preview to iterate on layout and styling fast. It doesn't aim to replace the platform — it gives you a quick build-and-preview loop in the editor, then pushes your changes back safely.
Highlights
- 🖥️ Local live preview that renders the widget (AngularJS + Bootstrap) and refreshes on save — no browser needed
- ⚡ Hot reload — CSS swaps in-place (no flicker), template recompiles in Angular, scripts reload automatically
- 🆕 Create widgets from VS Code — scaffold a new
sp_widget and start editing locally, no platform UI needed
- 🧠 Real IntelliSense — not just
gs, GlideRecord, $sp, spUtil, but the Script Includes from YOUR instance, synced automatically
- 🏃
server.js runs locally in a sandbox: gs, GlideRecord (with dot-walking), GlideAggregate, Script Include execution, and gs.getMessage/${} resolved from your instance
- 📴 Offline mode — develop with no instance at all, feeding data via a per-widget
fixtures.json
- 🔒 Working Context guard — blocks push when the Update Set is Default or belongs to a different scope
- 🧬 Conflict detection + visual diff before overwriting
- 📦 Git-friendly — each widget becomes a folder of files
- 🚫 No browser extension — everything via the REST API
Requirements
- Node.js 20+
- VS Code 1.85+
- A ServiceNow instance (PDI or dev) you can edit
- A user with read/write access to
sp_widget (and admin or a role with read on sys_script_include for instance IntelliSense)
Installation
- In VS Code, open the Extensions tab (
Ctrl/Cmd+Shift+X)
- Search for SN Widget Kit
- Click Install
Then open your widget project folder and run SN Widget: Login to Instance.
Build-from-source instructions are in Development at the bottom.
Basic workflow
Login → Pull widget → Open Live Preview → edit + save (hot reload) → Push
- Login:
SN Widget: Login to Instance
- Pull:
SN Widget: Pull Widget → pick from the list → downloads to widgets/<id>/
- Preview:
SN Widget: Open Live Preview → opens in the browser, connected over WebSocket
- Edit: save any file → the preview updates instantly
- Push:
SN Widget: Push Current Widget (or enable auto-sync)
Each widget is pulled as a folder:
<workspace>/widgets/<widget-id>/
├── template.html → template field
├── client.js → client_script field
├── server.js → script field (server script)
├── styles.scss → css field (SCSS, compiled in the preview)
├── link.js → link field
├── option-schema.json → option_schema field
├── demo-data.json → demo_data field
└── .widget-meta.json → metadata (sys_id, scope, timestamp) — not pushed
Command reference
All available in the Command Palette (Ctrl/Cmd+Shift+P), prefix SN Widget.
Connection
| Command |
What it does |
Login to Instance |
Asks for URL, username and password. Validates via sys_user. Password stored in VS Code SecretStorage. Triggers a background Script Include sync |
Logout |
Clears credentials and caches |
Pull / Push
| Command |
What it does |
Pull Widget |
Lists instance widgets (QuickPick) and downloads the chosen one to widgets/ |
Pull All Widgets |
Downloads all (with progress, cancelable) |
Create New Widget |
Asks for a name + id, creates the sp_widget on the instance, pulls it locally and opens it |
Push Current Widget |
Reads the active editor's widget folder and pushes (PATCH). Runs through the Working Context guard and conflict detection |
Toggle Auto-Sync |
Turns automatic push-on-save on/off |
Update Set / Working Context
| Command |
What it does |
Show Active Update Set |
Shows the active Update Set (name, state, ⚠️ if Default) |
Select Update Set |
Lists in progress Update Sets in the widget's scope and sets the chosen one as current (via sys_user_preference) |
Working Context Menu |
Quick menu (triggered from the status bar): select update set, show, toggle auto-sync, login/logout |
Preview / Hot Reload
| Command |
What it does |
Open Live Preview |
Starts the local server (if needed) and opens the widget preview in the browser |
Start Hot-Reload Server |
Starts the local HTTP+WebSocket server manually |
Stop Hot-Reload Server |
Stops the server |
Copy Portal Hot-Reload Bookmarklet |
Copies a bookmarklet to the clipboard — paste it in your browser bookmarks to enable hot reload on the real portal |
Refresh Portal Assets (CSS/Theme) |
Re-fetches the portal CSS/theme for 1:1 visual fidelity in the preview |
Snapshot Widget Data (for offline) |
Runs server.js live and saves the resulting data to snapshot.json for offline development |
IntelliSense / Types
| Command |
What it does |
Reinstall IntelliSense Types |
Rewrites the ServiceNow API .d.ts (gs, GlideRecord, $sp, ...). Normally installed automatically |
Sync Instance Types (Script Includes) |
Fetches all active Script Includes from the instance and generates .d.ts with declare class per scope |
Diagnose Script Include |
Diagnostic: why didn't a Script Include show up? (active, scope, ACL, parse, presence in the .d.ts) |
Quick UI (no Command Palette)
You don't have to live in Ctrl/Cmd+Shift+P:
- Control Panel: click the SN Widget Kit icon in the Activity Bar (left sidebar) for a panel with connection status, one-click actions (Preview · Push · Create · Pull · Snapshot · Update Set · Sync Types · Login/Logout) and toggles for Auto-Sync, Offline mode and High-fidelity artifacts. This is the main UI.
- Editor title bar: when you're in a widget file, Open Preview (◐) and Push (☁) icons appear top-right
- Right-click in the editor: Push Current Widget · Open Live Preview
- "ServiceNow Widgets" tree: per-widget inline actions — Preview · Push · Re-pull
- Keyboard shortcuts:
Ctrl/Cmd+Alt+U push · Ctrl/Cmd+Alt+P preview (only when focused in a widget file)
Live Preview (local)
Open Live Preview renders the widget on a local page (localhost:<previewPort>), loading AngularJS 1.8 and Bootstrap 3. When you save a file:
| Changed file |
Behavior |
styles.scss |
Compiles SCSS → CSS and swaps the <style> in-place, no flicker |
template.html |
Recompiles the widget in AngularJS (keeps the c controller state) |
client.js / server.js / link.js / schemas |
Page reload |
Visual fidelity: if you're logged in, the preview fetches your real portal CSS (proxied via /sn-proxy) — it looks identical to the portal. Without login, it falls back to Bootstrap from a CDN. Enable includePortalScripts to also load sp-angular (opt-in, may conflict).
server.js running locally
The preview executes your Server Script in a Node sandbox, with faithful stubs of the ServiceNow API:
gs — getUser() (with getDisplayName, getEmail, getLocation, hasRole, ...), nowDateTime, info/warn/error, addInfoMessage/addErrorMessage, date helpers (beginningOfThisMonth, etc.), getProperty, and 80+ methods
GlideRecord — when logged in, runs real queries via the Table API: addQuery, addEncodedQuery, orderBy, get(sysId), getValue, getDisplayValue, and dot-walking (gr.caller_id.name.toString())
GlideAggregate — addAggregate('COUNT'|'SUM'|'AVG'|'MIN'|'MAX'), groupBy, orderBy(Desc) (live or from fixtures)
- Script Includes —
new global.MyScriptInclude().method() is loaded on demand from the instance, evaluated and executed
gs.getUser() returns your real user (name, email, roles) via the API
gs.getMessage(key, args) and ${key} tokens resolve against your instance's sys_ui_message (with {0}/{1} substitution)
gs.addInfoMessage(...) / spUtil.addInfoMessage(...) show up as banners at the top of the preview, just like the portal. There's a logs panel (gs.info/warn/error) in the preview's top bar.
Offline (without login, or with offlineMode on), gs and GlideRecord use stub/fixture data; the widget still renders, but without real data.
Offline development (fixtures.json)
To iterate with no instance connection, drop a fixtures.json in the widget folder and turn on snWidgetKit.offlineMode:
// widgets/<id>/fixtures.json — keyed by table name
{
"sc_request": [
{ "sys_id": "1", "number": "REQ0010001", "active": "true", "sys_created_on": "2026-05-20 10:00:00" }
],
"sc_req_item": [
{ "sys_id": "a", "request": "1", "short_description": "Adobe Creative Cloud" }
]
}
new GlideRecord('sc_request') then iterates these records offline. In offline mode the preview never calls the instance.
Even easier — Snapshot: while logged in, run SN Widget: Snapshot Widget Data. It executes server.js live and saves the resulting data to snapshot.json. With Offline mode on, the preview renders straight from that snapshot (real data, zero instance calls) — no need to hand-write fixtures.
High-fidelity artifacts (opt-in)
Complex widgets that rely on Angular sp_ng_templates, sp_angular_providers or shared dependency CSS can enable snWidgetKit.highFidelityArtifacts. When on (and logged in), the preview fetches those related records and injects them. Off by default, since it runs widget-provided Angular code and adds instance calls.
Hot reload on the real portal (bookmarklet)
To see your changes on the actual portal without a manual refresh:
Copy Portal Hot-Reload Bookmarklet → paste it as a new browser bookmark
- Open any Service Portal page and click the bookmark (a "🔌 SN Hot Reload" banner appears)
- Edit + push (or auto-sync): CSS swaps in-place, template recompiles, scripts reload
It works because the extension runs a local WebSocket server (bound to 127.0.0.1 only) and the bookmarklet injects a client that listens for push events.
IntelliSense
Installed automatically when you open a workspace with widgets. It generates files under .sn-widget-kit-types/ and configures jsconfig.json:
- Static API (
glide.d.ts, service-portal.d.ts, widget-globals.d.ts): gs, GlideRecord, GlideAggregate, GlideDateTime, GlideAjax, GlideUser, $sp, spUtil, spModal, i18n, $scope, c, data, options, input
- From your instance (
instance-script-includes.d.ts): all active Script Includes become declare class per scope → new global.YourScript() autocompletes with the real methods
Automatic Script Include sync: runs on login, re-checks when you focus the VS Code window (throttled) or periodically (fallback, configurable). Cheap watermark-based detection — it only re-syncs when something changes on the instance.
Working Context guard
Before every push, the extension resolves your active Update Set and compares it with the widget's scope. The push is blocked when:
- The active Update Set is Default, or
- The Update Set's scope differs from the widget's scope
The block message includes a "Select Update Set" button. The status bar (bottom-left) shows instance · scope · update set and turns red when there's risk.
Configurable via enforceWorkingContext (default true; false = warn only).
Conflict detection + visual diff
If the widget changed on the instance between your pull and push, you get a warning with:
- Show Diff — opens
vscode.diff (remote ↔ local) for each differing file
- Overwrite — pushes anyway
- Cancel — aborts
Nothing is overwritten silently.
Configuration
In your workspace .vscode/settings.json (or user settings):
| Setting |
Default |
Description |
snWidgetKit.workspaceFolder |
"widgets" |
Folder where widgets are stored locally |
snWidgetKit.autoSync |
false |
Automatically push changes on save |
snWidgetKit.autoSyncDebounceMs |
500 |
Auto-sync debounce (ms) |
snWidgetKit.confirmOnConflict |
true |
Ask for confirmation when the remote changed since pull |
snWidgetKit.warnOnDefaultUpdateSet |
true |
Warn when pushing to the Default Update Set |
snWidgetKit.enforceWorkingContext |
true |
Block push if Update Set = Default or scope differs. false = warn only |
snWidgetKit.previewPort |
7890 |
Local preview/hot-reload server port |
snWidgetKit.hotReloadOnPush |
true |
Broadcast hot-reload to connected portal sessions after push |
snWidgetKit.includePortalScripts |
false |
Include portal JS (sp-angular) in the preview. Opt-in; may conflict |
snWidgetKit.offlineMode |
false |
Develop without the instance: use fixtures.json / demo-data.json instead of live data |
snWidgetKit.highFidelityArtifacts |
false |
Fetch and inject the widget's Angular ng-templates, providers and dependency CSS into the preview |
snWidgetKit.highFidelityServer |
false |
Run server.js on the instance (Scripted REST) for 100% server-side fidelity; falls back to the local sandbox |
snWidgetKit.remoteEvalPath |
"" |
Path of the remote-eval Scripted REST resource (e.g. /api/x_scope/snwk_preview/eval). Empty disables it |
snWidgetKit.manifestTtlMinutes |
5 |
TTL for the portal CSS/theme cache. 0 disables the cache |
snWidgetKit.instanceTypesSyncIntervalMinutes |
15 |
Fallback interval for the periodic Script Include check. 0 disables it |
Support
Questions or issues? Use the Q&A tab on this extension's Marketplace page.
License
Proprietary — © 2026 Guilherme Maziero. All rights reserved.
Free to install and use; redistribution, modification and reverse engineering
are not permitted. See the bundled LICENSE file.