Tooling Prerequisites
Install Node.js via NVM, the ServiceNow SDK CLI, and Claude Code. These three tools are the entire foundation of the workflow.
Install using NVM (Node Version Manager), not the direct Node installer — this avoids OS permission errors and lets you manage Node versions cleanly.
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
nvm install 18
nvm use 18
nvm install 18 then nvm use 18npm install -g @servicenow/sdk
npm install -g @anthropic-ai/claude-code
Verify all three tools are installed and reachable from your terminal:
| Tool | Command | Expected Output | Min Version |
|---|---|---|---|
| Node.js | node --version |
v18.20.4 or higher |
v18.x.x |
| npm | npm --version |
9.x.x or higher |
9.x.x |
| SN SDK | now-sdk --version |
4.4.x |
4.4.x |
| Claude Code | claude --version |
Any valid build string | Latest |
npm config get prefix
/Users/you/.nvm/versions/node/v18.20.4) and add /bin to your ~/.zshrc or ~/.bashrc:export PATH="$(npm config get prefix)/bin:$PATH"Then run
source ~/.zshrc and retry.claude? It automatically opens your default browser and prompts you to log in with your Anthropic account. Once you approve the OAuth grant, it stores a session token locally — you won't need to authenticate again. If the browser window doesn't appear, check your terminal output for a manual auth URL you can open directly.Connect to Your Instance
Register your ServiceNow instance with the SDK once. Credentials are stored in your OS keychain — never in project files — so you never type a password again.
Your instance URL looks like: https://yourcompany.service-now.com
The SDK stores credentials securely in your OS keychain — nothing is written to project files. Choose your authentication method below. Basic is the fastest to set up; OAuth is recommended for SSO environments, CI/CD pipelines, or shared team setups.
now-sdk auth --add <your-instance>
| Flag | Purpose |
|---|---|
mydev |
A friendly alias for this connection — you’ll use this in other commands |
now-sdk auth --list
dev → https://yourcompany.service-now.com (basic).env files with passwords.- Log into your ServiceNow instance as admin
- Open Application Manager (System Applications → All Available Applications → All)
- Search for "ServiceNow IDE" and install it
now-sdk auth --add <your-instance>
- The SDK prompts for the type of auth — choose OAuth
- Your browser opens the ServiceNow login page
- Sign in with your credentials (or SSO provider)
- After login, ServiceNow displays a one-time token in the browser
- Copy the token, paste it back into the terminal, press Enter
now-sdk auth --add mydev-oauth --type oauth --default
now-sdk auth --list
dev → https://yourcompany.service-now.com (oauth)Create & Scaffold the Project
Initialize the Society Management System project folder, scaffold the SDK structure, install dependencies, and pull type definitions from your instance.
mkdir SocietyMgmt
cd SocietyMgmt
now-sdk init
| Prompt | Enter This | Why |
|---|---|---|
| App name | Society Management System | Display name in ServiceNow |
| Scope prefix | x_society | Namespace to prevent conflicts with other apps |
| Auth alias | dev | The alias you registered in Step 2 |
npm install
added 312 packages, and audited 313 packages in 28s. If you see npm warn deprecated lines, those are safe to ignore — they come from the SDK’s own dependencies.npm run types
.types/ folder in your project and should be committed to git so teammates don’t need to fetch them individually.git init
git add .
git commit -m "Initial project setup"
code .
After init, your project looks like this. You’ll add Fluent files under src/fluent/ in the next steps.
SocietyMgmt/
│
├── src/
│ └── fluent/ ← TypeScript files that DEFINE your app
│ ├── index.now.ts ← Master export — every file must be listed here
│ ├── tables/ ← flat, maintenance_bill, service_request, visitor_log
│ ├── business-rules/ ← set-dates, prevent-double-booking
│ ├── script-includes/ ← SocietyUtils, MaintenanceBillingEngine
│ ├── scheduled-jobs/ ← mark-overdue-bills, generate-monthly-bills
│ ├── acls/ ← Security rules (resident, committee_member, admin)
│ └── navigation/ ← Sidebar menu items
│
├── package.json ← Project settings and build commands
├── now.config.json ← App identity (scope, name, scopeId)
└── .eslintrc ← Code quality rules
src/fluent/index.now.ts must export every Fluent file in your project. If you create a file and forget to add it here, that piece of your app will be silently skipped at deploy time — no error, just missing.Create AI Context Files
Start Claude Code and give it a briefing document. Without this, Claude starts each session knowing nothing about your project.
claude
Think of a Skill file as a permanent cheat-sheet you hand Claude once. It tells Claude the name of your app, how to build and deploy it, and what the app actually does — so you never have to explain it again at the start of a new session.
How we built this prompt: We asked Claude to inspect the project files and write the skill prompt itself. You can do the same for your own project — run Step A below first to let Claude generate a prompt tailored to your app, then use that output in Step B.
Paste this into Claude Code. It will inspect your project and output a ready-to-use skill prompt. Copy that output and use it in Step B.
Look at my project's now.config.json, package.json, and src/ folder structure.
Then write me a Claude Code skill file prompt that captures:
1. My app identity (name, scope prefix, SDK version, auth alias)
2. The SDK build/deploy/types/transform commands and what each one does
3. Any file structure rules that are easy to get wrong (e.g. index exports, naming conventions)
4. My app's core tables, automation logic, and user roles
The skill file must be created at .claude/skills/<your-app-name>/SKILL.md (folder named after the app, file named SKILL.md in all caps).
Output only the prompt text — I'll use it in the next step to create the skill file.
Paste the prompt Claude gave you in Step A (or copy the Society sample below if you're following this guide). Claude will read your project files and write the skill file automatically.
Create a skill file at .claude/skills/society/SKILL.md for this project.
Read now.config.json and package.json to get the real app values, then write a
skill that covers:
1. Project identity: app name, scope prefix, SDK version, auth alias
2. SDK command reference:
- npm run build: compiles TypeScript to XML, checks for errors, does NOT touch instance
- npm run deploy: builds + pushes everything to ServiceNow
- npm run types: fetches type definitions from instance (run when schema changes on instance)
- npm run transform: pulls current instance state to local files (always commit first)
3. File structure rules:
- Fluent files (.now.ts) define structure: tables, columns, ACLs, menus
- Every Fluent file MUST be exported from src/fluent/index.now.ts
- If you create a file and forget to export it, it will be silently skipped at deploy time
4. App context:
Society & Apartment Management System (India-Focused).
Replaces WhatsApp groups and Excel sheets with a proper ServiceNow app.
4 core tables:
- flat: master flat/unit registry — all other tables reference this
- maintenance_bill: one record per flat per month, auto-generated by scheduled job
- service_request: unified table for complaints, facility bookings, and notices
distinguished by request_type choice field
- visitor_log: gate entry log maintained by security staff
Server-side automation:
- Business Rule "set-dates": auto-fills opened_on on insert, resolved_on on resolve
- Business Rule "prevent-double-booking": aborts facility booking if slot already taken
- Script Include SocietyUtils: isSlotAvailable(), getUnpaidBillsByFlat(), getOpenRequestsByType()
- Script Include MaintenanceBillingEngine: generateMonthlyBills(), markOverdueBills()
- Scheduled Job (daily 1AM): marks unpaid past-due bills as overdue
- Scheduled Job (monthly 1st): auto-creates bill records for all active flats
Roles: resident, committee_member, admin.
.claude/skills/society/SKILL.md. From that point on, every Claude session in this project automatically loads that file — Claude already knows your app without you saying a word.
After the tables are built in Step 5, run this to generate a lookup doc at docs/field-reference.md. Claude can reference it in future prompts so it always uses the correct field names.
Read the following Fluent files and create a reference document at docs/field-reference.md:
- src/fluent/tables/flat.now.ts
- src/fluent/tables/maintenance_bill.now.ts
- src/fluent/tables/service_request.now.ts
- src/fluent/tables/visitor_log.now.ts
For each table and each field include: short field name, full scoped field name,
data type, allowed values or max length, and a one-line plain English description.
Also document at the bottom:
1. request_type values and which fields are relevant to each:
complaint → category, priority, assigned_to, vendor_name, resolution_notes
facility_booking → facility, booking_date, start_time, end_time, charges
notice → pinned, expires_on
2. Status workflows:
Maintenance Bill: unpaid → paid | overdue (auto-set by scheduled job)
Complaint: open → in_progress → resolved | rejected
Facility Booking: requested → confirmed | cancelled
Notice: active → expired (auto on expires_on date)
3. Script Include public methods:
SocietyUtils.isSlotAvailable(facility, date, start, end) → boolean
SocietyUtils.getUnpaidBillsByFlat(flat_sys_id) → GlideRecord
MaintenanceBillingEngine.generateMonthlyBills(month_year) → void
MaintenanceBillingEngine.markOverdueBills() → number of records updated
Build the App with Claude
One prompt creates all Fluent files — 4 smart tables, 2 business rules, 2 script includes, 2 scheduled jobs, ACLs, and navigation. Review what Claude creates and approve each file.
| File Created | Type | What It Does |
|---|---|---|
tables/flat.now.ts | Table | Master flat/unit registry — flat number, block, resident reference, ownership type (owner/tenant) |
tables/maintenance_bill.now.ts | Table | Monthly maintenance billing — references flat, tracks amount, due date, payment status & mode |
tables/service_request.now.ts | Table | Unified table for complaints, facility bookings, and notices via request_type choice field |
tables/visitor_log.now.ts | Table | Visitor entry log — name, purpose, host flat reference, entry/exit timestamps, logged by security |
business-rules/set-dates.now.ts | Business Rule | On insert: auto-sets opened_on to today. On status→resolved: auto-sets resolved_on |
business-rules/prevent-double-booking.now.ts | Business Rule | Before insert/update on booking type: aborts if the same facility has a confirmed slot overlap |
script-includes/SocietyUtils.now.ts | Script Include | Helpers: isSlotAvailable(), getUnpaidBillsByFlat(), getOpenRequestsByType() |
script-includes/MaintenanceBillingEngine.now.ts | Script Include | generateMonthlyBills(month_year) — creates bill records for all active flats. markOverdueBills() — flags unpaid past-due bills |
scheduled-jobs/mark-overdue-bills.now.ts | Scheduled Job | Runs daily at 1 AM — calls MaintenanceBillingEngine.markOverdueBills() |
scheduled-jobs/generate-monthly-bills.now.ts | Scheduled Job | Runs 1st of every month — calls MaintenanceBillingEngine.generateMonthlyBills() |
acls/society-acls.now.ts | ACLs | Residents read/create own records; committee_member can update all; admin can delete |
navigation/app-menu.now.ts | Navigation | Sidebar: Dashboard, My Bills, Complaints, Facility Booking, Notices, Visitor Log, Flat Registry |
Create the complete Society Management System app in one pass.
Create the necessary Fluent files for every component below, then add each export to
src/fluent/index.now.ts. Scope prefix is x_society.
───────────────────────────────────────────────────────────────
TABLE 1 — flat (master flat/unit registry)
───────────────────────────────────────────────────────────────
Purpose: single source of truth for every flat in the society.
Other tables reference this instead of storing flat_number as raw text.
Fields:
- flat_number: String max 10 chars, mandatory — e.g. A-101, B-204
- block: String max 5 chars — e.g. A, B, C
- floor: Integer — floor number
- resident: Reference to sys_user, mandatory — current resident
- ownership: Choice — owner, tenant. Default: owner
- move_in_date: Date
- contact_phone: String max 15 chars
- active: Boolean, default true — false when flat is vacant
───────────────────────────────────────────────────────────────
TABLE 2 — maintenance_bill
───────────────────────────────────────────────────────────────
Purpose: one record per flat per billing month.
Fields:
- flat: Reference to x_society_flat, mandatory
- month_year: String max 7 chars, mandatory — e.g. MAR-2026
- amount: Decimal, mandatory — charge in INR
- due_date: Date, mandatory
- status: Choice — unpaid, paid, overdue. Default: unpaid
- payment_date: Date
- payment_mode: Choice — cash, upi, bank_transfer, cheque
- remarks: String max 500 chars — committee notes
───────────────────────────────────────────────────────────────
TABLE 3 — service_request (unified: complaints + bookings + notices)
───────────────────────────────────────────────────────────────
Purpose: single table for all resident requests and society communications,
distinguished by request_type.
Common fields (all types):
- request_type: Choice — complaint, facility_booking, notice. Mandatory.
- title: String max 200 chars, mandatory
- description: String max 4000 chars
- opened_by: Reference to sys_user, mandatory
- flat: Reference to x_society_flat
- opened_on: Date — auto-set by business rule on insert
- status: Choice — open, in_progress, resolved, confirmed, cancelled, active, expired
Default: open
- assigned_to: Reference to sys_user — committee member
Complaint-specific fields:
- category: Choice — water, electricity, lift, parking, housekeeping, security, noise, other
- priority: Choice — low, medium, high. Default: medium
- vendor_name: String max 100 chars — outsourced vendor if any
- resolution_notes: String max 2000 chars
- resolved_on: Date — auto-set by business rule when status → resolved
Facility Booking-specific fields:
- facility: Choice — gym, community_hall, terrace, guest_room, swimming_pool
- booking_date: Date
- start_time: String max 5 chars — e.g. 10:00
- end_time: String max 5 chars — e.g. 14:00
- booking_charges: Decimal, default 0
Notice-specific fields:
- pinned: Boolean, default false
- expires_on: Date
───────────────────────────────────────────────────────────────
TABLE 4 — visitor_log
───────────────────────────────────────────────────────────────
Purpose: gate entry log maintained by security staff.
Fields:
- visitor_name: String max 100 chars, mandatory
- visitor_phone: String max 15 chars
- purpose: Choice — delivery, guest, service_vendor, cab, other
- host_flat: Reference to x_society_flat, mandatory
- vehicle_number: String max 15 chars
- entry_time: DateTime, mandatory
- exit_time: DateTime
- logged_by: Reference to sys_user — security staff
───────────────────────────────────────────────────────────────
BUSINESS RULE 1 — set-dates (on service_request)
───────────────────────────────────────────────────────────────
Write a before-insert and before-update business rule on x_society_service_request.
It should handle two things:
- On insert: automatically populate opened_on with today's date if it is not already set.
- On update: when the status field changes to "resolved", automatically set resolved_on
to today's date if it is not already populated.
Generate the appropriate ServiceNow server-side script for this logic.
───────────────────────────────────────────────────────────────
BUSINESS RULE 2 — prevent-double-booking (on service_request)
───────────────────────────────────────────────────────────────
Write a before-insert and before-update business rule on x_society_service_request.
It should only run when request_type is "facility_booking" and status is not "cancelled".
The rule must prevent a booking from saving if another confirmed booking already exists
for the same facility on the same date with an overlapping time slot.
If a conflict is found, abort the action and show a clear error message to the user.
Generate the ServiceNow server-side script that queries for conflicts and enforces this.
───────────────────────────────────────────────────────────────
SCRIPT INCLUDE 1 — SocietyUtils
───────────────────────────────────────────────────────────────
Create a server-side Script Include class called SocietyUtils (not client-callable).
It should expose three reusable helper methods:
1. A method to check if a facility time slot is available — takes facility, booking date,
start time, end time, and an optional record sys_id to exclude (for update scenarios).
Returns true if no confirmed bookings overlap with the given slot, false otherwise.
2. A method to get all unpaid or overdue maintenance bills for a given flat sys_id.
Returns results ordered by due date ascending.
3. A method to get all open (unresolved/unarchived) service_request records for a given
request_type. Returns results ordered by opened_on descending.
Generate clean, well-commented ServiceNow GlideRecord-based code for each method.
───────────────────────────────────────────────────────────────
SCRIPT INCLUDE 2 — MaintenanceBillingEngine
───────────────────────────────────────────────────────────────
Create a server-side Script Include class called MaintenanceBillingEngine (not client-callable).
It should expose two methods:
1. generateMonthlyBills(month_year): Takes a billing period string like "MAR-2026".
Finds all active flat records and for each one checks if a maintenance_bill already
exists for that month_year. If not, creates a new bill with status = unpaid and
due_date set to the 10th of the following month.
Log how many bills were created using gs.info with a [SocietyApp] prefix.
2. markOverdueBills(): Finds all maintenance_bill records that are still "unpaid"
but have a due_date in the past. Updates their status to "overdue".
Log how many records were updated using gs.info with a [SocietyApp] prefix.
Generate production-quality ServiceNow server-side code for both methods.
───────────────────────────────────────────────────────────────
SCHEDULED JOB 1 — mark-overdue-bills
───────────────────────────────────────────────────────────────
Name: "Society: Mark Overdue Bills"
Schedule: Daily at 01:00 AM
Write a short job script that instantiates MaintenanceBillingEngine and calls
markOverdueBills(). Generate the script.
───────────────────────────────────────────────────────────────
SCHEDULED JOB 2 — generate-monthly-bills
───────────────────────────────────────────────────────────────
Name: "Society: Generate Monthly Bills"
Schedule: Monthly, 1st of every month at 06:00 AM
Write a job script that resolves the current month and year into a "MMM-YYYY" string
and passes it to MaintenanceBillingEngine.generateMonthlyBills(). Generate the script.
───────────────────────────────────────────────────────────────
ACLs
───────────────────────────────────────────────────────────────
Apply across all four society tables:
ACL 1 — READ: Any authenticated user can read records.
ACL 2 — CREATE: Any authenticated user can create service_requests and visitor_log entries.
Only committee_member or admin can create maintenance_bill and flat records.
ACL 3 — WRITE: committee_member or admin can update all records.
Residents can only update their own service_request records (opened_by = current user)
when status is still "open".
ACL 4 — DELETE: Only admin can delete any record.
───────────────────────────────────────────────────────────────
NAVIGATION
───────────────────────────────────────────────────────────────
Application menu: "Society Management" — visible to all authenticated users.
Menu items:
1. "Dashboard" order 100 — all service_request records (overview)
2. "My Bills" order 200 — maintenance_bill filtered by current user’s flat
3. "Complaints" order 300 — service_request where request_type = complaint
4. "Facility Booking" order 400 — service_request where request_type = facility_booking
5. "Notices" order 500 — service_request where request_type = notice, pinned first
6. "Visitor Log" order 600 — all visitor_log records
7. "Flat Registry" order 700 — flat table (committee_member/admin role required)
Build & Deploy to ServiceNow
Exit Claude Code (Ctrl+C), then compile and push your app. Two commands — first build to verify no errors, then deploy to go live.
npm run build
✔ Compiled 4 modules ✔ No errors found Build completed in 2.1snpm run deploy
✔ Table: x_society_flat✔ Table: x_society_maintenance_bill✔ Table: x_society_service_request✔ Table: x_society_visitor_log✔ BusinessRule: Set Dates on Insert/Resolve✔ BusinessRule: Prevent Double Booking✔ ScriptInclude: SocietyUtils✔ ScriptInclude: MaintenanceBillingEngine✔ ScheduledJob: Society Mark Overdue Bills✔ ScheduledJob: Society Generate Monthly Bills✔ ACL: x_society (8 rules)✔ AppMenu: Society Management✔ Deployed successfully in 24.3s| Command | What It Does | When to Use |
|---|---|---|
npm run types | Re-fetches type definitions from instance | When schema was changed directly in the ServiceNow browser UI |
npm run transform | Pulls current instance state to local files | To import changes made via Studio — always commit your local work first |
Open your ServiceNow instance in a browser and run through these four checks. Each one confirms a different layer of the app landed correctly.
| Expected Menu Item | What It Opens |
|---|---|
| Dashboard | All service_request records (overview) |
| My Bills | Maintenance bills filtered to current user's flat |
| Complaints | service_request where request_type = complaint |
| Facility Booking | service_request where request_type = facility_booking |
| Notices | service_request where request_type = notice, pinned first |
| Visitor Log | All visitor_log records |
| Flat Registry | flat table (committee_member / admin role required) |
x_society_flat |
x_society_maintenance_bill |
x_society_service_request |
x_society_visitor_logSociety Management System is Live!
Log in to ServiceNow and look for Society Management in the left sidebar. 4 tables, 2 business rules, 2 script includes, 2 scheduled jobs, menus, and ACLs — all live. No more WhatsApp chaos!
| # | Action | What to Verify |
|---|---|---|
| 1 | Open Flat Registry → New → create a flat (e.g. A-101, Block A, Floor 1) | Record saves without errors; flat appears in the list |
| 2 | Open Complaints → New → submit a complaint referencing that flat | opened_on is auto-populated by the business rule — you should not need to fill it in manually |
| 3 | Change the complaint status to resolved and save | resolved_on auto-fills with today’s date — confirms the second branch of the set-dates business rule |
| 4 | Open Facility Booking → New → book the community hall for today, 10:00–14:00 → save. Then create a second booking for the same facility and overlapping time | The second booking is blocked with an error message — confirms the prevent-double-booking business rule is active |
| 5 | Navigate to Scheduled Jobs → find Society: Mark Overdue Bills → click Execute Now | Job runs without error; any maintenance_bill records with a past due date and status = unpaid flip to overdue |
| 6 | Log out and log in as a test user without the committee_member or admin role |
User can see complaints but cannot update records that don’t belong to them — confirms ACL rules are enforced |