Skip to content

Page and Portal State

Every portal page works with values that change while someone uses it: the year they picked in a filter, the rows a query returned, the customer they clicked in a table, who they are. This page explains where each of those values lives, how long it stays around, and how widgets pass values to each other.

If you remember one thing, make it this: a value's path tells you its lifetime. $vars is forgotten when you leave the page. $portal.state lasts for the whole visit. $params and $data belong to the page and its layouts.

The kinds of state

Path What it holds How long it lasts
$params The page's parameter values, such as a selected year or a customer id. Values can also arrive through the page address, from a link or from a button's Navigate to page action The page's own parameters last while you are on the page. Parameters declared on a layout last for the whole visit
$data The results of the page's data sources, and of the data sources on its layouts The page's own data sources load again every time you open the page. Layout data sources load once and are reused on every page
$record and $it The current record inside a widget that repeats its content: a Data List item, a Data Table row, a tab or section built from data, or a repeated chart series. $record is the record of the nearest repeating widget. A repeated chart series names its record $it.<alias>, $it.record by default While that item is drawn. Only available inside the widget that repeats
$vars Scratch values you decide on: the row a table selected, a value one widget wants another widget to see While you are on the page
$portal.state Values that must survive moving to another page, such as a region picked on the home page For the whole visit, on every page
$user The signed-in person viewing the portal. Read-only For the whole visit

Reloading the browser starts a fresh visit. $vars and $portal.state are empty again and every parameter goes back to its default. The one exception is values in the page address, such as ?year=2024. Those are applied again, which is what makes links shareable.

Windows are separate from the page they open over. A window has its own $params, $data and $vars. It shares only $portal.state with the page.

Connecting two widgets with $vars

Say you have a table of customers and want a details panel next to it that shows whoever is clicked. That is what $vars is for. One widget writes a value, the others read it. There is nothing to declare: write to any name you like.

Here is how the customer example comes together:

  1. Select the table and open its Selection settings. Turn on Enable, choose the Mode (one row or several) and set Selection to a path such as $vars.selectedRow.
  2. Add a Text widget for the details panel and use the value in its text:
Selected customer: {$vars.selectedRow.name}
  1. To hide the panel until something is picked, give it a Visible Expression:
{$vars.selectedRow} != null
  1. To load more detail for the selected customer, use the value as a parameter mapping on a data source, usually by its key: {$vars.selectedRow.id}.

The selection stores only the row's key fields, not the whole row. By default that is id, so clicking a customer stores { id: 42 }. If the panel needs the name too, add name under Key fields with Add field. In Multiple mode the path holds a list of these small records.

Other widgets write to $vars in the same way: the selection of a Data List, Tabs or Accordion, and the page and page-size paths of Pagination. Parameter widgets write to $params instead. Buttons don't write state at all. Their actions open windows, show toasts and move between pages.

$vars belongs to the page you are on. Open another page and it starts empty again. That is what makes it safe for scratch values. When a value must survive moving to another page, use $portal.state.

Values that outlive the page with $portal.state

$portal.state works exactly like $vars: bind to any path under it, with nothing to declare. The difference is that its values stay for the whole visit and every page can see them.

Use it when one page sets something and another page needs it. A region picked on the home page and used by every dashboard is a good fit. Use $vars for everything that only concerns the page you are on.

The person viewing the portal with $user

$user is the signed-in person. It is always available on every page and layout, with nothing to declare.

Path What it holds
{$user.displayName} The name to greet them by
{$user.email} Their email address
{$user.firstName}, {$user.lastName} The two halves of the name
{$user.id} Their numeric user id
{$user.language} Their language code, for example en
{$user.workspaceId}, {$user.workspaceName} The workspace the portal belongs to

A few typical uses:

  • A greeting in a Text widget: Welcome back, {$user.displayName}.
  • Showing a panel to one person only, with a Visible Expression: {$user.email} == '[email protected]'.
  • Loading someone's own rows, by mapping a data source parameter to {$user.email}.

$user is read-only. Binding an input to $user.email does nothing. The value comes from who is signed in, and any write to it is ignored.

$user is not a security boundary

Expressions run in the browser. A parameter built from $user is sent to the server from the browser like any other value, and a determined person can change it on the way. Use $user to show the right thing to the right person and to save them picking their own name from a filter. Never use it as the only thing between a user and data they should not see. Where data must be restricted, restrict it in the query, in the database, or in the API behind the data source.

Values shared by every page on a layout

Parameters and data sources can be declared on a layout as well as on a page. Everything declared on a layout is shared by every page that uses that layout.

Shared layout parameters

A parameter on a layout keeps its value as the reader moves between pages. Pick a year in a filter that sits on the layout, open another page, and the same year is still selected. A page can declare its own parameter with the same name. On that page the page's value wins, and the layout's value is untouched for the other pages.

Layout data sources load once

A data source on a layout, for example the current user's profile, is fetched the first time it is needed and then reused on every page that shares the layout. It is fetched again only when something it depends on changes, such as a parameter it uses or another data source it reads.

Reusing a workspace data source

Instead of defining a query on the page, a data source can point to one of the workspace's reusable data sources. In the data source editor, pick Reusable data source as the Type and choose it from the list. The page keeps its own name for it, its own parameter mapping and its own dependencies; the query and connection come from the reusable data source, so a change to it shows on every page and report that uses it.

Reusable data sources are managed under Reusable Data Sources in the Portals sidebar, the same list as in Reports.

Picking another Type again gives the page its own copy to edit, and deleting the page's data source leaves the reusable one in place. Using a reusable data source needs Read access to Data Sources (see Roles).

Reusing a workspace parameter

A page or layout parameter can use one of the workspace's reusable parameters instead of its own definition. When adding a parameter, pick Use existing above the Parameter section and choose it from the list. The page keeps its own name, label, Required and Display settings and default value. The type and options come from the reusable parameter, so a change to it shows on every page and report that uses it.

Reusable parameters are managed under Reusable Parameters in the Portals sidebar, the same list as in Reports. Deleting the page's parameter leaves the reusable one in place. Using a reusable parameter needs Read access to Reusable Parameters (see Roles).

Showing that data is loading

Any widget can show a loading state while its data is being fetched. Select the widget and open the Loading section in its properties:

  • Show loading overlay turns the overlay on for this widget.
  • Type picks the look. Spinner (the default) dims the widget and shows a spinner on top. Skeleton covers it with grey placeholder bars until the data arrives.

The overlay appears while any data source the widget reads through $data is loading: in its text, its settings, its repeat, or in any widget placed inside it. Loading always goes to the nearest overlay. Turn it on for a section, and the section shows one overlay for everything inside it. Turn it on for a chart inside that section as well, and the chart shows its own overlay for its own data while the section covers the rest.

The loading state is also available as $dataLoading.<name>, which is true while that data source is loading, so you can bind to it yourself. For example, use {$dataLoading.orders} in a visibility condition.

Passing values to another page

To open another page with values already filled in, do one of two things:

  • Give a Button the Navigate to page action and fill in the target's Page parameters. See Actions and Windows.
  • Point a Nav Link at a route with the values in it, for example /orders?id={$record.id}.

Either way the values travel in the page address, so the reader can bookmark or share the result. A few rules apply:

  • The target page must declare each parameter. Anything it doesn't declare is ignored.
  • An empty value leaves the target's default in place.
  • A value in the address also overrides a shared layout parameter of the same name.

Inspecting state

When something doesn't show what you expect, look at the values directly.

In the page editor, Data Explorer shows $params, $data, $vars, $user and $portal exactly as the current page sees them. For shared values it also shows which layout they come from.

The editor's Set Values saves parameter values for you, so the preview opens with them filled in. These values are yours alone and don't change what readers see.

Good to know

  • $portal.scopes is where the runtime keeps the values behind $params, $data and $vars. You can read it, and the Data Explorer shows it, but writes to it are ignored. Change a parameter through $params instead.
  • Inside a data table's Table Preview in the editor, $portal, $vars and $user read as empty. Check expressions that use them in the portal preview instead.