Access control

Login to see price without editing your theme

Prime Lock logo

Prime Lock Team

11 February 2026 · 8 min read

ACCESS CONTROL
Contents

You sell to trade buyers and to the public from one store. Public visitors should see the product and not the price. Logged-in trade accounts should see both. This is one of the most common requests in Shopify support forums, and there are three answers in circulation. All three are incomplete, in different ways.

Route 1: edit price.liquid

The standard advice. Find snippets/price.liquid, wrap the money output in a customer check, done. It works immediately and it costs nothing.

snippets/price.liquid: the usual edit
{% if customer %}
  <span class="price">{{ product.price | money }}</span>
{% else %}
  <a href="/account/login">Log in to see price</a>
{% endif %}

How it fails

  • Theme updates. The moment you edit a theme file you own the merge. Dawn ships changes to price.liquid regularly; every update becomes a manual diff, and most merchants respond by never updating.
  • Coverage. price.liquid is not the only place a price renders. Cart drawer, search suggestions, quick-add modals, upsell blocks and third-party sections each render their own money value.
  • The JSON endpoint. /products/<handle>.js is served by Shopify, not by your theme. It still returns the price, logged in or not.
  • Uninstalling. Snippets stay behind. Six months later nobody remembers which lines were added.

Route 2: hide it with CSS

The one-line fix that is not a fix
.price, .product__price { display: none; }

This is the fastest option and the worst one. The price is still in the HTML. It is in view-source, it is in the crawl, it is in the JSON-LD, and it is one browser devtools toggle away from being visible. Googlebot renders CSS, so if the price is in the markup and hidden from users, you have built a small, unintentional cloaking case.

Route 3: a Liquid conditional around the buy section

A step up from route one: instead of hiding just the money, wrap the whole buy-buttons block. Logged-out visitors get a login prompt where the add-to-cart button would be. Display coverage is much better, because you are removing the entire purchase affordance rather than one span.

It still has route one's theme-fork problem, and it still has the fundamental issue: removing the button does not remove the endpoint. The form posts to /cart/add. That endpoint is public. Anyone can post a variant ID to it, or skip it entirely with a cart permalink.

What an app embed does instead

App embed blocks are a theme app extension feature. You toggle one switch in Theme Editor → App embeds and the app's script is injected into the theme at runtime. Nothing is written into your theme files, so theme updates are unaffected and uninstalling leaves no residue.

At page load the embed evaluates your rules against the current customer (logged in or not, which tags they carry, whether the resource is locked) and replaces the price and buy section with whatever you configured. Because it runs against every rendered price on the page rather than one snippet, cart drawers and quick-adds are covered too.

Before and after

Before: forked theme, one surface covered
// snippets/price.liquid  (edited by hand, now off-template)
{% if customer %}{{ product.price | money }}{% else %}Log in{% endif %}

// theme update available -> manual merge, every time
// /products/wool-throw.js -> { "price": 4200 }   <- still public
After: app embed on, theme untouched
// theme files: unchanged, updates apply cleanly
// rule: collection "trade" -> visible to customers tagged `wholesale`
// display: price + buy section replaced with "Log in to see price"
// enforcement: checkout validation rejects restricted variants server-side

Why enforcement has to live at checkout

Every approach above, including the app embed's display layer, runs in the browser. Anything that runs in the browser can be skipped by not using the browser the way you expected. The relevant example is the cart permalink:

No product page involved
https://your-store.myshopify.com/cart/44192837465:1

That URL puts the variant in the cart and lands the visitor on the cart page. Your locked product page never rendered, so your gate never ran. Variant IDs are not secret; they are in the product JSON, in the collection JSON, and in the browser history of anyone who was once approved.

Shopify Functions close this. A cart and checkout validation function runs on Shopify's servers on every plan. It inspects the cart contents and the buyer identity, and it can block the checkout with a message you write. There is no client-side workaround because there is no client involved.

So the practical answer to "login to see price without editing my theme" is two layers: an app embed for what visitors see, and a validation function for what they can actually buy. One without the other is either ugly or useless.

Frequently asked

Prime Lock logo

Prime Lock Team

Prime Lock is built by Aspedan, the team behind Discount Prime and Prime MOQ. We write about the parts of Shopify that are harder than they look.

Decide who gets to see your store.

Early access opens with the free plan. Unlimited locks, enforced at checkout.

No newsletter. One email when Prime Lock is live.