| name | servicenow-sdk-fluent |
| description | Builds ServiceNow applications with the ServiceNow SDK and Fluent API (TypeScript): tables, flows, business rules, script includes, ACLs, REST, inbound email, and workflow-as-code. Aligns with official ServiceNow sdk-examples on GitHub when debugging or scaffolding. Use when the user works on ServiceNow SDK, Fluent DSL, now-sdk, @servicenow/sdk, .now.ts files, Glide/Flow Designer code, or email parsing on ServiceNow. Also when they say "create a ServiceNow app" or "build a flow" in that context.
|
ServiceNow SDK Application Builder
Read this SKILL.md first for prerequisites, CLI commands, and Key rules (tables, flows, inbound email).
For exhaustive API examples, read references/fluent-patterns.md.
For subflows, parallel blocks, for-each, and email pipelines, read references/flow-advanced.md.
For the official runnable samples (clone list, sample index, how to diff against your code), read references/sdk-examples-repo.md — companion to ServiceNow/sdk-examples.
For typed Fluent API reference (version-matched llmsFull / llms from ServiceNow), read references/sdk-api-docs-agents.md — links servicenow.github.io/sdk agent files and versions.json.
If a reference example conflicts with the Key rules sections below or with sdk-examples on main, prefer Key rules first, then reconcile using the matching sample file from sdk-examples; for API signatures, prefer version-matched llmsFull from versions.json.
This skill enables the agent to generate production-ready ServiceNow applications
using the ServiceNow SDK (now-sdk) and the Fluent DSL. Fluent is a TypeScript-based
domain-specific language that compiles to ServiceNow metadata XML. It covers tables,
flows, business rules, script includes, ACLs, REST APIs, and more.
Important: Authentication is handled externally
The user has already authenticated with their ServiceNow instance using the SDK CLI.
Auth credentials are stored in the system keychain — NOT in project files.
You do NOT need to create .env files or handle credentials.
If the user needs to set up a new auth profile, point them to:
now-sdk auth save <alias> --host https://<instance>.service-now.com --username <user> --default
But do NOT run auth commands yourself. The user handles this manually.
Prerequisites — Verify Before Generating Code
Before writing any Fluent code, quickly check the project has the basics:
-
now.config.json must exist in the project root with at minimum:
{
"scope": "x_<vendor>_<app>",
"scopeId": "<sys_id_of_app>"
}
For TypeScript projects, also needs:
{
"transpiledSourceDir": "dist/src"
}
-
package.json must have SDK dependencies:
{
"type": "module",
"scripts": {
"build": "now-sdk build",
"install-app": "now-sdk install",
"transform": "now-sdk transform",
"dependencies": "now-sdk dependencies"
},
"devDependencies": {
"@servicenow/sdk": "^4.0.0",
"@servicenow/glide":
If prerequisites are met, proceed directly to writing Fluent code.
Project Structure
All Fluent files go in src/fluent/ with the .now.ts extension.
Organize by concern:
my-sn-app/
├── now.config.json
├── package.json
├── tsconfig.json # if TypeScript
├── src/
│ ├── fluent/
│ │ ├── tables/
│ │ │ └── email-data.now.ts
│ │ ├── flows/
│ │ │ └── inbound-email-parser.now.ts
│ │ ├── business-rules/
│ │ ├── script-includes/
│ │ ├── acls/
│ │ └── index.now.ts # barrel exports (optional)
│ ├── server/ # JS/TS modules (.server.js or .server.ts)
│ └── client/ # front-end code (if fullstack)
├── @types/ # auto-generated type defs (via `now-sdk dependencies`)
└── metadata/ # auto-generated XML (build output, do not edit)
SDK CLI Commands Reference (v3.0+ / v4.x)
These are the commands Claude Code should use or instruct the user to run:
| Command | Purpose | Example |
|---|
now-sdk build | Compile Fluent → metadata XML | now-sdk build |
now-sdk install | Build + pack + deploy to instance | now-sdk install --auth <alias> |
now-sdk transform | Download/sync metadata from instance to local | now-sdk transform --auth <alias> |
now-sdk dependencies | Fetch table defs and type info from instance | now-sdk dependencies --auth <alias> |
now-sdk init | Scaffold a new app or convert existing | npx @servicenow/sdk init |
now-sdk pack | Package build output into installable archive | now-sdk pack |
now-sdk auth save | Store auth credentials in system keychain | User handles manually |
Build and Install Workflow
The standard development cycle is:
now-sdk build
now-sdk install --auth <alias>
Build flags:
--debug true — include debug info
--generate-deletes true — remove records from instance that were deleted locally
--lint true — run linting during build
--frozenKeys — fail if keys.ts needs updating (useful for CI)
Install flags:
--auth <alias> — which auth profile to use
--open-browser true — open the app in browser after install
Syncing from Instance
To pull changes made on the instance back to local:
now-sdk transform --auth <alias>
This downloads metadata and converts supported types to Fluent .now.ts files
in src/fluent/generated/.
Fetching Type Definitions
To get table schemas and type info for IntelliSense:
now-sdk dependencies --auth <alias>
This populates @types/servicenow/ with type definitions for GlideRecord etc.
Core Fluent Patterns
Read references/fluent-patterns.md for the full API reference with examples
covering Tables, Flows, Business Rules, Script Includes, ACLs, Records, and more.
The most important patterns for email parsing apps are summarized below.
Table Definition
import {
Table, StringColumn, ReferenceColumn, IntegerColumn,
DateTimeColumn, HtmlColumn, ChoiceColumn
} from '@servicenow/sdk/core'
export const x_vendor_app_email_data = Table({
name: 'x_vendor_app_email_data',
label: 'Parsed Email Data',
schema: {
x_vendor_app_sender: StringColumn({
label: 'Sender',
maxLength: 255,
mandatory: true
}),
x_vendor_app_subject: StringColumn({
label: 'Subject',
maxLength: 500
}),
x_vendor_app_received_date: DateTimeColumn({
label: 'Received Date'
}),
x_vendor_app_body_text: HtmlColumn({
label: 'Email Body'
}),
x_vendor_app_status: ChoiceColumn({
label: 'Processing Status',
choices: {
new: { label: 'New' },
: { : },
: { : },
},
:
})
}
})
Key rules for tables:
- Table name MUST start with the app scope prefix:
x_<vendor>_<app>_
- Column names MUST also be prefixed with the scope
- Available column types:
StringColumn, IntegerColumn, BooleanColumn,
DateTimeColumn, ReferenceColumn, ChoiceColumn, HtmlColumn,
DecimalColumn, URLColumn, DurationColumn, TimeColumn,
FieldListColumn, GlideListColumn
HtmlColumn — NOT HTMLColumn (casing matters, build will fail)
ChoiceColumn choices must be an object { key: { label } }, NOT an array
ChoiceColumn default value uses default:, NOT defaultValue:
- Use
ReferenceColumn with reference: '<table_name>' for foreign keys
Flow with Inbound Email Trigger
import { action, Flow, wfa, trigger } from '@servicenow/sdk/automation'
export const emailParserFlow = Flow(
{
$id: Now.ID['email_parser_flow'],
name: 'Email Parser Flow',
description: 'Parses inbound emails and extracts data to custom table',
},
wfa.trigger(
trigger.application.inboundEmail,
{ $id: Now.ID['inbound_email_trigger'] },
{
email_conditions: 'subjectLIKEorder%',
target_table: 'incident',
}
),
(params) => {
wfa.action(
action..,
{ : .[] },
{
: ,
: ,
}
)
wfa.(
action..,
{ : .[] },
{
: ,
: ({
: wfa.(params.., ),
: wfa.(params.., ),
: wfa.(params.., ),
: ,
}),
}
)
}
)
Key rules for flows:
- Every element needs a unique
$id using Now.ID['unique_key']
- The
$id keys must be unique across the entire application
description must be a single string literal — string concatenation with + will cause a build error
- Trigger types:
trigger.record.created, trigger.record.updated,
trigger.application.inboundEmail, trigger.schedule.daily, etc.
- Inbound email trigger config: use
email_conditions with encoded query (no spaces around operator).
Valid fields: email_conditions, target_table, order, stop_condition_evaluation
- Inbound email data pills — accessed directly on
params.trigger:
params.trigger.from_address — sender address (string)
params.trigger.subject — subject line (string)
params.trigger.body_text — plain-text body (string)
params.trigger.inbound_email — the sys_email record reference (use .body, .sys_id, etc.)
params.trigger.user — sys_user reference for the sender
- WRONG:
params.trigger.email.subject / params.trigger.email.from — these do NOT exist
createRecord inputs: table_name (NOT table) + values: TemplateValue({...}) (NOT a plain object)
TemplateValue is a global — no import needed
- Use
wfa.dataPill() to reference runtime values from trigger or prior actions
- Use
wfa.flowLogic.if() / wfa.flowLogic.elseIf() / wfa.flowLogic.else() for branching
- Actions:
action.core.log, action.core.createRecord, action.core.createTask,
action.core.lookUpRecord (capital U), ,
,
Flow Logic — Branching
wfa.flowLogic.if(
{
$id: Now.ID['check_condition'],
condition: `${wfa.dataPill(params.trigger.current.severity, 'string')}=1`
},
() => {
}
)
wfa.flowLogic.else(
{ $id: Now.ID['else_block'] },
() => {
}
)
Email Parsing Workflow — Step by Step
When the user asks to build an email parsing application, follow this sequence:
Step 1: Analyze the Sample Email
Ask the user for a sample email (or they may have already provided one).
Extract the fields that should be captured. Common fields include:
- Sender address
- Subject line
- Date/time received
- Key data from the body (order numbers, amounts, names, IDs, etc.)
- Attachments (flag presence)
Step 2: Design the Table Schema
Based on the extracted fields, generate a Table definition with appropriate
column types. Use the app scope prefix throughout. Explain your column choices.
Step 3: Generate the Inbound Email Flow
Create the Flow with:
- An inbound email trigger with appropriate conditions
- Parsing logic (via script actions or direct field mapping)
- Record creation in the custom table
- Error handling with a status field
- Optional: notification on parse failure
Step 4: Add Supporting Elements (if needed)
Depending on complexity, also generate:
- Business Rule: for post-insert processing or validation
- Script Include: for reusable parsing logic (regex extraction, etc.)
- ACL: to control who can read/write the parsed data table
Step 5: Build and Install
Run the build and install commands:
now-sdk build
now-sdk install --auth <alias>
If there are errors, help debug. Common issues:
- Scope prefix mismatch between
now.config.json and Fluent code
- Missing
$id or duplicate $id values
- Node version too old (requires v20+)
- Build lint errors — check imports and type usage
Naming Conventions
These are mandatory for ServiceNow SDK apps:
| Element | Pattern | Example |
|---|
| Table name | x_<vendor>_<app>_<name> | x_acme_emailp_parsed_data |
| Column name | x_<vendor>_<app>_<field> | x_acme_emailp_sender_address |
| Flow $id | Unique snake_case key | email_parser_flow |
| Action $id | Descriptive snake_case | create_parsed_email_record |
| File name | kebab-case with .now.ts extension | inbound-email-parser.now.ts |
Always derive <vendor> and <app> from the scope field in now.config.json.
Security Reminders
- NEVER put credentials in source files — auth is in the system keychain
- Ensure
.gitignore includes metadata/, dist/, node_modules/, .now/
- Inbound email flows run as the email sender by default. If the sender
is unknown, the flow runs as Guest. For elevated operations, use a
subflow that runs as System
- Always apply ACLs to custom tables to restrict access appropriately
- Enable the Email Filter plugin (
com.glide.email_filter) for reliable
inbound email trigger execution
Troubleshooting Quick Reference
| Problem | Solution |
|---|
Auth profile not found | User runs: now-sdk auth save <alias> --host <url> --username <user> --default |
Scope mismatch | Table/column name prefixes must match now.config.json scope |
Duplicate $id | Every Now.ID['key'] must be unique across the entire app |
Build fails on .ts | Check tsconfig.json has "outDir": "dist/src" and transpiledSourceDir is set in now.config.json |
Install 403 | Verify user has admin or app_developer role on the instance |
Flow not triggering | Check Email Filter plugin is active; verify email_conditions encoded query is correct (e.g. subjectLIKEfoo) |
TS2724: no exported member 'HTMLColumn' | Use HtmlColumn — the correct export name (casing matters) |
TS2353: 'value' does not exist in ChoiceConfig | ChoiceColumn choices must be an object { key: { label } } not an array; and use default: not defaultValue: |
TS2769: 'table' does not exist on createRecord | Use table_name: and values: TemplateValue({...}) — not table: and not a plain object |
TS243: Unknown instance type in Flow | Trigger data pills are on params.trigger.subject / .from_address / .body_text directly — not params.trigger.email.* |
TS303: Failed to parse property on description | description must be a single string literal — multi-line + concatenation is not supported in Fluent |
|
When Build Fails — Check SDK Examples First
IMPORTANT: If now-sdk build fails with an error you are not sure about, fetch the
relevant official SDK example file below using WebFetch before guessing at a fix.
The examples are the authoritative source of correct Fluent syntax.
Official SDK Example Files (fetch raw content on demand)
| What you're building | File to fetch |
|---|
| Inbound email flow | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/flow-sample/src/fluent/flow-trigger-inbound-email.now.ts |
| Record trigger flow | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/flow-sample/src/fluent/flow-trigger-record.now.ts |
| Service catalog flow | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/flow-sample/src/fluent/flow-trigger-catalog-item.now.ts |
| SLA task flow | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/flow-sample/src/fluent/flow-trigger-sla-task.now.ts |
| Knowledge mgmt flow | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/flow-sample/src/fluent/flow-trigger-knowledge-management.now.ts |
| Remote table query flow | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/flow-sample/src/fluent/flow-trigger-remote-table-query.now.ts |
| Subflow | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/flow-sample/src/fluent/subflow-sample.now.ts |
| Simple table | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/table-sample/src/fluent/table-simple.now.ts |
| Table with custom columns | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/table-sample/src/fluent/table-custom-column.now.ts |
| Table extending another | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/table-sample/src/fluent/table-extends.now.ts |
| Business rule | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/businessrule-sample/src/fluent/business-rule-1.now.ts |
| ACL | https://raw.githubusercontent.com/ServiceNow/sdk-examples/main/acl-sample/src/fluent/index.now.ts |
How to use these during debugging
- Build fails with an unfamiliar error → identify which example type matches the failing code
- Use
WebFetch to fetch the raw URL from the table above
- Compare the example's exact syntax against the failing code and fix accordingly
- Re-run
now-sdk build
The full examples index and workflow (clone, pnpm, sample table) are in references/sdk-examples-repo.md. Repo: github.com/ServiceNow/sdk-examples.
Additional References
For deeper patterns beyond email parsing, read:
references/sdk-api-docs-agents.md — Official agent-oriented API text: versions.json, llms / llmsFull per SDK version, Context7 vs fetch
references/sdk-examples-repo.md — Official sdk-examples repo: sample index, prerequisites, how to align local code with upstream Fluent files
references/fluent-patterns.md — Full Table, Flow, BusinessRule, ScriptInclude,
ACL, Record, and RestApi patterns with examples
references/flow-advanced.md — Advanced flow patterns: subflows, parallel blocks,
for-each loops, error handling, and approval flows
- SDK API reference (human + agent
llms*): https://servicenow.github.io/sdk/
- ServiceNow SDK docs: https://www.servicenow.com/docs/r/application-development/servicenow-sdk/define-metadata-code-fluent-sdk.html
- Build & install docs: https://www.servicenow.com/docs/r/application-development/servicenow-sdk/build-deploy-application-now-sdk.html
- Fluent MCP server (for AI-assisted dev): https://github.com/modesty/fluent-mcp
- SDK releases & changelog: https://github.com/ServiceNow/sdk/releases