Portal and custom application questions turn up in the technical round, usually after the panel has finished with Business Rules and ACLs. They are asked by the developer or lead who owns the front end, and they are used to separate people who have configured a portal from people who have only read about one. The eleven questions below come from real interview rounds collected by our trainers. Seven cover Service Portal itself, four cover building a custom scoped application. Each answer gives the definition, the reason the panel cares, and a worked example you can repeat at the table.
Service Portal questions
What is Service Portal?
Service Portal is the end user facing interface of ServiceNow. It sits alongside the platform UI, not inside it. When an employee raises a request, browses the catalog or reads a knowledge article at https://instance.service-now.com/sp, they are in Service Portal. When a fulfiller works the incident queue in the standard list and form view, they are in the platform UI.
The important point for an interview is that Service Portal is a separate rendering stack. It is built on AngularJS and Bootstrap, it does not render platform forms and lists, and platform client scripting objects do not automatically exist there.
A portal is made of a small set of records:
| Record | Table | What it holds |
|---|---|---|
| Portal | sp_portal |
URL suffix such as sp, home page, login page, 404 page, theme, main menu |
| Page | sp_page |
A single addressable screen, identified by its ID in ?id= |
| Widget | sp_widget |
A reusable block of HTML, client script, server script and CSS |
| Widget instance | sp_instance |
One placement of a widget on one page, with its own options |
| Theme | sp_theme |
Header widget, footer widget, colour variables, CSS and JS includes |
The theme is worth knowing by name because it answers the common follow up about branding. You do not edit each page to change the look. You point the portal record at a theme, and the theme carries the header widget, the footer widget, the SCSS variables for colours and fonts, and any additional style sheets. Change the theme and every page under that portal changes with it.
What are widgets?
A widget is the reusable unit of a Service Portal page. Everything visible on a portal page is inside a widget, including the header and the footer.
One widget record holds four parts that run in three different places:
- HTML template. An AngularJS template. It binds to
dataandoptionsand contains the directives and event handlers. - Client controller. JavaScript that runs in the browser. It has no access to tables, no GlideRecord, no
gs. - Server script. JavaScript that runs on the instance, inside an isolated scope, once per widget load. It has GlideRecord,
gs,$spand the current user's ACLs. - CSS / SCSS. Styling scoped to that widget.
There is also an option schema, a link function for direct DOM work, and demo data for the widget editor preview.
This is the answer where our trainers see the most damage. Candidates know the syntax but not the execution model. They cannot say when the code runs, where it runs and what data it can reach, and the client side versus server side split is the classic ServiceNow version of that gap. If you cannot state that the server script runs once on the instance before the page is delivered and the client controller runs in the browser after it, the rest of your portal answers will not be believed.
Here is a complete widget. It lists the logged in user's open incidents and lets them acknowledge one.
The HTML template:
<div class="panel panel-default">
<div class="panel-heading">{{::c.data.title}}</div>
<ul class="list-group">
<li class="list-group-item" ng-repeat="inc in c.data.incidents">
<strong>{{inc.number}}</strong> {{inc.shortDescription}}
<span class="label label-info">{{inc.state}}</span>
<button class="btn btn-xs btn-default"
ng-disabled="c.working"
ng-click="c.acknowledge(inc)">Acknowledge</button>
</li>
<li class="list-group-item" ng-if="!c.data.incidents.length">
No open incidents.
</li>
</ul>
</div>
The server script:
(function() {
/* Runs on the instance. Has GlideRecord, gs and $sp.
Populates the data object, which is serialised to the browser. */
data.title = options.title || 'My open incidents';
data.limit = parseInt(options.limit || 10, 10);
// input arrives only when the client calls c.server.update()
if (input && input.action === 'acknowledge' && input.sysId) {
var ack = new GlideRecord('incident');
if (ack.get(input.sysId) && ack.canWrite()) {
ack.setValue('comments', 'Acknowledged by the caller from the portal.');
ack.update();
data.message = 'Incident ' + ack.getValue('number') + ' updated.';
} else {
data.message = 'You cannot update that incident.';
}
}
data.incidents = [];
var gr = new GlideRecord('incident');
gr.addQuery('caller_id', gs.getUserID());
gr.addActiveQuery();
gr.orderByDesc('sys_created_on');
gr.setLimit(data.limit);
gr.query();
while (gr.next()) {
data.incidents.push({
sysId: gr.getUniqueValue(),
number: gr.getValue('number'),
shortDescription: gr.getValue('short_description'),
state: gr.getDisplayValue('state')
});
}
})();
The client controller:
api.controller = function($scope, spUtil) {
/* Runs in the browser. No GlideRecord here.
c.data is the data object the server script built. */
var c = this;
c.working = false;
c.acknowledge = function(incident) {
c.data.action = 'acknowledge';
c.data.sysId = incident.sysId;
c.working = true;
// sends c.data to the server script as input, then replaces c.data
c.server.update().then(function() {
c.data.action = undefined;
c.data.sysId = undefined;
c.working = false;
if (c.data.message)
spUtil.addInfoMessage(c.data.message);
});
};
// re-run the server script when another widget announces a change
$scope.$on('incident.updated', function() {
c.server.refresh();
});
};
Read the two scripts against each other. The GlideRecord query exists only in the server script. The button handler exists only in the client controller. input is the one channel from browser to instance, data is the one channel from instance to browser. That is the whole contract.
What is a widget instance?
A widget is the definition. A widget instance is one placement of that widget in one column of one page, stored in sp_instance.
The distinction matters because instances carry configuration. The same Data Table widget placed twice on the same page can show open incidents in one container and closed changes in the other, because each instance holds different option values. You do not copy the widget to change its behaviour, you set options on the instance.
Options come from the widget's option schema, a JSON definition of fields on the widget record. Each entry has a name, a label, a type such as string, boolean, integer or reference, and a default value. Once the schema exists, the instance record shows those fields in the page designer, and the values reach your code in two places:
options.titleinside the server scriptc.options.titleinside the client controller and the HTML template
In the widget above, options.title and options.limit are schema fields. An admin can drop that widget on three different pages with three different limits and never touch the script.
Two follow ups are common. First, instances can be given a bootstrap colour, a title, a glyph and a size directly from the page designer without any schema. Second, an instance can be cloned. Cloning the instance copies the placement and its options; cloning the widget copies the code and gives you a second definition to maintain, which you normally do not want.
What is the difference between a widget and a page?
A page is the container and the address. A widget is the content.
| Page | Widget | |
|---|---|---|
| Table | sp_page |
sp_widget |
| Identity | Page ID used in the URL, ?id=incident_list |
Widget ID, used by $sp.getWidget() and by instances |
| Contains | Containers, rows, columns, widget instances | HTML, client controller, server script, CSS, option schema |
| Reusable | On one portal at a time, per its ID | On any number of pages and portals |
| Has a server script | No | Yes |
A page has no logic of its own. It is a layout: containers hold rows, rows hold columns sized on the Bootstrap twelve column grid, and columns hold widget instances. When you navigate to ?id=sc_cat_item&sys_id=abc123, the portal loads the page record with ID sc_cat_item and every widget instance placed on it. Each of those widgets runs its own server script, and each can read sys_id from the URL with $sp.getParameter('sys_id').
So the rule to state in the interview is simple. If the requirement is a new screen at a new URL, you create a page. If the requirement is a new block of behaviour that will appear on several screens, you create a widget and place it.
What is the role of AngularJS in Service Portal?
Service Portal is an AngularJS 1.x application. That single fact explains most of the framework's behaviour.
Angular provides the following:
- Two way binding.
c.datain the controller and{{c.data.x}}in the template stay in step without any DOM code. This is why portal widgets have almost no jQuery in them. - Directives.
ng-repeat,ng-if,ng-model,ng-clickand the rest are what the widget template is written in. ServiceNow also ships its own directives, such assp-widgetfor embedding one widget inside another. - Dependency injection. The client controller signature
function($scope, spUtil, $http)is Angular injection. ServiceNow's own services,spUtil,spModal,spAriaFocusManagerand others, are injected the same way. - Single page routing. Navigation inside the portal replaces the view without a full page reload, which is why a page transition feels faster than a platform UI form load.
The practical consequence, and the thing panels want to hear, is that platform UI client scripting does not apply. The platform form renderer is not present, so the objects it creates are not present either. Angular manages the view, so you change the model and let the framework redraw, rather than reaching into the DOM.
Note for a senior round: newer ServiceNow interfaces built with UI Builder use a different component framework. Service Portal itself remains Angular based, and most customer portals in production today are Service Portal.
How do you pass data from server to client in a widget?
Through the data object.
The server script runs first, on the instance. Anything you attach to data is serialised and delivered to the browser, where the client controller sees it as c.data and the template sees it as c.data too. Nothing else crosses automatically. A variable declared in the server script and not attached to data does not exist on the client.
The return path is input. When the client calls c.server.update(), the current c.data object is posted back, the server script runs again, and it arrives as input. When the client calls c.server.get({action: 'x'}), the object you pass is posted as input without replacing the whole data object.
// server script
data.userName = gs.getUser().getFirstName(); // reaches the client
var secretQuery = 'active=true'; // does not reach the client
Three rules to state, because they are what separates a configured portal from a slow one:
- Send only what the template renders.
data.incidentswith four string fields per row is cheap. Pushing whole GlideRecord objects is not possible anyway, and pushing fifty fields per row is wasteful. - Keep every query server side. If you find yourself wanting a record in the client controller, the answer is another server call, not a client side lookup.
- Respect ACLs. The server script runs as the logged in user, so
canWrite()andcanRead()are real checks, not decoration.$sp.canReadRecord()exists for the same reason.
$sp is the server side API available inside every widget server script. It is a GlideSPScriptable object and it handles the portal specific work that plain GlideRecord does not:
| Call | Use |
|---|---|
$sp.getParameter('sys_id') |
Read a URL parameter from the portal address |
$sp.getRecord() |
Get the record the current page is displaying |
$sp.getValue(field) / $sp.getDisplayValue(field) |
Read one field from that record |
$sp.getWidget('widget-id', options) |
Render another widget inside this one and return its data |
$sp.canReadRecord(table, sysId) |
Check read access before sending anything to the client |
$sp.getStream(table, sysId) |
Get the activity stream for a record |
$sp.log(message) |
Write to the portal log |
When a panel asks a candidate to debug a widget that shows nothing, this is the ground to stand on. Check whether the server script actually attached the rows to data, check whether the query returned anything for this user rather than for admin, and check whether an ACL is filtering the result. Our trainers rate the debugging question higher than the definition question for exactly this reason: "your widget is blank, what do you check" tells the panel far more than "what is a widget".
What is $scope?
$scope is the AngularJS object that binds a controller to its view. Every expression in the template is evaluated against a scope, and when a property on the scope changes, Angular updates the DOM that reads it.
In Service Portal widgets you usually do not touch it directly, because ServiceNow uses the controllerAs pattern. The controller is published on the scope as c, which is why templates say c.data and controllers start with var c = this;. You inject $scope only when you need what the scope itself offers:
- Events.
$scope.$emit()sends an event up,$scope.$broadcast()sends it down,$scope.$on()listens. Widget to widget communication on the same page uses$rootScope.$broadcast()with a$scope.$on()listener in the receiving widget, as in theincident.updatedlistener in the example above. - Watchers.
$scope.$watch('c.data.filter', fn)reacts to a value changing. - Digest control.
$scope.$apply()is needed when a value changes outside Angular's knowledge, such as inside a plainsetTimeoutor a third party callback. - Portal context.
$scope.page,$scope.portaland$scope.userare available in the portal's scope hierarchy.
This is also where the g_form follow up lands. A custom portal widget cannot use g_form. g_form is created by the platform form renderer. Service Portal does not run the platform form renderer, so in your own widget there is no form object and no g_form, no g_list, and no g_scratchpad.
Two clarifications keep this answer accurate. Catalog Client Scripts and Catalog UI Policies marked to run in Service Portal do get a g_form, because the catalog item widget builds a form and exposes a subset of the API to those scripts, with DOM level calls such as g_form.getControl() unsupported. That is a supplied widget doing the work, not your code. For your own widget, the equivalent of setting a field is changing c.data, and the equivalent of a mandatory check is validating in the client controller before you call c.server.update(), then validating again in the server script because the client can be bypassed.
What the interviewer is actually checking on Service Portal
- Whether you can place a line of code in the right runtime. Server script, client controller, template, and what each one can reach.
- Whether you understand
dataandinputas the only two channels between browser and instance. - Whether you configure with widget options and instances rather than cloning widget code for every variation.
- Whether you know that platform client scripting objects do not exist in the portal, and can say what replaces them.
- Whether you can debug a blank widget instead of only defining one.
Custom application development questions
What is a scoped application?
A scoped application is an application with its own namespace and its own boundary. It is defined by a record in sys_scope and identified by a scope name of the form x_<vendor prefix>_<app name>, for example x_rzx_asset_audit.
Everything you build inside the application is stamped with that scope. Tables get the prefix, so a table called Audit Run becomes x_rzx_asset_audit_audit_run. Script Includes, Business Rules, UI Pages, Flows, roles and properties all carry the scope on their record.
The scope is not just a naming convention. It is enforced at runtime:
- Code in one scope cannot read or write another scope's tables unless that table's application access permits it.
- Code in one scope cannot call another scope's Script Include unless that Script Include allows cross scope calls.
- A scoped application gets its own system properties namespace and its own roles.
- Deleting the application deletes its files together, because they are all related to the same
sys_scoperecord.
When you create tables inside a scoped app you still decide between a new base table and extending an existing one. Extending Task gives you number, short description, state, assigned to, approvals, the activity stream and every Task level Business Rule and report, which is normally what you want for anything a fulfiller works. Extending an existing table also means inheriting its fields and its behaviour, so you do not extend Incident just to get four fields you could have added yourself.
This is where panels go five levels deep. Which table did you create, what was the requirement, why did you extend Task rather than build standalone, and what was your own role in it. Manufactured project experience does not survive that sequence, so prepare one real application you actually built, even a small one from training, and be able to walk every layer of it.
What is the difference between a scoped application and a global application?
| Scoped application | Global application | |
|---|---|---|
| Scope record | Own record in sys_scope, prefix x_... |
Runs in the Global scope |
| Namespace | Tables, scripts and properties prefixed | No prefix, shares the global namespace |
| Runtime protection | Cross scope access controlled and logged | No scope boundary enforced |
| API restrictions | Some legacy and unsupported APIs are blocked | Most APIs available |
| Distribution | Publish to the application repository or the ServiceNow Store | Moves by update set only |
| Naming collisions | Not possible with other apps | Possible with other work in Global |
What belongs in a scoped app is anything that is a distinct product: a new business capability with its own tables, roles and screens, anything you may install on more than one instance, anything a vendor or a different team will maintain.
What stays in Global is configuration of what ServiceNow already ships. A Business Rule on Incident, a UI Policy on Change Request, a catalog item, an ACL on sys_user. That work belongs to the base system, not to your application, and moving it into a scope only makes it harder to reach.
The honest summary for an interview is that scoped is the default for new development and Global is for configuring the out of the box product. Anyone who says everything should be scoped has not tried to write a Business Rule on a base table from inside a scope.
What is application access?
Application access is the set of controls that decide whether code running in one scope may touch an artefact belonging to another scope.
On a table, the Application Access section of the table record holds:
- Accessible from. Either "All application scopes" or "This application scope only". This is the main switch.
- Can create, Can read, Can update, Can delete. Which operations other scopes may perform, assuming they are allowed in at all.
- Allow access to this table via web services. Whether the table is exposed to inbound web service calls.
- Allow configuration. Whether another scope may add fields, Business Rules and other configuration to the table.
On a Script Include, the same section carries Accessible from, plus a caller access setting that governs whether other scopes may call it, with options to allow it, to track it in the cross scope privilege records, or to refuse it.
Two points that separate a good answer from a recited one. First, application access is not a replacement for ACLs. Application access asks whether the calling application may reach this table at all, and ACLs then ask whether this user with these roles may read or write this record. Both have to pass. Second, when a cross scope call is attempted, ServiceNow records it as a cross scope privilege, which an admin can then allow or deny. If your scoped app suddenly cannot read a table it read in development, that privilege record is the first place to look.
How do you move scoped applications between instances?
There are three supported routes, and the interviewer wants you to pick the right one for the situation rather than list all three and stop.
1. Application repository. From Studio, publish the application to the internal application repository with a version number. On the target instance, go to System Applications and install it, or update it to the new version. This is the normal path for moving your own application from development to test to production within the same instance family. Versions are tracked, so a rollback means installing the previous version.
2. Source control. Link the application in Studio to a Git repository, then commit and push. On the target instance, import the application from source control and switch to the branch or tag you want. This is the path for teams with multiple developers, because it gives you branches, code review and a real history. It is also the answer to give when asked how two developers work on one application at the same time.
3. Publish to update set. From Studio, publish the application to an update set, export that update set as XML, and load and commit it on the target. Use this when the instances cannot see each other, for example moving to a customer instance or to a personal developer instance.
The comparison to update sets is worth stating clearly, because it is the usual follow up:
| Application repository | Update set | |
|---|---|---|
| Unit moved | The whole application, versioned | A named collection of individual changes |
| Scope | Scoped applications | Global changes, and scoped changes made after publishing |
| Versioning | Yes, install or roll back a version | No version number, commit order matters |
| Typical use | Shipping the application | Configuration changes to base tables |
Two facts to have ready. Neither route moves data records by default. Reference data such as choice lists, roles and default configuration records must be added deliberately, for example through the application's own record inclusion in Studio or through a data load on the target. And once an application is installed on the target, later changes made on the source instance still need a new published version or a new update set. Publishing once does not create a live link.
What the interviewer is actually checking on custom applications
- Whether you have built an application rather than only configured existing ones.
- Whether you can defend a table design decision, especially extending Task against building standalone.
- Whether you understand the scope boundary as a runtime rule and not a naming style.
- Whether you know which artefacts belong in a scoped app and which belong in Global.
- Whether you can choose a migration route and justify it for the instances in front of you.
Preparing these answers properly
The eleven questions above cover the two areas where portal developers are separated from portal readers: knowing where each line of code runs, and knowing what a scope boundary actually enforces. Build one widget end to end in a personal developer instance, break it on purpose, and fix it. Then build one small scoped application with two tables, publish it to an update set, and install it somewhere else. After that, every question on this page has a real example behind it instead of a definition.
If you want that practice structured, with review of the widget you build and the application you scope, our ServiceNow training programme at RizeX Labs runs candidates through the full development track, including Service Portal and custom application development, with interview practice on the same question bank these answers come from.