mobile-development
SKILL.md
Mobile Development for ServiceNow
Mobile development enables native mobile experiences with offline capabilities.
Mobile Architecture
Mobile App Configuration
├── App Screens
│ ├── List Views
│ ├── Detail Views
│ └── Card Builders
├── Push Notifications
├── Offline Rules
└── Mobile Actions
Key Tables
| Table | Purpose |
|---|---|
sys_sg_mobile_app |
Mobile app configs |
sys_sg_screen |
Mobile screens |
sys_sg_card_builder |
Card builders |
sys_push_notification |
Push configs |
sys_sg_offline_rule |
Offline rules |
Mobile App Configuration (ES5)
Create Mobile App
// Create mobile app configuration (ES5 ONLY!)
var app = new GlideRecord("sys_sg_mobile_app")
app.initialize()
app.setValue("name", "IT Support")
app.setValue("description", "Mobile app for IT support tasks")
// App settings
app.setValue("active", true)
app.setValue("version", "1.0.0")
// Branding
app.setValue("primary_color", "#1976D2")
app.setValue("secondary_color", "#FFFFFF")
app.setValue("icon", "attachment_sys_id")
// Default screen
app.setValue("home_screen", homeScreenSysId)
// Roles
app.setValue("roles", "itil")
app.insert()
Configure Mobile Screen
// Create mobile screen (ES5 ONLY!)
var screen = new GlideRecord("sys_sg_screen")
screen.initialize()
screen.setValue("name", "My Incidents")
screen.setValue("mobile_app", mobileAppSysId)
screen.setValue("type", "list") // list, record, custom
// Data source
screen.setValue("table", "incident")
screen.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")
// Display
screen.setValue("title", "My Incidents")
screen.setValue("icon", "list")
// Ordering
screen.setValue("order", 100)
screen.insert()
Card Builder (ES5)
Create Card Configuration
// Create card builder for list display (ES5 ONLY!)
var card = new GlideRecord("sys_sg_card_builder")
card.initialize()
card.setValue("name", "Incident Card")
card.setValue("table", "incident")
// Card layout
card.setValue("primary_field", "number")
card.setValue("secondary_field", "short_description")
card.setValue("tertiary_field", "priority")
// Additional fields
card.setValue(
"fields",
JSON.stringify([
{ field: "caller_id", label: "Caller" },
{ field: "state", label: "Status" },
{ field: "opened_at", label: "Opened" },
]),
)
// Visual indicators
card.setValue("color_field", "priority")
card.setValue(
"color_mapping",
JSON.stringify({
1: "#D32F2F", // Critical - Red
2: "#F57C00", // High - Orange
3: "#FBC02D", // Moderate - Yellow
4: "#388E3C", // Low - Green
5: "#1976D2", // Planning - Blue
}),
)
card.insert()
Custom Card Actions
// Add actions to card (ES5 ONLY!)
function addCardAction(cardSysId, actionDef) {
var action = new GlideRecord("sys_sg_card_action")
action.initialize()
action.setValue("card_builder", cardSysId)
action.setValue("label", actionDef.label)
action.setValue("icon", actionDef.icon)
action.setValue("order", actionDef.order)
// Action type
action.setValue("action_type", actionDef.type) // script, navigate, share
// Script action (ES5 ONLY!)
if (actionDef.type === "script") {
action.setValue("script", actionDef.script)
}
action.insert()
}
// Example actions
addCardAction(cardSysId, {
label: "Acknowledge",
icon: "check",
order: 100,
type: "script",
script:
"(function(gr) {\n" +
" gr.state = 2; // In Progress\n" +
' gr.work_notes = "Acknowledged via mobile";\n' +
" gr.update();\n" +
' gs.addInfoMessage("Incident acknowledged");\n' +
"})(current);",
})
Push Notifications (ES5)
Configure Push Notification
// Create push notification config (ES5 ONLY!)
var push = new GlideRecord("sys_push_notification")
push.initialize()
push.setValue("name", "High Priority Incident Assigned")
push.setValue("description", "Notify when high priority incident assigned")
// Target table and condition
push.setValue("table", "incident")
push.setValue("condition", "priority<=2^assigned_to.changes()")
// Notification content
push.setValue("title", "High Priority Incident Assigned")
push.setValue("body", "${number}: ${short_description}")
// Recipients
push.setValue("recipient_type", "field")
push.setValue("recipient_field", "assigned_to")
// Deep link
push.setValue("deep_link", true)
push.setValue("link_url", "/incident/${sys_id}")
push.setValue("active", true)
push.insert()
Send Push Notification Programmatically
// Send push notification (ES5 ONLY!)
function sendPushNotification(userSysId, message) {
try {
var push = new sn_notification.PushNotification()
push.setTitle(message.title)
push.setBody(message.body)
if (message.data) {
push.setData(message.data)
}
if (message.deepLink) {
push.setDeepLink(message.deepLink)
}
push.send(userSysId)
return { success: true }
} catch (e) {
gs.error("Push notification failed: " + e.message)
return { success: false, error: e.message }
}
}
// Example
sendPushNotification(userSysId, {
title: "Task Assigned",
body: "You have a new task assigned",
deepLink: "/task/" + taskSysId,
})
Offline Capabilities (ES5)
Configure Offline Rules
// Create offline sync rule (ES5 ONLY!)
var rule = new GlideRecord("sys_sg_offline_rule")
rule.initialize()
rule.setValue("name", "My Open Incidents")
rule.setValue("mobile_app", mobileAppSysId)
rule.setValue("table", "incident")
// Sync filter
rule.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")
// Fields to sync
rule.setValue("fields", "number,short_description,description,priority,state,caller_id,opened_at")
// Related records
rule.setValue("include_references", true)
rule.setValue("reference_fields", "caller_id,assignment_group")
// Sync limits
rule.setValue("max_records", 100)
// Update frequency
rule.setValue("sync_frequency", "on_demand") // on_demand, periodic
rule.setValue("active", true)
rule.insert()
Handle Offline Data
// Check for offline changes on sync (ES5 ONLY!)
function processOfflineChanges(userId) {
var offlineQueue = new GlideRecord("sys_sg_offline_queue")
offlineQueue.addQuery("user", userId)
offlineQueue.addQuery("processed", false)
offlineQueue.orderBy("created_on")
offlineQueue.query()
var results = { processed: 0, errors: [] }
while (offlineQueue.next()) {
try {
var tableName = offlineQueue.getValue("table")
var recordSysId = offlineQueue.getValue("record")
var changes = JSON.parse(offlineQueue.getValue("changes"))
// Apply changes
var gr = new GlideRecord(tableName)
if (gr.get(recordSysId)) {
for (var field in changes) {
if (changes.hasOwnProperty(field)) {
gr.setValue(field, changes[field])
}
}
gr.update()
results.processed++
}
// Mark as processed
offlineQueue.processed = true
offlineQueue.update()
} catch (e) {
results.errors.push({
record: offlineQueue.getValue("record"),
error: e.message,
})
}
}
return results
}
Mobile Actions (ES5)
Create Mobile Action
// Create mobile-specific action (ES5 ONLY!)
var action = new GlideRecord("sys_sg_action")
action.initialize()
action.setValue("name", "Scan Barcode")
action.setValue("label", "Scan Asset")
action.setValue("description", "Scan barcode to find asset")
// Action type
action.setValue("type", "native") // native, script, link
action.setValue("native_action", "barcode_scan")
// Available on
action.setValue("screens", screenSysIds)
// Callback script (ES5 ONLY!)
action.setValue(
"callback_script",
"(function(result) {\n" +
" if (!result.value) return;\n" +
" \n" +
' var asset = new GlideRecord("alm_asset");\n' +
' asset.addQuery("asset_tag", result.value);\n' +
" asset.query();\n" +
" \n" +
" if (asset.next()) {\n" +
" // Navigate to asset\n" +
' sn_mobile.navigate("record", {\n' +
' table: "alm_asset",\n' +
" sys_id: asset.getUniqueValue()\n" +
" });\n" +
" } else {\n" +
' gs.addErrorMessage("Asset not found: " + result.value);\n' +
" }\n" +
"})(scanResult);",
)
action.insert()
Location-Based Action
// Get user location for mobile (ES5 ONLY!)
// Available in mobile context
function getCurrentLocation() {
try {
var location = sn_mobile.getLocation()
return {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
}
} catch (e) {
return null
}
}
// Use location for nearby assets
function findNearbyAssets(latitude, longitude, radiusMeters) {
var assets = []
var gr = new GlideRecord("alm_asset")
gr.addNotNullQuery("location.latitude")
gr.query()
while (gr.next()) {
var assetLat = parseFloat(gr.location.latitude)
var assetLon = parseFloat(gr.location.longitude)
var distance = calculateDistance(latitude, longitude, assetLat, assetLon)
if (distance <= radiusMeters) {
assets.push({
sys_id: gr.getUniqueValue(),
name: gr.getDisplayValue(),
distance: Math.round(distance),
})
}
}
return assets.sort(function (a, b) {
return a.distance - b.distance
})
}
MCP Tool Integration
Available Tools
| Tool | Purpose |
|---|---|
snow_query_table |
Query mobile configs |
snow_execute_script_with_output |
Test mobile scripts |
snow_find_artifact |
Find configurations |
Example Workflow
// 1. Query mobile apps
await snow_query_table({
table: "sys_sg_mobile_app",
query: "active=true",
fields: "name,description,version,roles",
})
// 2. Get push notification configs
await snow_query_table({
table: "sys_push_notification",
query: "active=true",
fields: "name,table,condition,title",
})
// 3. Check offline rules
await snow_query_table({
table: "sys_sg_offline_rule",
query: "active=true",
fields: "name,table,filter,max_records",
})
Best Practices
- Offline First - Design for connectivity issues
- Minimal Data - Sync only necessary fields
- Push Wisely - Don't overwhelm with notifications
- Native Features - Use camera, GPS, barcode
- Card Design - Key info at a glance
- Performance - Optimize for mobile
- Testing - Test on actual devices
- ES5 Only - No modern JavaScript syntax
Weekly Installs
49
Repository
groeimetai/snow-flowGitHub Stars
53
First Seen
Jan 22, 2026
Security Audits
Installed on
gemini-cli45
claude-code45
github-copilot44
codex44
opencode44
cursor44