packages/opencode/src/bundled-skills/workspace-builder/SKILL.md
This skill should be used when the user asks to "App Engine Studio", "workspace builder", "custom workspace", "AES", "low code", "app development", "studio", or any ServiceNow App Engine Studio development.
npx skillsauth add groeimetai/snow-flow workspace-builderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
App Engine Studio (AES) enables low-code application development with custom workspaces.
Application (sys_scope)
├── Tables & Forms
├── Workflows
├── Workspaces (sys_aw_workspace)
│ ├── Lists
│ ├── Forms
│ └── Dashboards
└── Portals
| Table | Purpose |
| -------------------- | -------------------- |
| sys_scope | Application scope |
| sys_app | Application record |
| sys_aw_workspace | Workspace definition |
| sys_ux_page | UI Builder pages |
| sys_ux_macroponent | Custom components |
// Create scoped application (ES5 ONLY!)
var app = new GlideRecord("sys_scope")
app.initialize()
// Basic info
app.setValue("name", "IT Asset Tracker")
app.setValue("scope", "x_myco_asset_track")
app.setValue("short_description", "Track IT assets across the organization")
app.setValue("version", "1.0.0")
// Vendor
app.setValue("vendor", "My Company")
app.setValue("vendor_prefix", "x_myco")
// License
app.setValue("licensable", true)
app.insert()
// Create table in scoped app (ES5 ONLY!)
function createAppTable(scope, tableDef) {
var table = new GlideRecord("sys_db_object")
table.initialize()
table.setValue("name", scope + "_" + tableDef.name)
table.setValue("label", tableDef.label)
table.setValue("super_class", tableDef.extends || "task")
// Scope assignment
table.setValue("sys_scope", getAppSysId(scope))
// Options
table.setValue("is_extendable", tableDef.extendable || false)
table.setValue("create_access_controls", true)
table.insert()
// Create fields
if (tableDef.fields) {
for (var i = 0; i < tableDef.fields.length; i++) {
createField(scope + "_" + tableDef.name, tableDef.fields[i])
}
}
return table.getUniqueValue()
}
// Example
createAppTable("x_myco_asset_track", {
name: "asset_item",
label: "Asset Item",
extends: "cmdb_ci",
fields: [
{ name: "u_purchase_date", label: "Purchase Date", type: "glide_date" },
{ name: "u_warranty_end", label: "Warranty End", type: "glide_date" },
{ name: "u_assigned_user", label: "Assigned User", type: "reference", reference: "sys_user" },
],
})
// Create workspace (ES5 ONLY!)
var workspace = new GlideRecord("sys_aw_workspace")
workspace.initialize()
workspace.setValue("name", "asset_tracker_workspace")
workspace.setValue("title", "Asset Tracker")
workspace.setValue("description", "Workspace for IT asset management")
// Primary table
workspace.setValue("primary_table", "x_myco_asset_track_asset_item")
// URL
workspace.setValue("url", "asset-tracker")
// Branding
workspace.setValue("icon", "laptop")
workspace.setValue("color", "#2E7D32")
// App scope
workspace.setValue("sys_scope", appScopeSysId)
// Features
workspace.setValue("agent_assist_enabled", false)
workspace.setValue("contextual_side_panel_enabled", true)
workspace.insert()
// Create workspace list (ES5 ONLY!)
function createWorkspaceList(workspaceSysId, listDef) {
var list = new GlideRecord("sys_aw_list")
list.initialize()
list.setValue("workspace", workspaceSysId)
list.setValue("name", listDef.name)
list.setValue("table", listDef.table)
// Filter
list.setValue("filter", listDef.filter || "")
// Columns
list.setValue("columns", listDef.columns.join(","))
// Sorting
if (listDef.orderBy) {
list.setValue("order_by", listDef.orderBy)
list.setValue("order_by_desc", listDef.orderDesc || false)
}
// Grouping
if (listDef.groupBy) {
list.setValue("group_by", listDef.groupBy)
}
list.insert()
return list.getUniqueValue()
}
// Example lists
createWorkspaceList(workspaceSysId, {
name: "My Assets",
table: "x_myco_asset_track_asset_item",
filter: "u_assigned_user=javascript:gs.getUserID()",
columns: ["number", "name", "u_purchase_date", "u_warranty_end", "state"],
})
createWorkspaceList(workspaceSysId, {
name: "Expiring Warranties",
table: "x_myco_asset_track_asset_item",
filter: "u_warranty_endBETWEENjavascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(-30)",
columns: ["number", "name", "u_assigned_user", "u_warranty_end"],
orderBy: "u_warranty_end",
})
// Create UI Builder page (ES5 ONLY!)
// Note: Full page creation typically done via UI Builder
var page = new GlideRecord("sys_ux_page")
page.initialize()
page.setValue("name", "asset_dashboard")
page.setValue("title", "Asset Dashboard")
page.setValue("description", "Dashboard for asset overview")
// Page type
page.setValue("page_type", "workspace")
// Workspace link
page.setValue("workspace", workspaceSysId)
// Scope
page.setValue("sys_scope", appScopeSysId)
page.insert()
// Create custom macroponent definition (ES5 ONLY!)
// Note: Actual components created via UI Builder
var component = new GlideRecord("sys_ux_macroponent")
component.initialize()
component.setValue("name", "asset_summary_card")
component.setValue("label", "Asset Summary Card")
component.setValue("description", "Displays asset summary information")
// Component category
component.setValue("category", "data_visualization")
// Scope
component.setValue("sys_scope", appScopeSysId)
// Properties (inputs)
component.setValue(
"properties",
JSON.stringify([
{ name: "title", type: "string", label: "Card Title" },
{ name: "assetTable", type: "string", label: "Asset Table" },
{ name: "filter", type: "string", label: "Filter" },
]),
)
component.insert()
// Data broker for workspace data (ES5 ONLY!)
// Data brokers provide data to UI Builder pages
var broker = new GlideRecord("sys_ux_data_broker")
broker.initialize()
broker.setValue("name", "asset_stats")
broker.setValue("label", "Asset Statistics")
// Data source type
broker.setValue("type", "script")
// Script to fetch data (ES5 ONLY!)
broker.setValue(
"script",
"(function getData(inputs) {\n" +
" var result = {\n" +
" total: 0,\n" +
" assigned: 0,\n" +
" available: 0,\n" +
" expiring_warranty: 0\n" +
" };\n" +
" \n" +
' var ga = new GlideAggregate("x_myco_asset_track_asset_item");\n' +
' ga.addAggregate("COUNT");\n' +
' ga.groupBy("state");\n' +
" ga.query();\n" +
" \n" +
" while (ga.next()) {\n" +
' var count = parseInt(ga.getAggregate("COUNT"), 10);\n' +
" result.total += count;\n" +
" \n" +
' var state = ga.getValue("state");\n' +
' if (state === "in_use") {\n' +
" result.assigned = count;\n" +
' } else if (state === "available") {\n' +
" result.available = count;\n" +
" }\n" +
" }\n" +
" \n" +
" // Expiring warranties\n" +
' var expiring = new GlideAggregate("x_myco_asset_track_asset_item");\n' +
' expiring.addQuery("u_warranty_end", "BETWEEN", "javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(-30)");\n' +
' expiring.addAggregate("COUNT");\n' +
" expiring.query();\n" +
" \n" +
" if (expiring.next()) {\n" +
' result.expiring_warranty = parseInt(expiring.getAggregate("COUNT"), 10);\n' +
" }\n" +
" \n" +
" return result;\n" +
"})(inputs);",
)
broker.setValue("sys_scope", appScopeSysId)
broker.insert()
// Create update set for app deployment (ES5 ONLY!)
function createAppUpdateSet(appName, description) {
var updateSet = new GlideRecord("sys_update_set")
updateSet.initialize()
updateSet.setValue("name", appName + " - " + new GlideDateTime().getDate())
updateSet.setValue("description", description)
updateSet.setValue("application", getAppSysId(appName))
updateSet.setValue("state", "in progress")
return updateSet.insert()
}
// Prepare app for export (ES5 ONLY!)
function prepareAppExport(appScope) {
// Validate all components
var issues = []
// Check for missing dependencies
var dependency = new GlideRecord("sys_app_dependency")
dependency.addQuery("app.scope", appScope)
dependency.query()
while (dependency.next()) {
if (!isDependencyInstalled(dependency.getValue("dependency"))) {
issues.push("Missing dependency: " + dependency.dependency.getDisplayValue())
}
}
// Validate update sets
var updateSet = new GlideRecord("sys_update_set")
updateSet.addQuery("application.scope", appScope)
updateSet.addQuery("state", "in progress")
updateSet.query()
while (updateSet.next()) {
issues.push("Open update set: " + updateSet.getValue("name"))
}
return {
ready: issues.length === 0,
issues: issues,
}
}
| Tool | Purpose |
| --------------------------------- | -------------------- |
| snow_query_table | Query app components |
| snow_execute_script_with_output | Test app scripts |
| snow_find_artifact | Find configurations |
| snow_update_set_create | Create update sets |
// 1. Query applications
await snow_query_table({
table: "sys_scope",
query: "scopeSTARTSWITHx_",
fields: "name,scope,version,vendor",
})
// 2. Find app tables
await snow_query_table({
table: "sys_db_object",
query: "nameSTARTSWITHx_myco",
fields: "name,label,super_class",
})
// 3. Get workspace configs
await snow_query_table({
table: "sys_aw_workspace",
query: "sys_scope.scopeSTARTSWITHx_",
fields: "name,title,primary_table,url",
})
tools
This skill should be used when the user asks to "create a widget", "build a widget", "service portal widget", "sp_widget", "fix widget", "widget not working", "ng-click not working", or any Service Portal widget development.
development
This skill should be used when the user asks to "create chatbot", "virtual agent", "VA topic", "NLU", "conversation", "chat flow", "topic block", or any ServiceNow Virtual Agent development.
development
This skill should be used when the user asks to "vendor", "supplier", "contract", "procurement", "SLA", "vendor risk", "vendor performance", or any ServiceNow Vendor Management development.
development
This skill should be used when the user asks to "update set", "create update set", "change tracking", "create something", "deploy", "make changes", "develop", "build a feature", or any ServiceNow development that requires change tracking.