---
title: "Set up offers for POS"
source: https://support.storeconnect.com/articles/custom-pos-offers
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.
---
# Set up offers for POS

Most offers belong in the promotions engine, which handles discounts, free shipping, free products, fixed prices, scheduling, usage limits, and codes. See [Promotions v2](advanced-promotions). Use this process for the rest: an offer the engine cannot express, or one that has to run at a register where the engine does not.

At POS, the engine applies automatic promotions only. Coupon codes are not yet applied at a register, so an offer a customer redeems by code has to be built here or taken at the web storefront.

An offer built this way is a **POS View**: a template that reads the cart, decides whether the offer applies, and sends a cart action. It is a record rather than code, so you can add an offer for one weekend and remove it afterwards.

## Choose how the offer reaches the cart

Decide this before you write the template, because it sets both what the offer can do and which limits apply to it.

| The offer | Use | The register enforces |
| --- | --- | --- |
| Takes an amount or percentage off the whole cart | `cart:discount` | The operator's **Maximum Discount Percentage**, each product's **Minimum Sell Price**, and the **Manual** value of **Exclude From Price Reductions** |
| Adds a product, free or at an agreed price | `cart:add_product` with `unit_price` | Nothing to enforce: the offer adds a line rather than reducing one |
| Takes an amount off particular lines | `cart:item:update` | Nothing. See the warning below |

`cart:discount` has no product, category, or line parameter, so it always applies to the whole cart and distributes the reduction across eligible lines. Reach for `cart:add_product` before `cart:item:update` where either would do, because giving a product away sidesteps the limits question entirely.

:::warning
A view cannot enforce the discount limits for itself. `Exclude From Price Reductions` is on no POS drop, and `current_staff` exposes only `id` and `name`, so a template cannot read the operator's **Maximum Discount Percentage**. Use `cart:item:update` only where you accept that the reduction overrides both the operator's ceiling and any product the merchant has marked **Manual** under **Exclude From Price Reductions**.
:::

## Before you start

- You need Salesforce admin access to create **POS View** and **POS Action Item** records.
- The products the offer adds or discounts need a **Product Code**, and they must be synced to the register. See [Manage POS data sync](manage-pos-data-sync).
- Decide the products, thresholds, and amounts the offer uses. Keep them in `{% assign %}` statements at the top of the template, so there is one place to change them. A POS view cannot read store variables, which is covered below.

## Build the offer

1.  Go to the **POS Views** list and select **New**.
2.  Enter a **POS View Name**, and an **Identifier** you will name the view by from the action, such as `offer_buy_two_get_one`.
3.  In the **HTML Template**, read the cart from `current_cart.items` and work out whether the offer applies. Each item gives you `quantity` and `product`, and a product gives you `product_code`.
4.  In the same template, call the cart action from a `<script>` block through `scAction`. See [scAction](pos-sc-action) for the syntax and the [POS actions reference](pos-actions-reference) for each action's parameters.
5.  Save the record.
6.  Go to the **POS Action Groups** list and open the group the offer's button belongs on, usually `home`. See [POS action groups](pos-action-groups).
7.  Add a **POS Action Item** with **Action** set to `modal:open:view` and **Params** set to `view_identifier=offer_buy_two_get_one`.
8.  On a register, reload the POS.

The offer's button now appears in the action group, and tapping it opens your view.

:::note
Parameters in a **Params** field are separated by semicolons. An ampersand is not a separator, so `view_identifier=x&title=y` is read as one parameter whose value is `x&title=y`.
:::

## Example: buy two, get one free

This view adds a free bottle of `RED-FENCE-SHIRAZ-2019` once the cart holds two, and does nothing otherwise. The product code and the threshold sit in `{% assign %}` statements at the top, so the offer is retuned by editing those two lines.

:::warning
Do not reach for `store_variables` here. It is declared as a POS global but raises at the register, and a view whose template raises renders as an empty box with no error on screen.
:::


```liquid

{% assign qualifying_code = 'RED-FENCE-SHIRAZ-2019' %}
{% assign threshold = 2 %}
{% assign qualifying_count = 0 %}

{% for item in current_cart.items %}
  {% if item.product.product_code == qualifying_code %}
    {% assign qualifying_count = qualifying_count | plus: item.quantity %}
  {% endif %}
{% endfor %}

<div class="offer">
  {% if qualifying_count >= threshold %}
    <p>This cart qualifies for a free {{ qualifying_code }}.</p>
    <button id="apply-offer" data-code="{{ qualifying_code }}">Add the free item</button>
  {% else %}
    <p>Add {{ threshold | minus: qualifying_count }} more to qualify.</p>
  {% endif %}
</div>

<script>
  (function () {
    var button = document.querySelector('#apply-offer')
    if (!button) return
    button.addEventListener('click', function () {
      scAction('cart:add_product', {
        product_code: button.dataset.code,
        quantity: 1,
        unit_price: 0,
        name: 'Buy two, get one free',
      })
      scAction('modal:close')
    })
  })()
</script>
```


Three details in that template are worth carrying into your own:

-   **Wrap the script in an immediately invoked function.** A view re-renders, and its scripts run again each time, so a top-level `const` or `let` throws on the second run.
-   **Set `unit_price` rather than a discount.** It locks the price, so the free line is not repriced if the customer on the cart changes.
-   **Set `name`** so the reason for the line is visible on the cart, the receipt, and the order.

## Keep the offer portable

Refer to products by **Product Code**, never by Salesforce ID, so the view still works after a move to another org. Keep the dates, thresholds, and on/off switches together at the top of the template, because a POS view cannot read store variables. A view is org-wide and cannot be scoped to one store, so branch on `current_store` or `current_outlet` where two stores in the org need different behavior. See [Deploy POS configuration to production](deploy-pos-configuration).

## What this does not do

-   **It does not report as a promotion.** A view's reduction is recorded as a discount on the order, not as a **Promotion2** record, so it does not appear in promotion reporting or count toward a promotion's usage limits. Build it in the engine where the campaign has to be measured.
-   **It does not apply on its own.** The offer runs when someone opens the view, so a staff-triggered button is the reliable shape. There is no hook that evaluates a view against every cart change.
-   **It does not see unsynced data.** A view reads the device's local database, so an offer set up in Salesforce minutes ago applies only after the register syncs.

## Results

Ring up a qualifying cart at a register and tap the offer's button. The free line is added at the price the template set, it is named on the cart and the receipt, and the order records what the customer was actually charged. Where your offer uses `cart:discount` instead, check as well that a product marked **Manual** under **Exclude From Price Reductions** is left at full price.

---

## 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/custom-pos-offers