# Servicenow Sdk Fluent

> 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.

- Skill: `cuneytbozok/servicenow-sdk-fluent` (Agent Skill, multi-file: 5 files)
- Install (CLI): `npx skillmds@latest add cuneytbozok/servicenow-sdk-fluent`
- Raw SKILL.md: https://api.skillmd.com/api/skills/cuneytbozok/servicenow-sdk-fluent/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: cuneytbozok (https://skillmd.com/u/cuneytbozok)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/cuneytbozok/servicenow-sdk-fluent

---


# 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](references/fluent-patterns.md).
For subflows, parallel blocks, for-each, and email pipelines, read [references/flow-advanced.md](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](references/sdk-examples-repo.md) — companion to [ServiceNow/sdk-examples](https://github.com/ServiceNow/sdk-examples).
For **typed Fluent API reference** (version-matched `llmsFull` / `llms` from ServiceNow), read [references/sdk-api-docs-agents.md](references/sdk-api-docs-agents.md) — links [servicenow.github.io/sdk](https://servicenow.github.io/sdk/) agent files and [versions.json](https://servicenow.github.io/sdk/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:
```bash
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:

1. **`now.config.json`** must exist in the project root with at minimum:
   ```json
   {
     "scope": "x_<vendor>_<app>",
     "scopeId": "<sys_id_of_app>"
   }
   ```
   For TypeScript projects, also needs:
   ```json
   {
     "transpiledSourceDir": "dist/src"
   }
   ```

2. **`package.json`** must have SDK dependencies:
   ```json
   {
     "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": "^27.0.0"
     }
   }
   ```
   For TypeScript, add:
   ```json
   {
     "scripts": {
       "build": "rm -rf dist && tsc -b && now-sdk build"
     },
     "devDependencies": {
       "typescript": "^5.5.0"
     }
   }
   ```

3. **If the project doesn't exist yet**, the user should scaffold it with:
   ```bash
   npx @servicenow/sdk init
   ```
   This walks through template selection and links the auth profile.

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:

```bash
# 1. Write or modify Fluent code in src/fluent/*.now.ts

# 2. Build — compiles Fluent to metadata XML
now-sdk build

# 3. Install — packages and deploys to the instance
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:
```bash
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:
```bash
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

```typescript
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 is an OBJECT (keys = values), NOT an array
      choices: {
        new:    { label: 'New' },
        parsed: { label: 'Parsed' },
        error:  { label: 'Error' },
      },
      default: 'new'   // use 'default', NOT 'defaultValue'
    })
  }
})
```

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

```typescript
import { action, Flow, wfa, trigger } from '@servicenow/sdk/automation'

export const emailParserFlow = Flow(
  {
    $id: Now.ID['email_parser_flow'],
    name: 'Email Parser Flow',
    // description must be a single string literal — NO string concatenation with +
    description: 'Parses inbound emails and extracts data to custom table',
  },
  wfa.trigger(
    trigger.application.inboundEmail,
    { $id: Now.ID['inbound_email_trigger'] },
    {
      // Use 'email_conditions' (NOT 'condition' or 'record_type')
      // Encoded query format: NO spaces around operator — 'subjectLIKEorder%'
      email_conditions: 'subjectLIKEorder%',
      target_table: 'incident',   // optional: table for reply record
    }
  ),
  (params) => {
    // Inbound email trigger data pills — direct on params.trigger (NOT params.trigger.email.*)
    //   params.trigger.from_address  — sender email address
    //   params.trigger.subject       — email subject
    //   params.trigger.body_text     — plain-text body
    //   params.trigger.inbound_email — reference to sys_email record (for .body, .sys_id, etc.)
    //   params.trigger.user          — reference to sys_user who sent the email

    // Log for debugging
    wfa.action(
      action.core.log,
      { $id: Now.ID['log_email_received'] },
      {
        log_level: 'info',
        log_message: `Processing email from: ${wfa.dataPill(params.trigger.from_address, 'string')}`,
      }
    )

    // Create record in custom table
    // createRecord uses 'table_name' (NOT 'table') and values: TemplateValue({}) (NOT a plain object)
    wfa.action(
      action.core.createRecord,
      { $id: Now.ID['create_parsed_record'] },
      {
        table_name: 'x_vendor_app_email_data',
        values: TemplateValue({
          x_vendor_app_sender:    wfa.dataPill(params.trigger.from_address, 'string'),
          x_vendor_app_subject:   wfa.dataPill(params.trigger.subject, 'string'),
          x_vendor_app_body_text: wfa.dataPill(params.trigger.body_text, 'string'),
          x_vendor_app_status:    'new',
        }),
      }
    )
  }
)
```

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), `action.core.sendEmail`,
  `action.core.getAttachmentsOnRecord`, `action.core.copyAttachment`

### Flow Logic — Branching

```typescript
wfa.flowLogic.if(
  {
    $id: Now.ID['check_condition'],
    condition: `${wfa.dataPill(params.trigger.current.severity, 'string')}=1`
  },
  () => {
    // actions for when condition is true
  }
)

wfa.flowLogic.else(
  { $id: Now.ID['else_block'] },
  () => {
    // actions for all other cases
  }
)
```

---

## 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:
```bash
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 |
| `Guest user errors` | Use subflow with `run_as: 'system'` for privileged operations |
| `Transform conflicts` | Generated files in `src/fluent/generated/` — migrate to proper files if needed |
| `Missing types` | Run `now-sdk dependencies --auth <alias>` to fetch type defs |

---

## 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

1. Build fails with an unfamiliar error → identify which example type matches the failing code
2. Use `WebFetch` to fetch the raw URL from the table above
3. Compare the example's exact syntax against the failing code and fix accordingly
4. Re-run `now-sdk build`

The full examples index and workflow (clone, pnpm, sample table) are in [references/sdk-examples-repo.md](references/sdk-examples-repo.md). Repo: [github.com/ServiceNow/sdk-examples](https://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](https://servicenow.github.io/sdk/versions.json), `llms` / `llmsFull` per SDK version, Context7 vs fetch
- `references/sdk-examples-repo.md` — **Official** [sdk-examples](https://github.com/ServiceNow/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

