What It Is

A UI Macro is a server-side template that generates HTML content during form rendering, using either Jelly XML syntax or plain HTML with embedded server-side variables. It solves the fundamental problem of injecting custom UI elements, complex data displays, or interactive components directly into ServiceNow forms where standard field types and formatters fall short. Unlike client-side UI policies or catalog client scripts, macros execute during the initial server response, allowing you to embed dynamic content, complex layouts, or even entire mini-applications within form sections.

Architecturally, UI Macros live in the sys_ui_macro table within the presentation layer of ServiceNow's MVC architecture. They integrate with the form rendering engine through UI Pages, formatter scripts, or direct macro calls from other Jelly templates. The macro processor runs after the form's field values are retrieved but before the final HTML is sent to the browser, giving macros access to current record data, session variables, and all server-side APIs including GlideRecord, GlideSystem, and custom Script Includes.

The relationship to ServiceNow's data model is direct and powerful—macros can query any table, manipulate field values, and even trigger server-side business logic during rendering. They execute within the same transaction context as the form itself, meaning they have full access to the current user's permissions, the record being displayed, and all related data. This makes them particularly valuable for creating custom dashboards within forms, displaying aggregated data from related tables, or building complex approval workflows that need real-time status displays.

You cannot function without UI Macros when you need to display complex relational data that spans multiple tables in a single view, create custom approval status panels with dynamic styling, build embedded reporting widgets on forms, or implement custom field renderers that require server-side calculations. Standard formatters and UI policies handle simple display logic, but when you need to render live charts, create dynamic tables with drill-down capabilities, or build custom workflow status displays that update based on real-time business rules, macros become the only viable solution.

UI Macros are primarily managed by developers due to their requirement for Jelly scripting knowledge and understanding of ServiceNow's server-side APIs. Platform owners typically define the standards and security guidelines for macro usage, while system administrators handle the deployment and form integration aspects. The skill barrier is significant—effective macro development requires understanding of XML, Jelly syntax, GlideRecord querying, and ServiceNow's rendering pipeline, making this a developer-centric feature rather than an admin configuration tool.

Recent ServiceNow releases have enhanced macro security with stricter Content Security Policy enforcement, affecting how external resources and inline JavaScript are handled within macros. Vancouver introduced improved macro debugging capabilities in the development environment, while Xanadu expanded macro support in Service Portal widgets. The core functionality remains consistent, but modern implementations must account for CSP restrictions and the platform's shift toward more secure, contained execution contexts.

Where to Find and Configure It

Navigate to System Definition > UI Macros to create and manage all macro definitions. This is where you define the macro name, write the Jelly or HTML template code, specify categories for organization, and set active status. Access System UI > UI Pages to create standalone pages that can include your macros, or go to System UI > Form Design to add macro elements directly to form sections.

In Studio applications, find macros under the User Interface section when creating new application files. App Engine Studio provides macro creation through the Experience > UI Builder interface for modern app development. View the underlying data structure by navigating directly to the sys_ui_macro.list table to see all macro records, their scope assignments, and usage tracking.

See macros in action on any form where they're embedded—right-click and View source to identify the rendered macro HTML. Check System Diagnostics > Session Debug > Debug Business Rules to trace macro execution and performance. Global macros are available across all applications, while scoped macros only function within their defined application scope—manage scope permissions through System Applications > Application Cross-Scope Access if cross-application macro sharing is required.

How It Works Step by Step

UI Macros execute during the server-side form rendering process, triggered when the form processor encounters a macro reference either embedded in a UI Page, called from another Jelly template, or included through a form section. The macro processor instantiates a new Jelly context, inherits all current session variables and form data, then parses the macro's XML template to generate the final HTML output. This execution happens after database queries populate form fields but before any client-side JavaScript runs, giving macros access to complete server-side context while allowing them to influence the initial page structure.

The macro execution environment provides access to standard ServiceNow server objects like gs (GlideSystem), current (current record), and g_user (current user), plus any parameters passed from the calling context. Variables and calculations performed within the macro are scoped to that execution instance and don't persist beyond the rendering cycle. The macro output becomes part of the larger HTML document, inheriting CSS styles and JavaScript contexts from the parent form or page.

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. Form processor encounters macro reference and loads macro definition from sys_ui_macro table
  2. Jelly context initializes with current session, user permissions, and form data variables
  3. Macro parameters are parsed and made available as local variables within the template
  4. Jelly processor executes server-side logic including GlideRecord queries and Script Include calls
  5. Template generates HTML output with dynamic values populated from server-side calculations
  6. Generated HTML is inserted into the parent form or page at the macro reference location
  7. Complete page renders to browser where client-side JavaScript and CSS take over
incident_approval_status.xml
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
  <g:evaluate var="jvar_approvals">
    var gr = new GlideRecord('sysapproval_approver');
    gr.addQuery('source_table', current.getTableName());
    gr.addQuery('source_id', current.getUniqueValue());
    gr.orderByDesc('sys_created_on');
    gr.query();
    
    var approvalData = [];
    while (gr.next()) {
      approvalData.push({
        approver: gr.approver.getDisplayValue(),
        state: gr.state.getDisplayValue(),
        comments: gr.comments.toString(),
        created: gr.sys_created_on.getDisplayValue()
      });
    }
    approvalData;
  </g:evaluate>
  
  <div class="approval-status-panel">
    <h3>Approval Status</h3>
    <j:forEach var="approval" items="${jvar_approvals}">
      <div class="approval-item ${approval.state}">
        <strong>${approval.approver}</strong> - ${approval.state}<br/>
        <span class="approval-date">${approval.created}</span>
        <j:if test="${!empty(approval.comments)}">
          <p class="approval-comments">${approval.comments}</p>
        </j:if>
      </div>
    </j:forEach>
  </div>
</j:jelly>

Real-World Scenarios

Change management teams need a consolidated view of all related incidents, problems, and tasks associated with a change request, displayed in a custom dashboard panel that updates based on the current change state. Standard related lists don't provide the aggregated metrics and conditional formatting required for at-a-glance status assessment.

Create a new UI Macro through System Definition > UI Macros with name change_related_dashboard. Use GlideRecord queries to pull incident, problem, and task counts with state-based filtering. Implement conditional CSS classes for status indicators and include drill-down links to filtered related lists. Add the macro to change request forms by navigating to System UI > Form Design, selecting the Change table, and inserting a new Macro element in the desired section.

Watch for performance issues with complex queries on high-volume systems—implement result limiting and consider caching for frequently accessed changes. Ensure the macro handles empty result sets gracefully and includes appropriate security checks to prevent unauthorized data exposure. Test macro rendering performance under load since server-side execution can impact form loading times for users with slower connections.

Custom Approval Workflow Status Visualization

Purchase requisitions require a visual approval workflow status that shows each approval stage, current approver, time spent at each stage, and any rejection comments in a timeline format. The standard approval-related list doesn't provide the chronological visualization and stage progress indicators that procurement teams need for efficient workflow management.

Build a macro that queries the sysapproval_approver table to retrieve all approval records for the current requisition, then uses Jelly forEach loops to generate a timeline visualization with CSS-styled progress indicators. Include GlideDateTime calculations to show approval duration and implement conditional formatting for overdue approvals. Add the macro to procurement forms through Form Design and position it prominently in the approval section for maximum visibility.

Consider that approval data changes frequently, so macro output becomes stale after initial rendering—implement page refresh triggers or consider client-side updates for real-time status changes. Verify that the macro properly handles parallel approval scenarios and complex approval routing rules. Test thoroughly with various approval states including cancelled, withdrawn, and escalated approvals to ensure robust error handling.

Embedded Asset Performance Metrics on Configuration Items

IT teams need real-time performance metrics, incident history, and maintenance schedules displayed directly on configuration item forms to make informed decisions during change planning and incident resolution. Standard CI forms don't provide the aggregated performance data and trend analysis required for proactive asset management.

Create a comprehensive macro that integrates with monitoring data tables, incident history, and maintenance schedules to build a unified asset health dashboard. Use GlideRecord to query performance metric tables, calculate availability percentages, and identify incident patterns. Implement chart generation using ServiceNow's built-in visualization libraries or custom HTML/CSS graphics. Deploy the macro to specific CI classes through Form Design with table-specific configurations to ensure relevant metrics appear for different asset types.

Monitor macro execution time carefully since performance data queries can be resource-intensive and impact form loading performance. Implement appropriate date range filtering to prevent excessive historical data retrieval and consider read-only database replicas for reporting queries if available. Ensure the macro includes proper error handling for missing monitoring data and gracefully handles CIs that lack performance metrics.

The Classic Mistake

⚠️

Creating macros that directly manipulate DOM elements without considering ServiceNow's client-side framework lifecycle.

bad_macro.xml
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
  <div id="custom_widget_${sys_id}">
    <input type="text" id="custom_field_${sys_id}" />
    <button onclick="saveCustomData();">Save</button>
  </div>
  
  <script>
    function saveCustomData() {
      var value = document.getElementById('custom_field_${sys_id}').value;
      // Direct DOM manipulation without framework awareness
      var hiddenField = document.getElementById('sys_original.${field_name}');
      hiddenField.value = value;
      // Force form submission
      document.forms[0].submit();
    }
  </script>
</j:jelly>

This approach fails because it bypasses ServiceNow's client-side form framework, which manages field validation, change tracking, and submission workflows. Users experience inconsistent behavior where the custom widget appears to work but doesn't trigger proper field validation or business rule execution. The platform's AJAX form handling expects all field interactions to go through the g_form API, and direct DOM manipulation creates a disconnect between the visual state and the underlying form model. This becomes particularly problematic when mandatory fields aren't properly validated or when client scripts don't execute as expected.

proper_macro.xml
<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
  <div id="custom_widget_${sys_id}" class="form-group">
    <label class="col-sm-2 control-label">${label}</label>
    <div class="col-sm-10">
      <input type="text" id="custom_input_${sys_id}" class="form-control" 
             onchange="updateFormField('${field_name}', this.value);" />
      <input type="hidden" name="${field_name}" id="${field_name}" value="${value}" />
    </div>
  </div>
  
  <script>
    function updateFormField(fieldName, value) {
      g_form.setValue(fieldName, value);
      g_form.setMandatory(fieldName, true);
    }
  </script>
</j:jelly>
💡

Always use g_form API methods for field interactions in macros — never manipulate DOM elements directly when dealing with form field values.

When to Use This vs Alternatives

Macros are the correct choice when you need to render complex, reusable UI components that combine multiple HTML elements with server-side data processing, particularly for custom field renderers or informational panels that appear consistently across multiple forms. They excel when you need both server-side Jelly processing and client-side interactivity in a single, maintainable package.

Choose Macros When You Need Server-Side Rendering

Use macros when your UI component requires access to server-side data that isn't available through client-side APIs, such as complex GlideRecord queries or system property evaluations during page rendering. UI Pages fall short here because they create separate page contexts, while Client Scripts can't access server-side data during initial rendering. Macros provide the perfect bridge between server-side data processing and inline form presentation.

Use UI Scripts Instead for Pure Client-Side Logic

When your customization only needs client-side JavaScript without HTML rendering, UI Scripts provide better performance and maintainability than macros. Macros add unnecessary server-side processing overhead when you're only manipulating existing form elements or making AJAX calls. UI Scripts also integrate more cleanly with the platform's script loading and caching mechanisms.

Combine Macros with UI Actions for Complete Workflows

Complex custom workflows often require macros for the UI presentation layer and UI Actions for the submission and processing logic. The macro handles the visual component and client-side validation, while the UI Action processes the server-side business logic and database operations. This separation maintains clean architecture and allows proper error handling across both client and server contexts.

Platform Interactions & Side Effects

  • Update Sets capture macro definitions in the sys_ui_macro table, but referenced resources like images or included scripts may not be automatically captured
  • ACL evaluation occurs during macro rendering, potentially hiding entire macro content based on field-level or table-level permissions
  • Session state and g_user context is available during server-side Jelly processing, but user preferences require explicit GlideUser queries
  • Form rendering performance degrades with complex macros as each macro instance executes server-side processing during page load
  • Business Rules trigger normally for fields modified through macro-generated form elements if using proper g_form API calls
  • Macro output is cached per user session, causing stale data when server-side queries return dynamic results that change frequently
  • Client Scripts and UI Policies execute after macro rendering, potentially conflicting with macro-generated field states
  • Mobile and Service Portal rendering may ignore macro content entirely, requiring separate responsive implementations
  • Notification templates can include macros, but email clients strip most JavaScript and advanced CSS styling
  • Scoped applications can access global macros but not vice versa, creating dependency issues during application development

Debugging and Troubleshooting

Macro failures typically manifest as blank spaces on forms where the macro should render, or as partial rendering with missing dynamic content. Users report seeing static HTML elements but no server-generated data, indicating Jelly processing failures. Administrators often see JavaScript console errors related to undefined functions or missing DOM elements when the macro's client-side code expects server-generated IDs or variables that weren't properly rendered.

Primary debugging occurs through System Log > All where Jelly parsing errors appear as XML processing exceptions, and through browser developer tools for client-side JavaScript errors. The com.glide.ui.jelly log source specifically captures macro rendering issues, while session debug logging with glide.ui.security.debug=true reveals ACL-related macro hiding. Look for specific error patterns like "Unable to resolve macro" or "Jelly script compilation failed" which indicate syntax or reference problems.

Performance issues appear as slow form loads with timeline entries showing extended macro processing times, particularly visible in the browser's Network tab where form rendering requests take multiple seconds. Database connection timeouts within macros generate "GlideRecord query timeout" messages in the application logs, while infinite loops or recursive macro calls create memory exhaustion errors that require instance restarts.

Diagnostic Checklist

  1. Verify macro XML syntax by viewing source and checking for malformed Jelly tags or unclosed elements
  2. Test macro rendering in isolation by creating a test UI Page that calls only the specific macro
  3. Check System Log > All for Jelly compilation errors during the timeframe when the form was loaded
  4. Validate all server-side variable references by adding debug output to confirm GlideRecord queries return expected results
  5. Review ACL permissions on referenced tables and fields that might prevent macro content from displaying
  6. Test with different user roles to identify permission-based rendering issues
  7. Examine browser console for JavaScript errors and verify all client-side function dependencies are loaded

Quick Reference

  • Macro rendering occurs server-side during initial page load, not during AJAX form refreshes or field updates
  • Maximum recommended Jelly processing time per macro is 200ms; longer operations cause noticeable form load delays
  • Variable scope within macros is limited to explicitly passed parameters; current and g_user are not automatically available
  • Macro names must be unique across the entire instance, including global scope versus application scope conflicts
  • HTML output from macros bypasses standard form field validation unless explicitly integrated with g_form API methods
  • Jelly tag libraries require explicit namespace declarations; missing xmlns attributes cause silent rendering failures
  • Client-side macro JavaScript executes in global scope, potentially conflicting with other form scripts and UI customizations
  • System property glide.ui.macro.lazy_jelly controls whether macro Jelly processing is deferred, affecting form load performance
  • Macro caching behavior changes between form views and list views, with list view macros cached more aggressively
  • Database queries within macros execute with the current user's ACL context, not elevated privileges like system-level processing