Skip to content

Architect track

Service Portal & Employee Center

How portals are built, and how to keep them fast and upgradeable.

4 sections · 24 entries

Portal anatomy

Every Service Portal (including Employee Center) is this hierarchy.

  1. Portal (sp_portal)URL suffix, theme, homepage, KB and catalog defaults, header and footer.
  2. Theme (sp_theme)SCSS variables, header/footer widgets, CSS and JS includes.
  3. Page (sp_page)Containers → rows → columns. Reached with ?id=<page_id>.
  4. Widget instance (sp_instance)A widget placed on a page, with its own option values.
  5. Widget (sp_widget)HTML template, CSS, client controller, server script, link function, option schema.
  6. Providers & dependenciesShared Angular services/directives and JS/CSS libraries used by widgets.

Widget data flow

The server script always runs first. Understand this and most portal bugs disappear.

  1. 1 · ServerServer script fills data (and reads options and input).
  2. 2 · ClientController reads c.data; the template binds to it.
  3. 3 · c.server.update()Sends c.data back as input; the server script reruns; c.data refreshes.
  4. 4 · c.server.get({action})Sends only what you pass: lighter than update() for one action.Hidden gem
  5. 5 · spUtil.recordWatch()Live updates when matching records change. Use narrow filters.

Widget patterns

A clean, secure skeleton to start every custom widget from.

Server script: action router

One widget, several actions, ACL-aware queries, nothing heavy on first load.

(function () {
  data.items = [];
  var limit = parseInt(options.limit, 10) || 10;

  if (input && input.action === 'close') {
    var rec = new GlideRecordSecure('incident');
    if (rec.get(input.sys_id) && rec.canWrite()) {
      rec.setValue('state', 6);
      rec.update();
      data.message = gs.getMessage('Closed');
    }
  }

  var gr = new GlideRecordSecure('incident');
  gr.addQuery('caller_id', gs.getUserID());
  gr.addActiveQuery();
  gr.orderByDesc('sys_updated_on');
  gr.setLimit(limit);
  gr.query();
  while (gr.next()) {
    data.items.push({
      sys_id: gr.getUniqueValue(),
      number: gr.getValue('number'),
      title: gr.getDisplayValue('short_description'),
    });
  }
})();

Client controller

Use c.server.get for actions, spUtil for messages, recordWatch for live lists.

api.controller = function ($scope, spUtil) {
  var c = this;

  c.close = function (item) {
    c.server.get({ action: 'close', sys_id: item.sys_id }).then(function (r) {
      c.data.items = r.data.items;
      spUtil.addInfoMessage(r.data.message);
    });
  };

  // refresh when my incidents change
  spUtil.recordWatch($scope, 'incident', 'active=true^caller_id=' + window.NOW.user_id);
};

Embed one widget inside anotherHidden gem

Reuse OOB widgets instead of rebuilding them.

// Server script
data.formWidget = $sp.getWidget('widget-form', {
  table: 'incident',
  sys_id: $sp.getParameter('sys_id'),
  view: 'ess',
});

<!-- HTML template -->
<sp-widget widget="data.formWidget"></sp-widget>

Portal best practices

What separates a portal that survives upgrades from one that gets rebuilt.

  • ✦Clone, never edit. Never modify OOB widgets. Clone or, better, embed them and add an option.
  • ✦Option schema. Make widgets configurable with options so one widget serves many pages.
  • ✦Security server-side. GlideRecordSecure and canRead/canWrite in server scripts. Client checks are cosmetic.
  • ✦Lean first load. Load the minimum on first render; fetch the rest with c.server.get on demand.
  • ✦No jQuery DOM hacks. Stay inside Angular bindings. DOM manipulation breaks on upgrades and accessibility.
  • ✦Watch recordWatch. Narrow filters only. A recordWatch on a busy table hurts the whole instance.Hidden gem
  • ✦Theme, not inline CSS. Brand through theme SCSS variables so every page stays consistent.
  • ✦Translations. Wrap strings in ${} in templates and gs.getMessage() in scripts from day one.
  • ✦Employee Center. For employee-facing portals, start from Employee Center taxonomy and content instead of a blank portal.
  • ✦Measure. Use the browser network tab and the transaction log to find slow widgets before users do.

Snippets are provided as-is under the MIT licence, without warranty. Test in a sub-production instance first. ServiceNow is a trademark of ServiceNow, Inc.; this page is independent and not affiliated with or endorsed by ServiceNow.