Fluent (ServiceNow SDK) Development
SkillFiles & storageThis skill should be used when the user asks to "build a fluent app", "create a servicenow app in typescript", or mentions "servicenow sdk", "now-sdk", "fluent", "scoped app as code", or "pro-code development" — or when the working directory contains a now.config.json or *.now.ts files.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the Fluent (ServiceNow SDK) Development skill
What this skill tells your AI
The instructions your AI receives, as published by serac-labs/serac in packages/skills/fluent-development/SKILL.md and read by ahel’s review.
Fluent is ServiceNow's pro-code model: application metadata as declarative TypeScript, with git as the source of truth instead of the instance. You edit .now.ts files locally, compile them (build), and push the compiled package to an instance (install). The CLI behind the tools is now-sdk from @servicenow/sdk (4.x, Node 20+).
Where these tools run: the
snow_fluent_*tools are local-only — they execute the ServiceNow SDK (now-sdk) on the developer's machine, so they only exist when the MCP server runs locally over stdio. Over a hosted HTTP transport (e.g. a web chat) they are not available, so do not call them; drive the same flow through git + CI instead: commit the project files, trigger the build/deploy pipeline, and follow the workflow runs.
1. Fluent vs the classic snow_* API tools
| Task | Use |
|---|---|
| Build/evolve an app whose definition should live in git | Fluent tools (snow_fluent_*) |
| Data operations (query/create/update records) | API tools (snow_query_table, ...) |
| ITSM/config changes on an existing non-Fluent instance | API tools (e.g. snow_create_business_rule) |
| Metadata types Fluent doesn't cover (see §5) | API tools, or keep as XML in metadata/ |
Fluent changes the definition of an app; the API tools change a live instance directly. Don't mix them for the same artifact: editing a Fluent-managed record via the API gets overwritten on the next install.
2. Project anatomy
now.config.json # { "scope": "x_acme_myapp", "scopeId": "<sys_app sys_id>", "name": "My App" }
package.json # devDeps: @servicenow/sdk, @servicenow/glide
src/fluent/*.now.ts # the Fluent DSL (entry: index.now.ts)
src/fluent/generated/keys.ts # Now.ID registry -> sys_ids. MUST be committed.
src/server/ # server-side code referenced from script: properties
metadata/ # original XML for records not (yet) converted to Fluent
dist/ # build output (gitignored)
now.config.jsonbinds the project to one scope:scopeIdis thesys_appsys_id (generated locally for new apps, taken from the instance with initfrom=sys_id). It contains no instance/connection info — auth lives separately.Now.ID['my-key']gives every record a stable identity; the build regeneratessrc/fluent/generated/keys.tsmapping those keys to sys_ids.- ❌ Forgetting to commit
keys.ts→ next build elsewhere mints new sys_ids → duplicate records on install. - ✅ Commit
keys.tswith every change; in CI run build withfrozen_keysso a stale keys file fails the pipeline ("Keys file is out-of-date..."). - Precedence: a record that exists both as a
.now.tsentity and as XML inmetadata/uses the XML version on build. Delete the XML twin after converting, or passerror_on_conflictto make the collision fatal instead of silent.
3. The development loop
snow_fluent_status— what project am I in? Scope, SDK pin, keys.ts state, XML-vs-Fluent counts. Run this first in any existing project.snow_fluent_explain— offline SDK docs (now-sdk explain). Always check the topic for an API before writing its DSL — property names changed across 4.x and guessing produces compile errors. Use the topic list to discover names; topics includekeys-file,ci-integration,developing-apps-guide, and one per API.snow_fluent_init— scaffold a new app, or convert an existing instance app withfrom=<sys_id>(conversion keeps everything as XML inmetadata/; nothing changes on the app at the start).- Edit
.now.tsfiles. snow_fluent_build— compile. Fix TypeScript errors and re-run until clean (compile errors → exit 1).snow_fluent_install— deploy to a dev instance. Then verify on the instance (e.g.snow_query_tableagainst the target table, orinstallwithinfo=trueto read the last install status).- To pick up changes made on the instance:
snow_fluent_download(instance → local XML, supports incremental), thensnow_fluent_transformto convert selected XML to Fluent (supports table targeting andfrom=<local file/dir>). Conversion is opt-in and incremental — migrate record-by-record, leave the rest as XML. - When referencing tables outside the app:
snow_fluent_dependenciesto sync type definitions, withadd_table+scopeto register a specific table.
4. Writing Fluent DSL — and the ES5 boundary
The ES5 rule applies to code that runs on the instance's Rhino engine — i.e. the string inside script: — NOT to the Fluent DSL itself. Fluent files are modern TypeScript compiled locally by the SDK. Do not "ES5-ify" .now.ts files; do not put ES6 inside script: strings.
// src/fluent/business-rules/close-children.now.ts
// (verify exact properties first: snow_fluent_explain topic for BusinessRule)
import '@servicenow/sdk/global'
import { BusinessRule } from '@servicenow/sdk/core'
// ✅ Modern TypeScript here — this never runs on Rhino
export const closeChildren = BusinessRule({
$id: Now.ID['close-children-br'],
name: 'Close child incidents',
table: 'incident',
when: 'after',
action: ['update'],
script: `
// ❌ const/let/arrow/template-literals — Rhino will reject them
// ✅ ES5 only inside this string:
var child = new GlideRecord('incident');
child.addQuery('parent_incident', current.getUniqueValue());
child.addQuery('active', true);
child.query();
while (child.next()) {
child.state = current.state;
child.update();
}
`,
})
- ❌
Now.IDinside aRecordAPI'sdatablock — it only resolves in$idand produces blank references elsewhere. - ✅
$id: Now.ID['stable-key']on every entity; never hand-write sys_ids.
5. Coverage limits — what stays API-side or XML
Fluent has dedicated APIs for the common core (tables, business rules, ACLs, client/UI scripts via ClientScript/UiAction/UiPolicy, script includes, roles, properties, scheduled scripts, Scripted REST, Flow Designer, Service Catalog, Service Portal, ATF, AI Agents). Not first-class: classic Workflow (wf_workflow), UI Builder pages, reports/PA dashboards, UI macros, UI scripts, processors, schedules (cmn_schedule), decision tables, data sources.
- ✅ For uncovered types: the generic
RecordAPI, or$override(4.7.0+) to set fields the typed API doesn't expose — or simply leave them as XML inmetadata/(XML round-trips unchanged through build/install). - ✅ Columns on tables outside the app:
Tableaugments(4.6.1+). - Global apps need SDK >= 4.4.0 and an Australia-release instance for the instance-side parts (moving global records into the app, transforming updates); on older instances stick to scoped apps. pnpm is not fully supported for global apps.
6. Install gotchas (read before deploying)
- Installs bypass update sets and create no rollback context. History lands in
sys_upgrade_historyonly. Never install straight to production — promote via the App Repository; dev/test instances only. - Demo data: the raw CLI defaults
--demoDatato TRUE; thesnow_fluent_installtool defaults it to FALSE — passdemo_data=trueonly when the user wants the project's demo data on the instance. reinstallis destructive: uninstall + fresh install (-r/--reinstall). It is the only "rollback", and side-effect records (e.g. auto-created M2M rows) are not cleaned up.- Flows are auto-published on install (SDK 4.5+); pass
skip_flow_activationto suppress that. - A build run outside a project ("Could not find package.json") still exits 0 — check for
dist/output, not just the exit code. - "Unable to install application as application was null" usually means the scope prefix doesn't match the instance's
glide.appcreator.company.code. - Since 4.4.0 unknown/misspelled properties are build errors (previously silently ignored) — another reason to run
snow_fluent_explainbefore writing DSL.
Output format
After an install, report: target instance, scope, install result (from info), whether demo data was included, and a reminder that the change is not in an update set and cannot be rolled back except via reinstall.
Signals
- GitHub stars
- 78
- Forks
- 26
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
fluent-development- Source
- github.com/serac-labs/serac