If you’ve ever done backend web development, you’ve probably had to construct your own API endpoints, allowing for URL parameter inputs that can be used in your website/webapp.
As a dedicated Shopify developer, I’ve always wondered if I could somehow do the same in the Shopify ecosystem without the use of any third-party tools, which I’ve successfully done using a novelty method I’ve talked about in my previous article.
In this article, I’ll be expanding on that idea by providing another use case for a recent Shopify project I was tasked with.
The problem
Everyone knows about Shopify’s 100-variant limit, hopefully raised to 1000 in the upcoming releases of Shopify’s system updates!
But in my case, even if Shopify’s variant limit was raised to 1000, it would not have been enough for the 10064 variants I had to somehow achieve seemingly for 1 product!
The task was to replicate Hanse Syntec System’s EPDM roof film product page, which contained 3 different option selectors, amounting to a total of 10064 possible option combinations!
At first I didn’t realize it, but this was a perfect use case for the novelty method I’ve discovered a while ago.
Let’s dig in…
10064 Variants
Hanse Syntec System’s EPDM roof film product page has 3 different options.
Option1 had 2 choices, Option2 had 17 choices, and Option3 had 296!!
2 x 17 x 296 = 10064 combinations!
The store I was working on was on a Shopify Plus plan, which already begs the question: “Why don’t you just use a Shopify function to calculate a price adjustment based on the dimensions?”
While this was also my first idea, it failed to meet 1 specific requirement, which was to give every variant out of the total 10064 an SKU, and to allow individual inventory management per variant.
Don’t ask me why.
The plan
Since Shopify functions were out of the question, I had no other app-less choice but to bulk-create 10064 variants, assigning each a unique SKU.
Which, when added to cart, would correspond to 1 out of the 10064 variants.
Thanks to the availability of productVariantsBulkUpdate, productCreate, and a simple shell script for the stagedUpload, I was able to easily create all 10064 variants in 1 fell swoop.
Now the hard part..
How should 3 choices <select>’d by a customer equate to the corresponding variant without making major compromises?
I certainly did not want the browser parsing through 10064 JSON objects to find a match every time a customer added EPDM roof film products to the cart…
In comes the secret sauce
Inspired by Shopify’s bundled section rendering mechanism, I decide that it “would be cool” if I could somehow just ask liquid to find the right variant for me in an AJAX request!
This is where my imaginary lightbulb went off and I realized that this would be a perfect use case for the novelty method I shared from my previous article
Let’s dig into the code.
Step 1
First, I set up a test, to see if I could loop through all 10064 variants, to find the right one, here I’m using sku, but I could also use option1.

Which seems to have worked perfectly fine.

Step 2
Find a way to dynamically pass the SKU variable to that particular piece of liquid code, which would allow me to query any variant using SKU/Option1

This is essentially the core of my idea, which is to pass any URL parameter to a collection template, utilizing a collection filter’s “url_to_remove” property
But there’s a problem, the provided input could be anything, and “anything” in a URL search bar is a bunch of %’s.
Directly using “1,5 mm - 5,03 m - 29,2 m” in the URL search bar turns it into this -> 1,5%20mm%20-%205,03%20m%20-%2029,2%20m
Step 3
“Sanitize” the input by encoding it with Base64 when sending the request, then decode it in liquid before using it!

Of course, the only reason I’m using base64 is because Shopify’s liquid has a convenient base64_decode filter
This is essentially the “backend code” done
{%- assign filter = collection.filters | first -%}
{%- assign sku = filter.url_to_remove | split: 'filter.p.sku=' | last | split: '&view=' | first | url_decode | base64_decode -%}
{%- layout none -%}
{%- paginate collections[collection.handle].products by 4200000 -%}
{%- for product in collections[collection.handle].products -%}
{%- assign variant = product.variants | where: 'sku', sku | first -%}
{%- if variant != blank -%}{{ variant | json }}{%- break -%}{%- endif -%}
{%- endfor -%}
{%- endpaginate -%}
Step 4 (final step)
Now we’re ready to send requets to our new API 😂
<script>
/* https://stackoverflow.com/questions/246801/how-can-you-encode-decode-a-string-to-base64-in-javascript#answer-26514148 */
const Base64={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}
fetch(window.location.origin + "/collections/all?filter.p.sku=" + Base64.encode(sku) + "&view=10064").then(d => d.text()).then(data => {
if(data.length > 0) {
let variant = JSON.parse(data);
...
}
})
</script>
At this point, you can do whatever it is the theme allows you to do to overwrite the variant ID of the request to /cart/add.js, probably through a formData object.
Thoughts & Considerations
Thanks to Shopify’s string encoding filters, we can use base64 for stuff like this, or even sha256 if you’re passing sensitive information like passwords
I mean the possibilities are almost endless, and this hacky method of using collection.filters.first.url_to_remove is just Shopify-breaking, in my opinion
Conclusion
I hope you found this interesting. Formulating such a hackky solution is only possible after years of experience with the Shopify platform.
If you have a unique problem or requirement that you need created on the Shopify platform, you are free to contact me at any time, and I’ll be sure to respond :)