Skip to content
domain-separation logo

Domain Separation for ServiceNow

domain-separation

Configure ServiceNow multi-tenant domain separation — domain hierarchy, sys_user_has_domain membership, domain-aware vs cross-domain queries, MSP tenant onboarding, and domain picker logic.

groeimetai/snow-flow0installs78stars

SKILL.md

Full skill instructions

Domain Separation for ServiceNow

Domain Separation enables multi-tenancy by partitioning data and processes between domains.

Domain Architecture

TOP (Global)
    ├── Domain A (Customer 1)
    │   ├── Sub-domain A1
    │   └── Sub-domain A2
    └── Domain B (Customer 2)
        └── Sub-domain B1

Key Tables

TablePurpose
domainDomain definitions
sys_user_has_domainUser domain membership
domain_pathDomain hierarchy paths
sys_db_objectTable domain settings

Domain Configuration (ES5)

Create Domain

// Create domain (ES5 ONLY!)
var domain = new GlideRecord("domain")
domain.initialize()

domain.setValue("name", "Acme Corp")
domain.setValue("description", "Domain for Acme Corporation")

// Parent domain (empty for top-level)
domain.setValue("parent", parentDomainSysId)

// Domain visibility
domain.setValue("active", true)

domain.insert()

Domain-Aware Queries

// Query respecting domain separation (ES5 ONLY!)
function getDomainAwareRecords(tableName, query) {
  var gr = new GlideRecord(tableName)

  // Domain separation is automatic when enabled
  // Records are filtered to user's visible domains

  if (query) {
    gr.addEncodedQuery(query)
  }
  gr.query()

  var records = []
  while (gr.next()) {
    records.push({
      sys_id: gr.getUniqueValue(),
      sys_domain: gr.getValue("sys_domain"),
      sys_domain_path: gr.getValue("sys_domain_path"),
    })
  }

  return records
}

Cross-Domain Access

// Access records across domains (requires elevated privileges) (ES5 ONLY!)
function getCrossdomainRecords(tableName) {
  var gr = new GlideRecord(tableName)

  // Disable domain separation for this query
  gr.setQueryReferences(false)

  // Query all domains
  gr.queryNoDomain()

  var records = []
  while (gr.next()) {
    records.push({
      sys_id: gr.getUniqueValue(),
      domain: gr.sys_domain.getDisplayValue(),
    })
  }

  return records
}

User Domain Membership (ES5)

Assign User to Domain

// Add user to domain (ES5 ONLY!)
function addUserToDomain(userSysId, domainSysId, isPrimary) {
  // Check if already assigned
  var existing = new GlideRecord("sys_user_has_domain")
  existing.addQuery("user", userSysId)
  existing.addQuery("domain", domainSysId)
  existing.query()

  if (existing.next()) {
    return existing.getUniqueValue()
  }

  // Create assignment
  var assignment = new GlideRecord("sys_user_has_domain")
  assignment.initialize()
  assignment.setValue("user", userSysId)
  assignment.setValue("domain", domainSysId)
  assignment.setValue("primary", isPrimary)
  return assignment.insert()
}

Get User's Domains

// Get domains accessible to user (ES5 ONLY!)
function getUserDomains(userSysId) {
  var domains = []

  var membership = new GlideRecord("sys_user_has_domain")
  membership.addQuery("user", userSysId)
  membership.query()

  while (membership.next()) {
    var domain = membership.domain.getRefRecord()
    domains.push({
      sys_id: domain.getUniqueValue(),
      name: domain.getValue("name"),
      is_primary: membership.getValue("primary") === "true",
    })
  }

  return domains
}

Domain-Separated Tables (ES5)

Configure Table for Domain Separation

// Enable domain separation on table (ES5 ONLY!)
// Note: This is typically done via UI, shown for reference

var tableConfig = new GlideRecord("sys_db_object")
if (tableConfig.get("name", "u_custom_table")) {
  // Enable domain separation
  tableConfig.setValue("domain_separated", true)

  // Domain separation type
  // 'simple' = records belong to one domain
  // 'containment' = records visible to parent domains
  tableConfig.setValue("domain_id_type", "simple")

  tableConfig.update()
}

Create Record in Specific Domain

// Create record in specific domain (ES5 ONLY!)
function createInDomain(tableName, data, domainSysId) {
  var gr = new GlideRecord(tableName)
  gr.initialize()

  // Set field values
  for (var field in data) {
    if (data.hasOwnProperty(field)) {
      gr.setValue(field, data[field])
    }
  }

  // Set domain
  gr.setValue("sys_domain", domainSysId)

  return gr.insert()
}

Domain Picker (ES5)

Get Available Domains for Picker

// Get domains for domain picker widget (ES5 ONLY!)
function getDomainsForPicker() {
  var domains = []
  var userId = gs.getUserID()

  // Get user's accessible domains
  var membership = new GlideRecord("sys_user_has_domain")
  membership.addQuery("user", userId)
  membership.query()

  while (membership.next()) {
    var domain = membership.domain.getRefRecord()
    if (domain.getValue("active") === "true") {
      domains.push({
        sys_id: domain.getUniqueValue(),
        name: domain.getValue("name"),
        is_primary: membership.getValue("primary") === "true",
        is_current: domain.getUniqueValue() === gs.getSession().getCurrentDomainID(),
      })
    }
  }

  // Sort: primary first, then alphabetically
  domains.sort(function (a, b) {
    if (a.is_primary && !b.is_primary) return -1
    if (!a.is_primary && b.is_primary) return 1
    return a.name.localeCompare(b.name)
  })

  return domains
}

Switch Current Domain

// Switch user's current domain (ES5 ONLY!)
function switchDomain(domainSysId) {
  var session = gs.getSession()

  // Verify user has access
  var membership = new GlideRecord("sys_user_has_domain")
  membership.addQuery("user", gs.getUserID())
  membership.addQuery("domain", domainSysId)
  membership.query()

  if (!membership.next()) {
    gs.addErrorMessage("You do not have access to this domain")
    return false
  }

  // Switch domain
  session.setDomainID(domainSysId)
  gs.addInfoMessage("Switched to domain: " + membership.domain.getDisplayValue())

  return true
}

Domain Visibility Rules (ES5)

Check Domain Visibility

// Check if record is visible in current domain (ES5 ONLY!)
function isRecordVisibleInDomain(tableName, recordSysId) {
  var gr = new GlideRecord(tableName)
  gr.addQuery("sys_id", recordSysId)
  gr.query()

  // If record is found, it's visible in current domain context
  return gr.hasNext()
}

Get Domain Path

// Get full domain hierarchy path (ES5 ONLY!)
function getDomainPath(domainSysId) {
  var path = []

  var domain = new GlideRecord("domain")
  if (!domain.get(domainSysId)) {
    return path
  }

  // Build path from current to root
  while (domain.isValidRecord()) {
    path.unshift({
      sys_id: domain.getUniqueValue(),
      name: domain.getValue("name"),
    })

    if (!domain.parent) break
    domain = domain.parent.getRefRecord()
  }

  return path
}

MSP/​Managed Services Patterns (ES5)

Onboard New Tenant

// Create new tenant domain with initial setup (ES5 ONLY!)
function onboardTenant(tenantData) {
  // Create domain
  var domain = new GlideRecord("domain")
  domain.initialize()
  domain.setValue("name", tenantData.name)
  domain.setValue("parent", tenantData.parentDomain || "")
  var domainSysId = domain.insert()

  // Create tenant admin user
  var adminUser = new GlideRecord("sys_user")
  adminUser.initialize()
  adminUser.setValue("user_name", tenantData.adminEmail)
  adminUser.setValue("email", tenantData.adminEmail)
  adminUser.setValue("first_name", tenantData.adminFirstName)
  adminUser.setValue("last_name", tenantData.adminLastName)
  var adminSysId = adminUser.insert()

  // Assign user to domain
  addUserToDomain(adminSysId, domainSysId, true)

  // Assign tenant admin role
  var role = new GlideRecord("sys_user_has_role")
  role.initialize()
  role.setValue("user", adminSysId)
  role.setValue("role", getTenantAdminRoleSysId())
  role.insert()

  return {
    domain_sys_id: domainSysId,
    admin_sys_id: adminSysId,
  }
}

MCP Tool Integration

Available Tools

ToolPurpose
snow_query_tableQuery domain-aware data
snow_execute_script_with_outputTest domain scripts
snow_find_artifactFind domain configurations

Example Workflow

// 1. Query domains
await snow_query_table({
  table: "domain",
  query: "active=true",
  fields: "name,parent,sys_id",
})

// 2. Get user domain memberships
await snow_query_table({
  table: "sys_user_has_domain",
  query: "user=user_sys_id",
  fields: "domain,primary",
})

// 3. Check domain-separated tables
await snow_query_table({
  table: "sys_db_object",
  query: "domain_separated=true",
  fields: "name,label,domain_id_type",
})

Best Practices

  1. Plan Hierarchy - Design domain structure before implementation
  2. Minimal Domains - Only create necessary separation
  3. User Access - Assign minimum required domains
  4. Testing - Test with domain picker
  5. Global Data - Keep shared data in TOP domain
  6. Performance - Domain queries add overhead
  7. Documentation - Document domain purposes
  8. ES5 Only - No modern JavaScript syntax

More skills from groeimetai

reporting-dashboards logo
groeimetai/snow-flow

reporting-dashboards

Build ServiceNow reports (list/bar/pie/line/pivot/single-score) on sys_report, sys_dashboard layouts with widget placement, scheduled report delivery, and drill-down configuration.

78 0
View
rest-integration logo
groeimetai/snow-flow

rest-integration

Make outbound REST calls from ServiceNow with sn_ws.RESTMessageV2 — basic/bearer/OAuth/API-key auth, error handling by status code, response parsing, sys_rest_message records, and retry with exponential backoff.

78 0
View
mcp-tool-discovery logo
groeimetai/snow-flow

mcp-tool-discovery

Use tool_search to discover and enable lazy-loaded Serac MCP tools (snow_*, jira_*, github_*, etc). Explains naming conventions, search strategies, and always-available activity tools.

78 0
View
atf-testing logo
groeimetai/snow-flow

atf-testing

Build ServiceNow Automated Test Framework tests and suites — impersonation, form steps, assertions, server-side script steps, test parameters, and execution via snow_create_atf_test / snow_execute_atf_test.

78 0
View
import-export logo
groeimetai/snow-flow

import-export

Move data in and out of ServiceNow — CSV parsing into import sets, GlideImportSetTransformer runs, CSV/JSON/XML exports, scheduled data sources, bulk update and deleteMultiple safety patterns.

78 0
View
discovery-patterns logo
groeimetai/snow-flow

discovery-patterns

Configure ServiceNow Discovery — schedules, IP ranges, credential affinities, MID Server assignment, custom probes/sensors, identification rules, and run-status monitoring on discovery_* tables.

78 0
View
csm-patterns logo
groeimetai/snow-flow

csm-patterns

Build ServiceNow Customer Service Management — customer_account, customer_contact, sn_customerservice_case routing, service entitlements with usage decrement, and Customer Portal case submission widgets.

78 0
View
data-policies logo
groeimetai/snow-flow

data-policies

Manage ServiceNow dictionary (sys_dictionary), table/field creation, choice lists, dictionary overrides, and sys_data_policy2 rules that enforce mandatory/read-only/visible field behavior on the data layer.

78 0
View
mobile-development logo
groeimetai/snow-flow

mobile-development

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.

78 0
View
email-notifications logo
groeimetai/snow-flow

email-notifications

Create ServiceNow sysevent_email_action notifications — templates with substitution variables, mail scripts for dynamic content/recipients/attachments, custom events, weights, and digest configuration.

78 0
View
es5-compliance logo
groeimetai/snow-flow

es5-compliance

Enforce ES5-only syntax for ServiceNow server-side scripts (Rhino engine) — convert const/let, arrow functions, template literals, destructuring, for-of, and async/await to ES5 equivalents.

78 0
View
change-management logo
groeimetai/snow-flow

change-management

Create and transition ServiceNow change requests (normal/standard/emergency), change tasks, affected CIs, approval routing, CAB scheduling, and conflict detection across maintenance windows.

78 0
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View