scoped-apps
SKILL.md
Scoped Application Development for ServiceNow
Scoped applications provide isolation and portability for custom development in ServiceNow.
Why Use Scoped Apps?
| Feature | Global Scope | Scoped App |
|---|---|---|
| Naming conflicts | Possible | Prevented (x_prefix) |
| Portability | Difficult | Easy (Update Sets) |
| Security | Open | Controlled (Cross-scope) |
| Store publishing | No | Yes |
| Dependencies | Implicit | Explicit |
Creating a Scoped Application
Via Studio (Recommended)
1. Navigate: System Applications > Studio
2. Click: Create Application
3. Enter:
- Name: "My Custom App"
- Scope: "x_mycom_myapp" (auto-generated)
- Version: 1.0.0
4. Configure:
- Runtime access: Check tables needing cross-scope access
Via MCP
snow_create_application({
name: "My Custom Application",
scope: "x_mycom_custom",
version: "1.0.0",
description: "Custom application for...",
})
Scope Naming Convention
x_[vendor]_[app]
Examples:
- x_acme_hr (ACME Corp HR App)
- x_mycom_inventory (My Company Inventory)
- x_snc_global (ServiceNow Global)
Table Naming
// Scoped tables are automatically prefixed
// Table name in Studio: "task_tracker"
// Actual table name: "x_mycom_myapp_task_tracker"
// Creating records
var gr = new GlideRecord("x_mycom_myapp_task_tracker")
gr.initialize()
gr.setValue("name", "My Task")
gr.insert()
Script Include in Scoped App
var TaskManager = Class.create()
TaskManager.prototype = {
initialize: function () {
this.tableName = "x_mycom_myapp_task_tracker"
},
createTask: function (name, description) {
var gr = new GlideRecord(this.tableName)
gr.initialize()
gr.setValue("name", name)
gr.setValue("description", description)
return gr.insert()
},
// Mark as accessible from other scopes
// Requires: "Accessible from: All application scopes"
getTask: function (sysId) {
var gr = new GlideRecord(this.tableName)
if (gr.get(sysId)) {
return {
name: gr.getValue("name"),
description: gr.getValue("description"),
}
}
return null
},
type: "TaskManager",
}
Cross-Scope Access
Calling Other Scope's Script Include
// From scope: x_mycom_otherapp
// Calling: x_mycom_myapp.TaskManager
// Option 1: Direct call (if accessible)
var tm = new x_mycom_myapp.TaskManager()
var task = tm.getTask(sysId)
// Option 2: GlideScopedEvaluator
var evaluator = new GlideScopedEvaluator()
evaluator.putVariable("sysId", sysId)
var result = evaluator.evaluateScript("x_mycom_myapp", "new TaskManager().getTask(sysId)")
Accessing Other Scope's Tables
// Check if cross-scope access is allowed
var gr = new GlideRecord("x_other_app_table")
if (!gr.isValid()) {
gs.error("No access to x_other_app_table")
return
}
// If accessible, query normally
gr.addQuery("active", true)
gr.query()
Application Properties
Define Properties
// In Application > Properties
// Name: x_mycom_myapp.default_priority
// Value: 3
// Type: string
// In Application > Modules
// Create "Properties" module pointing to:
// /sys_properties_list.do?sysparm_query=name=x_mycom_myapp
Use Properties
// Get property value
var defaultPriority = gs.getProperty("x_mycom_myapp.default_priority", "3")
// Set property value (requires admin)
gs.setProperty("x_mycom_myapp.default_priority", "2")
Application Files Structure
x_mycom_myapp/
├── Tables
│ ├── x_mycom_myapp_task
│ └── x_mycom_myapp_config
├── Script Includes
│ ├── TaskManager
│ └── ConfigUtils
├── Business Rules
│ └── Validate Task
├── UI Pages
│ └── task_dashboard
├── REST API
│ └── Task API
├── Scheduled Jobs
│ └── Daily Cleanup
└── Application Properties
├── default_priority
└── enable_notifications
REST API in Scoped App
Define Scripted REST API
// Resource: /api/x_mycom_myapp/tasks
// HTTP Method: GET
;(function process(request, response) {
var tasks = []
var gr = new GlideRecord("x_mycom_myapp_task_tracker")
gr.addQuery("active", true)
gr.query()
while (gr.next()) {
tasks.push({
sys_id: gr.getUniqueValue(),
name: gr.getValue("name"),
status: gr.getValue("status"),
})
}
response.setBody({
result: tasks,
count: tasks.length,
})
})(request, response)
Calling the API
curl -X GET \
"https://instance.service-now.com/api/x_mycom_myapp/tasks" \
-H "Authorization: Bearer token"
Application Dependencies
Declare Dependencies
Application > Dependencies
Add:
- sn_hr_core (HR Core)
- sn_cmdb (CMDB)
Check Dependencies in Code
// Check if plugin is active
if (GlidePluginManager.isActive("com.snc.hr.core")) {
// HR Core is available
var hrCase = new sn_hr_core.hr_case()
}
Publishing to Store
Checklist Before Publishing
□ All tables have proper ACLs
□ No hard-coded sys_ids
□ No hard-coded instance URLs
□ All dependencies declared
□ Properties have default values
□ Documentation complete
□ Test cases pass
□ No global scope modifications
□ Update Set tested on clean instance
Version Management
Major.Minor.Patch
1.0.0 - Initial release
1.1.0 - New feature added
1.1.1 - Bug fix
2.0.0 - Breaking change
Common Mistakes
| Mistake | Problem | Solution |
|---|---|---|
| Global modifications | Won't deploy cleanly | Keep changes in scope |
| Hard-coded sys_ids | Fails on other instances | Use properties or lookups |
| Missing ACLs | Security vulnerabilities | Create ACLs for all tables |
| No error handling | Silent failures | Add try/catch, logging |
| Accessing global tables directly | Upgrade conflicts | Use references, not copies |
Best Practices
- Single Responsibility - One app per business function
- Explicit Dependencies - Declare all requirements
- Property-Driven - Configurable without code changes
- Defensive Coding - Check access before operations
- Documentation - Include README, release notes
- Testing - Automated tests for critical functions
- Versioning - Semantic versioning for updates
Weekly Installs
48
Repository
groeimetai/snow-flowGitHub Stars
53
First Seen
Jan 22, 2026
Security Audits
Installed on
claude-code44
github-copilot43
gemini-cli43
opencode43
codex43
cursor42