April 25, 2026 · 5 min read
Rethinking Access Control in Shopify
A lightweight, native Shopify approach to protect product and collection content based on customer login state and tags without relying on third-party apps like Locksmith.
Access control in Shopify is typically handled through apps like Locksmith. They are reliable and widely adopted, but they introduce additional dependencies, runtime overhead, and often limit flexibility when building highly tailored experiences.
For projects where performance, control, and maintainability matter, it can be more effective to implement access logic natively within the theme.
This post walks through a simple but powerful pattern: a reusable section called Vendor Vault, designed to conditionally gate content across products, collections, or any template. The Goal
We want to protect content based on:
- Whether the customer is logged in
- Whether the customer has a specific tag (e.g.
vip)
Instead of routing or server-side restrictions, we handle this directly in the theme layer with a clean separation between:
- Liquid (access evaluation)
- Frontend JavaScript (UI control)
The Approach
We create a reusable Shopify section called Vendor Vault that:
- Acts as a toggleable feature (
enabled) - Accepts a required customer tag
- Outputs structured config to the frontend
- Controls visibility of content dynamically
This section can be dropped into any template without breaking existing layouts.
Template Strategy (Important)
Rather than conditionally injecting logic into all templates, we isolate the behavior:
- Create a separate product template
- Create a separate collection template
- Add the Vendor Vault section to these templates
- Assign templates selectively in the Shopify admin
This keeps the implementation clean and avoids unintended side effects.
For previewing on unpublished themes, use:
?view={template-name}
This allows testing without publishing the theme.
Step 1: Passing Access State from Liquid
We evaluate access in Liquid and expose a config object to JavaScript:
{% if section.settings.enabled %}
{% assign required_tag = section.settings.customer_tag | strip %}
<script>
window.__vendorVault = {
access: {% if customer and required_tag != blank and customer.tags contains required_tag %}true{% else %}false{% endif %},
heading: {{ section.settings.heading | json }},
description: {{ section.settings.description | json }},
buttonText: {{ section.settings.button_text | json }},
buttonUrl: {{ section.settings.button_url | json }}
};
</script>
{% endif %}
What’s happening here:
- We compute access server-side using Liquid
- We serialize section settings safely using
json - We expose a global config (
window.__vendorVault) for frontend consumption
This keeps business logic deterministic and avoids duplicating logic in JavaScript.
Step 2: Controlling the UI on the Frontend
We then use a small, isolated script to toggle visibility:
(function () { const config = window.__vendorVault; if (!config) return; const content = document.getElementById('content'); if (!content) return; if (config.access) { content.classList.remove('hide'); } else { content.classList.add('hide'); const fallback = document.createElement('div'); fallback.className = 'vendor-vault-locked'; fallback.innerHTML = ` <h2>${config.heading}</h2> <p>${config.description}</p> ${config.buttonText ? `<a href="${config.buttonUrl}" class="button">${config.buttonText}</a>` : ''} `; content.parentNode.insertBefore(fallback, content); }})();
Key decisions:
- Uses an IIFE to avoid polluting global scope
- Gracefully exits if config or DOM node is missing
- Injects fallback UI only when access is denied
- Keeps DOM mutations minima
Step 3: Structuring the Section Schema
The schema allows non-technical users to control behavior directly from the theme editor:
{
"name": "Vendor Vault",
"settings": [
{
"type": "checkbox",
"id": "enabled",
"label": "Enable vendor vault visibility control",
"default": true
},
{
"type": "text",
"id": "customer_tag",
"label": "Customer tag to check (exact text)",
"default": "vip"
},
{
"type": "header",
"content": "Section Text"
},
{
"type": "text",
"id": "heading",
"default": "You currently do not have access to this content."
},
{
"type": "text",
"id": "description",
"default": "You do not qualify for this content. Please contact us for more information."
},
{
"type": "text",
"id": "button_text",
"default": "Back to Home"
},
{
"type": "url",
"id": "button_url",
"default": "/"
}
]
}
Why This Approach Works
1. No App Dependency
Removes reliance on third-party apps for simple gating logic.
2. Performance Friendly
No additional scripts, network calls, or app overhead.
3. Fully Customizable
Control messaging, behavior, and targeting directly in the theme.
4. Reusable by Design
Drop-in section that works across templates without refactoring.
When to Use This vs Apps
Use this approach when:
- You need lightweight conditional visibility
- You care about performance and control
- You want to avoid app bloat
Use apps when:
- You need strict access enforcement
- You require advanced rule builders
- Non-technical teams need full control
Final Thoughts
Most Shopify stores over-rely on apps for problems that can be solved natively with clean architecture.
This pattern is not about replacing apps entirely, but about choosing the right level of abstraction.
In many cases, a small, well-structured section like Vendor Vault can deliver the same outcome with:
- Less complexity
- Better performance
- Full control