AS
AgSkills.dev
MARKETPLACE

domain-separation

This skill should be used when the user asks to "domain separation", "multi-tenant", "domain path", "domain visibility", "domain picker", "MSP", "managed services", or any ServiceNow Domain Separation development.

43
10

Preview

SKILL.md
name
domain-separation
description
This skill should be used when the user asks to "domain separation", "multi-tenant", "domain path", "domain visibility", "domain picker", "MSP", "managed services", or any ServiceNow Domain Separation development.
license
Apache-2.0
compatibility
Designed for Snow-Code and ServiceNow development
author
groeimetai
version
"1.0.0"
category
servicenow

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
GitHub Repository
groeimetai/snow-flow
Stars
43
Forks
10
Open Repository
Install Skill
Download ZIP1 files