What It Is

GROQ (Graph-Relational Object Queries) is Sanity's specialized query language for retrieving structured content from its content lake, functioning as a simpler alternative to GraphQL with SQL-like syntax patterns. Unlike ServiceNow's native GlideRecord API that queries relational database tables, GROQ operates on document-based content structures stored in Sanity's cloud infrastructure. The language solves the fundamental problem of efficiently fetching, filtering, and projecting content data from Sanity's headless CMS into formats that ServiceNow applications can consume through REST integrations or custom widgets. GROQ queries execute server-side within Sanity's infrastructure and return JSON responses that ServiceNow can process through standard integration patterns.

Architecturally, GROQ exists entirely outside ServiceNow's platform stack—it's Sanity's proprietary technology that ServiceNow administrators encounter when building integrations between ServiceNow and Sanity CMS systems. Within ServiceNow implementations, GROQ queries typically appear in Script Includes, Business Rules, or custom REST Message configurations where developers construct HTTP requests to Sanity's Content Delivery Network (CDN) endpoints. The queries themselves are embedded as URL parameters or request body content when ServiceNow makes outbound API calls to Sanity's https://[project-id].api.sanity.io/v2021-10-21/data/query/[dataset] endpoints. Understanding GROQ becomes essential when ServiceNow needs to consume Sanity content for knowledge articles, portal pages, or any content management scenario where editorial teams manage content in Sanity while ServiceNow displays or processes that content.

GROQ operates on Sanity's document-based data model where content exists as JSON documents with defined schemas, relationships through references, and rich text stored as portable text blocks. Unlike ServiceNow's relational table structure with dot-walking through reference fields, GROQ uses projection operators and filtering expressions to traverse document relationships and extract specific data subsets. The language supports complex operations like joins across document types, conditional filtering based on document properties, and transformation of nested content structures into flattened result sets that integrate cleanly with ServiceNow's JSON processing capabilities. ServiceNow developers must understand GROQ's projection syntax, filtering operators, and relationship traversal patterns to effectively extract content from Sanity's document graph.

You cannot function without GROQ knowledge when implementing ServiceNow integrations that consume Sanity content for knowledge management systems, customer portal content, or marketing automation workflows where content teams manage structured content in Sanity while ServiceNow applications display that content to end users. Enterprise implementations commonly require GROQ expertise when building headless ServiceNow portals that pull article content, FAQ data, or product information from Sanity's content lake for display in Service Portal widgets or Employee Center components. Without GROQ proficiency, developers resort to fetching entire Sanity documents and performing client-side filtering within ServiceNow, creating performance bottlenecks and unnecessary data transfer overhead that impacts portal loading times and API rate limit consumption.

ServiceNow developers and integration specialists manage GROQ implementations rather than platform administrators, as the language requires understanding both Sanity's content structure and ServiceNow's integration patterns. Platform owners define the architectural patterns and security policies for Sanity integrations, while developers write the actual GROQ queries and implement the ServiceNow-side processing logic. Administrators typically encounter GROQ indirectly through configuration of REST Message records, Scheduled Jobs that sync content, or troubleshooting integration failures where GROQ syntax errors or data model mismatches cause content fetching to fail.

GROQ syntax and capabilities remain stable across ServiceNow releases since it's external to the platform, but ServiceNow's integration capabilities have improved with enhanced REST Message functionality in Vancouver and expanded JSON processing capabilities in Washington and Xanadu releases. Recent ServiceNow versions provide better error handling for malformed GROQ responses and improved debugging tools in REST Message > Test interfaces, making GROQ integration development more manageable for ServiceNow teams working with Sanity content systems.

Where to Find and Configure It

GROQ queries appear in ServiceNow through System Web Services > Outbound > REST Message records where you configure the base Sanity API endpoint and embed GROQ queries as URL parameters or request body content. Within REST Message records, navigate to the HTTP Methods related list to define specific GROQ queries for different content retrieval scenarios. The Test link on HTTP Method records provides a testing interface where you can validate GROQ syntax and preview response data structure.

In development environments, access GROQ implementation through System Applications > Studio where Script Includes and Business Rules contain the actual GROQ query strings and response processing logic. Navigate to System Definition > Script Includes to find utility classes that construct GROQ queries dynamically based on ServiceNow record data or user input parameters. Scoped applications handle GROQ exactly like global applications since the queries execute entirely within Sanity's external infrastructure, though scoped applications must define their own REST Message configurations rather than sharing global ones.

Monitor GROQ integration activity through System Logs > All filtered by Source contains 'REST' to see REST Message execution logs that include GROQ query parameters and response data. Check System Web Services > REST Message Logs for detailed request/response debugging information when GROQ queries return unexpected results or encounter Sanity API errors. Production GROQ queries typically execute from System Definition > Scheduled Jobs that sync Sanity content into ServiceNow knowledge base tables or Service Portal content tables on defined intervals.

How It Works Step by Step

GROQ operates as a server-side query processor within Sanity's content delivery infrastructure, parsing query syntax and executing document traversal operations against Sanity's content lake before returning filtered JSON responses to ServiceNow. The language uses a projection-based approach where queries specify exactly which document fields to return, combined with filtering expressions that limit result sets based on document properties, references, or computed values. Unlike ServiceNow's GlideRecord API that loads entire database records and allows dot-walking through fields, GROQ executes field selection and relationship traversal on Sanity's servers, returning only the requested data subset to minimize bandwidth usage and improve response times.

ServiceNow processes GROQ queries by constructing HTTP GET requests to Sanity's Content Delivery Network endpoints, URL-encoding the GROQ query string as a parameter, and parsing the returned JSON response through standard JavaScript object manipulation or JSONv2 API methods. The ServiceNow platform handles GROQ responses identically to any external REST API response, with developers responsible for error handling, data transformation, and persistence of retrieved content into ServiceNow tables or direct consumption by Service Portal widgets. Performance optimization occurs through GROQ query design rather than ServiceNow-side caching, as Sanity's CDN provides response caching and the GROQ language supports efficient document filtering that reduces payload sizes.

Free Newsletter

Enjoying this? Get one deep-dive per week.

Join 1,000+ ServiceNow pros — scripts, GlideRecord patterns, Flow Designer techniques, and career moves. Free.

No spam · Unsubscribe anytime

The Execution Order

  1. ServiceNow triggers GROQ execution through Business Rule, Scheduled Job, or Service Portal widget code that calls a Script Include containing REST Message invocation logic
  2. Script Include constructs GROQ query string based on input parameters, current record data, or user context variables
  3. REST Message executes HTTP GET request to Sanity CDN endpoint with URL-encoded GROQ query parameter and project authentication token
  4. Sanity's query engine parses GROQ syntax, validates document access permissions, and executes document traversal operations against the content lake
  5. Sanity applies projection operators to extract requested fields, executes filtering conditions, and serializes matching documents into JSON response payload
  6. ServiceNow receives JSON response, parses content through JSONv2 or native JavaScript, and processes data for storage in ServiceNow tables or direct widget consumption
  7. Error handling catches Sanity API failures, GROQ syntax errors, or network timeouts and logs failures through ServiceNow's standard error logging mechanisms
SanityContentAPI.js
var SanityContentAPI = Class.create();
SanityContentAPI.prototype = {
    initialize: function() {
        this.baseUrl = gs.getProperty('sanity.project.url');
        this.token = gs.getProperty('sanity.api.token');
    },
    
    getKnowledgeArticles: function(category, limit) {
        var groqQuery = '*[_type == "article" && category == "' + category + '"] | order(publishedAt desc) [0...' + limit + '] {title, slug, body, publishedAt, author->{name, email}}';
        var restMessage = new sn_ws.RESTMessageV2('Sanity Content API', 'GET Query');
        restMessage.setStringParameterNoEscape('query', groqQuery);
        restMessage.setRequestHeader('Authorization', 'Bearer ' + this.token);
        
        var response = restMessage.execute();
        if (response.getStatusCode() == 200) {
            return JSON.parse(response.getBody()).result;
        }
        gs.error('Sanity API Error: ' + response.getBody());
        return [];
    },
    
    type: 'SanityContentAPI'
};

Real-World Scenarios

Syncing Knowledge Articles from Sanity CMS

Marketing team manages knowledge article content in Sanity CMS with rich text editing and workflow approval, requiring automated sync to ServiceNow knowledge base for customer portal display. Content includes article metadata, categorization, author information, and published status that must map to ServiceNow knowledge article fields.

SanityKnowledgeSync.js
// Scheduled Job: Daily Knowledge Article Sync
var syncJob = new SanityKnowledgeSync();
syncJob.syncPublishedArticles();

var SanityKnowledgeSync = Class.create();
SanityKnowledgeSync.prototype = {
    syncPublishedArticles: function() {
        var groqQuery = '*[_type == "knowledgeArticle" && published == true && _updatedAt > "' + this.getLastSyncTime() + '"] {_id, title, slug, category->{title, _id}, author->{name, email}, body, publishedAt, _updatedAt}';
        
        var restMessage = new sn_ws.RESTMessageV2('Sanity CMS', 'Query Content');
        restMessage.setStringParameterNoEscape('query', groqQuery);
        var response = restMessage.execute();
        
        if (response.getStatusCode() == 200) {
            var articles = JSON.parse(response.getBody()).result;
            articles.forEach(function(article) {
                this.createOrUpdateKnowledge(article);
            }.bind(this));
        }
    },
    
    type: 'SanityKnowledgeSync'
};

Configure the REST Message endpoint URL as https://[project].api.sanity.io/v2021-10-21/data/query/production with GET method and query parameter variable. Watch for Sanity's portable text format in article body fields that requires conversion to ServiceNow's HTML format, and handle reference resolution carefully as Sanity returns reference objects rather than denormalized data. Set up proper error handling for rate limiting since Sanity enforces request quotas based on your subscription tier.

Dynamic Service Portal Content Based on User Role

Service Portal needs to display different content blocks based on user department and role, with content managed by business teams in Sanity with targeting rules. Portal widget must fetch personalized content on page load and handle fallback content when no targeted content matches user profile.

PersonalizedContentWidget.js
// Service Portal Widget Server Script
(function() {
    var userDept = gs.getUser().getDepartment().getDisplayValue();
    var userRoles = gs.getUser().getRoles().toString().split(',');
    
    // GROQ query with conditional filtering
    var groqQuery = '*[_type == "portalContent" && ' +
        '(targeting.departments match "' + userDept + '" || ' +
        'targeting.roles[] in [' + userRoles.map(r => '"' + r + '"').join(',') + '] || ' +
        'targeting == null)] | order(priority desc) [0...5] ' +
        '{title, content, priority, targeting, validUntil}';
    
    var contentAPI = new SanityContentAPI();
    var results = contentAPI.executeQuery(groqQuery);
    
    data.personalizedContent = results.filter(function(item) {
        return !item.validUntil || new Date(item.validUntil) > new Date();
    });
    
    if (data.personalizedContent.length === 0) {
        data.personalizedContent = contentAPI.getFallbackContent();
    }
})();

Configure Sanity content schema to include targeting arrays for departments and roles, ensuring consistent naming with ServiceNow role names and department values. Handle null targeting gracefully in GROQ queries as content without targeting rules should display to all users, and implement client-side caching in the widget to avoid repeated API calls during user session. Test edge cases where users have multiple roles or department changes haven't synchronized between systems yet.

Real-Time FAQ Content for Chatbot Integration

ServiceNow Virtual Agent needs current FAQ content from Sanity CMS for natural language processing, requiring real-time GROQ queries during conversation flow to fetch relevant answers based on user intent classification. Content team updates FAQ entries in Sanity with tagging and categorization that Virtual Agent uses for response matching.

VirtualAgentFAQ.js
// Virtual Agent Topic Script
var intentKeywords = input.intent_keywords.split(',');
var userCategory = input.user_category || 'general';

// Build dynamic GROQ query for FAQ matching
var keywordFilter = intentKeywords.map(function(keyword) {
    return 'pt::text(content) match "*' + keyword.trim() + '*"';
}).join(' || ');

var groqQuery = '*[_type == "faq" && ' +
    'active == true && ' +
    '(category == "' + userCategory + '" || category == "general") && ' +
    '(' + keywordFilter + ')] ' +
    '| score(pt::text(content) match "' + intentKeywords[0] + '") ' +
    '| order(_score desc) [0...3] ' +
    '{question, content, category, lastUpdated}';

var faqAPI = new SanityContentAPI();
var matchedFAQs = faqAPI.executeQuery(groqQuery);

if (matchedFAQs.length > 0) {
    outputs.response_text = matchedFAQs[0].content;
    outputs.confidence_score = 0.8;
} else {
    outputs.response_text = "I couldn't find specific information about that. Let me connect you with support.";
    outputs.confidence_score = 0.2;
}

Implement GROQ's full-text search operators carefully as Sanity's search behavior differs from ServiceNow's text indexing, particularly with special characters and phrase matching. Configure Virtual Agent conversation timeout handling since external API calls can introduce latency that exceeds Virtual Agent's response time limits, and establish fallback responses when Sanity API is unavailable. Monitor GROQ query performance as complex text matching operations can exceed Sanity's query execution limits during high conversation volume periods.

The Classic Mistake

⚠️

Fetching all referenced objects without field selection causes massive over-fetching and slow queries.

The most damaging GROQ mistake is writing queries that fetch entire referenced objects when you only need specific fields. Developers coming from SQL or other query languages instinctively grab everything, not realizing that GROQ's reference resolution can exponentially increase payload size. This happens because GROQ makes it so easy to traverse relationships with the -> operator that developers forget about field selection.

bad-query.js
// BAD: Fetches everything from referenced objects
const query = groq`
  *[_type == "incident"] {
    _id,
    title,
    description,
    assignee->,
    category->,
    location->,
    attachments[]{
      asset->
    },
    comments[]{
      author->
    }
  }
`;

This query can return megabytes of data when you expected kilobytes. ServiceNow's content delivery network starts throttling responses over 100KB, causing timeouts that appear as random network errors. The query engine resolves every reference completely, pulling in binary data from attachments, full user profiles with encrypted fields, and nested location hierarchies. Users see loading spinners that never complete, and the browser's network tab shows massive JSON payloads with 90% unused data.

good-query.js
// GOOD: Selective field fetching with reference projection
const query = groq`
  *[_type == "incident"] {
    _id,
    title,
    description,
    "assigneeName": assignee->name,
    "assigneeEmail": assignee->email,
    "categoryTitle": category->title,
    "locationName": location->name,
    "attachmentCount": count(attachments),
    "latestComment": comments[0].text
  }
`;
💡

Always project specific fields from references using the field->specificField syntax or object projection syntax. Never use bare references (->) without field selection in production queries.

When to Use This vs Alternatives

GROQ is the right choice when you need to fetch hierarchical, document-based content with complex relationships from Sanity's content lake. It excels at queries that would require multiple SQL joins or REST API calls, especially when you need to reshape data for frontend consumption with specific field selections and calculated properties.

Choose GROQ When You Need Document Traversal

Use GROQ when fetching content that spans multiple document types with references, like incidents with related knowledge articles, attachments, and user profiles. GraphQL would require schema definitions and resolvers for each relationship, while REST APIs would need multiple round trips. GROQ handles this in a single query with built-in reference resolution and field projection that maps perfectly to ServiceNow's document-oriented content structure.

Use GlideRecord When You Need Transactional Operations

Switch to GlideRecord or GlideAggregate when you need to update records, enforce ACLs, or trigger business rules. GROQ is read-only and bypasses ServiceNow's security and workflow layers, making it unsuitable for any operation that modifies data or requires audit trails. For reporting queries that need real-time aggregation with COUNT() or SUM() functions, GlideAggregate performs better on large datasets.

Combine GROQ with ServiceNow's Table API

Use both when building content-heavy portals or mobile apps that need fast content delivery plus real-time ServiceNow data. Fetch static content, knowledge articles, and media assets via GROQ for speed and flexibility, then use ServiceNow's REST API for live ticket data, user sessions, and transactional operations. This hybrid approach leverages Sanity's CDN for content while maintaining ServiceNow's security model for operational data.

Platform Interactions & Side Effects

  • GROQ queries bypass ServiceNow's ACL engine completely - queries execute with full read access regardless of user context or field-level security
  • Query execution does not trigger Business Rules, Script Includes, or any server-side ServiceNow logic - data flows directly from Sanity's content lake
  • No audit records are written to sys_audit table - GROQ access is invisible to ServiceNow's audit trail and compliance reporting
  • Results are cached at Sanity's CDN edge locations, not in ServiceNow's cache.do system - cache invalidation requires Sanity webhook calls
  • Session state and user context from gs.getUser() or gs.getSession() are not available within GROQ query execution context
  • Update Sets do not capture GROQ query definitions - queries exist only in Sanity Studio and client-side code, creating deployment gaps
  • Data Dictionary field changes in ServiceNow do not automatically update GROQ queries - schema mismatches cause runtime errors
  • Email Notifications and Scheduled Jobs cannot directly execute GROQ queries - requires REST API bridges or webhook integrations
  • Performance Analytics and Reporting do not see GROQ query execution - invisible to ServiceNow's performance monitoring and capacity planning
  • Content Security Policy headers may block GROQ API calls from Service Portal pages unless glide.ui.security.csp_enabled includes Sanity's domains

Debugging and Troubleshooting

The most common GROQ failures manifest as empty result arrays or undefined reference fields, often appearing as blank sections in Service Portal widgets or mobile apps. Users see loading states that complete successfully but display no content, while developers get valid HTTP 200 responses with empty [] arrays. This happens when document types don't match exactly, field names have typos, or references point to unpublished content. ServiceNow's standard logging in System Log > All won't show GROQ errors because queries execute outside ServiceNow's application server.

For debugging, use your browser's Network tab to inspect the actual HTTP requests to *.api.sanity.io endpoints. Look for 400 errors with messages like "Unknown document type" or "Unknown function" which indicate syntax problems. The Sanity Studio's Vision plugin provides real-time query testing with syntax highlighting and result preview. Performance issues show up as slow response times in the Network tab, typically caused by missing indexes on filtered fields or over-fetching through reference chains.

Authentication errors appear as 401 responses with "Insufficient permissions" messages, usually caused by missing API tokens in the SANITY_API_TOKEN environment variable or incorrect project ID configuration. CORS errors in Service Portal indicate that *.service-now.com domains aren't whitelisted in Sanity's project settings. Schema validation errors show specific field paths like "Expected array, got string at path 'attachments'" when document structure doesn't match query expectations.

Diagnostic Checklist:

  • Test the exact query in Sanity Studio's Vision plugin to isolate syntax and data issues from ServiceNow integration problems
  • Verify document types exist and match exactly - *[_type == "incident"] vs *[_type == "incidents"] will return different results
  • Check that referenced documents are published in the target dataset - unpublished references resolve to null
  • Confirm API token has read permissions for the dataset and project ID matches between Sanity Studio and ServiceNow configuration
  • Use browser DevTools to inspect actual API URLs and payloads - query string encoding issues often cause parsing failures
  • Add explicit defined() checks to filter conditions when dealing with optional fields or reference chains
  • Validate CORS settings in Sanity project configuration include all ServiceNow instance domains and Service Portal URLs

Quick Reference

  • GROQ queries are limited to 5MB response size and 10,000 documents per query - larger result sets require pagination with [0...100] slicing
  • Reference resolution with -> operator is limited to 5 levels deep to prevent infinite loops and circular references
  • Field names starting with underscore like _type and _id are system fields and always available, while custom fields may be undefined
  • CDN caching at Sanity's edge can serve stale data for up to 60 seconds after content updates unless cache-busting parameters are used
  • Date filtering requires ISO 8601 format strings - _createdAt > "2023-01-01T00:00:00Z" works, timestamps or epoch values do not
  • Sanity's free tier allows 100,000 API requests per month - production ServiceNow instances with active portals easily exceed this limit
  • Null reference fields (assignee->) return null rather than empty objects, breaking JavaScript property access without null checks
  • Order clauses like order(_createdAt desc) must come after filter conditions but before field selection in query structure
  • Text search with match() function requires full-text indexes on target fields and supports stemming but not fuzzy matching
  • Asset URLs from image.asset->url include automatic CDN optimization parameters but bypass ServiceNow's attachment security model