This client-side implementation provides a deterministic reference for customer login status in Shopify. Open the flems example by pressing the badge to see it in action:
In your theme.liquid
or layout file, add the following data attribute to the <html>
element. While not required, it is recommended for general use, though the solution will work programmatically.
<html data-customer="{%- if customer -%}true{%- else -%}false{%- endif -%}">
Below is the JavaScript implementation. We use a Mutation Observer to efficiently interface with Liquid and ensure support for cases where partial DOM replacements occur. The global window
definition and event-based (pub/sub) approach allow for multiple listeners and sequential operations across the webshop.
Object.defineProperty(window, 'customer', {
configurable: false,
writable: false,
enumerable: false,
value: (sub => {
const attr = 'data-customer';
const root = document.documentElement;
const auth = (el = root) => el.getAttribute(attr) === 'true';
const fire = x => x.length === 3 ? x[0].call(x[1], x.pop()) : x[0].call(x[1]);
const observer = new MutationObserver(mutations => {
for (const { type, target } of mutations) {
if (type !== 'attributes') continue;
target.hasAttribute(attr) && sub[auth(target) ? 'login' : 'logout'].forEach(fire);
}
});
observer.observe(root, {
attributes: true,
attributeFilter: [ attr ]
});
if (auth()) setTimeout(() => customer.do('login'));
return {
get observer () { return observer; },
get loggedin () { return auth(); },
on: (name, cb, scope = null) => {
const idx = sub[name].push([ cb, scope ]) - 1;
return () => sub[name].splice(idx, 1);
},
do: (ev, arg) => {
if (arg !== undefined) sub[ev].forEach(x => x.length === 2 && x.push(arg))
if (ev === 'login') {
auth() ? sub.login.forEach(fire) : root.setAttribute(attr, 'true');
} else {
auth() ? root.setAttribute(attr, 'false') : sub.logout.forEach(fire);
}
}
};
})({ login: [], logout: [] })
});
Here is a minified version of the above code:
Object.defineProperty(window,"customer",{configurable:!1,writable:!1,enumerable:!1,value:(t=>{const e="data-customer",o=document.documentElement,r=(t=o)=>"true"===t.getAttribute(e),u=t=>3===t.length?t[0].call(t[1],t.pop()):t[0].call(t[1]),n=new MutationObserver((o=>{for(const{type:n,target:l}of o)"attributes"===n&&l.hasAttribute(e)&&t[r(l)?"login":"logout"].forEach(u)}));return n.observe(o,{attributes:!0,attributeFilter:[e]}),r()&&setTimeout((()=>customer.do("login"))),{get observer(){return n},get loggedin(){return r()},on:(e,o,r=null)=>{const u=t[e].push([o,r])-1;return()=>t[e].splice(u,1)},do:(n,l)=>{void 0!==l&&t[n].forEach((t=>2===t.length&&t.push(l))),"login"===n?r()?t.login.forEach(u):o.setAttribute(e,"true"):r()?o.setAttribute(e,"false"):t.logout.forEach(u)}}})({login:[],logout:[]})});
Simply drop the JavaScript into your theme (bundle or wherever) and use the customer.on
method. Below is the complete API and usage details:
This function expects 2 parameters and optionally accepts a 3rd this
context binding. There are only 2 available subscribers: customer.on('login')
and/or customer.on('logout')
. Examples:
// Basic login callback
customer.on('login', () => console.log('logged in'));
// Login callback with "this" scope
customer.on('login', function() { console.log(this); }, { foo: 'bar' });
// Basic logout callback
customer.on('logout', () => console.log('logged out'));
// Logout callback with "this" scope
customer.on('logout', function() { console.log(this); }, { foo: 'bar' });
If you want to remove subscriptions, each customer.on
event returns a function, simply call it and to remove it from the sub store, for example:
const a = customer.on('login', () => console.log('logged in #1'))
const b = customer.on('login', () => console.log('logged in #2'))
// We can remove subscription a as follows:
a()
// Now, if you were to trigger calls only b (logged in #2) will fire
customer.do('login');
// You can optionally trigger a once callback by doing:
customer.on('login', () => console.log('logged in #3'))()
This method programmatically triggers subscribed events. It expects a string value of either login
or logout
as its first parameter. Optionally, you can pass additional arguments that subscribed events will receive.
// Trigger login events
customer.do('login');
// Trigger logout events
customer.do('logout');
// Passing data to subscription events
customer.on('login', (a, b, c) => console.log(a, b, c));
// When calling customer.do, pass data as follows:
customer.do('login', 'foo', {x: 1}, ['xxx']);
A readonly getter that returns a boolean value indicating if the customer is logged in.
if (customer.loggedin) console.log('logged in!');
Provides access to the Mutation Observer instance.
Follow me on X if you like Shopify and banter.
You are not allowed to use this in themes published to theme store. Everyone else, you are fine, DWTFYW.