
domain-separation
by groeimetai
๐ค AI-powered ServiceNow development with 400+ MCP tools. Works with Claude, GPT, Gemini, Ollama & 75+ providers. Deploy widgets, manage incidents, automate workflows - all through natural language. Open-source Build Agent alternative.
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 metadata: author: groeimetai version: "1.0.0" category: servicenow tools:
- snow_query_table
- snow_execute_script_with_output
- snow_find_artifact
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
| Table | Purpose |
|---|---|
domain | Domain definitions |
sys_user_has_domain | User domain membership |
domain_path | Domain hierarchy paths |
sys_db_object | Table 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
| Tool | Purpose |
|---|---|
snow_query_table | Query domain-aware data |
snow_execute_script_with_output | Test domain scripts |
snow_find_artifact | Find 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
- Plan Hierarchy - Design domain structure before implementation
- Minimal Domains - Only create necessary separation
- User Access - Assign minimum required domains
- Testing - Test with domain picker
- Global Data - Keep shared data in TOP domain
- Performance - Domain queries add overhead
- Documentation - Document domain purposes
- ES5 Only - No modern JavaScript syntax
Score
Total Score
Based on repository quality metrics
SKILL.mdใใกใคใซใๅซใพใใฆใใ
ใฉใคใปใณในใ่จญๅฎใใใฆใใ
100ๆๅญไปฅไธใฎ่ชฌๆใใใ
GitHub Stars 100ไปฅไธ
1ใถๆไปฅๅ ใซๆดๆฐ
10ๅไปฅไธใใฉใผใฏใใใฆใใ
ใชใผใใณIssueใ50ๆชๆบ
ใใญใฐใฉใใณใฐ่จ่ชใ่จญๅฎใใใฆใใ
1ใคไปฅไธใฎใฟใฐใ่จญๅฎใใใฆใใ
Reviews
Reviews coming soon


