ServiceNow
Building ServiceNow Apps with the Now SDK and Fluent (TypeScript, Source-Control-First)
Two ways to build on ServiceNow
For years, building an application on ServiceNow meant clicking through the platform UI — Studio, App Engine Studio, the form designer — and letting the instance store every artifact as a record. That still works. But ServiceNow now ships a second, complementary path: the Now SDK (also called the ServiceNow SDK), where you describe your application as TypeScript source files on your own machine using a declarative library called Fluent, then build and deploy it to an instance.
This article is a practical, code-first tour of that workflow, grounded in the official ServiceNow/sdk-examples repository, the Australia-family Fluent docs, and npx @servicenow/sdk explain (CLI docs for Fluent APIs).
Why a code-first SDK exists
- Real source control. Update Sets and even scoped-app Git integration are record-centric. With the SDK, your app is the source tree — clean diffs, pull requests, and code review work the way engineers expect.
- Local development. Author in VS Code with TypeScript types, autocomplete, and your own linters and formatters. No round-trip to the instance to write a Business Rule.
- Repeatable builds. A build step turns your TypeScript into deployable metadata, so the same commit produces the same app on dev, test, and prod.
- It complements Studio — it does not replace it. You can still open the app in the platform; the SDK simply gives a code path for teams that want one.
Prerequisites
- Node.js v20+ and a package manager (the samples use
pnpm; npm works too). - The SDK packages:
@servicenow/sdk,@servicenow/glide(server-side type definitions), andtypescript. - Access to a ServiceNow instance where you can authenticate and deploy a scoped application.
Anatomy of an SDK project
Every SDK app is a normal Node project. The pieces that matter:
my-app/
now.config.json // app scope + build config
package.json // deps + the "now-sdk build" script
src/
fluent/ // your Fluent (*.now.ts) declarations
index.now.ts
generated/keys.ts // auto-generated stable IDs (do not hand-edit)
server/ // server-side .js used by rules/script includes
The now.config.json pins the application scope so builds are deterministic:
{
"scope": "x_myapp",
"scopeId": "fc1b5713c3db3110d6489a038a40dd84",
"serverModulesDir": "src/server"
}
And package.json wires up the build command:
{
"name": "my-app",
"scripts": {
"build": "now-sdk build"
},
"devDependencies": {
"@servicenow/sdk": "latest",
"@servicenow/glide": "latest",
"typescript": "latest"
}
}
Fluent by example
Fluent gives you a typed function for each platform artifact. You import it from @servicenow/sdk/core, call it with a configuration object, and the build turns that into the corresponding record(s) in your scoped app. The magic identifier Now.ID['...'] assigns a stable, generated key so re-deploys update the same record instead of creating duplicates.
1. A table
import { DateTimeColumn, IntegerColumn, StringColumn, Table } from '@servicenow/sdk/core'
export const x_myapp_order = Table({
name: 'x_myapp_order',
label: 'Order',
schema: {
order_number: StringColumn({ label: 'Order Number', mandatory: true }),
quantity: IntegerColumn({ label: 'Quantity', mandatory: true }),
ordered_at: DateTimeColumn({ label: 'Ordered At' }),
},
})
2. A Business Rule
JavaScript modules are the modern, preferred approach for server-side logic in Fluent projects (typed Glide APIs via @servicenow/glide, import/export, reuse). Now.include() still inlines a file at build time when you want a classic script field.
import { BusinessRule } from '@servicenow/sdk/core'
import { setOrderPriority } from '../server/set-order-priority'
BusinessRule({
$id: Now.ID['set_order_priority'],
name: 'Set Order Priority',
active: true,
table: 'x_myapp_order',
when: 'before',
action: ['insert', 'update'],
script: setOrderPriority,
})
The module under src/server/ is ordinary ServiceNow server JavaScript — the same GlideRecord / gs APIs you already know — with optional TypeScript types from @servicenow/glide.
3. A Script Include
import { ScriptInclude } from '@servicenow/sdk/core'
ScriptInclude({
$id: Now.ID['order-utils'],
name: 'OrderUtils',
active: true,
apiName: 'x_myapp.OrderUtils',
script: Now.include('./OrderUtils.server.js'),
})
4. An ACL
Security is code too. Roles and record ACLs are declared alongside everything else, so access control ships and reviews with the feature it protects:
import { Acl, Role } from '@servicenow/sdk/core'
export const orderManager = Role({ name: 'x_myapp.order_manager' })
Acl({
$id: Now.ID['order_write_acl'],
type: 'record',
operation: 'write',
table: 'x_myapp_order',
roles: [orderManager],
})
pnpm install is the fastest way to see real, buildable Fluent.Build and deploy
The everyday loop is small:
# scaffold a new app (interactive, or use --appName/--scopeName flags) npx @servicenow/sdk init # type-check + transform Fluent into deployable metadata npx @servicenow/sdk build # authenticate, then install the built app on the instance # (CLI alias: now-sdk deploy → now-sdk install) npx @servicenow/sdk auth --add <instance-url> --type basic npx @servicenow/sdk install
Because the output is deterministic, the same command runs in CI: install dependencies, now-sdk build, run your ATF tests, then deploy to the target instance. That is the whole point — a ServiceNow app that behaves like any other codebase.
SDK / Fluent vs App Engine Studio — when to use which
| Use the Now SDK + Fluent when… | Use App Engine Studio / UI when… |
|---|---|
| The team is engineering-led and wants Git, PRs, and code review | Citizen developers or admins are building quickly in-platform |
| You need repeatable CI/CD builds across environments | A single instance, low change velocity, Update Sets are enough |
| You want TypeScript types, local linting, and diff-able source | You prefer guided, low-code form and flow designers |
| The app is large, long-lived, and shared across a team | The app is small or experimental |
They are not mutually exclusive: an app built with the SDK still opens in the platform, and you can adopt the SDK for the parts of an app that benefit most from source control.
Gotchas worth knowing
- Do not hand-edit
generated/keys.ts. Those stable IDs are what let re-deploys update existing records instead of duplicating them. - Prefer server modules; classic scripts still follow platform rules. Fluent is modern TypeScript, but server modules and inlined scripts still run under the platform engine — keep scoped-vs-global scripting rules in mind. Never invent sys_ids; always use
Now.ID['key']. - Scope is fixed by
now.config.json. Changing scope after records exist is disruptive; decide the scope up front. - The SDK evolves quickly. Pin your
@servicenow/sdkversion and check the release notes before upgrading — argument shapes and available components expand release over release.
Where to go next
- ServiceNow/sdk-examples — official, buildable samples for every component type.
- ServiceNow SDK (Australia docs) — CLI, Fluent, and install workflow.
npx @servicenow/sdk explain <topic>— local Fluent API reference (BusinessRule, Table, Acl, Flow, …).
If you already know GlideRecord and scoped apps, the SDK is not a new platform to learn — it is a cleaner front door to the one you already use.