Earl Duque
Administrator
Administrator

I feel like I get to teach this ServiceNow concept at least once a month and so it’s about time that I write it all out!

 

Hi everyone, I’m Earl, one of your developer advocates here at ServiceNow, and today we’ll explore the dance between the server script and the client script, and how the data and input objects enable our widgets to communicate back and forth.

 

Basically, if you’ve ever worked with ServiceNow widgets and found yourself wondering, “Wait, how does data actually get from point A to point B?” then this is for you.

 

The Cheat Sheet

 

Maybe you’re here just for the cheat sheet/summary. Here you go:

Flowchart 3.png

 

The Big Picture

 

Before we jump into code, let’s take a moment to paint the bigger picture. In a ServiceNow Service Portal widget, we have two main pieces:

 

  1. Server Script – This runs on the ServiceNow server. It initializes or updates the data object.

  2. Client Script – This runs in the user’s browser. It sees $scope.data and can interact with the user, update things on the screen, and—crucially—send information back to the server using $scope.server.update().

Understand the data_input object in Service Portal widgets - visual selection.png

 

At first, it might sound a bit like a tennis match: the ball (our data) goes from server to client, then from client to server, and back again. But once you see it in action, it feels quite intuitive.

 

A Minimal Example

 

Server Script

(function() {
    data.serverMessage = "Hello from the server!";
})();

Client Controller

api.controller=function() {
    var c = this;
    console.log("Server says:", c.data.serverMessage);
}

HTML Template

<div>
  <p>The server message is: {{data.serverMessage}}</p>
</div>

 

Here’s our first mini-walkthrough. You can think of it as the “Hello World” of ServiceNow widgets.

 

  • In our Server Script, we’re setting data.serverMessage = "Hello from the server!".

  • The Client Controller sees c.data.serverMessage as soon as the page loads.

  • Our HTML template simply displays it with {{data.serverMessage}}.

 

Your data is officially traveling across the internet from your instance to the user’s browser. It’s a small start, but it’s the foundation of everything else we’ll be exploring.

 

Introducing input

/* Server Script */
(function() {
    if (input && input.displayName) {
        data.displayName = input.displayName;
    } else {
        data.displayName = "No name provided";
    }
})();

Now let’s add a twist: the input object. Sometimes, your widget needs parameters—maybe the user wants to provide some information. In Service Portal, that’s where input comes into play.

Think of input as the “stuff coming in” from the client script. If input.displayName exists (from the client script/user), we set data.displayName to that; otherwise, we default it to “No name provided.”

This ensures your widget can be adapted to different situations without needing to rewrite the code every time.

 

Client-to-Server Communication

 

So far, we’ve learned that the server sends data to the client on page load. But what if we want to go the other way? Let’s create a user-driven scenario:

 

  1. The user types a greeting in an input box.

  2. We send that greeting to the server script.

  3. The server script modifies some data based on the user’s input.

  4. The server sends a response back.

 

Understand the data_input object in Service Portal widgets - visual selection (1).png

 

Code for Two-Way Data Exchange

 

Server Script

(function() {
    data.serverMessage = "Hello from the server!";

    if (input && input.greet && input.greetText) {
        data.serverMessage = "Server responds: " + input.greetText;
    }

    if (input && input.displayName) {
        data.displayName = input.displayName;
    } else {
        data.displayName = "No name provided";
    }
})();

Client Controller

api.controller=function() {
    var c = this;
    c.clientText = "";

    c.sayHello = function() {
        c.data.greet = true;
        c.data.greetText = c.clientText;

        c.server.update().then(function() {
            alert("Server says: " + c.data.serverMessage);
        });
    };
}

HTML Template

<div>
  <h3>Hello World Widget</h3>
  <p>Display Name: {{data.displayName}}</p>

  <div>
    <input type="text" ng-model="c.clientText" placeholder="Type a greeting..." />
    <button class="btn btn-primary" ng-click="c.sayHello()">Send Greeting</button>
  </div>

  <p>Server Message: {{data.serverMessage}}</p>
</div>

 

Here’s where the magic happens:

 

  1. Server Script sets a default serverMessage and updates it if we see input.greetText.

  2. Client Script collects the user’s input in c.clientText and uses c.server.update() to send data back.

  3. ServiceNow receives this data as input.greetText, updates data.serverMessage, and returns it to the client.

  4. The client’s c.data is refreshed, so we can display the new serverMessage.

 

This pattern is crucial for any widget that needs to respond dynamically to user actions. You can query tables, run server-side logic, and funnel the results back to the user, all in a matter of milliseconds.

 

Recap of the Data Flow

 

Let’s recap the journey of our data:

 

  1. Page Load

    • The server script runs, initializing the data object.

    • This data object travels to the browser as c.data.

  2. Client Updates

    • The user interacts with the widget (e.g., types in a text box).

    • The client sets c.data properties and calls c.server.update().

  3. Server Re-run

    • The server script runs again, reading what the client sent via input.*.

    • The script updates the data object based on that new input.

  4. Data Returns

    • The updated data object is sent back to the browser, refreshing c.data.

 

Understand the data_input object in Service Portal widgets - visual selection (2).png

 

Real example

 

Server Script

(function() {
	var incidentGr = new GlideRecord('task');
	if (input && input.textQuery){
		data.textQuery = input.textQuery || 'no query provided';
		incidentGr.addEncodedQuery('short_descriptionLIKE' + input.textQuery);
	}
	incidentGr.setLimit(10);
	incidentGr.query();
	data.tasks = [];
	while(incidentGr.next()){
		var task = {
			number: incidentGr.getValue('number'),
			short_description: incidentGr.getValue('short_description')
		}
		data.tasks.push(task);
	}
})();

Client Controller

api.controller=function() {
    var c = this;
    c.clientText = "";

    c.searchForText = function() {
        c.data.textQuery = c.clientText;

        c.server.update().then(function() {
            console.log(c.data.tasks)
        });
    };
}

HTML Template

<div>
  <h3>List of tasks</h3>
  <p>Text query: {{data.textQuery}}</p>

  <div>
    <input type="text" ng-model="c.clientText" placeholder="Text query..." />
    <button class="btn btn-primary" ng-click="c.searchForText()">Query</button>
  </div>

  <ul ng-repeat="task in data.tasks">
    <li>
      {{task.number}} - {{task.short_description}}
    </li>
  </ul>
</div>

 

EarlDuque_0-1739231345023.png

 

 

Putting It into Practice

 

Next time you’re building a widget:

 

  1. Start simple: set default data in your server script and confirm you can display it in the template.

  2. Introduce user input: accept some text or a choice in the client script.

  3. Communicate: use c.server.update() to send data back, and have the server script respond accordingly.

 

Final Thoughts

 

You’ve just learned how the data object is formed on the server, delivered to the client, and then travels back again via input on updates. In the real example, you can see the common use-case of taking a user’s input (text, clicks, etc.) and affect the server script of the widget itself.

 

That’s all for today. For most of us Service Portal veterans, this data/input interaction is second nature to use at this point and hopefully that’s you now too!

3 Comments