Skip to content
Log in

POS views

On this page

A POS View is a single HTML template that takes over part of a POS screen. It can contain HTML, CSS, JavaScript, and Liquid, so it’s the option to reach for when action groups and layouts can’t produce the screen you need.

Each view is one record with two fields that matter: an Identifier you refer to it by, and the HTML Template itself.

Where you can use a view

A view is either attached to a record through that record’s POS View lookup field, or named by an action’s parameters. What you attach it to decides how much of the screen it replaces and which Liquid variables it receives.

Attached to What it replaces What you get in Liquid
POS Layout (list or grid) The records list. The search bar, filters, filter chips, and action buttons still render around it records, the record drops for the current page
POS Layout (record) The headline and field list inside the record card. The back button and actions still render record
POS Layout Field How that one field renders, on a card or in a labeled row record
POS Action Item The contents of the button, not what tapping it does The item’s params, plus action_item, plus product on a cart:add_product item

A layout view renders once for the whole screen, so on a grid layout your template draws the entire grid rather than one card. To render markup per record, attach the view to a POS Layout Field instead.

On a form layout, such as the customer form, a field view has to include a form element (<input>, <select>, <textarea>) whose name attribute matches the field name. Without it the value is not captured when the form is submitted.

Two actions take a view by identifier rather than through a lookup field:

  • modal:open:view opens a view as a modal. Pass the identifier as the view_identifier param. Any other params become Liquid variables, alongside the context carried over from an earlier action in the chain.
  • nav:view opens a view as a full-page screen. Pass view (the identifier) or view_sc_id. Supply record_id and object_name together to get a record variable.

The order confirmation screen and the quick search panel are layouts too, so you customize them by attaching a view to those layouts rather than through any separate mechanism. The order confirmation view also gets an outcome_message variable alongside record.

Layout views and view pages don’t scroll on their own, so wrap your content in an overflow container.

Reserved identifiers

One identifier changes behavior by name alone. A view saved with the Identifier product_action_card replaces the default card for every cart:add_product action item that has no view of its own, and receives the item’s params, action_item, and product.

Create a view

:::tip If you are not familiar with using frontend code, you can ask an AI agent to do this work for you, and request design changes in plain language. To use this method for creating views, see build in POS using AI agents. :::

The following describes how to manually create a POS view record and attach it to one of the records in the table above.

Before you start

  • You need Salesforce admin access to create POS View records, and to edit the POS Layout, POS Layout Field, or POS Action Item record you will attach it to.
  • Decide where the view will be used. That choice sets which Liquid variables your template receives, so it’s worth settling before you write any markup.
  1. Go to the POS Views list and select New.
  2. Enter a POS View Name. This is the internal name administrators see in Salesforce, not anything staff see on a register.
  3. Enter an Identifier if you will refer to the view by name: from another view with {% render %}, from a modal:open:view or nav:view action, or to claim a reserved identifier such as product_action_card. A view attached only through a lookup field doesn’t need one.
  4. Enter the HTML Template. The record won’t save while this is empty, and the field holds up to 131,072 characters.
  5. Save the record.
  6. Open the POS Layout, POS Layout Field, or POS Action Item record the view belongs to, and set its POS View lookup field. Skip this step for a view you open by identifier from a modal:open:view or nav:view action.
  7. On a register, reload the POS.

:::warning Salesforce does not enforce uniqueness on Identifier. If two views share one, the POS takes whichever it finds first, and which that is isn’t predictable. Search the POS Views list for an identifier before reusing it. :::

The screen you attached the view to now renders your template in place of its default. If it still shows the default, the register hasn’t picked up the new record yet. Sync the device from Settings then Manage data, described in Manage POS data.

Update a view

Use this process to change a view that is already attached. Editing the HTML Template takes effect wherever the view is used, so a shared view changes every screen that renders it.

  1. Go to the POS Views list and open the view you want to change. The list shows Identifier alongside the name, and you can search on either.
  2. Edit the HTML Template.
  3. Save the record.
  4. On a register, reload the POS.

The register now renders your edited template.

To see what a view is attached to before you change it, open the view and check its POS Layout Fields and POS Action Items related lists. Layouts that use the view are not among them, so check those by filtering the POS Layouts list on its POS View field.

For a worked example that builds a view and wires it to a layout field from scratch, see Add custom fields to the End Shift form.

Liquid on a register

The POS runs LiquidJS in the register’s browser, not the Liquid engine your storefront theme uses. The syntax is the same and standard filters like upcase and date work, but the StoreConnect-specific set is much smaller.

Globals that work

These twelve globals are available in any view:

Global What it gives you
current_store The store
current_outlet The outlet the register belongs to
current_register The register
current_staff The signed-in POS user
current_cart The cart open on the register
current_customer The customer on the current cart
current_account The account on the current cart
current_order The current order
current_product The product in context
current_pricebook The price book in use
all_products The purchasable products synced to this device
store_variables Declared, but currently raises. See the note below

Globals that raise an error

Thirty storefront globals are declared in the POS but not implemented, and calling one throws rather than rendering empty. The ones most likely to be reached for out of habit are current_request, current_page, current_search, current_product_category, theme_variables, session_variables, all_pages, all_menus, all_media, and all_content_blocks.

If a view renders blank or the screen errors, an unimplemented global is the first thing to check.

:::warning store_variables is declared as a global but does not work in a POS view. Reading it raises, and a view whose template raises renders as an empty box with no error on screen. Put the values your template needs in {% assign %} statements at the top of it instead. Store variables that configure POS behavior, such as the pos.payment_options keys, are unaffected: the app reads those directly rather than through Liquid. :::

Extra filters and tags

On top of the standard LiquidJS set, the POS registers these:

  • money formats a number as currency.
  • keys, merge, set_key, unset_key, collect_keys, rename_keys work on maps, and mirror the map filters on the web storefront.
  • serialize and deserialize convert between a value and a string, which is how you pass structured data into a data- attribute for your JavaScript to read.
  • {% query %} reads records from the device’s local database.
  • {% new %} creates a Map or a List in the template, optionally seeded from JSON: {% new Map totals %} or {% new List codes = '["a", "b"]' %}.

Storefront filters that aren’t in that list are not available on a register.

Reusing a view inside another view

Views resolve each other by Identifier, so a shared piece of markup can live in its own view and be pulled into others. A rendered view gets its own scope, so pass in anything it needs by name:

```liquid

{% render ‘product_card_body’, product: record, show_price: true %} ```

Globals still reach the rendered view, but the calling template’s own variables do not. There’s no folder structure, so use the identifier exactly as it appears on the record.

JavaScript in a view

Script tags in a view do run, but not the way they would in an ordinary page. The POS renders your template, then finds each <script> and executes it. Inline code and src scripts both work, and three consequences are worth knowing:

  • Scripts run after the view is on the page. DOMContentLoaded has already fired, so don’t wait for it. Your elements exist by the time your code runs.
  • Scripts run in global scope. They’re not scoped to the view.
  • Scripts run again when the view re-renders. A top-level const or let throws on the second run because the name is already declared. Wrap your code in an immediately invoked function, or hang what you need off window.

```html

```

The exception is a field view on a list or grid layout. Those templates are rendered in one batch for every visible record and injected as HTML, and injected HTML never runs its scripts. To get code onto a list or grid screen, put the script in a view attached to the layout itself rather than to one of its fields.

Your JavaScript can drive the POS through the scAction function, which calls any built-in action such as adding to the cart, starting checkout, printing a receipt, or saving a record. See scAction for the syntax and the POS actions reference for every action and its parameters.

To call something outside Salesforce from inside a view, see Bring external data into POS.

What a view can’t do

  • It can’t reach data that hasn’t synced. A view runs against the device’s local database, so it sees what the POS has already downloaded, not whatever is in Salesforce right now.
  • It can’t run Apex. Server-side logic has to be reached over HTTP like any other external call.
  • It can’t be scoped to one store. Like every POS configuration record, a view is org-wide, so two stores in the same Salesforce org share it. Branch inside the template on current_store or current_outlet where behavior needs to differ.

If a view renders blank after a reload, open the browser console on the register before changing anything else. An unimplemented global or a Liquid syntax error reports there, and names the line it failed on.

Was this article helpful?

Was this article helpful?