{"title":"POS layouts","slug":"pos-layouts","url":"https://support.storeconnect.com/articles/pos-layouts","url_markdown":"https://support.storeconnect.com/articles/pos-layouts.md","subtitle":null,"summary":"Configure POS layouts to control how records are displayed and interacted with in the POS. Layouts can be list, record, form, or grid type, and support custom fields, filters, action items, and POS views for tailoring staff workflows.","type":"Help_Documentation","video_url":"","keywords":"POS layout, list layout, record layout, form layout, grid layout, action items, POS views, custom fields, filters, POS layout identifier, action groups, layout fields, Pos_Layout__c, configure POS layout, POS action item, cart actions, navigation actions, modal actions, print actions, grid card, CSS card sizing, layout filter, system layouts, snake_case identifier, action chaining","last_modified":"2026-08-21T07:12:35+0000","body_markdown":"POS layouts control how records are displayed and interacted with in the StoreConnect POS. Each layout targets a specific Salesforce object and defines which fields appear, what filters staff can use, and what actions are available.\n\nLayouts are configured in Salesforce and consumed by the POS automatically based on their **identifier** — the value that tells the POS where to place the layout in the interface.\n\n## Layout types\n\nPOS layouts come in four types, set via the **Type** field on the `Pos_Layout__c` record:\n\n| Type | Description |\n|------|-------------|\n| **List** | Displays multiple records in a scrollable list. Supports filters, search, and record-click actions. |\n| **Record** | Displays a single record's fields in a detail view. |\n| **Form** | Presents an editable form. Used for data entry workflows like the end-of-shift form and customer form. |\n| **Grid** | Displays action items arranged in rows, columns, and pages. Used for home screens and product category grids. |\n\n:::note\nList and grid layouts load data at render time. They do not update automatically if records change while the layout is open — staff need to navigate away and back, or apply a filter, to see updated records.\n:::\n\n## Layout fields\n\nLayout fields (`Pos_Layout_Field__c`) define which fields appear on a layout and in what order. Each field record belongs to a layout and specifies:\n\n- **Field Name** — the API name of the field to sync and display. For a standard field, this is the field's own API name. For a custom data mapping field, use the bare field API name — no `data.` prefix here; that prefix is used only when reading the field inside a POS View's Liquid template (`{{ record.data.field_name }}`, see below).\n- **Display Name** — the label shown in the POS. Supports Liquid template syntax (for example, `{{ record.name }}`). Defaults to the field name in sentence case if left blank.\n- **Position** — a number controlling display order (ascending).\n- **POS View** — optionally renders a custom Liquid view instead of the raw field value.\n\n:::warning\nFor a custom data mapping field to sync to POS, set both of these to the same all-lowercase value:\n\n- The Custom Data Mapping's **Field API Name** (for example `customer_since__c`, not `Customer_Since__c`)\n- The layout field's **Field Name** (the same lowercase value, no `data.` prefix)\n\nNaming the field here is what makes it available on the device. A **POS Layout Filter**, or a field named in the layout's **Sort**, registers a field the same way.\n\nAfter adding or changing a custom data mapping field, run a full **Clear \u0026 Resync** on the affected device (see [POS storage, sync, and device administration](pos-storage-sync-and-administration)) — a delta sync will not pick up a field that wasn't in the schema it last synced against.\n\nTo diagnose a field that renders blank, see [Verify custom data is available in Liquid](verify-custom-data-in-liquid).\n:::\n\n### Custom views on layout fields\n\nAny layout field on any layout type can reference a **POS View** (`Pos_View__c`). When a view is assigned, the POS renders the view's Liquid template instead of a standard field value or input control.\n\n:::warning\nIn list and grid layouts, field views are pre-rendered in batch for all visible records at once. As a result, `\u003cscript\u003e` tags inside a field view template are **not** executed. If your view requires scripting, use a layout-level view instead (assign the view to the layout record, not to an individual field).\n:::\n\nViews have access to the following Liquid variables:\n\n| Variable | Description |\n|----------|-------------|\n| `{{ record.field_name }}` | Any field on the current record |\n| `{{ record.id }}` | Record ID |\n| `{{ record.name }}` | Record name |\n| `{{ record.image.small_url }}` | Thumbnail image URL |\n| `{{ record.image.large_url }}` | Full-size image URL |\n| `{{ record.pricing.price | money }}` | Formatted price |\n| `{{ record.data.field_name }}` | Custom data mapping fields |\n| `{{ record.account.data.field_name }}` | Nested relationship field access |\n\n**Example: display a product image**\n\n\n```liquid\n\n{% if record.image %}\n  \u003cimg src=\"{{ record.image.small_url }}\" alt=\"{{ record.name }}\" /\u003e\n{% endif %}\n```\n\n\n**Example: price with sale badge**\n\n\n```liquid\n\n{{ record.pricing.price | money }}\n{% if record.data.on_sale %}\n  \u003cspan class=\"badge\"\u003eSale\u003c/span\u003e\n{% endif %}\n```\n\n\n## Action items\n\nThree action item slots are available on every layout, configured via lookup fields on the `Pos_Layout__c` record:\n\n| Field | Placement | Works with |\n|-------|-----------|------------|\n| **Primary Action Item** | Button at the bottom of the layout | All layout types |\n| **Secondary Action Item** | Button at the top left of the layout | All layout types |\n| **Record Action Item** | Triggered when a staff member taps a record row or grid card | List and Grid layouts |\n\nEach slot references a `Pos_Action_Item__c` record, which defines the action, label, appearance, and optional child group.\n\n### Action item properties\n\n| Property | Description |\n|----------|-------------|\n| **Display Name** | Button or menu label. Supports Liquid template syntax. |\n| **Type** | `action` — a single button that triggers an action. `group` — opens a child action group when tapped. |\n| **Action** | The action identifier to execute (e.g., `cart:add_product`). Required for `action` type. |\n| **Params** | Action parameters expressed as Liquid (evaluated against the current record). |\n| **Icon** | An icon shown on the button (e.g., `bag`, `plus_circle`, `home`, `people`, `cog`, `more`). |\n| **Color** | Hex color for the button (e.g., `#4a90e2`). |\n| **Image** | A custom image asset displayed on the button. |\n| **Position** | Sort order within a group. |\n| **Child Action Group** | The action group to display when this item is tapped (for `group` type). |\n| **Next Action Item** | An action to trigger automatically after this one completes, receiving context from the result. |\n\n:::tip\nUse the **Record Action Item** to make list rows interactive — for example, opening a detail view or adding a product to the cart when a staff member taps a row.\n:::\n\n### Action chaining\n\nAction items can be chained using the **Next Action Item** lookup. When the primary action completes successfully, the POS automatically triggers the next action and passes along context from the result.\n\nFor example, a `cart:add_product` action can chain to a `modal:open` action. The next action's params can reference `{{ cart_item_sc_id }}` — the cart item ID returned by the add.\n\n:::note\nIf the primary action fails, the chain stops and the next action does not trigger. An error is surfaced to the POS interface.\n:::\n\n## Action types reference\n\nThe following action identifiers are available for use in `Pos_Action_Item__c` records. All params support Liquid template syntax, evaluated against the current record.\n\n### Cart actions\n\n| Action | Description | Key params |\n|--------|-------------|------------|\n| `cart:add_product` | Add a product to the cart | See detail below |\n| `cart:add_contact` | Assign a contact to the cart | `contact_sc_id` |\n| `cart:remove_contact` | Remove the assigned contact from the cart | — |\n| `cart:resume` | Resume a parked cart | — |\n| `cart:discount` | Open the cart discount flow | — |\n| `cart:item_update` | Update a cart item | — |\n| `cart:item_remove` | Remove an item from the cart | — |\n\n#### `cart:add_product` params\n\n| Param | Type | Description |\n|-------|------|-------------|\n| `product_id` | String | Product Salesforce ID |\n| `product_code` | String | Product code (alternative to `product_id`) |\n| `quantity` | Integer | Quantity to add (default: 1) |\n| `unit_price` | Decimal | Override the unit price |\n| `unit_discount` | Decimal | Apply a discount to the unit price |\n| `serial_numbers` | Array | Serial numbers to assign to the cart line item |\n| `name` | String | Override the display name in the cart |\n| `pending` | Boolean | Mark the item as pending |\n\n**Example: add product from the current record with quantity and price from custom fields**\n\n\n```liquid\n\nproduct_id={{ record.id }}\nquantity={{ record.s_c__Quantity__c }}\nunit_price={{ record.s_c__Price__c }}\n```\n\n\nAfter `cart:add_product` completes, the context passed to a chained next action includes `product_id` and `cart_item_sc_id`.\n\n### Navigation actions\n\n| Action | Description | Key params |\n|--------|-------------|------------|\n| `nav:home` | Navigate to the home screen | — |\n| `nav:customers` | Open the customers list | — |\n| `nav:customers:create` | Open the create customer form | — |\n| `nav:customers:update` | Open a customer's edit form | `customer_id` |\n| `nav:contacts` | Open the contacts list | — |\n| `nav:products` | Open the products list | — |\n| `nav:orders` | Open the orders list | — |\n| `nav:carts` | Open the carts list | — |\n| `nav:fulfillments` | Open fulfillments / pickups | — |\n| `nav:suppliers` | Open the suppliers list | — |\n| `nav:labels` | Open the labels list | — |\n| `nav:stock_requests` | Open stock requests | — |\n| `nav:stock_adjustments` | Open stock adjustments | — |\n| `nav:settings` | Open POS settings | — |\n| `nav:layout` | Navigate to a custom layout | `layout` (layout identifier) |\n| `nav:session_start_user` | Start a session for a specific user | `user_id` |\n| `nav:session_add_user` | Add a user to the current session | — |\n| `nav:session_end` | End the current shift | — |\n| `nav:disconnect_register` | Disconnect the register | — |\n\n### Modal actions\n\n| Action | Description | Key params |\n|--------|-------------|------------|\n| `modal:open` | Open a custom POS View in a modal | `view_identifier` |\n| `modal:close` | Close the current modal | — |\n| `modal:open:email_receipt` | Open the email receipt modal | — |\n| `modal:open:return` | Open the return / refund flow | — |\n| `modal:open:url` | Load a URL in a modal | `url` |\n| `modal:open:alert` | Display an alert modal | — |\n\n### Record actions\n\n| Action | Description | Key params |\n|--------|-------------|------------|\n| `record:save` | Save the current form record | — |\n| `record:delete` | Delete the current record | — |\n| `open:layout` | Open a layout for a specific record | `layout` (identifier), `record_id` |\n\n### Print actions\n\n| Action | Description |\n|--------|-------------|\n| `print:receipt` | Print the receipt |\n| `print:template` | Print using a label or receipt template |\n| `print:labels` | Print labels |\n| `print:email_receipt` | Email the receipt |\n\n### System actions\n\n| Action | Description |\n|--------|-------------|\n| `lock` | Lock the POS and return to user login |\n| `sync` | Trigger a data sync |\n| `close` | Close or go back |\n| `pay_by_link:generate` | Generate a pay-by-link payment |\n| `printer:remove` | Remove the connected printer |\n\n## Action groups\n\nAction groups define the POS navigation menus, sidebar, top bar, home screen grid, and other interface areas. They are configured separately from layouts and work by grouping action items into named containers placed at specific locations in the POS UI.\n\nSee [POS action groups](pos-action-groups) for full configuration details, system identifiers, and how to create nested menus.\n\n## Grid layouts\n\nGrid layouts (type: **Grid**) display records as cards in a visual grid. They are used for the home screen, product quick-access panels, and category grids.\n\nGrid cards are auto-filled — the POS arranges cards automatically based on the records returned. No manual row, column, or page positioning is required.\n\nEach card displays the layout fields you configure (for example, a product image, name, and price). Use **POS Views** on fields to render custom Liquid content inside each card.\n\n### Tapping a grid card\n\nUse the **Record Action Item** on the grid layout to define what happens when staff tap a card. A common pattern is to open a record layout showing the item's detail:\n\n\n```\naction: open:layout\nparams: layout=my_record_layout;record_id={{ record.id }}\n```\n\n\nWhen staff tap a card to open a detail view, the grid stays mounted in the background. Search, filters, and scroll position are preserved when the detail view closes.\n\n### Grid card sizing\n\nGrid cards automatically reflow to fill available space using CSS Grid's `auto-fill` algorithm. You can adjust card dimensions via CSS custom properties set in your POS `\u003chead\u003e`:\n\n| CSS variable | Default | Description |\n|--------------|---------|-------------|\n| `--sc-card-min-width` | `200px` | Minimum card width before a column wraps to the next row |\n| `--sc-card-grid-gap` | `var(--sc-spacing-2)` | Space between cards |\n| `--sc-card-image-aspect-ratio` | `1.3 / 1` | Aspect ratio of the card's image area (width / height) |\n\nNo manual row or column configuration is required — the grid fills automatically based on these values and the available container width.\n\n### Layout-level view on a grid\n\nYou can assign a **POS View** to the grid layout itself (not just to individual fields). When set, the view's Liquid template replaces the default card rendering for each record, giving you full control over card HTML.\n\n## Layout filters\n\nFilters can be added to **List** type layouts to let staff narrow down records. Filter configuration, including auto-generated options, is covered in [POS layout filters](pos-layout-filters).\n\n## System layouts\n\nThe following layout identifiers are reserved for specific POS screens. Create a `Pos_Layout__c` record with a matching identifier to customize that screen.\n\n### Contact and customer layouts\n\n| Identifier | Type | Object | Purpose |\n|-----------|------|--------|---------|\n| `contact_list` | List | Contact | Customer list view |\n| `contact_record` | Record | Contact | Customer detail view |\n| `customer_form` | Form | Contact | Create or edit customer form |\n\n### Cart layouts\n\n| Identifier | Type | Object | Purpose |\n|-----------|------|--------|---------|\n| `cart_contact_list` | List | Contact | Contact selection within the cart |\n| `cart_record` | Record | Cart | Cart detail view |\n| `delivery_address` | Record | Cart | Delivery address entry |\n| `delivery_rates` | Record | Cart | Delivery rate selection |\n\n### Order layouts\n\n| Identifier | Type | Object | Purpose |\n|-----------|------|--------|---------|\n| `order_list` | List | Order | Order list view |\n| `order_record` | Record | Order | Order detail view |\n\n### Product layouts\n\n| Identifier | Type | Object | Purpose |\n|-----------|------|--------|---------|\n| `product_record` | Record | Product | Product detail view |\n\n### Shift layouts\n\n| Identifier | Type | Object | Purpose |\n|-----------|------|--------|---------|\n| `end_shift_form` | Form | Shift | End-of-shift form presented when ending a shift |\n\n:::note\nForm type layouts (`customer_form`, `end_shift_form`) extend the standard built-in forms. Layout fields you add appear alongside the default fields. Assign a **POS View** to a field to render custom Liquid content in place of a standard input.\n:::\n\n## Legacy layout view override\n\nThe built-in POS layouts for products, carts, customers, and orders (used when no custom layout record is configured) also support a layout-level view override. When a `Pos_View__c` is assigned to one of these legacy layouts, the view replaces the content area of the layout. The header — including the search bar, filters, and action buttons — remains visible.\n\nThis lets you customize the display of built-in screens without creating a full replacement layout.\n\n\n## Layout identifier convention\n\nFrom v21, all POS layout identifiers use **snake_case** (underscores) as the standard naming convention. If you have existing layouts or action groups using dash-case identifiers (e.g., `cart-contact-list`), those continue to work, but the POS will log a deprecation warning to the browser console when a dash-case fallback is used.\n\nUpdate existing identifier values in Salesforce to the snake_case equivalents at your convenience to clear the warnings.\n\n:::note\nThe backward-compatibility fallback converts underscores to dashes when looking up a record that is not found by its snake_case identifier. This means identifiers with underscores automatically fall back to their dash-case equivalent. Identifiers that do not contain underscores (such as `end_shift_form`) were already stored in snake_case and are not affected.\n\nNo removal date for this fallback has been set. Dash-case identifiers will continue to trigger a deprecation warning indefinitely until updated.\n:::\n\n### Common identifier migration reference\n\n#### Layout identifiers\n\n| New (snake_case) | Old (dash-case) |\n|------------------|-----------------|\n| `cart_contact_list` | `cart-contact-list` |\n| `cart_contact_record` | `cart-contact-record` |\n| `cart_list` | `cart-list` |\n| `cart_record` | `cart-record` |\n| `contact_record` | `contact-record` |\n| `customer_form` | `customer-form` |\n| `customer_list` | `customer-list` |\n| `customer_record` | `customer-record` |\n| `end_shift_form` | `end-shift-form` |\n| `order_list` | `order-list` |\n| `order_record` | `order-record` |\n| `product_list` | `product-list` |\n| `product_record` | `product-record` |\n\n#### Action group identifiers\n\n| New (snake_case) | Old (dash-case) |\n|------------------|-----------------|\n| `sidebar_main_menu` | `sidebar-main-menu` |\n| `sidebar_fixed_menu` | `sidebar-fixed-menu` |\n| `topbar_main_menu` | `topbar-main-menu` |\n| `register_actions` | `register-actions` |\n| `session_start_actions` | `session-start-actions` |\n| `session_actions` | `session-actions` |\n\n#### View identifiers\n\n| New (snake_case) | Old (dash-case) |\n|------------------|-----------------|\n| `modal_view` | `modal-view` |\n\n:::tip\nTo check whether a layout, action group, or view is using a deprecated identifier, open the browser developer console on the POS and look for messages in the format: `Using deprecated identifier 'old-identifier'. Please use 'new_identifier' instead.`\n:::\n\n## Creating a POS layout\n\n1. In Salesforce, open the **POS Layouts** object (search for it in the App Launcher).\n2. Click **New**.\n3. Complete the layout record:\n   - **Name** — an internal name for the layout\n   - **Display Name** — the name shown to staff in the POS (supports Liquid template syntax)\n   - **Type** — List, Record, Form, or Grid\n   - **Identifier** — the value that places the layout in the POS UI (use a system identifier for built-in screens, or a custom value for custom views)\n   - **Object Name** — the API name of the Salesforce object this layout applies to\n   - **Description** — optional notes for administrators\n4. Save the layout record.\n5. In the **POS Layout Fields** related list, add the fields to display.\n6. If this is a **List** layout and staff need to narrow the records, add filters via the **POS Layout Filters** related list. See [POS layout filters](pos-layout-filters). Other layout types do not use filters.\n7. (Optional) Assign action items using the **Primary Action Item**, **Secondary Action Item**, and **Record Action Item** lookup fields to add buttons and row taps to the layout.\n8. (Optional) Assign a **POS View** to render a fully custom Liquid view for the entire layout, instead of the default display.\n\n## Initial filter and sort\n\nFor **List** layouts, the `Pos_Layout__c` record accepts initial filter and sort values that apply before staff interact with the layout:\n\n| Field | Format | Example |\n|-------|--------|---------|\n| **Filter** | Semicolon-separated `key=value` pairs. Use commas to separate multiple values for multiselect fields. | `status=active;type=standard` |\n| **Sort** | Semicolon-separated `field:direction` pairs | `name:asc;created_date:desc` |\n\nThe filter key can reference standard Salesforce fields or custom data mapping fields (e.g., `data.customer_type`).\n\nThese values set the default state of the layout when it first loads. Staff can apply additional filters on top of these defaults but cannot clear values set here through the standard filter controls.\n\n## Object reference\n\nFor a full field listing, see [POS Layout Object Reference](pos-layout-object-reference), [POS Layout Field Object Reference](pos-layout-field-object-reference), and [POS Layout Filter Object Reference](pos-layout-filter-object-reference)."}