---
title: "Deploy POS configuration to production"
source: https://support.storeconnect.com/articles/deploy-pos-configuration
type: article
format: markdown
site: StoreConnect Support — product and developer documentation for StoreConnect
site_index: https://storeconnect.com/llms.txt
docs_index: https://support.storeconnect.com/llms.txt
note: Append .md to any page or article URL on this site to get its Markdown form.
---
# Deploy POS configuration to production

A POS setup built in a sandbox does not arrive in production with a metadata deployment. Almost all of it is Salesforce **records**, not metadata, so it moves the same way catalog data moves: exported, remapped, and loaded in dependency order.

Use this alongside [Move data from sandbox to production](migrating-from-sandbox), which covers the general export and matching approach. This article covers the parts specific to POS.

## Two layers, in this order

Getting these the wrong way round is the most common cause of a load that fails halfway.

1.  **Metadata.** Custom objects and custom fields, plus the field-level security that lets the sync user read them. Deploy these with your usual metadata tooling. Nothing that references a field can load until the field exists.
2.  **Data.** Custom Data Mappings, then the POS configuration records, then the store-specific settings.

## What travels, and what does not

| Item | How it moves |
|---|---|
| Custom objects and fields | Metadata deployment |
| Custom Data Mappings | Data load, matched on object and field API name |
| POS Views | Data load |
| POS Action Groups and Action Items | Data load, with a second pass for child group references |
| POS Layouts, Layout Fields, Layout Filters | Data load, after the views and action items they reference |
| POS Print Templates | Data load |
| Store Variables | Data load, repointed to the production **Store** |
| Outlets and registers | Created in production, not copied |
| Register codes, domains, API credentials | Set per environment, never copied |

Outlets and registers are environment-specific by nature, and a register code copied from a sandbox is a code someone else already knows. Create them in production and connect devices there.

## Load the records in dependency order

POS configuration is a small dependency graph. Load it in this order:

1.  **Custom Data Mappings**, once their objects and fields exist as metadata.
2.  **POS Views**, which reference nothing else.
3.  **POS Action Groups**, which may reference a **Product Category**.
4.  **POS Action Items**, which reference a parent action group.
5.  **POS Layouts**, which reference a POS View and up to three action items.
6.  **POS Layout Fields** and **POS Layout Filters**, which reference their layout.
7.  **Store Variables**, pointed at the production store.

Action items that open a child action group create a circular reference, in the same way **Store** and **Page** do. Load the items first without the child group reference, then update them once every group exists.

## Provision POS users in production

Staff are not configuration and do not migrate. Provision them in the target org before you load anything that points at them.

A register operator never signs in to Salesforce with a password. The POS sign-in takes a POS user's username, the outlet's **Register Code**, and that user's PIN, and the PIN is checked against the **PIN** field on their **User** record. What each person needs is a **User** record for the **Outlet User** to point at, so their sales are attributed to them, rather than a Salesforce seat.

That user record can be a Chatter Free user, which costs no Salesforce license:

1.  In **Setup**, go to **Users** and create the user.
2.  Assign the **Chatter Free** license.
3.  Assign the **Chatter Free User** profile.
4.  Set their **PIN**, which is a custom field added to the User object. See [Set user pins for POS access](create-and-set-a-pin).
5.  Create the **Outlet User** on the production outlet, linked to that user and to an **Outlet User Type**. See [Add a POS user](add-a-pos-user).

No StoreConnect permission set is needed on top of that. A Chatter Free user with a PIN and an active **Outlet User** record can sign in at a register and sell.

### What this means for the migration

-   **Create the users first.** An **Outlet User** record cannot load until the user it points to exists in production.
-   **Match users by username, not by ID.** The **User** lookup on an **Outlet User** holds a Salesforce ID that does not exist in the target org. Export the username and match on that, stripping the `.{SandboxName}` suffix that Salesforce appends to every sandbox username.
-   **Set PINs fresh in production.** A PIN is a credential. Copying sandbox PINs into production carries a shared secret into a live environment, and anyone who saw the sandbox knows them.
-   **Outlet User Types do migrate.** They are ordinary records carrying a name and a **Maximum Discount Percentage**, so load them before the outlet users that reference them.
-   **Check the license count before you start.** A migration that stops halfway because the org ran out of user licenses leaves outlet users partly loaded.

## Design so nothing holds a Salesforce ID

Most POS migration pain is self-inflicted, and nearly all of it comes from configuration that refers to the source org by Salesforce ID. A setup designed without them moves with no hand-editing at all, which is the difference between a repeatable deployment and an afternoon of find-and-replace.

Three kinds of reference turn up in POS configuration, and they behave very differently.

| Reference | Survives the move | Examples |
|---|---|---|
| A name or key | Yes, identical in both orgs | **Identifier** on a layout, view, or action group; **Field Name**; **Object API Name**; a Store Variable key; a product code |
| A StoreConnect External ID | Yes, when the record it points to is migrated carrying its own external ID | `s_c__sC_Id__c`, and the `*_sc_id` parameters actions take at run time |
| A Salesforce ID | No, it points at nothing in the target org | 15 or 18 character IDs, lookup fields, `product_id`, `print_template_sfid` |

The patterns below all come down to preferring the first row, and resolving the third at run time rather than authoring it.

**Address placement by identifier.** A layout, view, and action group are placed by **Identifier**, not by a lookup. That name is the whole address and is the same in both orgs, so match on it when you load rather than on record ID.

**Name fields and objects, do not point at them.** A **POS Layout Field** names its Salesforce field in **Field Name**, and a **Custom Data Mapping** names its object and field in **Object API Name** and **Field API Name**. These are text, so they carry across untouched, provided the metadata was deployed first.

**Add products by code, not by ID.** Where a view or action item adds a product to the cart, `cart:add_product` accepts a `product_code` as an alternative to `product_id`. The product code is your own catalog value and is the same in both orgs, so a tile built on a code needs no remapping. Prefer it everywhere. See the [POS actions reference](pos-actions-reference).

**Put environment-specific values in Store Variables.** Outlet identifiers, register codes, external endpoints, and feature switches differ per environment. A view that hardcodes one loads cleanly into production and then behaves as though it is broken, because the value it tests against no longer exists. A view gated on the source org's register codes renders an empty grid, which reads as a broken tile rather than a bad reference. Read these from a **Store Variable** instead, so the value changes with the environment while the view does not.

**Resolve at render time what you cannot name.** A run-time identifier passed by the platform, such as `{{ record.id }}` into an action, is not a hardcoded reference and is safe, because it is resolved when the screen renders. The problem is only ever an ID an author typed in. Where an action genuinely requires an ID with no name-based alternative, such as `print_template_sfid`, hold it in a Store Variable or look the record up by name in the view rather than embedding the literal.

**Carry external IDs for the lookups that remain.** The lookups between POS records, and any lookup to a product category or print template, hold Salesforce IDs. Export the related record's StoreConnect External ID alongside each one so the relationship can be rebuilt, as described in [Move data from sandbox to production](migrating-from-sandbox).

Applied consistently, the only things left to set per environment are the outlets, registers, users, and the handful of values that were always environment-specific, and those are created in production anyway.

## Global tiles, and using categories instead

POS configuration is org-wide. None of the POS configuration objects carry a store, outlet, or register field, so a layout or action group with a given **Identifier** applies across the whole Salesforce org, and two stores in one org share it.

This has two consequences worth planning for:

-   A tile set is global by default. Making one behave differently in one store or outlet is done inside the view, not by scoping the record, so build that conditional logic in from the start rather than discovering it when the second store arrives.
-   There is nothing to remap for store scope when you migrate, because there is no store field to remap. The identifier is the whole address.

Where a grid's contents change often, consider driving it from a product category rather than from individual action items. A grid-type action group can reference a **Product Category**, and the grid is then populated from that category's products. For a migration this matters twice over: you maintain category membership, which is catalog data you are already moving, instead of hand-building and re-mapping a tile per product, and changing what appears on the counter later becomes a catalog edit rather than a configuration deployment. The category lookup itself is a Salesforce ID, so it is one reference to remap rather than dozens.

## Automate the move

Because it is data rather than metadata, this is a job for a data tool rather than a package deployment.

-   A data loader handles it well once the order and the matching keys are settled, and it is the most predictable option for a first migration.
-   A DevOps tool that supports data sets can capture the whole graph and replay it, which is worth setting up if you expect to do this repeatedly.
-   An AI agent driving the Salesforce CLI can read the source configuration and recreate it in the target, which suits a setup with a lot of Liquid in it. A CLI write is immediate, with no staging step, so point it at the target deliberately. See [Build POS with an AI agent](pos-with-ai-agents).

Whichever route you use, keep the export and the load order in version control so the next migration is a repeat rather than a rediscovery.

## Verify on a device, not only in Salesforce

Records loading successfully is not the same as a working counter.

1.  Confirm the records exist in production, with their lookups populated rather than blank.
2.  Connect a register and let it sync, then confirm the screens render. Configuration reaches a device through the sync, so a device that has not resynced still shows the old setup. See [Manage POS data sync](manage-pos-data-sync).
3.  Where you migrated a custom object or field, confirm it actually reached the device. A **Custom Data Mapping** does not sync anything on its own: the device schema is built from POS Layouts and their layout fields, and a new object's table is only created when the app boots. This is the most common reason a migrated screen renders empty. See [POS storage, sync, and device administration](pos-storage-sync-and-administration).
4.  Open each migrated screen and check that grids and lists have contents. An empty grid where the configuration looks correct is the signature of a view testing against a value from the source org.
5.  Run one transaction end to end on a migrated register, including a print, before letting staff use it.

The setup is deployed when a register in production renders the same screens as the sandbox and completes a sale on them.

---

## Follow StoreConnect

- [Email Newsletter](https://storeconnect.com/c/lp-newsletter)
- [LinkedIn Newsletter](https://www.linkedin.com/build-relation/newsletter-follow?entityUrn=7444956928444862464)
- [YouTube](https://www.youtube.com/channel/UCngKdP2x8l1wcbAKW3tvU8g)
- [LinkedIn](https://www.linkedin.com/company/storeconnect)
- [X / Twitter](https://x.com/storeconnecthq)

## Popular Links

- [Partners](https://storeconnect.com/partners)
- [Become a Partner](https://storeconnect.com/become-a-partner)
- [News](https://storeconnect.com/articles/news)
- [Events](https://storeconnect.com/articles/events)
- [Live Events](https://storeconnect.com/live-events)
- [Feature Comparison](https://storeconnect.com/how-we-compare)
- [Download a free trial](https://appexchange.salesforce.com/appxListingDetail?listingId=a0N3A00000FMkeKUAT)
- [Book a Demo](https://storeconnect.com/contact)

## Documentation

- [Help documentation](https://support.storeconnect.com/help-documentation)
- [AI agents](https://support.storeconnect.com/ai)
- [Videos & tutorials](https://support.storeconnect.com/videos-tutorials)
- [Developer reference](https://support.storeconnect.com/developer-reference)
- [Release notes](https://support.storeconnect.com/release-notes)
- [Troubleshooting](https://support.storeconnect.com/troubleshooting)
- [Trust Center](https://trust.getstoreconnect.com/)
- [Status Page](https://status.storeconnect.com/)

## Contact

- info@getstoreconnect.com
- US +1 415 745 3230
- AUS +61 2 8365 2308

100 S Ashley Dr, Suite 600-2461
Tampa FL 33602-600 USA

Level 22, Sydney Place
180 George Street
Sydney, NSW, 2000, AUS

## Machine-readable

- [Site index for agents](https://storeconnect.com/llms.txt): curated map of the StoreConnect site in llms.txt format
- [Documentation index for agents](https://support.storeconnect.com/llms.txt): full technical and product documentation map

Every page and article on this site has a Markdown rendering: append `.md` to its URL.

Continue in Markdown: [Help documentation](https://support.storeconnect.com/help-documentation.md) · [Developer reference](https://support.storeconnect.com/developer-reference.md) · [Videos & tutorials](https://support.storeconnect.com/videos-tutorials.md) · [Release notes](https://support.storeconnect.com/release-notes.md)

---

StoreConnect Support — https://support.storeconnect.com/articles/deploy-pos-configuration