| name | mobile-development |
| description | Configure ServiceNow Mobile (Now Mobile/Agent) — sys_sg_mobile_app screens, card builders, push notifications, offline sync rules, mobile actions for barcode/GPS, and offline-queue change processing. |
| license | Apache-2.0 |
| compatibility | Designed for Snow-Code and ServiceNow development |
| metadata | {"author":"serac","version":"1.0.0","category":"servicenow"} |
| tools | ["snow_query_table","snow_execute_script","snow_artifact_manage"] |
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
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.setValue("active", true)
app.setValue("version", "1.0.0")
app.setValue("primary_color", "#1976D2")
app.setValue("secondary_color", "#FFFFFF")
app.setValue("icon", "attachment_sys_id")
app.setValue("home_screen", homeScreenSysId)
app.setValue("roles", "itil")
app.insert()
Configure Mobile Screen
var screen = new GlideRecord("sys_sg_screen")
screen.initialize()
screen.setValue("name", "My Incidents")
screen.setValue("mobile_app", mobileAppSysId)
screen.setValue("type", "list")
screen.setValue("table", "incident")
screen.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")
screen.setValue("title", "My Incidents")
screen.setValue("icon", "list")
screen.setValue("order", 100)
screen.insert()
Card Builder (ES5)
Create Card Configuration
var card = new GlideRecord("sys_sg_card_builder")
card.initialize()
card.setValue("name", "Incident Card")
card.setValue("table", "incident")
card.setValue("primary_field", "number")
card.setValue("secondary_field", "short_description")
card.setValue("tertiary_field", "priority")
card.setValue(
"fields",
JSON.stringify([
{ field: "caller_id", label: "Caller" },
{ field: "state", label: "Status" },
{ field: "opened_at", label: "Opened" },
]),
)
card.setValue("color_field", "priority")
card.setValue(
"color_mapping",
JSON.stringify({
1: "#D32F2F",
2: "#F57C00",
3: "#FBC02D",
4: "#388E3C",
5: "#1976D2",
}),
)
card.insert()
Custom Card Actions
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.setValue("action_type", actionDef.type)
if (actionDef.type === "script") {
action.setValue("script", actionDef.script)
}
action.insert()
}
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
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")
push.setValue("table", "incident")
push.setValue("condition", "priority<=2^assigned_to.changes()")
push.setValue("title", "High Priority Incident Assigned")
push.setValue("body", "${number}: ${short_description}")
push.setValue("recipient_type", "field")
push.setValue("recipient_field", "assigned_to")
push.setValue("deep_link", true)
push.setValue("link_url", "/incident/${sys_id}")
push.setValue("active", true)
push.insert()
Send Push Notification Programmatically
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 }
}
}
sendPushNotification(userSysId, {
title: "Task Assigned",
body: "You have a new task assigned",
deepLink: "/task/" + taskSysId,
})
Offline Capabilities (ES5)
Configure Offline Rules
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")
rule.setValue("filter", "assigned_to=javascript:gs.getUserID()^active=true")
rule.setValue("fields", "number,short_description,description,priority,state,caller_id,opened_at")
rule.setValue("include_references", true)
rule.setValue("reference_fields", "caller_id,assignment_group")
rule.setValue("max_records", 100)
rule.setValue("sync_frequency", "on_demand")
rule.setValue("active", true)
rule.insert()
Handle Offline Data
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"))
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++
}
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
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.setValue("type", "native")
action.setValue("native_action", "barcode_scan")
action.setValue("screens", screenSysIds)
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
function getCurrentLocation() {
try {
var location = sn_mobile.getLocation()
return {
latitude: location.latitude,
longitude: location.longitude,
accuracy: location.accuracy,
}
} catch (e) {
return null
}
}
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 | Test mobile scripts |
snow_artifact_manage (action='find') | Find configurations |
Example Workflow
await snow_query_table({
table: "sys_sg_mobile_app",
query: "active=true",
fields: "name,description,version,roles",
})
await snow_query_table({
table: "sys_push_notification",
query: "active=true",
fields: "name,table,condition,title",
})
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