skills/development/fluent-sdk/SKILL.md
Hybrid ServiceNow development using the three-tier approach - NowSDK Fluent for metadata-as-code, MCP REST for runtime operations, and fix scripts as manual fallback
npx skillsauth add happy-technologies-llc/happy-servicenow-skills fluent-sdkInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
This skill implements a three-tier development strategy for ServiceNow, routing each operation to the most capable tool:
TIER 1: NowSDK Fluent (metadata-as-code)
Flows, tables, business rules, catalog items, ACLs, scoped apps
TypeScript → build → deploy as scoped application
↓ falls through when...
- Runtime data operations needed
- Live instance queries required
- Existing record manipulation
TIER 2: MCP REST Tools (runtime operations)
CRUD on live data, queries, update set management, background scripts
Direct REST API calls to instance
↓ falls through when...
- REST API has documented limitations
- UI-only operations (Flow compilation, UI Policy action linking)
- Complex server-side operations not exposed via REST
TIER 3: Fix Scripts (manual fallback)
SN-Create-Fix-Script for operations neither tier can handle
Generated scripts for manual execution in instance
Used for: Flow compilation, UI Policy setValue(), complex GlideRecord ops
When to use this skill:
This skill builds on ServiceNow's official AI skills from github.com/ServiceNow/sdk:
vendor/now-sdk-setup.md — Environment setup (Node 20+, SDK install, auth). Run this first if now-sdk commands fail.vendor/now-sdk-explain.md — Self-documenting SDK reference via npx @servicenow/sdk explain. Always consult this before writing Fluent code — it covers every metadata type, convention, and project structure detail.Before writing any Fluent code, follow the explain skill's protocol:
npx @servicenow/sdk explain <search-term> --list --format=raw — find relevant topicsnpx @servicenow/sdk explain <topic> --peek --format=raw — preview before committingnpx @servicenow/sdk explain <topic> --format=raw — read the full topicThis ensures you're using the latest API signatures and conventions, not stale examples.
node --version)npm install -g @servicenow/sdk@latest)admin or sn_sdk.user rolenow-sdk auth completed for target instancedevelopment/mcp-server for MCP tool reference, vendor/now-sdk-explain.md for Fluent API docsUse this matrix to determine which tier handles each operation:
| Operation | Tier 1 (Fluent) | Tier 2 (MCP REST) | Tier 3 (Fix Script) |
|-----------|-----------------|--------------------|--------------------|
| Create flow with logic | YES - full Flow DSL | No - cannot create flows | No |
| Compile/publish flow | Automatic on deploy | No | YES - manual in UI |
| Create table + schema | YES - Table() with typed columns | Yes - but no version control | No |
| Business rules | YES - BusinessRule() with scripts | Yes - SN-Create-Record on sys_script | No |
| Catalog items | YES - CatalogItem() with variables | Yes - but UI policies limited | Partial |
| UI Policy actions | YES - deployed with app | No - REST can't link actions | YES - setValue() script |
| ACLs | YES - Acl() definition | Yes - SN-Create-Record on sys_security_acl | No |
| Script includes | YES - with server modules | Yes - SN-Create-Record on sys_script_include | No |
| Query live data | No - not for runtime | YES - SN-Query-Table | No |
| Update existing records | No - creates new metadata | YES - SN-Update-Record | No |
| Update set management | Automatic on deploy | YES - SN-Set-Update-Set, inspect, move | No |
| Background script execution | No | YES - SN-Execute-Background-Script | No |
| Scoped app creation | YES - now.config.json defines scope | Yes - SN-Create-Record on sys_app | No |
| Deploy to instance | YES - now-sdk deploy | No - records created individually | No |
| Complex GlideRecord ops | No | Partial | YES - full server-side API |
Follow vendor/now-sdk-setup.md to verify NowSDK is installed and working:
# Check Node version (must be 20+)
node --version
# Check SDK version (must be 4.6.0+)
npx @servicenow/sdk --version
# If not installed or outdated:
npm install -g @servicenow/sdk@latest
# Authenticate to your instance
now-sdk auth
# Follow prompts: instance URL, credentials
Verify with explain command:
npx @servicenow/sdk explain quickstart --list --format=raw
Then familiarize yourself with the Fluent API for whatever you're about to build:
# Example: about to create flows
npx @servicenow/sdk explain Flow --list --peek --format=raw
npx @servicenow/sdk explain Flow --format=raw
Before writing any code, classify the request:
Route to Tier 1 (Fluent) when:
Route to Tier 2 (MCP REST) when:
Route to Tier 3 (Fix Script) when:
setValue()Create the project structure for a new scoped application.
Project Structure:
my-sn-app/
├── now.config.json # Scope and instance config
├── package.json # Dependencies
├── tsconfig.json # TypeScript config (optional)
├── src/
│ └── fluent/
│ ├── generated/
│ │ └── keys.ts # Auto-generated sys_id mappings
│ ├── tables.now.ts # Table definitions
│ ├── rules.now.ts # Business rules
│ ├── flows.now.ts # Flow definitions
│ ├── catalog.now.ts # Catalog items
│ ├── acls.now.ts # Access controls
│ └── *.server.js # External script files
└── src/
└── server/ # Server-side TypeScript modules
now.config.json:
{
"scope": "x_myapp",
"scopeId": "<sys_id from instance or generated on first deploy>"
}
package.json:
{
"name": "my-sn-app",
"version": "1.0.0",
"scripts": {
"build": "now-sdk build",
"deploy": "now-sdk deploy",
"explain": "now-sdk explain"
},
"devDependencies": {
"@servicenow/glide": "catalog:",
"@servicenow/sdk": "catalog:",
"typescript": "catalog:"
}
}
// src/fluent/tables.now.ts
import {
Table, StringColumn, IntegerColumn, BooleanColumn,
DateColumn, ReferenceColumn
} from '@servicenow/sdk/core'
export const x_myapp_request = Table({
name: 'x_myapp_request',
schema: {
title: StringColumn({ mandatory: true, label: 'Title', maxLength: 200 }),
priority: IntegerColumn({ mandatory: true, label: 'Priority' }),
active: BooleanColumn({ mandatory: true, label: 'Active', default: true }),
due_date: DateColumn({ label: 'Due Date' }),
assigned_to: ReferenceColumn({
label: 'Assigned To',
referenceTable: 'sys_user',
}),
},
})
// src/fluent/rules.now.ts
import { BusinessRule } from '@servicenow/sdk/core'
export const validatePriority = BusinessRule({
$id: Now.ID['validate_priority'],
name: 'Validate Priority on Create',
active: true,
table: 'x_myapp_request',
when: 'before',
insert: true,
script: Now.include('./validate-priority.server.js'),
})
// src/fluent/validate-priority.server.js
(function executeRule(current, previous) {
if (!current.priority || current.priority < 1 || current.priority > 5) {
current.priority = 3; // Default to medium
}
if (!current.assigned_to && current.priority == 1) {
gs.addErrorMessage('Critical requests must have an assignee');
current.setAbortAction(true);
}
})(current, previous);
// src/fluent/flows.now.ts
import { action, Flow, wfa, trigger } from '@servicenow/sdk/automation'
export const escalationFlow = Flow(
{
$id: Now.ID['request_escalation_flow'],
name: 'Request Escalation Flow',
description: 'Escalates high-priority requests when unassigned after creation',
},
wfa.trigger(
trigger.record.created,
{ $id: Now.ID['new_request_trigger'] },
{
table: 'x_myapp_request',
condition: 'priority=1^assigned_toISEMPTY',
run_flow_in: 'background',
run_on_extended: 'false',
run_when_setting: 'both',
run_when_user_setting: 'any',
run_when_user_list: [],
}
),
(params) => {
// Log the escalation
wfa.action(
action.core.log,
{ $id: Now.ID['log_escalation'] },
{
log_level: 'warn',
log_message: `Escalating unassigned critical request: ${wfa.dataPill(params.trigger.current.title, 'string')}`,
}
)
// Look up the on-call manager
const manager = wfa.action(
action.core.lookUpRecord,
{ $id: Now.ID['lookup_oncall_manager'] },
{
table: 'sys_user',
conditions: 'active=true^roles=manager',
sort_type: 'sort_asc',
if_multiple_records_are_found_action: 'use_first_record',
}
)
// Auto-assign to manager
wfa.action(
action.core.updateRecord,
{ $id: Now.ID['assign_to_manager'] },
{
table_name: 'x_myapp_request',
record: wfa.dataPill(params.trigger.current.sys_id, 'reference'),
values: TemplateValue({
assigned_to: wfa.dataPill(manager.Record.sys_id, 'reference'),
}),
}
)
// Send notification
wfa.action(
action.core.sendEmail,
{ $id: Now.ID['notify_manager'] },
{
table_name: 'x_myapp_request',
watermark_email: true,
ah_subject: `Critical Request Escalation: ${wfa.dataPill(params.trigger.current.title, 'string')}`,
ah_body: `A critical request has been auto-assigned to you.`,
record: wfa.dataPill(params.trigger.current.sys_id, 'reference'),
ah_to: wfa.dataPill(manager.Record.email, 'string'),
}
)
}
)
// src/fluent/catalog.now.ts
import {
CatalogItem, VariableSet, SingleLineTextVariable,
MultiLineTextVariable, SelectBoxVariable, ReferenceVariable,
RequestedForVariable
} from '@servicenow/sdk/core'
const requestInfoVarSet = VariableSet({
$id: Now.ID['request_info_varset'],
title: 'Request Details',
description: 'Core information for the request',
internalName: 'request_info_varset',
type: 'singleRow',
layout: 'normal',
order: 100,
displayTitle: true,
version: 1,
variables: {
requestTitle: SingleLineTextVariable({
question: 'Request Title',
mandatory: true,
order: 100,
}),
requestDescription: MultiLineTextVariable({
question: 'Description',
mandatory: true,
order: 200,
}),
requestPriority: SelectBoxVariable({
question: 'Priority',
mandatory: true,
order: 300,
choices: {
critical: { label: '1 - Critical' },
high: { label: '2 - High' },
medium: { label: '3 - Medium' },
low: { label: '4 - Low' },
},
defaultValue: 'medium',
}),
},
name: 'Request Details',
})
export const newRequestCatalogItem = CatalogItem({
$id: Now.ID['new_request_catalog_item'],
name: 'New Service Request',
shortDescription: 'Submit a new service request',
description: 'Use this form to submit a service request. Critical requests trigger automatic escalation.',
catalogs: ['e0d08b13c3330100c8b837659bba8fb4'], // Service Catalog
categories: ['d258b953c611227a0146101fb1be7c31'], // Hardware (or your category)
variableSets: [{ variableSet: requestInfoVarSet, order: 100 }],
executionPlan: '523da512c611228900811a37c97c2014',
variables: {
requestedFor: RequestedForVariable({
order: 1,
question: 'Requested For',
}),
assignmentGroup: ReferenceVariable({
order: 2,
question: 'Assignment Group',
referenceTable: 'sys_user_group',
}),
},
})
# Build the project (validates TypeScript, generates metadata)
now-sdk build
# Deploy to instance (creates/updates scoped app and all metadata)
now-sdk deploy
After Fluent deployment, use MCP tools to verify everything landed correctly.
Tool: SN-Query-Table
Parameters:
table_name: sys_update_xml
query: application=<app_sys_id>
fields: sys_id,type,name,sys_created_on
limit: 50
Tool: SN-Get-Table-Schema
Parameters:
table_name: x_myapp_request
Tool: SN-Query-Table
Parameters:
table_name: sys_script
query: collection=x_myapp_request^active=true
fields: name,when,active
For operations that neither Fluent nor REST can handle, generate fix scripts.
Flow Compilation (post-deploy):
Tool: SN-Create-Fix-Script
Parameters:
name: "Compile Escalation Flow"
description: "Compile the request escalation flow after Fluent deployment"
script: |
// Navigate to Flow Designer and compile:
// 1. Open Flow Designer
// 2. Find "Request Escalation Flow"
// 3. Click "Activate" to compile and enable
//
// This step cannot be automated - Flow Designer compilation
// requires the UI runtime environment.
gs.info('Manual step: Compile flow in Flow Designer UI');
UI Policy Action Linking (if needed):
Tool: SN-Execute-Background-Script
Parameters:
script: |
var gr = new GlideRecord('sys_ui_policy_action');
gr.addQuery('ui_policy', '<ui_policy_sys_id>');
gr.addQuery('catalog_variable', '');
gr.query();
while (gr.next()) {
gr.catalog_variable = 'IO:<variable_sys_id>';
gr.update();
}
description: "Link UI Policy actions to catalog variables"
execution_method: trigger
Bulk Data Operations:
Tool: SN-Create-Fix-Script
Parameters:
name: "Seed Sample Data"
description: "Create initial sample records for testing"
script: |
var records = [
{ title: 'Test Request 1', priority: 1 },
{ title: 'Test Request 2', priority: 3 },
{ title: 'Test Request 3', priority: 5 }
];
records.forEach(function(data) {
var gr = new GlideRecord('x_myapp_request');
gr.initialize();
gr.title = data.title;
gr.priority = data.priority;
gr.active = true;
gr.insert();
});
gs.info('Created ' + records.length + ' sample records');
A complete end-to-end example combining all three tiers:
GOAL: Build a scoped app with escalation flow, catalog item, and seed data
TIER 1 (Fluent):
1. Create now.config.json with scope
2. Define table schema in tables.now.ts
3. Define business rules in rules.now.ts
4. Define escalation flow in flows.now.ts
5. Define catalog item in catalog.now.ts
6. now-sdk build && now-sdk deploy
TIER 2 (MCP REST):
7. SN-Query-Table → verify all records in update set
8. SN-Get-Table-Schema → confirm table columns created
9. SN-Query-Table on sys_script → verify business rules active
10. SN-Execute-Background-Script → seed sample data
TIER 3 (Fix Script):
11. SN-Create-Fix-Script → flow compilation instructions
12. SN-Create-Fix-Script → any UI Policy action linking
13. Manual: compile flow in Flow Designer UI
The SDK has built-in documentation accessible via CLI:
# List all available topics
npx @servicenow/sdk explain --list --format=raw
# Search for a topic
npx @servicenow/sdk explain flow --list --peek --format=raw
# Read full topic (only after previewing)
npx @servicenow/sdk explain flow --format=raw
# Key topics:
# quickstart - Getting started
# Table - Table definitions
# Flow - Flow automation
# BusinessRule - Business rule definitions
# CatalogItem - Service catalog items
# Acl - Access control lists
# ScriptInclude - Script includes
# naming - Naming conventions
# structure - Project structure
Important: Always use --peek first before reading a full topic to avoid wasting context on irrelevant content.
// Core metadata
import {
Table, StringColumn, IntegerColumn, BooleanColumn,
DateColumn, ReferenceColumn, Record, BusinessRule,
ScriptInclude, ClientScript, Acl, Role
} from '@servicenow/sdk/core'
// Flow automation
import {
Flow, Subflow, action, wfa, trigger
} from '@servicenow/sdk/automation'
// Catalog
import {
CatalogItem, VariableSet, SingleLineTextVariable,
MultiLineTextVariable, SelectBoxVariable, ReferenceVariable,
CheckBoxVariable, DateTimeVariable, RequestedForVariable,
EmailVariable
} from '@servicenow/sdk/core'
| Helper | Purpose | Example |
|--------|---------|---------|
| Now.ID['key'] | Reference a record by key name | $id: Now.ID['my_rule'] |
| Now.include('./file.js') | Include external script file | script: Now.include('./rule.server.js') |
| Now.ref(export) | Cross-reference another Fluent export | table: Now.ref(myTable) |
| Now.attach('./file') | Attach file to record | attachment: Now.attach('./logo.png') |
| TemplateValue({}) | Set field values in flow actions | values: TemplateValue({ state: '2' }) |
| wfa.dataPill(ref, type) | Reference flow data pills | wfa.dataPill(params.trigger.current.sys_id, 'reference') |
// Conditional
wfa.flowLogic.if({ $id, condition, annotation }, () => { ... })
wfa.flowLogic.elseIf({ $id, condition, annotation }, () => { ... })
wfa.flowLogic.else({ $id }, () => { ... })
// Loops
wfa.flowLogic.forEach(dataPill, { $id }, () => { ... })
// Subflow outputs
wfa.flowLogic.assignSubflowOutputs({ $id }, params.outputs, { ... })
| Action | Purpose |
|--------|---------|
| action.core.log | Log message with level |
| action.core.lookUpRecord | Query for a single record |
| action.core.updateRecord | Update a record |
| action.core.createRecord | Create a new record |
| action.core.sendEmail | Send email notification |
| action.core.sendSms | Send SMS |
| action.core.sendNotification | Send notification via template |
| Trigger | Fires When |
|---------|-----------|
| trigger.record.created | Record created |
| trigger.record.updated | Record updated |
| trigger.record.createdOrUpdated | Record created or updated |
| trigger.record.deleted | Record deleted |
npm install -g @servicenow/sdk@latest
# Verify: npx @servicenow/sdk --version (must be 4.6.0+)
# Re-authenticate
now-sdk auth
# Ensure instance has SDK plugin enabled
# Ensure user has admin or sn_sdk.user role
# Check explain docs for the specific type
npx @servicenow/sdk explain <TypeName> --format=raw
# Common issues:
# - Missing $id on records → add Now.ID['unique_key']
# - Invalid column types → check Table column type imports
# - Script file not found → verify Now.include() path is relative to .now.ts file
Verify now.config.json has the correct scope and scopeId. The scopeId should match the sys_id of the sys_app record on your instance.
If now-sdk deploy fails for specific record types, fall through:
SN-Create-Record)SN-Create-Fix-Script)testing
Manage supplier onboarding, qualification, performance monitoring, and offboarding with auditable lifecycle controls
tools
Identify emerging risks, prioritize intake signals, and route candidates into formal GRC risk assessment workflows
documentation
Screen inbound documents for completeness, policy risk, and routing readiness before extraction or case workflows
testing
Generate concise task summaries with status, timeline, blockers, SLA risk, and recommended next actions