---
sidebar_label: Keys File
sidebar_position: 5
tags: [keys-file, keys, keys.ts, Now.ID, sys_id, record identity, coalesce, composite key, explicit key]
---

# The keys.ts File

Every Fluent project has an auto-generated `keys.ts` file that maps human-readable identifiers to ServiceNow sys_ids. It's the registry that makes `Now.ID['my-record']` work.

## Location

```
src/fluent/generated/keys.ts
```

This file is **auto-generated by the build system** — you should not normally need to edit it by hand.

## Purpose

ServiceNow identifies every record by a 32-character sys_id (e.g., `4103297d12554b488d489c0bf1ceff19`). Working with raw sys_ids in source code is error-prone and unreadable. The keys file solves this by mapping meaningful names to sys_ids:

```typescript fluent
$id: Now.ID['validate-on-insert']  // readable
// resolves to sys_id: '4103297d12554b488d489c0bf1ceff19'
```

## Structure

```typescript fluent
import '@servicenow/sdk/global'

declare global {
    namespace Now {
        namespace Internal {
            interface Keys extends KeysRegistry {
                explicit: {
                    'validate-on-insert': {
                        table: 'sys_script'
                        id: '4103297d12554b488d489c0bf1ceff19'
                    }
                    'my-acl': {
                        table: 'sys_security_acl'
                        id: 'a3a4966e8c38446d8e1c25620e4e73f4'
                    }
                }
                composite: [
                    {
                        table: 'sys_documentation'
                        id: '00e80b3ed0124620a32c6f9ac0472cf4'
                        key: {
                            name: 'x_myapp_table'
                            element: 'name'
                            language: 'en'
                        }
                    }
                ]
                deleted: {}
            }
        }
    }
}
```

## Key types

### Explicit keys

Direct mappings from a developer-chosen name to a table and sys_id. These are created when you use `$id: Now.ID['some-name']` in your Fluent code.

```typescript fluent
explicit: {
    'my-business-rule': {
        table: 'sys_script'
        id: '4103297d12554b488d489c0bf1ceff19'
    }
}
```

- **You choose the key name** — it can be any string (`'my-rule'`, `'validate-on-insert'`, `'1'`)
- **The sys_id is auto-generated** on first build and stable thereafter
- IDE autocomplete suggests existing keys when you type `Now.ID['`

### Composite keys

For records identified by a combination of field values (coalesce keys) rather than a single developer-chosen name. These are auto-generated for child and descendant records like table columns, documentation entries, and choice values.

```typescript fluent
composite: [
    {
        table: 'sys_choice'
        id: 'ae25f03b01c14366942460e8cfeec032'
        key: {
            name: 'x_myapp_task'
            element: 'state'
            value: 'todo'
        }
    }
]
```

You don't interact with composite keys directly — they're managed by the build system.

### Deleted keys

Tracks records that have been removed from the project. This prevents sys_id reuse and enables clean uninstall.

## How Now.ID works

### First build — key is new

```typescript fluent
// In your .now.ts file:
BusinessRule({
    $id: Now.ID['my-new-rule'],  // Key doesn't exist yet
    ...
})
```

The build system:
1. Sees `'my-new-rule'` is not in keys.ts
2. Generates a new sys_id
3. Adds the entry to keys.ts
4. Uses that sys_id in the XML output

### Subsequent builds — key exists

```typescript fluent
// In your .now.ts file:
BusinessRule({
    $id: Now.ID['my-new-rule'],  // Key exists in keys.ts
    ...
})
```

The build system:
1. Looks up `'my-new-rule'` in keys.ts
2. Finds the existing sys_id
3. Uses the **same** sys_id — ensuring the record is updated, not duplicated


## Where Now.ID resolves

`Now.ID['key']` only resolves to a hashed sys_id when used in the `$id` property. It does **not** resolve inside `data` fields — the literal key string is written to the database instead, causing reference fields to appear blank.

```typescript fluent
import { Record } from '@servicenow/sdk/core'

export const vendorAcme = Record({
    $id: Now.ID['vendor-acme'], // CORRECT: Now.ID resolves here
    table: 'x_snc_vendor_man_vendor',
    data: {
        name: 'Acme Corp',
    },
})

export const contractAcme = Record({
    $id: Now.ID['contract-acme-1'],
    table: 'x_snc_vendor_man_vendor_contract',
    data: {
        vendor: vendorAcme,              // CORRECT: pass the record variable directly
        // vendor: Now.ID['vendor-acme'], // WRONG: writes literal "vendor-acme" to DB
    },
})
```

| Scenario | Pattern |
|----------|---------|
| Same-app record reference | `exportedRecord` (pass the variable directly) |
| Platform record not in your app | sys_id string (e.g., `'77ad8176731313005754660c4cf6a7de'`) |

## Best practices

- **Don't edit keys.ts manually** unless you need to fix a specific mapping
- **Do commit keys.ts to version control** — it's the source of truth for record identity
- **Use meaningful key names** — `'validate-priority-on-insert'` is better than `'1'`
- **Don't worry about composite keys** — they're fully automatic
