Service Catalog Development for ServiceNow
The Service Catalog allows users to request services and items through a self-service portal.
Catalog Components
| Component | Purpose | Example |
|---|---|---|
| Catalog | Container for categories | IT Service Catalog |
| Category | Group of items | Hardware, Software |
| Item | Requestable service | New Laptop Request |
| Variable | Form field on item | Laptop Model dropdown |
| Variable Set | Reusable variable group | User Details |
| Producer | Creates records directly | Report an Incident |
| Order Guide | Multi-item wizard | New Employee Setup |
Catalog Item Structure
code
Catalog Item: Request New Laptop
├── Variables
│ ├── laptop_model (Reference: cmdb_model)
│ ├── reason (Multi-line text)
│ └── urgency (Choice: Low, Medium, High)
├── Variable Sets
│ └── Delivery Information (Address, Contact)
├── Catalog Client Scripts
│ ├── onLoad: Set defaults
│ └── onChange: Update price
├── Workflows/Flows
│ └── Laptop Approval Flow
└── Fulfillment
└── Creates Task for IT
Variable Types
| Type | Use Case | Example |
|---|---|---|
| Single Line Text | Short input | Employee ID |
| Multi Line Text | Long input | Business Justification |
| Select Box | Single choice | Priority |
| Check Box | Yes/No | Express Delivery |
| Reference | Link to table | Requested For |
| Date | Date picker | Needed By Date |
| Lookup Select Box | Filtered reference | Model by Category |
| List Collector | Multiple selections | CC Recipients |
| Container Start/End | Visual grouping | Hardware Options |
| Macro | Custom widget | Cost Calculator |
Creating Catalog Variables
Basic Variable
javascript
// Via MCP
snow_create_catalog_variable({
catalog_item: "laptop_request",
name: "laptop_model",
type: "reference",
reference: "cmdb_model",
reference_qual: "category=computer",
mandatory: true,
order: 100,
})
Variable with Dynamic Default
javascript
// Variable: requested_for // Type: Reference (sys_user) // Default value (script): javascript: gs.getUserID()
Variable with Reference Qualifier
javascript
// Variable: assignment_group // Type: Reference (sys_user_group) // Reference Qualifier: // Simple: active=true^type=it // Dynamic (Script): javascript: 'active=true^manager=' + gs.getUserID() // Advanced (using current variables): javascript: 'u_department=' + current.variables.department
Catalog Client Scripts
Set Defaults onLoad
javascript
function onLoad() {
// Set default values
g_form.setValue("urgency", "low")
// Hide admin-only fields
if (!g_user.hasRole("catalog_admin")) {
g_form.setDisplay("cost_center", false)
}
// Set default date to tomorrow
var tomorrow = new GlideDateTime()
tomorrow.addDays(1)
g_form.setValue("needed_by", tomorrow.getDate().getValue())
}
Dynamic Pricing onChange
javascript
function onChange(control, oldValue, newValue, isLoading) {
if (isLoading) return
// Get price from selected model
var ga = new GlideAjax("CatalogUtils")
ga.addParam("sysparm_name", "getModelPrice")
ga.addParam("sysparm_model", newValue)
ga.getXMLAnswer(function (price) {
g_form.setValue("item_price", price)
updateTotal()
})
}
function updateTotal() {
var price = parseFloat(g_form.getValue("item_price")) || 0
var quantity = parseInt(g_form.getValue("quantity")) || 1
g_form.setValue("total_cost", (price * quantity).toFixed(2))
}
Validation onSubmit
javascript
function onSubmit() {
// Validate business justification for high-cost items
var cost = parseFloat(g_form.getValue("total_cost"))
var justification = g_form.getValue("business_justification")
if (cost > 1000 && !justification) {
g_form.showFieldMsg("business_justification", "Required for items over $1000", "error")
return false
}
// Validate date is in future
var neededBy = g_form.getValue("needed_by")
var today = new GlideDateTime().getDate().getValue()
if (neededBy < today) {
g_form.showFieldMsg("needed_by", "Date must be in the future", "error")
return false
}
return true
}
Variable Sets
Creating Reusable Variable Sets
code
Variable Set: User Contact Information ├── contact_name (Single Line Text) ├── contact_email (Email) ├── contact_phone (Single Line Text) └── preferred_contact (Choice: Email, Phone, Either) Use in multiple catalog items: - New Laptop Request - Software Installation - Network Access Request
Accessing Variable Set Values
javascript
// In workflow or script var ritm = current // sc_req_item // Access variable from variable set var contactEmail = ritm.variables.contact_email var preferredContact = ritm.variables.preferred_contact
Catalog Workflows/Flows
Approval Pattern
code
Flow Trigger: sc_req_item created ├── If: Total cost > $5000 │ └── Request Approval: Department Manager │ └── If: Rejected │ └── Update: RITM state = Closed Incomplete ├── If: Total cost > $25000 │ └── Request Approval: VP ├── Create: Catalog Task for Fulfillment └── Wait: Task completion
Fulfillment Script
javascript
// In catalog item's "Execution Plan" or workflow
var ritm = current // sc_req_item
// Create an incident from catalog request
var inc = new GlideRecord("incident")
inc.initialize()
inc.setValue("short_description", ritm.short_description)
inc.setValue("description", ritm.description)
inc.setValue("caller_id", ritm.request.requested_for)
inc.setValue("category", ritm.variables.category)
inc.setValue("priority", ritm.variables.urgency)
inc.insert()
// Link incident to request
ritm.setValue("u_fulfillment_record", inc.getUniqueValue())
ritm.update()
Record Producers
Creating Incidents via Catalog
javascript
// Record Producer: Report an Issue
// Table: incident
// Script:
// Map variables to incident fields
current.short_description = producer.short_description
current.description = producer.description
current.caller_id = gs.getUserID()
current.category = producer.category
current.subcategory = producer.subcategory
current.priority = producer.urgency == "urgent" ? "2" : "3"
// Set assignment based on category
if (producer.category == "network") {
current.assignment_group.setDisplayValue("Network Support")
} else {
current.assignment_group.setDisplayValue("Service Desk")
}
Order Guides
Multi-Step Request Wizard
code
Order Guide: New Employee Onboarding ├── Step 1: Employee Information │ └── Variable Set: Employee Details ├── Step 2: Hardware Selection │ ├── Catalog Item: Laptop │ ├── Catalog Item: Monitor │ └── Catalog Item: Peripherals ├── Step 3: Software Requests │ └── Rule: Show software based on department ├── Step 4: Access Requests │ └── Cascade Variable: Copy employee info └── Submit: Creates multiple RITMs
Order Guide Rule
javascript
// Rule: Show software items based on department
function rule(item, guide_variables) {
var dept = guide_variables.department
// Show engineering software only for Engineering
if (item.name == "Engineering Software Suite") {
return dept == "engineering"
}
// Show finance software only for Finance
if (item.name == "Financial Tools") {
return dept == "finance"
}
return true // Show all other items
}
Pricing & Approvals
Dynamic Pricing
javascript
// Catalog Item Script (Pricing)
// Runs when item is added to cart
var basePrice = parseFloat(current.price) || 0
var quantity = parseInt(current.variables.quantity) || 1
var expedited = current.variables.expedited == "true"
var total = basePrice * quantity
if (expedited) {
total *= 1.5 // 50% rush fee
}
current.recurring_price = 0
current.price = total
Approval Rules
code
Approval Definition: High-Value Purchases Condition: total_cost > 5000 Approver: requested_for.manager Wait for: Approval Rejection action: Cancel request
Best Practices
- •Variable Naming - Use descriptive, lowercase names (no spaces)
- •Variable Sets - Reuse common variable groups
- •Reference Qualifiers - Filter to relevant records only
- •Client Scripts - Minimize server calls (use GlideAjax sparingly)
- •Fulfillment - Create tasks, don't complete directly
- •Testing - Test as different user roles
- •Mobile - Test catalog items on mobile/tablet
- •Documentation - Add help text to variables