UI Generation from Natural Language

SkillDev tools

Generate platform UI components from natural language, including client scripts, data binding, event handlers, pages, and UI Builder experiences.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the UI Generation from Natural Language skill

What this skill tells your AI

The instructions your AI receives, as published by happy-technologies-llc/happy-platform-skills in skills/development/ui-generation/SKILL.md and read by ahel’s review.

Overview

This skill covers generating ServiceNow UI components from natural language descriptions:

  • Creating client scripts (onChange, onLoad, onSubmit, onCellEdit) for forms and lists
  • Building data binding configurations for UI Builder and Service Portal widgets
  • Generating event handler logic for user interactions
  • Creating UI Pages with Jelly/HTML templates and processing scripts
  • Building Service Portal widgets with AngularJS controllers and HTML templates
  • Generating UI Builder experience pages with data resources and event mappings
  • Producing UI macros for reusable form decorations and formatters

When to use: When developers or citizen developers need to create UI components from business requirements described in plain language, or when rapidly prototyping form behaviors, portal widgets, or workspace pages.

Prerequisites

  • Roles: admin, personalize_form, or sp_admin (for Service Portal widgets)
  • Plugins: com.glide.ui.ui_page (UI Pages), com.glide.service-portal (Service Portal), com.sn_ui_builder (UI Builder)
  • Access: Write access to sys_script_client, sys_ui_page, sp_widget, sys_ux_page_registry tables
  • Knowledge: JavaScript fundamentals, GlideForm API, AngularJS basics (for Service Portal), UI Builder concepts
  • Related Skills: development/client-scripts for client script patterns, catalog/ui-policies for catalog form behavior

Procedure

Step 1: Parse the Natural Language Description

Analyze the user request to identify:

  • Component type: Client script, UI page, Service Portal widget, UI Builder page, UI macro
  • Target table/form: Which table or form the component applies to
  • Behavior: What should happen (show/hide fields, validate data, fetch records, render UI)
  • Trigger: What event initiates the behavior (field change, form load, button click, page load)
  • Data dependencies: What data the component needs to read or write

Step 2: Query Existing Components for Patterns

MCP Approach:

Tool: SN-Query-Table
Parameters:
  table_name: sys_script_client
  query: table=incident^active=true^typeLIKEonChange
  fields: sys_id,name,table,type,field_name,script,active,ui_type
  limit: 10

REST Approach:

GET /api/now/table/sys_script_client
  ?sysparm_query=table=incident^active=true^typeLIKEonChange
  &sysparm_fields=sys_id,name,table,type,field_name,script,active,ui_type
  &sysparm_limit=10

Step 3: Generate Client Scripts

Translate natural language to GlideForm API calls. Common patterns:

onChange -- Show/Hide Fields:

MCP Approach:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script_client
  data:
    name: "Show VPN fields when category is Network"
    table: incident
    type: onChange
    field_name: category
    ui_type: 0
    active: true
    script: |
      function onChange(control, oldValue, newValue, isLoading) {
        if (isLoading) return;
        var isNetwork = (newValue == 'network');
        g_form.setVisible('u_vpn_type', isNetwork);
        g_form.setVisible('u_network_segment', isNetwork);
        if (isNetwork) {
          g_form.setMandatory('u_vpn_type', true);
        } else {
          g_form.setMandatory('u_vpn_type', false);
        }
      }

REST Approach:

POST /api/now/table/sys_script_client
Body: {
  "name": "Show VPN fields when category is Network",
  "table": "incident",
  "type": "onChange",
  "field_name": "category",
  "ui_type": "0",
  "active": "true",
  "script": "function onChange(control, oldValue, newValue, isLoading) { ... }"
}

Step 4: Generate onLoad Scripts for Form Initialization

MCP Approach:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script_client
  data:
    name: "Initialize priority banner on form load"
    table: incident
    type: onLoad
    ui_type: 0
    active: true
    script: |
      function onLoad() {
        var priority = g_form.getValue('priority');
        if (priority == '1') {
          g_form.showFieldMsg('priority', 'Critical incident - follow P1 procedures', 'error');
          g_form.setReadOnly('category', true);
          g_form.setReadOnly('subcategory', true);
        }
      }

Step 5: Generate onSubmit Validation Scripts

MCP Approach:

Tool: SN-Create-Record
Parameters:
  table_name: sys_script_client
  data:
    name: "Validate resolution notes before close"
    table: incident
    type: onSubmit
    ui_type: 0
    active: true
    script: |
      function onSubmit() {
        var state = g_form.getValue('state');
        if (state == '6') {
          var resolution = g_form.getValue('close_notes');
          if (!resolution || resolution.trim().length < 20) {
            g_form.showFieldMsg('close_notes', 'Resolution notes must be at least 20 characters', 'error');
            return false;
          }
        }
        return true;
      }

Step 6: Generate Service Portal Widgets

MCP Approach:

Tool: SN-Create-Record
Parameters:
  table_name: sp_widget
  data:
    name: "Team Incident Dashboard"
    id: "team-incident-dashboard"
    template: |
      <div class="panel panel-default">
        <div class="panel-heading"><h3>{{c.data.title}}</h3></div>
        <div class="panel-body">
          <div ng-repeat="inc in c.data.incidents track by inc.sys_id">
            <div class="alert alert-{{inc.priorityClass}}">
              <strong>{{inc.number}}</strong> - {{inc.short_description}}
              <span class="pull-right badge">{{inc.state}}</span>
            </div>
          </div>
          <p ng-if="c.data.incidents.length === 0">No open incidents.</p>
        </div>
      </div>
    client_script: |
      api.controller = function($scope) {
        var c = this;
      };
    server_script: |
      (function() {
        data.title = "My Team's Open Incidents";
        var gr = new GlideRecord('incident');
        gr.addQuery('assignment_group', gs.getUser().getMyGroups());
        gr.addActiveQuery();
        gr.orderByDesc('priority');
        gr.setLimit(20);
        gr.query();
        data.incidents = [];
        while (gr.next()) {
          data.incidents.push({
            sys_id: gr.getUniqueValue(),
            number: gr.getValue('number'),
            short_description: gr.getValue('short_description'),
            state: gr.getDisplayValue('state'),
            priority: gr.getValue('priority'),
            priorityClass: {'1':'danger','2':'warning','3':'info','4':'success'}[gr.getValue('priority')] || 'default'
          });
        }
      })();

REST Approach:

POST /api/now/table/sp_widget
Body: {
  "name": "Team Incident Dashboard",
  "id": "team-incident-dashboard",
  "template": "<div class='panel panel-default'>...</div>",
  "client_script": "api.controller = function($scope) { var c = this; };",
  "server_script": "(function() { ... })();"
}

Step 7: Generate UI Builder Experience Pages

MCP Approach:

Tool: SN-Create-Record
Parameters:
  table_name: sys_ux_page_registry
  data:
    title: "Executive Incident Overview"
    path: /executive-incident-overview
    description: "Executive dashboard showing incident metrics and trends"
    category: workspace
    active: true

Configure data resources for the page:

Tool: SN-Create-Record
Parameters:
  table_name: sys_ux_data_broker_transform
  data:
    name: "Incident Metrics Data Resource"
    page: "<page_sys_id>"
    table: incident
    query: "active=true^priority<=2"
    fields: "number,short_description,priority,state,assigned_to,sys_created_on"
    max_rows: 50

Step 8: Generate Event Handlers

Configure UI Builder event-to-action mappings:

MCP Approach:

Tool: SN-Create-Record
Parameters:
  table_name: sys_ux_event
  data:
    name: "Row Click Navigate to Record"
    page: "<page_sys_id>"
    component: "<component_sys_id>"
    event_name: "click"
    action_type: "navigate"
    action_config: {
      "route": "/record/incident/${sys_id}",
      "target": "_self"
    }

Step 9: Generate UI Macros for Form Decorations

MCP Approach:

Tool: SN-Create-Record
Parameters:
  table_name: sys_ui_macro
  data:
    name: "sla_countdown_banner"
    description: "Displays a countdown timer for SLA breach on incident form"
    xml: |
      <?xml version="1.0" encoding="utf-8"?>
      <j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide">
        <g:evaluate var="jvar_sla_info" expression="
          var sla = new GlideRecord('task_sla');
          sla.addQuery('task', current.sys_id);
          sla.addQuery('has_breached', false);
          sla.addActiveQuery();
          sla.orderBy('planned_end_time');
          sla.setLimit(1);
          sla.query();
          sla.next() ? sla.getDisplayValue('planned_end_time') : 'N/A';
        "/>
        <div style="background:#fff3cd;padding:10px;border-radius:4px;margin-bottom:10px;">
          <strong>SLA Target:</strong> ${jvar_sla_info}
        </div>
      </j:jelly>

Step 10: Validate and Test Generated Components

MCP Approach:

Tool: SN-Query-Table
Parameters:
  table_name: sys_script_client
  query: sys_id=<created_sys_id>
  fields: sys_id,name,table,type,active,sys_updated_on
  limit: 1

Verify no script syntax errors by checking the sys_update_xml table for recent update records:

Tool: SN-Query-Table
Parameters:
  table_name: sys_update_xml
  query: name=sys_script_client_<sys_id>^ORDERBYDESCsys_created_on
  fields: sys_id,name,action,sys_created_on
  limit: 1

Tool Usage

ToolPurposeWhen to Use
SN-Query-TableFind existing UI components for patternsPattern discovery, validation
SN-Create-RecordCreate client scripts, widgets, UI pagesBuilding new UI components
SN-Update-RecordModify scripts, templates, configurationsIterating on generated components
SN-Get-Table-SchemaDiscover field names for data bindingUnderstanding form fields and data model

Best Practices

  1. Always set ui_type correctly -- use 0 for Desktop, 1 for Mobile/Service Portal, 2 for Both
  2. Use g_form API methods rather than direct DOM manipulation for client scripts
  3. Minimize server calls in client scripts -- use GlideAjax for async server lookups
  4. Scope client scripts appropriately -- avoid global scope pollution with IIFEs
  5. Test on multiple browsers -- avoid browser-specific JavaScript features
  6. Include null checks in all generated onChange handlers for empty field values
  7. Use isLoading guard in onChange scripts to prevent execution during form load
  8. Set widgets to read-only during server calls to prevent double-submission
  9. Follow accessibility standards -- include ARIA labels and keyboard navigation in custom widgets
  10. Version control -- always associate UI components with an update set before creation

Troubleshooting

IssueCauseResolution
Client script not firingWrong type or field_name valueVerify type matches event and field_name matches column name
g_form undefined errorScript running outside form contextEnsure script is attached to correct table and ui_type
Widget data not loadingServer script GlideRecord query errorCheck table permissions and query syntax in server script
UI page blankJelly syntax error in XMLValidate XML structure and check for unescaped characters
Event handler not respondingComponent ID mismatchVerify component sys_id in event configuration
Form fields not hidingField name vs label mismatchUse column name (e.g., u_vpn_type) not label

Examples

Example 1: Natural Language to Client Script

Input: "When someone selects 'Hardware' as the category on the incident form, make the model and serial number fields mandatory and show a message to fill them in."

Generated: onChange client script on incident.category that calls g_form.setMandatory() and g_form.showFieldMsg() for u_model and u_serial_number when newValue == 'hardware'.

Example 2: Natural Language to Service Portal Widget

Input: "Create a widget that shows the logged-in user's open requests with status bars showing percentage complete."

Generated: Service Portal widget with server script querying sc_req_item for the current user, client-side AngularJS rendering with Bootstrap progress bars, and click-to-navigate functionality.

Example 3: Natural Language to UI Builder Page

Input: "Build a workspace page that shows a chart of incidents by priority and a list of my team's open incidents below it."

Generated: UI Builder page registration with two data resources (aggregation for chart, list for table), a bar chart component bound to priority aggregation, and a data table component with row-click navigation.

Related Skills

  • development/client-scripts - In-depth client script development patterns
  • development/ui-actions - UI action buttons and links
  • catalog/ui-policies - Catalog-specific UI policies
  • development/scripted-rest-apis - Backend APIs for widget data
  • genai/flow-generation - Flow Designer automation for UI-triggered processes

Signals

GitHub stars
37
Forks
13
Last commit
Jul 2026
Advanced
Catalog kind
skill
Gateway key
ui-generation
Source
github.com/happy-technologies-llc/happy-platform-skills