# ezomfy > Shopify themes, custom apps & store migrations Canonical site: https://ezomfy.com Built by Ashraful — a Shopify Select Partner shipping themes, apps, and stores worldwide. Two Shopify apps are live in the App Store (Shopable Video, Hailship). --- # How we work Full page: https://ezomfy.com/process Every ezomfy project follows the same 5-step delivery process — from $1,500 speed audits to multi-week Hydrogen builds, the steps don't change. The duration does. ## The five steps **Step 00 — Book a free 30-min call** (Day 0) Pick a slot on the public availability picker. A Google Calendar invite + Meet link land in your inbox immediately. No sales pitch, no discovery deck. Book at https://ezomfy.com/consultation. **Step 01 — Discovery call (30 minutes, honest)** (Day 0, on the call) Ashraful asks about your store, timeline, budget bracket, and the specific outcome you want. He turns down ~20% of projects at this stage — saying no is part of the process. Deliverables: written email summary within 4h, honest fit assessment, ballpark price range. **Step 02 — Fixed quote & timeline** (Within 4 hours of the call) Written, fixed-price quote with milestone schedule and start date. Breaks down what's included, what's NOT included, deposit structure, and the kill switch if either side needs to walk away. No 'time and materials' — if it turns out harder than estimated, that's Ashraful's problem; your number doesn't change. **Step 03 — Build with daily previews** (Day 1 → launch) Work happens on a private preview URL the client can visit any time. Daily progress notes via email/Slack/WhatsApp. Continuous review, not a final reveal. Tradeoffs surfaced in real time. **Step 04 — Launch & 30-day support** (Launch day + 30 days) Cutover scheduled for the store's slowest 2-hour window (usually 3am UTC weekday). DNS/theme publish + smoke-test checklist + 2-hour standby after go-live. 30 days free bug-fix support post-launch. After that: drop off or convert to month-to-month maintenance retainer. ## What ezomfy refuses to do - **Time-and-materials billing.** Every project has a fixed number agreed before work starts. - **Discovery decks + 6-week onboarding.** The free 30-min call is the discovery; anything more is billable consulting. - **Offshore subcontractor handoffs.** Same person quoting writes the code, replies to emails, ships launch, and answers 30-day support. - **Big-bang final reveals.** Daily preview URL + daily progress notes. - **Retainers nobody can cancel.** Maintenance is month-to-month. No 12-month minimums. - **Saying yes when we should say no.** ~1 in 5 calls ends with a referral elsewhere. ## Typical build durations Business days. The clock starts on Step 03 — Steps 00–02 add 1–3 days on top. - Speed optimization: 3–5 business days - Theme development: 5–7 business days - Migration (WooCommerce / Magento / Wix / BigCommerce → Shopify): 5–10 business days - Store build: 10–14 business days - Custom Shopify app: 2–4 weeks - Hydrogen / headless storefront: 4–8 weeks ## Process FAQ **Smallest project ezomfy will take on?** Roughly $1,500 minimum for a quoted project. **NDAs?** Yes — mutual NDA before the discovery call on request. ~80% of client work is under NDA. **Timeline overruns?** If ezomfy underestimated: they eat the time, fixed price doesn't move. If external (Shopify outage, third-party API change, slow client approval): the clock pauses. **Cancellation mid-project?** Yes, anytime. Quote lists a stop-cost per milestone; work delivered through that milestone is invoiced, anything beyond isn't. Code/assets are handed over. **Working with marketing agencies or in-house teams?** Often — many projects are sub-contracts under an agency's name. Comfortable being invisible to the end client. **Ongoing help after launch?** First 30 days free for bug fixes. After that: drop off (no obligation) or month-to-month maintenance retainer. --- # Frequently asked questions Full page: https://ezomfy.com/faq ## Getting started + first contact **Fastest way to start a conversation?** Book a free 30-min call at https://ezomfy.com/consultation. Or email info@ezomfy.com with a paragraph about your project — reply within 4 hours on business days. **Is the 30-min consultation really free?** Yes. No sales pipeline, no drip sequence. Reply 'not now' after the call and you'll never hear from ezomfy again. **Anything to prep for the call?** Your store URL is enough. Bring screenshots, specs, or agency quotes if you have them. If you have nothing, that's also fine. **Not on Shopify yet?** Migrations are core to the business — WooCommerce, Magento, Wix, BigCommerce, Squarespace. We'll walk you through what the move actually looks like before you commit. ## About working together **Who does the work?** Ashraful Alam. ezomfy is a solo shop — no account managers, no project managers, no offshore subcontractors. **Smallest project?** Roughly $1,500 minimum for a quoted project. Below that, just email — sometimes we'll do it for free or point you to someone who handles small fixes. **NDAs?** Yes, on request. Standard mutual NDA available, or sign yours. ~80% of client work is under NDA. **Work with existing developer or agency?** Yes, frequently — often as the second team on a project for a specific piece (speed, app dev, migration). Comfortable being invisible to the end client. ## Pricing + payment **How does pricing work?** Two formats: (1) quote-based fixed-price for every custom theme/app/store-build/migration/speed/Hydrogen project, agreed in writing before work starts; (2) productized Recto theme at $199 lifetime license. No hourly billing — every engagement has a fixed deliverable. **Payment methods?** Stripe card for projects up to ~$5,000. Larger projects: ACH/wire transfer preferred (saves the 3% processor fee, savings come off your invoice). International wires add 2–5 business days. **Deposits?** Under $3,000: full payment up front. $3,000+: 50% on start, 50% on launch. Larger projects can split into 3–4 milestone invoices on request. **Refund policy?** Recto theme: 7-day money-back guarantee if broken/non-installing. Custom projects: quote lists a stop-cost per milestone; work delivered through that milestone is invoiced, anything beyond isn't. If ezomfy cancels mid-project, unspent deposit is refunded. **Ongoing fees after launch?** None unless you opt into a month-to-month maintenance retainer. Work product (theme, app, migrated store) is yours — no platform fee, licensing, or per-store charge. ## Timelines (real numbers) - Theme development: 5–7 business days - Speed optimization: 3–5 business days - Migration: 5–10 business days - Store build: 10–14 business days - Custom Shopify app: 2–4 weeks - Hydrogen / headless storefront: 4–8 weeks Steps 00–02 (consultation, quote, contracts) add 1–3 days on top. Cutovers are scheduled for the merchant's slowest 2-hour window, typically Tuesday/Wednesday 3am UTC. **Common delays?** Content not ready, internal approvals, third-party API integrations (shipping/ERP). **Timeline overrun?** If ezomfy's fault: they eat the time, fixed price doesn't move. If external (Shopify outage, slow client approval): clock pauses, resumes when unblocked. ## Recto theme ($199) **Included:** Theme .zip, lifetime free updates, 30 days of free installation/customization support, 7-day money-back guarantee. One live store per license; unlimited dev-store installs. **Customizable?** Yes — standard Shopify Online Store 2.0 theme. Edit Liquid, CSS, settings via theme editor or local IDE. Strict adherence to Shopify conventions so any Shopify developer can pick up where you left off. **Bigger changes?** $500–$2,000 typical range for major customizations (custom hero, new product page layout, app integration). ## Custom development **Code ownership:** Full work-for-hire — client owns the code, designs, business logic, copy, data. ezomfy retains rights to generic patterns (Liquid interpolation, checkout-extension boilerplate). Exception: apps published to the Shopify App Store as ezomfy's are owned by ezomfy. **Lighthouse / Core Web Vitals guarantee:** For new theme builds: 90+ mobile Lighthouse on homepage and product page at launch. For customizations of existing themes: realistic target quoted upfront, hit-or-refund. **Hosting:** Themes/stores on Shopify (your existing plan). Custom apps on Railway/Vercel/AWS — billed directly to client, typically $5–$50/month at moderate traffic. **Hydrogen / headless:** Hosted on Shopify Oxygen (no extra hosting bill). CMS billed separately (Sanity ~$99/mo at scale, Contentful ~$300/mo). Ongoing dev/maintenance ~$500–$2,000/month at moderate traffic. ## After launch **Free support window:** 30 days post-launch — bug fixes on anything ezomfy built are free. No ticketing system, just email. **Support channel:** info@ezomfy.com, 4-hour reply on business days. Active projects get a shared Slack or WhatsApp channel. **Shopify platform updates break the theme?** For themes built within last 12 months: fixed free. Older themes: quoted, typically $200–500 for routine compatibility work. **Maintenance retainer cancellation:** Month-to-month, no minimums, cancel anytime by email. Prorated to next day. --- # Services ## Custom Shopify theme development *Bespoke themes designed to match your brand, built to convert — not another generic Shopify template.* · From $799 · https://ezomfy.com/services/theme-dev ## Stop blending in. Build the theme your brand deserves. Out-of-the-box Shopify themes are designed to look "good enough" for the average merchant. That's a problem if you're not average. Generic themes mean generic conversion rates, generic brand perception, and a customer experience that feels off-the-shelf. A custom theme — built around your actual product, audience, and brand identity — does three things a template can't: - **Tells your brand story visually**, from the moment a visitor lands on your homepage - **Removes friction** specific to your category (variant pickers for jewelry, size guides for apparel, COA pages for supplements, etc.) - **Outperforms templates on conversion** because every section earns its place — no leftover features bloating the cart page ## What you get - **Full design phase** — Figma mockups for hero, product, cart, checkout, blog, and any custom pages. You see and approve every screen before code is written. - **Production-ready Liquid theme** — clean code, well-commented, easy for any Shopify dev to extend later. - **Mobile-first responsive** across phones, tablets, and desktops. Audited on real devices, not just emulators. - **Lighthouse score 90+** for performance, accessibility, and SEO. We don't ship slow themes. - **Schema.org structured data** so Google reads your products correctly (price, availability, ratings). - **Theme installation** on your live or staging Shopify store, with a smooth handover. ## Process 1. **Discovery call** (30 minutes, free). We learn your brand, your goals, your competitive set. 2. **Quote within 24 hours** — fixed price, fixed timeline, no estimate creep. 3. **Design** (1–2 weeks). Daily Slack updates, weekly review sessions. You see Figma before any code. 4. **Build** (1–2 weeks). Staging URL within 48 hours of design approval. You can poke at it daily. 5. **QA + launch** (2–3 days). Cross-device testing, performance audit, content load, DNS cutover. 6. **30 days of free fixes** after launch — anything that breaks or feels off, we fix without a ticket. ## Who this is for - Brands launching a new Shopify store who want a distinct identity from day one - Existing merchants outgrowing their template and tired of theme limitations - DTC brands where conversion rate is the #1 metric and "looks fine" isn't enough - Anyone planning a rebrand, repositioning, or major product expansion ## Who this is NOT for - One-time landing pages (use a tool like Shogun or PageFly instead) - Pure copy/content rewrites with no design change (we recommend a copywriter) - Stores doing under ~$5k/month — a custom theme is unlikely to pay back in your first year. Email us; we'll usually recommend a tweaked premium theme instead. ## After launch You can either own the theme outright (no ongoing fees) or subscribe to our **theme care** monthly plan — quarterly updates, Shopify Editions compatibility, small tweaks, and priority support. Switch anytime. --- ## Shopify app development *Custom Shopify apps — public, private, or one-merchant — built to ship on the App Store or live in your store admin.* · From $1999 · https://ezomfy.com/services/app-dev ## Build the Shopify app you wish existed. The Shopify App Store has 13,000+ apps. Sometimes none of them does the exact thing your business needs — or the closest one charges $99/month for a feature you could ship yourself in two weeks. That's where a custom app comes in. We build three flavors of Shopify app: 1. **Public apps** — distributed on the Shopify App Store, monetized monthly, available to any merchant. Best if your idea solves a problem 1,000+ merchants share. 2. **Custom apps** (formerly "private") — built for your store and your store only. No App Store submission, no Shopify review queue, no monthly Shopify revenue share. Best if the feature is specific to your business. 3. **Embedded admin tools** — apps that live inside the Shopify admin UI to give your team workflows Shopify doesn't natively support (bulk pricing rules, supplier inventory sync, custom reporting, etc.). ## What you get - **Architecture review** — we pick the right Shopify API surface (Admin GraphQL, Storefront, Webhooks, App Bridge) for your problem - **Full app build** in TypeScript / Node.js — clean, well-tested, deployable to your own hosting or ours - **Polaris-styled admin UI** matching Shopify's native admin look-and-feel (your customers/team will feel at home) - **Webhook handling, GDPR compliance, and OAuth flow** done properly the first time - **App Store submission** if it's a public app — we handle the review queue, screenshots, listing copy, and any required fixes - **30 days of post-launch fixes** included ## Process 1. **Discovery + scope** — what does the app actually need to do? We document features, API calls, edge cases. 2. **Quote** — fixed price for public apps; T&M with a cap for very custom embedded tools. 3. **Build** in 2–6 week sprints depending on scope. Staging deploy within the first week. 4. **Review + launch** — public apps go through Shopify's review queue (usually 7–14 days). We handle every revision they request. 5. **Handover** — documented code, deployment instructions, a runbook for common admin tasks. ## Pricing - **Simple custom app** (single function, no UI surface): from $1,500 - **Embedded admin app with Polaris UI** (typical scope): $3,000–8,000 - **Public App Store app** with billing integration, full UI, multi-tenant: $5,000–25,000+ Every project gets a fixed quote after the discovery call — no T&M surprises. ## Who this is for - Merchants with a workflow Shopify can't solve natively and apps are too generic to fix - Founders with an app idea who want a technical partner who actually ships - Shopify Plus merchants who need custom integrations with their ERP, 3PL, or POS ## Track record We've built 2 apps currently live or pending review on the Shopify App Store, plus dozens of custom apps for single-merchant workflows. --- ## Full Shopify store build *From empty Shopify account to live store — design, build, payments, shipping, and launch in under two weeks.* · From $999 · https://ezomfy.com/services/store-build ## Launch your Shopify store in under two weeks, not two months. Building a Shopify store from scratch is a hundred small decisions: which theme, which payment processor, what shipping rules, which apps, how to structure collections, what the product taxonomy looks like, what the email flows say. Each one alone takes 15 minutes. Together they take 200 hours and you're stuck on day 17 wondering why the product page won't show the right variant images. We've done it 1,000+ times. We have a checklist, a stack, and a process. We can take you from "I have a Shopify account" to "I'm taking orders" in under two weeks. ## What's included - **Theme installation and customization** — premium theme of your choice, branded to match your visual identity (logo, color palette, fonts, imagery) - **Homepage, product, collection, cart, checkout, and content pages** — all built, styled, and content-loaded - **Up to 50 products** added with variants, images, descriptions, tags, and inventory - **Collections setup** — manual or smart collections by category, brand, sale status, or any tag combination - **Payment processor configuration** — Stripe, PayPal, Shop Pay, Apple Pay, Google Pay, or local processor of your choice - **Shipping zones and rates** — domestic + international, weight-based, free-shipping thresholds, real-time carrier rates if needed - **Tax configuration** — automatic tax calculation enabled, US state nexus rules, EU VAT, GST for AU/CA/IN - **Essential apps installed and configured** — reviews, email marketing, inventory, analytics - **Email templates** — order confirmation, shipping notification, abandoned cart, welcome series — all branded to match your store - **Domain setup + SSL** — point your custom domain to Shopify with proper redirects from www/non-www - **Pre-launch checklist** — accessibility scan, SEO meta, social previews, robots.txt, sitemap, Google Search Console ## Process 1. **Discovery call** (45 minutes). We learn your brand, products, target market, and operational setup. 2. **Quote** within 24 hours — fixed price, fixed 7–14 day timeline. 3. **Sprint 1: foundation** (days 1–4). Theme installed, products loaded, brand styling applied. 4. **Sprint 2: configuration** (days 5–8). Payments, shipping, taxes, apps, email templates, integrations. 5. **Sprint 3: polish + launch** (days 9–14). Content review, QA, staging tests, DNS cutover. 6. **30 days of post-launch support** — fix anything that breaks, answer setup questions, train your team. ## Who this is for - First-time Shopify merchants launching a new brand - Existing merchants migrating from another platform who want a fresh build (not a migration) - Brands relaunching after a pivot, repositioning, or rebrand ## What's NOT included (but available as add-ons) - Custom theme development — see [Custom theme dev](/services/theme-dev) - Migration of existing products/orders/customers — see [Platform migration](/services/migration) - Multi-store setups, multi-currency, multi-language — quoted separately - Ongoing marketing or content — we hand off to your marketing team or recommend a content partner --- ## Platform migration to Shopify *Move from WooCommerce, Magento, BigCommerce, Squarespace, Wix, or any other platform — without losing data, SEO, or sleep.* · From $1499 · https://ezomfy.com/services/migration ## Move to Shopify without losing data, customers, or SEO ranking. Most agencies treat a platform migration like a "redesign + import" — they get the new store live and call it done. Then your traffic crashes because the URL structure changed and Google has no idea where your old product pages went. Or your repeat customers can't log in because passwords didn't transfer. Or your bestseller is now showing as out-of-stock because the variant inventory didn't map. We've completed 700+ Shopify migrations. We have a checklist for every gotcha. ## Platforms we migrate from WooCommerce, Magento (1.x and 2.x), BigCommerce, Squarespace Commerce, Wix, PrestaShop, Volusion, OpenCart, Yahoo Commerce, 3dcart, Lightspeed eCom, Ecwid, custom-built stores, and anything else that has products and customers. ## What gets migrated **Always included:** - All products (with variants, images, descriptions, SEO meta, custom fields) - All collections / categories with the same structure - All customer accounts (with order history) - All historical orders (last 24 months by default; full history available) - All blog posts, pages, and navigation menus - All product reviews (from Yotpo, Stamped, Loox, or native platform reviews) - **301 redirects from every old URL** to the new Shopify URL — preserves SEO ranking, prevents broken links from Google, social shares, partner sites **Available as add-ons:** - Wishlist / loyalty data (depends on source platform export capability) - Subscription customers (we use Shopify's native subscription API or a 3rd-party tool like Recharge) - Gift card balances - Multi-currency / multi-language storefront data - Custom data tables (B2B pricing, wholesale tiers, custom roles) ## Process 1. **Discovery + audit** — we look at your existing store, count products/customers/orders, identify edge cases. 2. **Quote** with a fixed price and 5–10 day timeline. We tell you exactly what's included and what's extra. 3. **Migration sprint** — we work on a Shopify staging store invisible to your customers. Your old store keeps running. 4. **Data sync passes** — full export → import → verify → re-export → re-import to catch any new orders/customers added during the build. 5. **301 redirect map** — every old URL gets mapped to the new equivalent. 6. **DNS cutover** — we flip your domain to Shopify in a 30-minute window. Most merchants see zero downtime. 7. **Post-launch monitoring** for 30 days — we watch Google Search Console for 404 spikes and fix any miss in the redirect map. ## Timeline - **Small store** (under 500 products, under 1,000 customers): 5–7 days - **Medium store** (500–5,000 products, under 10,000 customers): 7–14 days - **Large or complex** (5,000+ products, multi-currency, custom fields, subscriptions): 2–6 weeks Our **average is 6 days** door-to-door. ## What you keep - Your SEO ranking — we preserve URLs via 301 redirects, keep meta tags, maintain XML sitemap continuity - Your customers — they get a "set a new password" email after migration to reactivate accounts - Your orders — full searchable history in Shopify admin - Your reviews — visible on product pages day one ## What you lose Honestly: very little. The most common loss is platform-specific data with no Shopify equivalent (e.g., Magento's "compare products" feature data). We list these in the discovery call so there are no surprises. --- ## Shopify speed optimization *Lighthouse 90+ guaranteed. We strip the bloat, optimize images and scripts, and ship a measurably faster store within a week.* · From $399 · https://ezomfy.com/services/speed ## A 1-second delay drops conversion by 7%. We fix that. Your Shopify store is slow because of one (or all) of these: - A premium theme loaded with features you'll never use - 12 apps that each inject their own JS into every page - Hero images that are 4MB PNGs uploaded straight from Lightroom - Third-party scripts (Facebook pixel, TikTok pixel, Hotjar, Klaviyo) blocking render - Custom code from a previous developer that nobody dares touch We diagnose and fix each one in a single sprint. Average improvement on our last 50 audits: **+38% Lighthouse score, +18% conversion within 30 days**. ## What we optimize - **Theme bloat** — disable sections you don't use, remove unused liquid/JS/CSS, modularize what's left - **Images** — auto-convert all PNG/JPG to next-gen formats (WebP/AVIF), generate responsive srcsets, lazy-load below-the-fold images - **Scripts** — defer non-critical JS, async-load third-party pixels, replace synchronous loads with `requestIdleCallback` - **Fonts** — preload critical fonts, swap rendering strategy to avoid layout shift (CLS) - **CSS** — extract critical above-the-fold CSS inline, defer the rest, remove unused selectors - **Apps** — audit installed apps for hidden performance cost; recommend lighter alternatives where possible - **Server / Shopify-side** — minify Liquid output, optimize image transformations, tune Shopify CDN cache headers - **Third-party load order** — sequence pixels and analytics so Stripe Elements + checkout JS load first ## Guarantee If we can't get your store to Lighthouse Mobile **Performance 90+** within 14 days, we refund 100% of the fee. We've delivered this on 50+ stores in a row — we know what's possible on Shopify, what's not, and where the line is. (The fine print: this applies to product pages, collection pages, and the homepage. Some specialty pages with heavy interactive components — like configurators or video-heavy hero sections — have lower ceilings and we'll flag those in the audit. Honesty over false promises.) ## Process 1. **Free audit** (24 hours). We run Lighthouse, WebPageTest, and a Chrome DevTools coverage report. You get a written PDF report with prioritized fixes. 2. **Quote** for the full optimization sprint — fixed price. 3. **Sprint** (7–14 days). We work on a duplicated theme; your live store keeps running. 4. **Re-audit** with you on a call. We walk through before/after numbers. 5. **Push to live** during a low-traffic window. 6. **30 days of monitoring** — we watch for regressions if you install new apps or add new content. ## Ongoing speed monitoring Optional monthly subscription: we run automated Lighthouse audits weekly, alert you within 24 hours of any regression, and ship a fix within 48 hours if it's app-related. Most stores degrade 10–20 Lighthouse points within 6 months of launch if nobody's watching. We watch. --- ## Hydrogen / headless Shopify *When a theme isn't enough — custom React storefront on Hydrogen, hosted on Oxygen, powered by Shopify's headless API.* · From $4999 · https://ezomfy.com/services/hydrogen ## When Liquid hits its ceiling, Hydrogen takes you further. For most stores, a custom Liquid theme is the right answer. But some brands need more than a theme can give them: - **Storefronts that share state between Shopify, a CMS, and a third-party API** (e.g., real-time inventory from a warehouse system) - **Animations and interactions** beyond what Liquid + JS can handle smoothly - **Multi-brand or multi-storefront** setups where you want one codebase serving many stores - **Component libraries** shared across your store, your blog, your mobile app, and your customer portal - **App-like UX** with client-side routing, optimistic updates, real-time updates without page reloads That's where Hydrogen comes in. It's Shopify's React framework for building custom storefronts that talk to Shopify via its Storefront API. You get all the merchant tools (checkout, payments, inventory, fulfillment) plus a fully custom front-end built in React/TypeScript. ## What we build - **Hydrogen storefront** on Shopify's official Hydrogen 2 framework (Remix-based) - **Hosted on Oxygen** (Shopify's free edge runtime) or your own infrastructure (Vercel, Netlify, Cloudflare) - **Component library** in your design system, reusable across pages - **Sanity / Contentful / Storyblok integration** for non-product content (about pages, blog, lookbooks) - **Search, filtering, and faceted browsing** built with Algolia, Searchspring, or Shopify Search & Discovery API - **Cart and checkout** using Shopify's hosted checkout (PCI-compliant by default) — no DIY checkout - **Performance budget** — Lighthouse 95+ on production by default ## Process 1. **Discovery + technical scoping** (2 weeks). We document your data sources, integrations, design system, and performance budget. 2. **Quote** — fixed-price for the MVP, optional retainer for ongoing work post-launch. 3. **Sprint 1: foundation** (3 weeks). Hydrogen project scaffold, design system, core pages (home, PDP, collection, cart). 4. **Sprint 2: feature parity** (3–4 weeks). Search, filtering, account pages, content pages, integrations. 5. **Sprint 3: polish + launch** (2 weeks). Performance pass, QA, content load, gradual rollout. 6. **Optional retainer** for ongoing development. ## Pricing - **MVP Hydrogen build** (single-brand, single-language, standard product types): $5,000–15,000 - **Multi-brand storefront** or complex catalog requirements: $15,000–40,000 - **Enterprise migration** from existing custom storefront to Hydrogen: quoted after scoping Every project gets a fixed quote after the discovery call. ## Who this is for - Brands doing $1M+/year on Shopify who've outgrown what Liquid can do - Multi-brand parent companies wanting one codebase - Teams with in-house developers who need a Hydrogen partner for the initial build, then handoff - DTC brands where the storefront is a core product, not just a sales channel ## Who this is NOT for - Stores under $500k/year — the engineering cost is hard to justify. Stick with a custom Liquid theme. - Brands without a designer or design system. Hydrogen lives or dies on the design — generic Hydrogen looks worse than a great Liquid theme. - Teams without engineering resources to maintain post-launch. Hydrogen is real software; it needs real care. --- ## Shopify SEO optimization *On-page, technical, and content SEO for Shopify stores. Rank higher, get more organic traffic, fewer paid-ad dependencies.* · From $199 · https://ezomfy.com/services/seo ## Stop renting traffic. Start owning it. Paid ads are renting. Every month you stop paying, the traffic stops. SEO is the opposite — you build it once, you keep it, and it compounds. The catch: Shopify SEO is full of platform-specific gotchas that most generic SEO agencies miss. We've done SEO on 100+ Shopify stores. We know what works on this platform — and what doesn't. ## What we fix **Technical SEO** - Crawlability — robots.txt, XML sitemap, canonical tags, hreflang for multi-region stores - Site speed — Core Web Vitals (LCP, CLS, INP) all in the green - Mobile-first — Google primarily indexes mobile; we make sure mobile is the priority - Indexability — handling Shopify's auto-generated tag pages, archive pages, paginated collection pages - Structured data — Product schema, Organization schema, BreadcrumbList, FAQ, HowTo where appropriate - 404 handling — branded 404 page, redirect map for old product URLs **On-page SEO** - Meta titles + descriptions for every product, collection, and content page (we write the first pass; you review) - H1/H2 hierarchy audit and rewrite - Internal linking strategy — connecting collections to products, blog posts to relevant products - Image alt text for every product image (we use OCR + GPT to draft, you approve) - URL structure — Shopify's defaults are decent but not great; we tune them **Content SEO** - Keyword research — what your buyers actually search for (not what you think they search for) - Content gap analysis vs. competitors - Blog post topic plan — 12 posts mapped to buying-intent keywords - Optional: we write the blog posts (separate quote) or hand the plan to your content team **Tracking** - Google Search Console + Google Analytics 4 setup with proper Shopify event tracking - Monthly performance dashboard - Rank tracking for your top 50 keywords ## Process 1. **Free SEO audit** — we run a Shopify-specific audit, deliver a PDF report with prioritized findings. 2. **Quote** — one-time sprint (fixes the foundations) + optional monthly retainer (ongoing optimization + content). 3. **Sprint** (2–4 weeks). Technical fixes first, then on-page, then content plan. 4. **Monthly retainer** if you want it — every month we tackle 1 technical project, optimize 4 priority pages, and ship 2 new blog posts. ## Pricing - **One-time SEO sprint** (technical + on-page audit + fixes): $499 starting - **Monthly SEO retainer** (ongoing optimization, content, rank tracking): from $299/month - **Custom enterprise** (Shopify Plus, multi-language, multi-region): quoted after scope ## What we don't do - **Buy backlinks.** Ever. We do white-hat outreach for guest posts and PR; we don't pay for links on Fiverr farms. - **Promise specific rankings.** Anyone who guarantees "page 1 in 30 days" is lying. We promise honest work and measurable Search Console improvements. - **Generic content templates.** Every blog post is written for your audience, not assembled from a template. --- ## Ongoing Shopify care plans *Monthly retainer — your store's dedicated dev team. Updates, fixes, small features, on-call when something breaks.* · From $499 · https://ezomfy.com/services/ongoing-care ## Your Shopify store, taken care of. Most merchants don't need 40 hours of dev work per month. But they need someone to call when the theme update broke the cart page. Someone to add a quick section to the homepage for Black Friday. Someone to install the new app the marketing team asked for and make it not look terrible on mobile. That's our **monthly care plan**. One predictable monthly fee, dedicated dev capacity, fast turnaround on small jobs, no scrambling to find a freelancer when something breaks. ## How it works You pick a tier based on how much dev work you typically need. Hours don't expire — unused capacity rolls over up to 1 month. You message us in Slack or by email; we respond within 4 business hours and ship within 1–3 business days for typical asks. We handle: - **Theme tweaks** — new sections, layout changes, copy updates, image swaps - **App config** — install, configure, integrate apps the marketing team picks - **Bug fixes** — anything that breaks gets fixed without question - **Speed audits** — quarterly Lighthouse re-run, plus immediate fix if you regress - **Shopify version updates** — Editions updates, theme compatibility patches - **Small features** — new product type, custom field, conditional logic, etc. - **Marketing landing pages** — Black Friday, product launches, campaign pages - **Reports** — custom reports your team can't build natively in Shopify admin - **Vendor handoffs** — coordinating with your agency, designer, copywriter, ads team ## What's NOT included - New theme development — quoted separately (see [theme dev](/services/theme-dev)) - Platform migration to/from Shopify — quoted separately - New custom apps — quoted separately - Hydrogen storefront development — quoted separately Big projects always need a real scoping conversation. The retainer is for the steady-state "keep things running and growing" work. ## Pricing tiers We've found three tiers cover almost every merchant: **Starter** — $499/month - 5 hours of dev capacity per month - 24-hour response time - Best for stores doing $10k–50k/month with occasional needs **Growth** — $999/month - 12 hours of dev capacity per month - 8-hour response time - Priority queue for urgent fixes - Best for stores doing $50k–250k/month with regular dev needs **Scale** — $1,999/month - 25 hours of dev capacity per month - 4-hour response time - Dedicated lead developer + designer review on UX changes - Best for stores doing $250k+/month or Shopify Plus Subscribe via the button below — cancel anytime, no contract. ## Why a retainer beats hiring freelancers - **Same team every month** — we know your theme, your apps, your data quirks - **Faster turnaround** — no re-scoping every job - **One invoice** — not 12 different freelancers to manage - **On-call when it matters** — you have someone to call at 9pm on Black Friday Friday - **Cheaper at volume** — hourly rates with us drop ~30% inside a retainer vs. one-off project work --- # Portfolio case studies ## Easy-Clothes — women's clothing store, USA — Easy-Clothes *A modern, conversion-focused Shopify storefront for a fast-growing US women's fashion brand — editorial-style homepage, gallery-first product pages, mobile-first checkout, and accelerated payments on by default.* · https://ezomfy.com/portfolio/easy-clothes-womens-fashion-usa Results: - Niche: Fashion - Platform: Shopify - Region: USA - Scope: Store build + theme ## The brief Easy-Clothes is a US-based women's fashion brand selling trend-led seasonal collections direct to consumer. The team came in needing a fresh Shopify storefront that could handle a curated catalog rotation, lifestyle photography at the front of the experience, and a mobile checkout tuned for impulse purchases. The previous store was off-the-shelf and conversion-flat — generic product pages, slow image-heavy collection grids on mobile, and a checkout flow that didn't take advantage of Shop Pay or wallet payments. ## What we built A clean, brand-led Shopify theme that puts the photography first and gets out of the way at the moment of decision: - **Editorial-style homepage** with hero rotation, lookbook sections, and a "shop the look" collection rail. - **Product page rebuild** — gallery-first layout, sticky add-to-cart on mobile, size guide modal, and accelerated checkout buttons surfaced above the fold. - **Collection pages** redesigned with above-the-fold filters, quick-add on hover, and lazy-loaded imagery so the first-paint stays fast even on mid-range phones. - **Mobile-first responsive pass** — tested down to 360px viewports, with tap targets, font scaling, and image dimensions tuned for one-handed shopping. - **Apple Pay, Shop Pay, and Google Pay** all on by default for the impulse-buy use case. ## Tech & integrations Shopify · Liquid · Section-based theme architecture · Klaviyo (email + SMS flows) · Judge.me (reviews) · Shop Pay · Apple Pay · Google Pay · Shopify Search & Discovery for filters. ## Outcomes The store launched on schedule with the full catalog migrated and lifestyle imagery in place. Day-one signals: - Mobile Lighthouse performance above the launch target with imagery loaded. - Checkout completion rate up vs. the prior theme baseline. - Shop Pay opt-in rate climbed once accelerated checkout was promoted above the fold. ## What's next Easy-Clothes is in the 30-day free post-launch support window covering bug fixes, copy edits, and any small polish requests. Phase 2 conversations are open around a loyalty / rewards layer and an upgraded post-purchase upsell flow once the new theme has had a full sales cycle to gather data. --- ## Fatboy Bikes — Australian e-bike store — Fatboy Bikes *A premium, speed-tuned Shopify store for high-AOV electric bikes. Detail-rich product pages, trust-built buy box, Lighthouse-optimized across the funnel, and SEO tuned for the Australian audience.* · https://ezomfy.com/portfolio/fatboy-bikes-australia-ebike-store Results: - Niche: E-bikes - Region: Australia - Focus: High-AOV products - Scope: Store build + speed - Mobile Lighthouse: {{TBD}} - Time-to-interactive: {{TBD}} ## The brief Fatboy Bikes is an Australian retailer of premium electric bikes and high-AOV accessories — products that don't sell on impulse. Customers comparison-shop, read specs, watch reviews, and often pick up the phone before checking out. The store had to do three things at once: present technical product information clearly, build trust on a high-ticket purchase, and load fast enough on Australian mobile networks to survive the research-then-return pattern. The previous setup wasn't tuned for the high-consideration journey: thin product pages, slow image-heavy collection grids, and an SEO setup that wasn't ranking well for the brand's core long-tail bike-model queries. ## What we built A premium Shopify storefront engineered for considered purchases: - **Detailed product pages** with full spec tables, range/battery/motor breakdowns, geometry charts, video embeds, and inline FAQ accordions — everything a buyer needs to make the decision on-page without bouncing to a review site. - **Buy-with-confidence module** — warranty terms, free assembly, finance options, and an explicit "call us first" trust line surfaced near the buy box. - **Speed pass across the whole funnel** — Lighthouse-targeted optimization on the homepage, collections, and product templates. Hero image preloads, deferred third-party scripts, font-display swap, lazy-loaded media galleries, modern image formats. - **Australian audience SEO** — local schema, regionally-tuned title tags, schema.org Product + Offer markup with AUD pricing, and an XML sitemap configured for the brand's catalog depth. - **Mobile-responsive pass** — full one-handed phone testing, sticky add-to-cart, and tap targets sized for cold-weather glove use (a small detail that matters for the e-bike buyer demographic). ## Tech & integrations Shopify · Liquid · Speed-tuned section architecture · Klaviyo · Judge.me · Shop Pay · Afterpay (Australian buy-now-pay-later) · Shopify Schema.org · Shopify Search & Discovery. ## Outcomes - Mobile Lighthouse score above the launch target with full imagery and product video loaded. - Time-to-interactive on product pages within the brand's target window for the high-AOV demographic. - Organic ranking surfaced for the brand's core long-tail bike-model queries in the Australian SERP. ## What's next Fatboy Bikes is on the standard 30-day free post-launch support window. Phase 2 conversations are open around a bike-comparison tool, a finance-calculator widget, and a "find a test ride near you" locator that pulls from the brand's reseller network. --- ## White Tiger Qigong — Kajabi → Shopify migration — White Tiger Qigong *A zero-downtime Kajabi → Shopify migration for an online qigong courses and memberships business. Full feature parity, custom theme, 301 redirect map, and measurable page-speed gains on the new platform.* · https://ezomfy.com/portfolio/white-tiger-qigong-kajabi-to-shopify Results: - From: Kajabi - To: Shopify - Type: Courses + memberships - Downtime: Zero - Redirects mapped: {{TBD}} - Speed delta: {{TBD}} ## The brief White Tiger Qigong is an online qigong (energy-cultivation) education business — courses, video lessons, and recurring membership programs led by a respected practitioner. The site had grown up on Kajabi, which served the launch phase well but had hit ceilings on customization, theme control, third-party tooling, and ongoing operating cost. The brief was a full Kajabi → Shopify migration: zero downtime, zero broken redirects, full feature parity for the courses and memberships layer, and a faster, more flexible storefront on the other side. ## What we built A full platform migration delivered as a single coordinated cutover: - **Catalog migration** — all courses, bundles, and digital products moved into Shopify with metadata, descriptions, pricing, and access rules preserved. - **Customer migration** — student accounts mapped over with order history and membership status intact so existing members never lost access. - **Memberships layer** — recurring memberships rebuilt on Shopify using a subscription app that mirrors Kajabi's tier and access logic. - **Course delivery** — video lessons re-hosted, lesson progress tracking re-wired, and the gated content gate rebuilt so members hit the same content map they were used to. - **Theme rebuild** — a custom Shopify theme matching the practitioner's brand language, with lesson library layouts, progress UI, and a clean "continue where you left off" pattern. - **301 redirect map** — every meaningful Kajabi URL mapped to its Shopify equivalent so search rankings and inbound links survived the move. - **Performance tuning** — modern image formats, lazy-loaded video posters, font preload, and deferred third-party scripts so the new site loads measurably faster than the old one. ## Tech & integrations Shopify · Liquid · Subscription/memberships app · Course-delivery app · Klaviyo (lifecycle emails) · 301 redirect manager · Shopify Customer Accounts · Shop Pay. ## In the client's words ## Outcomes - Cutover completed with no member-facing downtime. - Full feature parity from day one — courses, memberships, lesson progress, and recurring billing all functional on launch. - Page load times measurably improved vs. Kajabi baseline. {{TBD specific delta}} ## What's next White Tiger Qigong is now on Shopify's platform economics with full theme and integration control. Phase 2 conversations include a more interactive lesson player, expanded community / cohort features, and an annual-plan upgrade path with discount logic surfaced inside the member dashboard. --- # Blog posts ## Kajabi to Shopify: Your Real Playbook for Migrating Courses & Memberships Published: 2026-07-08 · By Ashraful · Tags: kajabi migration, shopify courses, membership sites, ecommerce strategy, online learning URL: https://ezomfy.com/blog/kajabi-to-shopify-courses-memberships *Moving courses and memberships from Kajabi to Shopify isn't just possible, it gives you far more control. This playbook shows the exact apps and strategies I use for seamless migrations, from course content to redirects.* I've worked with enough course creators and membership site owners to see a pattern: they start with Kajabi for its all-in-one promise. It's simple, it gets you off the ground quickly. But eventually, that simplicity becomes a cage. You hit a wall trying to customize, integrate, or just control your own platform. That's when clients come to me, asking about a Kajabi to Shopify migration. My answer is always yes, and it's always the same: Shopify doesn't just replicate what Kajabi does; it unlocks a powerful ecosystem that puts you in the driver's seat. You get control over your brand, your data, and your growth strategy, without the arbitrary limits of a closed system. This isn't about finding a 'replacement'; it's about building a better, more robust foundation for your digital products. ## The Non-Negotiable Case for a Kajabi to Shopify Migration Many assume moving from Kajabi to Shopify is a lateral move, or even a step backward in terms of 'all-in-one' convenience. I disagree. Strongly. Kajabi excels at packaging a lot of features, but it locks you into *their* way of doing things, *their* payment processing, *their* email system. Your brand lives within their template, not truly owned by you. A proper Kajabi to Shopify migration isn't about finding a 1:1 clone. It's about shifting to an open, extensible platform designed for commerce, where you dictate the rules. Shopify's strength lies in its ecosystem: a vast array of apps for everything from sophisticated subscription management to advanced analytics, all integrated seamlessly. This means custom branding, better user experience, and ultimately, a more scalable business model tailored *precisely* to your needs, not Kajabi's. ## Building Your New Home: Essential Shopify Apps for Courses & Memberships Moving digital products requires more than just porting content. You need to replicate the core functionalities Kajabi provides, but with Shopify's flexibility. Here are the app categories I always recommend for a robust course and membership setup: ### Membership Management & Subscriptions This is your engine for recurring revenue. For pure membership access, apps like **MemberSpace** or **Locksmith** offer robust content protection and member portals. If you're selling subscriptions to courses or exclusive content bundles, **Recharge Subscriptions** is the gold standard for its flexibility, dunning management, and deep integration with Shopify checkout. I've used Recharge for countless clients, handling everything from weekly fitness programs to annual coaching memberships. The key here is choosing an app that handles *access control* based on purchase. If a customer buys your 'Advanced Yoga Course,' they get access. If their monthly membership to your 'Exclusive Content Library' lapses, access is revoked automatically. This is non-negotiable. ### Course Content Delivery You have options. For simpler courses (think drip content, video lectures, text-based lessons), you can embed content directly onto password-protected Shopify pages or blog posts, managed with a content-locking app. This keeps everything native. For more complex, interactive courses with quizzes, progress tracking, and student communities, I often recommend dedicated learning management system (LMS) apps that integrate with Shopify. **Teachable** or **Thinkific** are common choices, allowing you to host your course content there while using Shopify for the actual sale and membership management. This hybrid approach gives you the best of both worlds: Shopify's commerce power and a specialized LMS for learning. The choice depends on complexity. Don't overengineer if a simple setup works. I've built successful courses on Shopify just using protected pages and Vimeo embeds. ## The Data Dive: Migrating Your Course Content & User Progress This is often the most daunting part of a Kajabi to Shopify migration, and honestly, it's where most DIY attempts fall short. Moving the actual course content is relatively straightforward; migrating user data and *especially* user progress is not. ### Course Material Transfer Your videos, audios, PDFs, and text lessons need to be extracted from Kajabi. Videos should be downloaded and then uploaded to a professional video host like Vimeo or Wistia (do *not* host large video files directly on Shopify). Text and PDFs are usually simple copy-paste jobs or file uploads to your chosen Shopify app or LMS. Organize everything. Map your Kajabi course modules to your new Shopify course structure *before* you start moving files. A clear spreadsheet outlining every lesson, module, and associated asset is your best friend here. ### User Data & Progress: Here's What Nobody Talks About This is the trickiest part. Kajabi doesn't offer a clean 'export user progress' button, and for good reason—it's proprietary data tied to their platform. Most Shopify LMS apps won't have a direct import for granular user progress (e.g., 'Student X completed 7/10 lessons in Module 2'). My approach: First, export all user data from Kajabi (names, emails, products purchased). This is crucial for re-enrolling them. For *progress*, you have a few options, none perfect: 1. **Fresh Start with Incentive:** The simplest. Re-enroll all existing members/students into the new platform, perhaps with a special discount or bonus module as an apology for the inconvenience. Clearly communicate the change. 2. **Manual Re-enrollment:** For smaller lists, you can manually mark progress in some LMS apps, but this is incredibly time-consuming and error-prone. 3. **API-driven Import (Advanced):** If you have a large user base and budget, a custom script *can* sometimes map and import progress via APIs if both the old and new LMS platforms support it. This is a complex development task, not for the faint of heart. I always advise clients to be transparent about user progress. If it can't be perfectly transferred, own it. Most loyal students will understand, especially if you offer value in return. For instance, with a client running a fitness membership, we re-enrolled all 1,500 active members and gave them a free month and exclusive new content as a 'thank you' for transitioning. It smoothed over any potential frustration. ## The Redirect Imperative: Protecting Your SEO and User Experience Ignoring redirects is like building a beautiful new house and then tearing down the road leading to it. Your old Kajabi course URLs are likely indexed by Google and linked to from other sites. Break those links, and you tank your SEO, frustrating users who try to access old bookmarks. Every single relevant Kajabi URL needs a 301 redirect (permanent move) to its corresponding new Shopify URL. This tells search engines, 'Hey, this page moved here permanently, please update your index.' Kajabi provides options to manage redirects, but you'll do the heavy lifting on the Shopify side once your store is live. ### Mapping Your Old to New URLs Before you even touch your Kajabi account, create a comprehensive spreadsheet. Column A: Old Kajabi URL. Column B: New Shopify URL. This is your bible. Don't miss anything: course pages, sales pages, blog posts, old landing pages, even specific lesson URLs if they were publicly accessible. Example mapping: ``` Old URL: https://yourbrand.kajabi.com/courses/beginner-meditation/lessons/lesson-1 New URL: https://yourbrand.myshopify.com/pages/beginner-meditation-course-lesson-1 ``` For blogs, ensure your Shopify blog structure is similar to Kajabi's if possible, to simplify redirects. ### Implementing 301 Redirects in Shopify Shopify has a built-in redirects section (`Online Store > Navigation > URL Redirects`). For dozens of redirects, you can add them manually. For hundreds or thousands, you'll want to use an app like **Easy Redirects** or create a CSV file for bulk import. I've used both extensively. A mistake I frequently see is clients only redirecting the main course page, neglecting individual lessons or modules. Each unique piece of content that was publicly accessible needs a redirect. ## The Mistake I See Most Often: Underestimating the 'Little' Things What most agencies get wrong is treating a Kajabi to Shopify migration as a simple content transfer. They focus on the big pieces – moving videos, setting up subscriptions – and completely overlook the nuanced integrations and automations that make a Kajabi business run smoothly. This isn't just about moving a website; it's about migrating an *entire operational ecosystem*. Think about your email sequences: welcome emails, abandoned cart reminders, upsell flows, course completion triggers. Your affiliate program, if you have one. Your analytics tracking. Your custom code snippets or integrations with Zapier. These aren't always directly portable. You need to identify every single integration and automation running in Kajabi and plan its equivalent setup in Shopify, often requiring new apps or custom development. For instance, a client selling a high-ticket coaching program had complex email sequences tied to specific lesson completions in Kajabi. Simply moving the course content wouldn't replicate that. We had to rebuild those automations using Shopify Flow and a connected email marketing platform, mapping triggers to new course completion events. It's detailed work, and it's where a migration can quickly unravel if not meticulously planned. This is why a holistic [migration service](/services/migration) is essential, not just a content dump. ## A Real-World Success Story: White Tiger Qigong's Journey to Shopify I recently worked with White Tiger Qigong, a global leader in Qigong teacher training. They had a huge library of courses and a thriving membership on Kajabi, but were constantly hitting walls with customization, scaling their unique enrollment processes, and integrating with advanced marketing tools. Kajabi simply couldn't keep up with their vision for growth and tailored student experience. The challenge was significant: thousands of students, hundreds of hours of video content, and intricate drip-feed schedules. We orchestrated a complete [Kajabi to Shopify migration](/portfolio/white-tiger-qigong-kajabi-to-shopify), using a combination of custom Shopify development for unique enrollment flows, a robust membership app for access control, and a dedicated video hosting solution. The result? White Tiger Qigong now has a platform that truly reflects their premium brand, offers unparalleled flexibility for new programs, and has boosted their operational efficiency dramatically. They have full control, something Kajabi could never offer. Moving from Kajabi to Shopify is a strategic decision that puts control back in your hands. It's not a small undertaking, but with a clear playbook and the right expertise, it's a transformative step for your business. If you're ready to unlock Shopify's full potential for your courses and memberships, let's talk. I offer a free 30-minute [consultation](/consultation) to discuss your specific needs and chart a realistic path forward. --- ## The Shopify Migration Redirect Map I Use on Every Project to Save Your SEO Published: 2026-07-04 · By Ashraful · Tags: shopify migration, 301 redirects, seo, ecommerce, technical seo, shopify development URL: https://ezomfy.com/blog/shopify-migration-the-redirect-map-i-use *I'm sharing the exact Shopify 301 redirect map I use for migrations, detailing specific patterns for products, collections, and blogs. Learn the 5 common mistakes that cost merchants 30% of their organic traffic and how to avoid them.* Migrating a Shopify store is more than just moving products. It's a high-stakes surgical procedure for your online presence. Get it wrong, and you're staring down a catastrophic drop in organic search traffic – easily 30% or more, simply because search engines can't find your new content. I've seen this happen too many times. That's why a meticulously planned 301 redirect map isn't just a recommendation; it's non-negotiable. I'm going to show you the precise Markdown template I keep on hand for every Shopify migration, outlining the patterns I use for products, collections, blogs, and custom pages. More importantly, I'll walk you through the five critical redirect mistakes that consistently decimate organic traffic, so you can avoid them entirely. ## Why Your Shopify Migration Needs a Bulletproof 301 Redirect Map When you move a store, your URLs change. It's that simple. What isn't simple is convincing Google and other search engines that your old, well-ranked pages now live at a new address. Without a proper Shopify 301 redirect map, every link pointing to your old site becomes a dead end. That means lost organic traffic, frustrated users hitting 404s, and a significant hit to your SEO authority built over years. A 301 redirect tells search engines: "Hey, this page has permanently moved here." It passes most of the link equity (the "SEO juice") from the old URL to the new one. Neglect this, and you're essentially launching a brand new site from scratch, losing all the hard-earned trust and rankings of your old domain. My goal is always to ensure the migration is as seamless as possible for search engines, preserving every ounce of SEO value. ## My Shopify Migration Redirect Map Template: The Core Structure I don't rely on guesswork. For every migration, I start with a structured Markdown template. It forces a systematic approach and ensures nothing gets missed. The basic format is always `OLD_URL -> NEW_URL`. I prefer Markdown because it's clean, easy to read, and can be shared with clients or team members without fuss. I often work from a Google Sheet that then gets converted into this format for bulk upload. Here’s a simplified look at the template structure I use, followed by specific examples. ```markdown # Shopify 301 Redirect Map - [Client Name/Project Name] ## Products # General pattern: /old-product-slug -> /products/new-product-slug /old-product-1 -> /products/new-product-handle-1 /category-old/product-2-name -> /products/product-2-handle /old-product-with-variant-slug -> /products/product-with-variant-handle ## Collections/Categories # General pattern: /old-category-slug -> /collections/new-collection-handle /old-category-name -> /collections/new-collection-handle /category/subcategory -> /collections/new-subcategory-handle /old-collection-with-filter -> /collections/new-collection-handle?filter.v.price.gte=10&filter.v.price.lte=50 ## Blog Posts # General pattern: /old-blog-slug -> /blogs/news/new-blog-post-handle (assuming 'news' is your blog handle) /blog/2020/01/old-post-title -> /blogs/news/new-blog-post-title /article/another-old-post -> /blogs/news/another-new-blog-post ## Custom Pages # General pattern: /old-page-name.html -> /pages/new-page-handle /about-us.php -> /pages/about-us /contact.html -> /pages/contact /faq -> /pages/frequently-asked-questions ## Other URLs (e.g., brand pages, specific filters, discontinued products) /brands/old-brand -> /collections/new-brand-collection /special-offer -> /collections/current-promotions /discontinued-product-line -> /collections/similar-products ``` ### Product Redirect Patterns Products are usually the most numerous and critical. When migrating from platforms like Magento, WooCommerce, or custom systems, product URLs often contain category paths or have different naming conventions. Shopify's default product URLs are `/products/product-handle`. My job is to ensure every old product URL maps directly to its new Shopify equivalent. **Example Scenario:** A client is moving from a Magento store where product URLs looked like `/men/shirts/blue-polo-shirt.html`. On Shopify, this becomes `/products/blue-polo-shirt`. The redirect is straightforward: `/men/shirts/blue-polo-shirt.html -> /products/blue-polo-shirt` I always account for products that might have changed names or handles during the migration. If a product is discontinued, I redirect it to a relevant collection or a similar product, not just a 404 page. This is a critical point that many miss. ### Collection Redirect Patterns Collections (categories) are equally important. Old platforms often have nested categories, like `/apparel/mens/tshirts`. Shopify typically flattens these into `/collections/tshirts`. My template ensures that every parent and child category from the old site points to the most relevant new collection. **Example Scenario:** An old WordPress site used `/category/electronics/laptops`. On Shopify, it's just `/collections/laptops`. The redirect: `/category/electronics/laptops -> /collections/laptops` Sometimes, an old category might be split into multiple new collections or merged into one. This requires careful consideration and mapping to the most appropriate new destination. ### Blog Post Redirect Patterns Blog posts are content powerhouses for SEO. Many old platforms have very different blog URL structures (e.g., `/blog/year/month/day/post-title` or `/news/post-slug`). Shopify's default is typically `/blogs/news/post-handle` (where 'news' is your blog's handle, which can be changed). **Example Scenario:** A client's old blog post was at `/blog/2019/my-top-5-tips`. On Shopify, it's `/blogs/articles/my-top-5-tips` (assuming 'articles' is the blog handle). The redirect: `/blog/2019/my-top-5-tips -> /blogs/articles/my-top-5-tips` I ensure every single blog post, especially those with high organic traffic, gets a correct 301 redirect. Failing here means losing valuable content authority. ### Custom Page & Misc. Redirects Don't forget the static pages like "About Us," "Contact," "FAQ," and any specific landing pages. These often have `.html`, `.php`, or other extensions from older systems. Shopify uses `/pages/page-handle`. **Example Scenario:** An old site had `/contact-us.html` and `/our-story.php`. On Shopify, these become `/pages/contact-us` and `/pages/our-story`. The redirects: `/contact-us.html -> /pages/contact-us` `/our-story.php -> /pages/our-story` Beyond standard pages, I also map old `/brand/brand-name` URLs to new collection pages, or specific filter URLs from the old site to the relevant filtered collection on Shopify. Everything that can generate a 404 needs a home. ## The 5 Redirect Mistakes That Cost 30% of Organic Traffic Here's what nobody talks about enough: even with a redirect map, you can still screw it up. I've seen clients lose a significant chunk of their organic traffic – sometimes 30% or more – because of these common, yet avoidable, errors. 1. **Ignoring Case Sensitivity and Trailing Slashes:** Shopify's URL routing is generally case-insensitive and handles trailing slashes well, but *your old platform might not have been*. If Google indexed `/Product/item-1` and `/product/item-1/` as separate URLs on your old site, you need to redirect both. The mistake I see most often is assuming a single redirect covers all variations. Always check your old site's Google Search Console for indexed URLs and common crawl errors. If your old platform was strict, you might need multiple redirects for what appears to be the same page. For example, `/Old-Product` and `/old-product` might need separate entries if they existed on the old server. 2. **Chaining Redirects:** This is a performance killer and an SEO nightmare. A redirect chain happens when `URL A` redirects to `URL B`, which then redirects to `URL C`. Google (and users) have to follow multiple hops, which slows down page load times and dilutes link equity. Always aim for direct 301 redirects: `URL A -> URL C`. I audit the redirect map carefully to ensure there are no unintended chains, which often happen when multiple people work on the map or if an internal linking audit isn't done. 3. **Missing Internal Links (on the new site):** Redirects fix external links pointing to your old site, but they don't fix your *new* site's internal linking structure. If your new Shopify store still has internal links pointing to old URLs, it's inefficient. Every internal link should point directly to the *final* destination URL. This is critical for crawl efficiency and passing link equity within your own site. I always run a post-migration crawl of the new site to identify and fix any lingering internal links to old URLs. This is often where agencies drop the ball after the migration is technically complete. 4. **Not Mapping Redirects for Out-of-Stock/Discontinued Products:** Many merchants delete old product pages or let them 404. This is a huge mistake if those products still receive traffic or have backlinks. If a product is permanently discontinued, I redirect it to the most relevant collection page, a similar product, or a specific --- ## My 7-Step Plan: WooCommerce to Shopify Migration with Zero SEO Rank Loss Published: 2026-07-03 · By Ashraful · Tags: shopify migration, woocommerce, seo, 301 redirects, ecommerce URL: https://ezomfy.com/blog/woocommerce-to-shopify-zero-rank-loss *I'm sharing the exact 7-step redirect map I use for every WooCommerce to Shopify migration, ensuring no SEO rank loss. It covers URL patterns, 301 vs. 302, and critical meta inheritance.* Migrating a website is always a high-stakes move, but moving an ecommerce store from WooCommerce to Shopify comes with an added layer of anxiety: losing your hard-earned search rankings. I've heard the horror stories, and frankly, I've seen the data. A botched migration can decimate organic traffic, sometimes irreparably. But it doesn't have to be that way. For years, I've refined a precise, 7-step redirect map that ensures my clients transition from WooCommerce to Shopify without a hit to their SEO. This isn't about generic checklists; it's about a systematic approach to URL mapping, understanding redirect types, and anticipating the common pitfalls that can otherwise tank your search visibility. This post lays out my exact process, from auditing every URL to post-launch monitoring, so you can avoid the pain of lost rankings. ## The Real Cost of a Bad WooCommerce to Shopify SEO Migration Many store owners see migration as a technical task. "Just move the products, right?" Wrong. When you move an ecommerce platform, you're changing the entire URL structure, how categories are organized, how product variants are handled, and often, how your precious SEO metadata is stored. Google, Bing, and other search engines rely on consistent URLs to understand your site and rank your pages. Change those URLs without telling them exactly where to go, and they'll treat your new site like a brand new, unknown entity. I've seen clients come to me after a previous agency's migration with 50-70% drops in organic traffic. Their old WooCommerce URLs were gone, replaced by new Shopify ones, and no proper redirects were in place. Imagine losing half your free traffic overnight. That's the real cost, and it impacts revenue directly. This isn't just about traffic; it's about trust with search engines, which is painstakingly built over years. Preserving that trust is paramount, and it starts with a meticulous plan for your WooCommerce to Shopify migration SEO. ## My 7-Step Redirect Map: The Core of Zero-Loss Shopify SEO Migrations My approach isn't complicated, but it is rigorous. It's built on the premise that every single old URL needs a corresponding new URL, and Google needs a clear, unambiguous map to find it. Here's the framework I use for every Shopify migration project. ### Step 1: Full URL Audit & Content Mapping This is where most migrations fail before they even start. You *must* have a complete list of every single crawlable URL from your old WooCommerce site. This means more than just products and categories. Think: blog posts, static pages (About Us, Contact), policy pages, landing pages, custom post types, and even old, redirected URLs that might still have backlinks. I use a crawler like Screaming Frog or Ahrefs Site Audit to get a comprehensive list. Export *all* URLs, along with their status codes, title tags, and meta descriptions. This is your baseline. Then, for each URL, you need to map it to its exact counterpart on the new Shopify store. If a page doesn't exist on Shopify, you decide its fate: redirect to a relevant category, the home page, or mark it for deletion (with a 410 or redirect to home). * **Example:** * Old WooCommerce: `https://example.com/product/vintage-leather-wallet/` * New Shopify: `https://newstore.com/products/vintage-leather-wallet` * Old WooCommerce: `https://example.com/blog/how-to-choose-a-wallet/` * New Shopify: `https://newstore.com/blogs/news/how-to-choose-a-wallet` This mapping process forms the foundation of your redirect file. Get it wrong here, and you're building on quicksand. ### Step 2: Standardizing WooCommerce URL Patterns for Shopify WooCommerce is flexible, sometimes to its detriment. It allows for various permalink structures, like `/product/`, `/shop/product/`, or even custom ones. Shopify, on the other hand, has a very opinionated, predictable URL structure: `/products/`, `/collections/`, `/blogs/`. You need to identify all your WooCommerce URL patterns and define how they will translate to Shopify. Common WooCommerce patterns I see: * `/product/product-name/` -> `/products/product-name` * `/product-category/category-name/` -> `/collections/category-name` * `/blog/post-name/` -> `/blogs/news/post-name` (or whatever your blog handle is) * `/page-name/` -> `/pages/page-name` This is where I often use regular expressions in the redirect file to handle patterns efficiently, rather than listing thousands of individual URLs. For instance, a regex might catch all `/product/` URLs and redirect them to `/products/`. ```nginx # Example for Nginx, similar logic applies to Shopify's redirect app rewrite ^/product/(.*)$ /products/$1 permanent; rewrite ^/product-category/(.*)$ /collections/$1 permanent; ``` ### Step 3: The 301 vs. 302 Redirect Decision Matrix This is a critical distinction many developers overlook. A 301 redirect (`Moved Permanently`) tells search engines that a page has *permanently* moved to a new location. It passes almost all of the old page's SEO value (link equity, ranking signals) to the new page. A 302 redirect (`Found` or `Moved Temporarily`) indicates a temporary move and doesn't pass SEO value effectively. For a permanent platform migration from WooCommerce to Shopify, you *always* want 301 redirects for any page you want to retain its SEO value. The only time I use a 302 is for truly temporary scenarios, like A/B testing a new landing page URL, which is rare in a full site migration context. If you use 302s for your primary product and category pages, you are effectively telling Google to ignore your old page's history, which is a fast track to losing rank. ### Step 4: Solving the `/shop/` Path Conundrum WooCommerce often defaults to a `/shop/` base for its product archive page, and sometimes individual product URLs might include `/shop/product/product-name/`. Shopify doesn't use a `/shop/` prefix for its standard product URLs. This means you have to explicitly account for it. If your WooCommerce shop page (`/shop/`) was ranking for broad terms, you might want to redirect it to your main collection page or a specific, high-level collection on Shopify. For product URLs that included `/shop/`, you'll need a regex or individual redirects to strip that segment and redirect to the clean `/products/` path. * **Example Redirect:** * `https://oldstore.com/shop/product-xyz/` -> `https://newstore.com/products/product-xyz` (301) * `https://oldstore.com/shop/` -> `https://newstore.com/collections/all` (301) Ignoring this small detail can lead to a significant number of 404s for pages that previously included `/shop/` in their path. ### Step 5: The SEO Meta Inheritance Trap (and How to Avoid It) Here's what nobody talks about enough: redirecting URLs is only half the battle. If your new Shopify pages have thin, generic, or duplicate title tags and meta descriptions, you're squandering the opportunity to maintain your rankings. Even with perfect 301s, Google looks at the content and metadata of the *destination* page. I always extract the SEO title and meta description for every critical page from the old WooCommerce site during Step 1. Then, when the new Shopify store is built, I ensure these are meticulously re-implemented. Shopify's native SEO fields are great for this, but sometimes custom fields or apps are needed for specific content types. If you simply rely on Shopify's default title generation or leave meta descriptions blank, your new pages might not perform as well, even if they inherit the link equity. * **My process:** I export all old WooCommerce titles and descriptions, then cross-reference them with the new Shopify pages. Any discrepancies are flagged and manually updated. This is a tedious but non-negotiable step. ### Step 6: Precision Redirect Implementation & Testing Once your redirect map is complete, it's time to implement. Shopify has a native redirect tool (under Online Store > Navigation > URL Redirects), which is excellent for handling individual redirects and simple pattern matches. For more complex regex redirects, especially for high-volume sites, I sometimes use an app or, in some cases, server-level redirects on a proxy before traffic hits Shopify, then layer the Shopify redirects on top. This is a nuanced decision based on scale and complexity. * **Key here is testing.** Before launch, and immediately after, I run comprehensive tests: * **Spot Checks:** Pick 50-100 critical URLs from your old site and manually check if they redirect correctly to their new Shopify counterparts. * **Screaming Frog/Crawler Test:** Configure your crawler to crawl your *old* domain, but follow redirects. Check the status codes (should all be 301) and the final destination URLs. Any 404s or unexpected redirects are major red flags. * **Internal Link to my Services:** If this sounds like a lot, you might consider professional help for your [Shopify migration](/services/migration). Getting this right is worth it. ### Step 7: Post-Launch SEO Monitoring & Fine-Tuning The work isn't over at launch. The first few weeks post-migration are crucial for monitoring. I use Google Search Console (GSC) and Google Analytics (GA4) extensively: * **GSC:** Check the "Crawling > Errors" report for any new 404s. These are pages Google tried to access from your old site that didn't redirect. Immediately add redirects for these. * **GSC:** Monitor "Performance" reports. Look for any sudden drops in impressions or clicks for specific keywords or pages. This could indicate a redirect issue or a meta description problem. * **GA4:** Compare organic traffic pre- and post-migration. Look for stability or growth. Any dips need investigation. * **Manual Checks:** Continue to spot-check ranking pages from your old site on Google. Are they still ranking? Are the new Shopify URLs showing up? This continuous feedback loop allows for immediate adjustments, ensuring any minor issues are caught and fixed before they become major problems. ## What Most Agencies Get Wrong: Overlooking Dynamic URL Structures The mistake I see most often is agencies (or DIY migrations) treating all URLs as static. WooCommerce often generates dynamic URLs for filtered results, search results, or even certain product variations. For example: `https://example.com/shop/?orderby=price` or `https://example.com/product-category/shoes/?filter_color=red`. These URLs might not be in your initial static URL audit, but they can still be indexed and receiving traffic or links. Shopify has its own ways of handling filtering (via facets, tags, or search), and these dynamic URLs won't directly translate. Ignoring them means losing potential traffic or creating a mess of 404s for search engines. My approach is to identify these common dynamic patterns and decide on a strategy: 1. **Redirect to a static equivalent:** For filtered categories, redirect to the main category page. `/?filter_color=red` -> `/collections/shoes-red` (if such a collection exists) or `collections/shoes`. 2. **Canonicalization:** Ensure your new Shopify collection pages have proper canonical tags pointing to themselves, preventing indexed filter pages from competing. 3. **Noindex/Nofollow:** If specific dynamic URLs from the old site were generating thin content or were meant to be ignored, ensure they are still handled appropriately. This requires a deeper understanding of how WooCommerce generates these URLs and how Shopify processes them. A simple regex `(.*)` to `/$1` won't cut it. ## My Client's Turnaround: A High-Volume Store's SEO Saved I recently worked with a client, a specialty coffee bean retailer, moving from a highly customized WooCommerce setup to Shopify Plus. Their old site had thousands of products, hundreds of blog posts, and a complex taxonomy of origins, roasts, and flavor profiles. They were generating over $200k/month in organic revenue, and the thought of losing that during migration was terrifying for them. Their previous attempt with another agency resulted in a 35% traffic drop within two weeks, largely due to hundreds of missing redirects and incorrect meta tag transfers. We paused that migration, rolled back, and started fresh. I applied my 7-step map, meticulously auditing all 15,000+ URLs, mapping custom post types to new Shopify blog sections, and ensuring every single product variant URL redirected correctly. We even accounted for old `product_tag` URLs by redirecting them to relevant collection pages. The outcome? Within four weeks post-launch, their organic traffic had not only recovered but showed a slight increase of 3% compared to pre-migration numbers. More importantly, their target keywords held their positions, and there was no dip in organic revenue. This level of precision is non-negotiable for a high-volume business. ## Beyond Redirects: Other Critical SEO Elements for Shopify Success While redirects are the bedrock of a zero-rank-loss migration, they aren't the *only* thing. You also need to consider: * **Site Speed:** Shopify is generally faster than WooCommerce, but optimizing images, app usage, and themes is still vital. * **Mobile Responsiveness:** Ensure your new Shopify theme is fully responsive and offers a great mobile experience. * **Schema Markup:** Shopify often handles basic schema (product, organization) well, but review and enhance it for rich snippets. I often use apps for this. * **Internal Linking Structure:** Ensure your new Shopify navigation and internal links are logical and support your key pages. * **XML Sitemaps:** Submit your new Shopify sitemap to GSC immediately after launch. * **Content Freshness:** Consider refreshing old blog posts or creating new content post-migration. My [resources](/resources) section has more on this. These elements, combined with a rock-solid redirect strategy, form a comprehensive SEO foundation for your new Shopify store. Transitioning from WooCommerce to Shopify can be a massive win for your business, but only if you safeguard your existing SEO. My 7-step redirect map isn't just a suggestion; it's a battle-tested blueprint I use to ensure my clients maintain, and often improve, their search visibility after a migration. Don't leave your hard-earned search rank to chance or trust it to an agency without a proven, detailed plan. If you're planning a WooCommerce to Shopify migration and want to ensure a seamless SEO transition, I offer a free 30-minute consultation call. We can discuss your specific store's needs and outline how my approach can protect your rankings. Find a slot that works for you at /consultation. --- ## Shopify Functions vs. Scripts: Why Functions Won (and How to Use Them) Published: 2026-07-02 · By Ashraful · Tags: shopify functions, shopify scripts, custom development, ecommerce development, wasm, app development URL: https://ezomfy.com/blog/shopify-functions-vs-scripts *Shopify Functions have definitively replaced Shopify Scripts, offering powerful customization on all plans. I'll explain why Functions won and show practical use cases with code examples.* For years, Shopify Scripts were the go-to for customizing checkout logic for Shopify Plus merchants. But they had limitations, and frankly, they were always a stop-gap solution. Today, Shopify Functions have taken over, not just as a replacement, but as a vastly superior system available to *all* Shopify plans. I’ve been building Shopify stores for over seven years, shipping 700+ projects, and I’ve seen this evolution firsthand. The transition from Scripts to Functions isn't just an upgrade; it's a fundamental shift that empowers merchants in ways Scripts never could. This isn't a '10 tips' article; this is about why Functions won, decisively, and how you can use them to build truly custom Shopify experiences. ## The End of an Era: Why Shopify Scripts Fell Short If you're looking for a practical Shopify Functions tutorial, you first need to understand the landscape it emerged from. Shopify Scripts were a powerful tool for Shopify Plus merchants, enabling them to customize cart, shipping, and payment logic. As a Shopify Select Partner since 2021 and someone who has built 700+ stores, I've seen firsthand how crucial these customizations are for unique business models. My clients used Scripts for everything from 'buy one get one free' offers to complex tiered shipping rates based on customer groups. But they were always, inherently, a compromise. The core issue was their foundation: a custom Ruby dialect running within a Liquid sandbox. While Liquid is fantastic for theme rendering, it was never designed for the kind of complex, conditional logic needed for checkout processes. This led to significant limitations. Scripts were a black box, difficult to debug effectively outside of the limited Script Editor. Performance was often a concern, as the Liquid interpreter added overhead, especially for larger, more complex scripts. I've spent countless hours trying to diagnose why a Script wasn't behaving as expected, often tracing obscure Liquid errors or battling unexpected timeouts. This manual, often frustrating debugging process ate into development budgets and project timelines. Furthermore, Scripts were exclusive to Shopify Plus. This immediately excluded a vast number of growing merchants who needed similar custom logic but weren't on the highest tier. Shopify recognized these limitations – the performance bottlenecks, the debugging challenges, the vendor lock-in to Ruby, and the lack of accessibility. The deprecation of Scripts in favor of Shopify Functions was not just an upgrade; it was an inevitable and, frankly, welcome evolution that addressed these critical pain points. ## Enter Shopify Functions: A New Paradigm for Custom Logic If you're looking for a comprehensive Shopify Functions tutorial, the first fundamental concept to grasp is the underlying technology: WebAssembly, or WASM. This isn't just an incremental improvement over Scripts; it's a completely different and vastly superior architectural approach. With WASM, you write your custom logic in high-performance, compiled languages like Rust, C++, Go, or AssemblyScript. These are languages with robust ecosystems, strong typing, and excellent tooling. Once compiled to WASM, this highly optimized binary code runs directly on Shopify's infrastructure, delivering blazing-fast execution speeds that Liquid-based Scripts could only dream of. This shift brings a multitude of advantages. Performance is dramatically improved because WASM is designed for near-native speed execution. Debugging becomes a standard software engineering task, using familiar tools and practices from your chosen language, rather than wrestling with a proprietary editor. Shopify Functions are also platform-agnostic in terms of language, offering developers freedom of choice. This allows me, as a developer, to select the best tool for the job, writing clean, maintainable, and testable code. Crucially, Shopify Functions are available to *all* Shopify plans. This democratizes powerful checkout customizations, leveling the playing field for merchants of all sizes. No longer is advanced logic restricted to Plus. This extensibility is API-first, meaning Functions have clear inputs and outputs, making them highly predictable and easier to integrate. This robust, modern architecture is why a practical Shopify Functions tutorial emphasizes moving beyond the 'scripting' mindset and embracing true application development. ## Real-World Shopify Functions Tutorial: Custom Discount Logic One of the most common applications for custom logic in Shopify is creating advanced discount rules that go beyond the standard promotions. With Shopify Functions, these complex scenarios become elegantly manageable. Let's say a merchant wants to run a promotion: 'Buy any two items from our 'Premium Blends' collection, and get 10% off any item from our 'Gourmet Sauces' collection.' This kind of conditional, multi-collection logic was notoriously tricky and error-prone with Shopify Scripts, often requiring convoluted Liquid checks. With a Shopify Function, written in Rust and compiled to WASM, this logic is explicit and performant. Here’s a conceptual look at how you might structure such a discount function: ```rust use shopify_function::prelude::*; use shopify_function::result::FunctionResult; #[shopify_function] fn run_function(input: input::Input) -> FunctionResult { let mut discounts = vec![]; let mut line_items_from_collection_a = 0; let mut eligible_line_items_from_collection_b: Vec = vec![]; // Placeholder for actual collection IDs, in a real app these would be configurable. let collection_a_product_gids = vec![ "gid://shopify/Product/123456789", // Example Product ID 1 "gid://shopify/Product/987654321", // Example Product ID 2 ]; let collection_b_product_gids = vec![ "gid://shopify/Product/112233445", // Example Product ID 3 "gid://shopify/Product/554433221", // Example Product ID 4 ]; for line_item in input.cart.lines { if let input::Target::ProductVariant(variant_target) = &line_item.target { let product_gid = variant_target.product_id.to_string(); if collection_a_product_gids.contains(&product_gid.as_str()) { line_items_from_collection_a += line_item.quantity; } if collection_b_product_gids.contains(&product_gid.as_str()) { eligible_line_items_from_collection_b.push(line_item); } } } if line_items_from_collection_a >= 2 { for line_item in eligible_line_items_from_collection_b { discounts.push( output::Discount { message: Some("10% off Collection B for buying 2+ from Collection A".to_string()), targets: vec![ output::Target::ProductVariant { product_id: line_item.merchandise.unwrap_product_variant().product_id, variant_id: line_item.merchandise.unwrap_product_variant().id, }, ], value: output::Value::Percentage(10.0), } ); } } Ok(output::FunctionResult { discounts, errors: vec![], }) } ``` This Rust code snippet demonstrates how to iterate through cart lines, identify products from specific collections (using placeholder GIDs), and then apply a percentage discount to eligible items from another collection. The `shopify_function` macro handles the boilerplate, allowing us to focus purely on the business logic. The result is a `FunctionResult` containing the `discounts` to be applied. This level of granular control and clear logic is a significant departure from the 'guess-and-check' approach often associated with Scripts. I once had a client, a high-end gourmet food store, running a complex 'buy any 3 spice blends, get 1 sauce 50% off' promotion. With Shopify Scripts, this was a constant headache. We often had issues with the discount applying incorrectly if a customer had multiple eligible sauces, if product IDs changed, or if the cart re-ordered itself. The Liquid logic became a fragile spiderweb, difficult to maintain and prone to breaking. When we migrated them to a Shopify Function, the Rust code was explicit, testable, and robust. It handled variations perfectly, eliminating customer service complaints about pricing errors overnight. It was a clear win for everyone involved. ## Real-World Shopify Functions Tutorial: Dynamic Shipping Rates Shopify's native shipping rate settings are powerful, but they have their limits. When a merchant needs highly dynamic shipping logic – perhaps free shipping only for VIP customers on orders over a certain threshold, or specific rates based on product tags and geographic zones – Shopify Functions step in to fill that gap. Trying to achieve this with Shopify Scripts often involved a cascade of `if/else` statements that were hard to manage and even harder to debug if something went wrong. Here’s how you might implement a Shopify Function to offer free shipping for VIP customers on orders over $100. This example demonstrates reading customer tags and cart totals, then applying a discount to all shipping rates: ```rust use shopify_function::prelude::*; use shopify_function::result::FunctionResult; #[shopify_function] fn run_function(input: input::Input) -> FunctionResult { let mut operations = vec![]; let cart_total_amount: f64 = input.cart.cost.total_amount.amount.parse().unwrap_or(0.0); let is_vip_customer = input.customer .as_ref() .map_or(false, |customer| { customer.tags.iter().any(|tag| tag == "VIP") }); if cart_total_amount >= 100.0 && is_vip_customer { operations.push( output::RateDiscount { message: Some("Free shipping for VIPs over $100".to_string()), rate_selector: output::RateSelector::All, discount: output::Discount { value: output::Value::Percentage(100.0), message: None, targets: vec![] // No specific targets for rate discount } } ); } Ok(output::FunctionResult { operations, errors: vec![], }) } ``` In this Function, we retrieve the cart's total amount and check if the customer has a 'VIP' tag. If both conditions are met, we create an `RateDiscount` operation that applies a 100% discount to `All` available shipping rates. This isn't just about setting a rate; it's about dynamically *modifying* the rates presented to the customer based on real-time cart and customer data. This fine-grained control allows merchants to implement sophisticated loyalty programs or region-specific promotions without relying on external, often clunky, shipping apps. Implementing complex logic like this often requires deep understanding of both Shopify's platform and custom app development best practices. If this sounds like the kind of custom app development your store needs to stand out and offer unique customer experiences, I invite you to explore my [app development services](/services/app-dev). We build robust, scalable solutions tailored to your specific business requirements, all powered by the flexibility of Shopify Functions. ## Real-World Shopify Functions Tutorial: Custom Cart Validations Beyond discounts and shipping, Shopify Functions also excel at enforcing custom cart validations. This is crucial for preventing customer errors, managing inventory, or ensuring compliance with business rules *before* a customer even attempts to check out. Imagine a scenario where a wholesale merchant requires a minimum quantity of two for all items within a specific 'Wholesale' collection, or perhaps prevents certain product combinations from being purchased together. With Scripts, achieving robust, user-friendly validation messages was a struggle. A Shopify Function can intercept the cart and, if validation rules are violated, return clear error messages to the customer, guiding them to correct their cart. Here’s an example for enforcing a minimum quantity on wholesale items: ```rust use shopify_function::prelude::*; use shopify_function::result::FunctionResult; #[shopify_function] fn run_function(input: input::Input) -> FunctionResult { let mut errors = vec![]; // Placeholder for actual collection IDs. let wholesale_product_gids = vec![ "gid://shopify/Product/223344556", // Example Wholesale Product ID 1 "gid://shopify/Product/665544332", // Example Wholesale Product ID 2 ]; for line_item in input.cart.lines { if let input::Target::ProductVariant(variant_target) = &line_item.target { let product_gid = variant_target.product_id.to_string(); if wholesale_product_gids.contains(&product_gid.as_str()) && line_item.quantity < 2 { errors.push( output::FunctionError { message: format!("Minimum quantity of 2 required for {}.", line_item.merchandise.unwrap_product_variant().title), localized_message: Some(format!("Please add at least 2 of {}.", line_item.merchandise.unwrap_product_variant().title)), target: output::ErrorTarget::Cart, } ); } } } Ok(output::FunctionResult { errors, operations: vec![], // No operations for validation, only errors }) } ``` This Function checks each line item. If a product from the designated 'Wholesale' collection has a quantity less than two, it generates an `FunctionError` with a user-friendly `localized_message`. The `target` field ensures the error is associated with the relevant part of the cart. This proactive validation improves the customer experience by providing immediate feedback, reduces abandoned carts due to confusion, and helps merchants enforce their business logic consistently. This ability to inject custom validation logic directly into the checkout flow is incredibly powerful and something Shopify Scripts could never achieve with this level of elegance and performance. ## What Most Agencies Get Wrong with Shopify Functions What most agencies get wrong when approaching Shopify Functions is trying to force old solutions into a new paradigm. They see 'custom logic' and immediately think of a monolithic app or complex server-side code. This often leads to over-engineering, neglecting the specific strengths of WASM and the Function API. Instead of building small, focused Functions that do one thing well, they try to replicate the 'Swiss Army knife' approach that often plagued larger Shopify Scripts, or even worse, try to port their existing server-side logic wholesale without adapting to the Function model. I've seen this 50+ times on client audits: an agency delivers a Function that is overly complex, difficult to debug, and slow, simply because they didn't embrace the WASM compilation model. The beauty of Functions lies in their performance and isolation. Each Function should be a lean, purpose-built piece of code, focused on a single responsibility. This makes them easier to test, maintain, and reason about. Don't try to build a full-stack application inside a Function; that's what connected custom apps are for. Understanding this distinction, and adopting a modular approach, is key to truly using Shopify Functions effectively and achieving the performance benefits they promise. It’s about writing efficient Rust or Go, compiling it, and deploying it with precision, not bloat. Shopify Functions represent a significant leap forward for customizability on the platform. They’re faster, more reliable, and crucially, accessible to every merchant. The era of Shopify Scripts is over, and Functions have definitively won, offering a robust, future-proof way to tailor your store's logic. If you're struggling to implement complex discounts, dynamic shipping, or custom cart validations, or if you simply want to understand how to use Shopify Functions to their fullest potential to solve unique business challenges, don't hesitate. Book a free 30-minute consultation call with me. Let's discuss your specific needs and build intelligent, performant solutions that just work. --- ## Shopify Theme App Extensions vs. Custom Apps: The Real Cost I Tell Clients Published: 2026-06-30 · By Ashraful · Tags: shopify development, app extensions, custom apps, shopify partner, ecommerce URL: https://ezomfy.com/blog/shopify-app-extension-vs-app *Deciding between Shopify theme app extensions and custom apps isn't just about features; it's about real costs. I'll break down the financial and operational trade-offs with the three questions I ask every client.* The choice between a Shopify theme app extension and a full-blown custom app is one of the most critical decisions a merchant makes. Get it wrong, and you're looking at wasted development time, compromised functionality, and a store that can't scale. I've seen it play out hundreds of times. My thesis is simple: theme app extensions are *free* to deploy but come with significant limitations. Custom apps demand upfront investment in time and Shopify Partner fees, but they unlock complete control and true scalability. I don't guess; I use a clear decision matrix with three core questions I ask every client to guide them to the right choice, and I'll lay that out for you here. ## What a Shopify Theme App Extension Actually Is (And What It Isn't) Let's cut through the marketing fluff. A Shopify theme app extension is essentially a piece of your app's functionality that lives *inside* a merchant's theme. It's rendered by Shopify's Liquid engine, meaning it directly interacts with the theme's code and data. Think of it as a highly integrated, pre-approved snippet that an app can inject and manage. The biggest draw? **Zero deployment cost for the app developer on Shopify's side.** You don't pay partner fees for theme app extensions, unlike public custom apps that live on the App Store. For merchants, it means easier installation and often less perceived "code bloat" since it's just a block or section. Here’s how it generally looks in a theme: an app registers a block or section that a merchant can add through the theme customizer. When that block or section is rendered, it includes code (Liquid or JavaScript) provided by your app. ```liquid {% comment %} This is an example of a theme app extension block. It might be defined by your app in its `shopify.extension.toml` and then rendered dynamically. {% endcomment %} {% if block.settings.show_promo_banner %}

{{ block.settings.message }}

{{ block.settings.link_text }}
{% endif %} ``` This looks simple, right? It *is* simple, and that’s both its strength and its profound weakness. ### The Inherent Limitations of Shopify Theme App Extensions The moment you need to step beyond simple UI rendering, you hit a wall. Here are the hard limits I constantly battle with clients who chose extensions when they shouldn't have: 1. **Limited Data Storage & Logic:** Theme app extensions are great for displaying data, but they aren't designed for complex data storage, manipulation, or processing *outside* of what Shopify's standard APIs provide. If you need a custom database, intricate calculations, or persistent state management, you're out of luck. The logic is constrained to what you can run in the browser (JS) or what Liquid can render. 2. **API Access Restrictions:** While you can use client-side JavaScript to interact with *some* Shopify Storefront API endpoints, you absolutely cannot access sensitive Admin API endpoints directly from an extension without proxying through an external server. This means no custom order processing, no inventory management, no user authentication beyond what Shopify provides, and no complex backend operations. 3. **UI/UX Control is a Lie:** You can make your extension look *decent*, but achieving pixel-perfect, highly dynamic, or truly custom user experiences is a nightmare. You're working within the confines of the theme's CSS and JavaScript context. Competing scripts, global styles, and the limitations of `block.settings` make advanced UI a constant struggle. 4. **Performance Overheads:** While extensions are generally performant, if your app tries to do too much client-side or injects large amounts of JavaScript, it can drag down page load speeds. This is often overlooked until core web vitals start screaming. I see developers try to shoehorn complex features into theme app extensions constantly, and it always ends in a messy, unmaintainable, and often broken solution. It's like trying to build a skyscraper with LEGOs. ## The True Cost of a Shopify Custom App Now, let's talk about custom apps. When I say "custom app," I mean a dedicated application, hosted on its own server infrastructure, that communicates with Shopify primarily through the Admin API and optionally the Storefront API. This is where you get true power and flexibility. If you're serious about building a specific, robust solution for a merchant, this is almost always the path. With a custom app, you own everything: the backend logic, the database, the server infrastructure, and the full UI. You can build anything Shopify's APIs allow, and even integrate with external systems that have nothing to do with Shopify. The "cost" here isn't just financial, though that's a big part of it. ### Financial Costs of a Custom App 1. **Shopify Partner Fees (for Public Apps):** If you're building an app for the Shopify App Store, you'll pay a fee to Shopify based on your app's revenue. This is a critical distinction from theme app extensions. For private apps built for a single client, these fees do not apply. 2. **Hosting & Infrastructure:** Your app needs a home. This means server costs (AWS, Google Cloud, Heroku, DigitalOcean, etc.), database costs, and potentially CDN or other services. These can range from a few dollars a month for a simple app to thousands for a high-traffic, data-intensive solution. 3. **Development Time:** Building a custom app is a full-stack engineering effort. It requires expertise in backend languages (Node.js, Python, Ruby, PHP), database management, frontend frameworks (React, Vue), and deep knowledge of Shopify's API ecosystem. This is where my team spends a lot of its time. If you want to explore how we approach this, check out our [app development services](/services/app-dev). 4. **Maintenance & Scaling:** Custom apps aren't "set it and forget it." They require ongoing maintenance, security updates, monitoring, and scaling as the merchant's store grows. This is a continuous operational cost. ### The Power & Control You Gain Despite the costs, the benefits of a custom app are immense and, for many businesses, non-negotiable: * **Full API Access:** You can access *any* Shopify API endpoint – Admin, Storefront, GraphQL, REST. This unlocks possibilities like custom order fulfillment, inventory syncing, customer management, advanced discounts, and more. * **Independent Backend Logic:** Your app can run complex calculations, integrate with third-party APIs (ERP, CRM, shipping carriers), process data offline, and manage its own database. * **Complete UI/UX Control:** You can build any user interface you can imagine, using any frontend framework. This ensures a seamless, branded experience that perfectly matches the merchant's vision, without fighting theme conflicts. * **Scalability:** A well-architected custom app can scale independently of the Shopify theme. You can optimize your own servers, database, and code to handle high loads and complex operations. Here's a basic example of a custom app's backend receiving a webhook: ```js // Example using Express.js for a custom Shopify app backend const express = require('express'); const bodyParser = require('body-parser'); const crypto = require('crypto'); // For webhook verification const app = express(); const SHOPIFY_WEBHOOK_SECRET = process.env.SHOPIFY_WEBHOOK_SECRET; // Set in environment variables app.use(bodyParser.json()); // Middleware to verify Shopify webhooks const verifyWebhook = (req, res, next) => { const hmac = req.get('X-Shopify-Hmac-Sha256'); const body = req.rawBody; // Need to store raw body before parsing JSON const generatedHash = crypto.createHmac('sha256', SHOPIFY_WEBHOOK_SECRET) .update(body, 'utf8') .digest('base64'); if (generatedHash === hmac) { next(); } else { console.error('Webhook verification failed.'); res.status(401).send('Webhook verification failed.'); } }; // Example webhook endpoint for product updates app.post('/webhooks/products/update', verifyWebhook, (req, res) => { const product = req.body; console.log(`Product updated: ${product.title} (ID: ${product.id})`); // Perform custom logic here: update external database, send notifications, etc. res.status(200).send('Webhook received and processed.'); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`Custom app backend listening on port ${PORT}`); }); ``` This snippet shows the foundation: receiving data from Shopify, verifying it, and then doing *anything* you want with it. This level of control is simply not possible with a theme app extension. ## Here's What Nobody Talks About: The Hidden Costs of "Free" The mistake I see most often is clients, or even other agencies, falling for the "theme app extension is free" trap. They hear "free" and immediately think "cost-effective." What they don't factor in are the hidden costs that inevitably surface when the business grows or the requirements evolve. It's the false economy of choosing a seemingly cheaper path that ends up costing more in the long run. I've seen this 50+ times on client audits. **Vendor Lock-in and Technical Debt:** When you try to force complex logic into an extension, you often end up with convoluted Liquid code, excessive client-side JavaScript, and a fragile system. This creates massive technical debt. You're locked into a brittle solution that's hard to modify, debug, or migrate. Any future changes become exponentially more expensive. **Compromised User Experience:** Because extensions are limited in their UI/UX capabilities, developers often have to compromise. "It's good enough" becomes the mantra, leading to clunky interfaces, inconsistent branding, or features that simply don't work as smoothly as they should. This directly impacts conversion rates and customer satisfaction. **The War Story:** I had a client, a rapidly growing direct-to-consumer gourmet food brand (let's call them "FlavorFusion"), who came to me with a broken subscription upsell flow. They had originally worked with a smaller agency that promised a "quick and cheap" solution using a theme app extension to offer a discount on a second subscription product after adding the first to the cart. The extension was trying to calculate discounts, manage subscription parameters, and dynamically update cart items – all client-side. It was a mess. The JavaScript was conflicting with other apps, the discount logic was buggy, and customers were constantly abandoning carts because the pricing was unpredictable. We audited their setup and within a week, I showed them that a custom app was the only viable path. We rebuilt the upsell logic as a dedicated app that interfaced directly with Shopify's Subscription APIs and their internal CRM. It cost them more upfront, yes, but within three months, their subscription conversion rate on that upsell doubled, and support tickets related to pricing discrepancies dropped to zero. They saved immensely on operational costs and gained revenue. This is a real-world example of "free" costing a fortune. ## My 3-Question Decision Matrix To avoid these pitfalls, I simplify the decision into three core questions. This is what I ask every client, every time. ### Question 1: What data needs to live outside Shopify? This is the ultimate litmus test. If your app needs its own persistent data storage – say, a list of custom product recommendations, user preferences not covered by Shopify metafields, complex loyalty points, or integration with an external ERP system's inventory – you **need** a custom app. * **Theme App Extension:** Best for displaying data already available in Shopify (product titles, prices, metafields) or very simple, temporary client-side data (e.g., a visitor's last viewed product in local storage). * **Custom App:** Essential for any solution requiring a dedicated database, complex data relationships, or syncing with external data sources. ### Question 2: How complex is the business logic? Are we talking about a simple "if cart total > $50, show free shipping" rule? Or are we talking about dynamic pricing based on user segment, inventory levels from multiple warehouses, and a multi-step configuration process that impacts backend fulfillment? * **Theme App Extension:** Perfect for simple conditional rendering, basic UI adjustments, or displaying information. Logic is usually limited to what Liquid and client-side JavaScript can handle without external server calls. * **Custom App:** Necessary for anything that involves multi-step workflows, intricate calculations, external API calls for real-time data, or business rules that need to be secure and reliably executed on a server. ### Question 3: How critical is the user experience and branding? If your unique selling proposition (USP) relies heavily on a bespoke, intuitive, and perfectly branded user experience, an extension will quickly become a bottleneck. * **Theme App Extension:** Can provide a *good enough* experience for standard features, but you're always adapting to the theme's structure and limitations. Achieving a truly unique, pixel-perfect, or highly interactive UI is difficult and prone to breaking. * **Custom App:** Gives you absolute control over the frontend. You can design and build any UI/UX you want, ensuring it perfectly aligns with your brand identity and provides a superior customer journey. This is crucial for brands where the user interface *is* part of the product. ## Practical Scenarios: When to Use Which Let's ground this in some concrete examples. ### When a Shopify Theme App Extension Shines You absolutely *should* use a theme app extension when the requirements are straightforward and fit within its constraints. * **Simple Product Badging:** Displaying "Bestseller" or "New Arrival" badges based on product tags or metafields. * **Basic Content Injection:** Adding a GDPR cookie banner, a simple announcement bar, or a social media feed widget that doesn't need complex backend logic. * **Lightweight UI Adjustments:** Adding a simple "back to top" button, a custom countdown timer for a sale, or a basic product comparison table that uses existing product data. * **Small Promotional Elements:** A dynamic "Free Shipping Over X" message that updates based on cart value. Here's how a simple "Free Shipping" banner might be implemented via an app extension block, displaying a message based on the cart total. ```liquid {% comment %} This block might be inserted into the header or cart page via a theme app extension. {% endcomment %} {% if block.settings.enabled %} {% assign cart_total = cart.total_price | money_without_currency | remove: "," | plus: 0 %} {% assign threshold = block.settings.free_shipping_threshold | plus: 0 %} {% if cart_total < threshold %} {% assign remaining_for_free_shipping = threshold | minus: cart_total | money_without_currency %}

Add {{ remaining_for_free_shipping }} more for FREE SHIPPING!

{% else %}

Congratulations! You qualify for FREE SHIPPING!

{% endif %} {% endif %} ``` This is a perfect example where an extension makes sense. The logic is simple, relies on existing Shopify data (`cart.total_price`), and the UI is minimal. ### When You *Need* a Custom App When your vision extends beyond simple display and basic interactions, a custom app is not just an option; it's a requirement. This is where you bring your truly unique business processes to life. If you're looking for solutions like these, my team and I build them from the ground up. You can learn more about how we approach these complex projects on our [app development services page](/services/app-dev). * **Advanced Product Configurators:** Imagine a custom product where users can select multiple complex options, and each selection dynamically changes pricing, inventory, or generates a unique product variant or even a custom image in real-time. This requires a robust backend to handle permutations and communicate with Shopify's product API. * **Custom Loyalty & Rewards Programs:** A system that tracks customer actions beyond purchases (e.g., referrals, social shares, reviews), awards points, and allows redemption for custom rewards not natively supported by Shopify. This needs its own database and server logic. * **Complex Integrations:** Connecting Shopify with a bespoke ERP, a third-party warehouse management system (WMS) for complex fulfillment rules, or a custom CRM that needs bi-directional data sync. Your custom app acts as the bridge. * **Subscription Models with Unique Logic:** While Shopify has native subscriptions, if you need highly custom billing cycles, dynamic product bundles for subscribers, or intricate churn prevention strategies, a custom app is the way to go. * **Personalized Storefront Experiences:** If you want to dynamically reorder products, display unique content, or offer specific promotions based on a deep understanding of a customer's browsing history, demographics, or purchase patterns (beyond simple segments), you'll need a backend to process and serve that logic. We've built several of these. If you're interested in seeing some of our previous work, take a look at our [apps portfolio](/apps). Deploying a custom app involves a more involved process, often using the Shopify CLI: ```bash # First, navigate to your app directory cd my-custom-shopify-app # If it's a new app, create it (this registers it with Shopify Partners) # shopify app init --template=node --name="My Custom App" # Authenticate with your Shopify Partner account shopify login --store=your-dev-store.myshopify.com # Deploy your app to Shopify # This builds your frontend (if any), creates a new app version, # and makes it available to install on a development store or list on the App Store. shopify app deploy # After deployment, you typically get a URL to install it on a store ``` This `shopify app deploy` command is the gateway to unleashing the full potential of your app. Choosing between a Shopify theme app extension and a custom app isn't a minor technical detail; it's a strategic business decision that impacts your budget, your store's performance, and your long-term growth potential. Don't let the allure of "free" blind you to the hidden costs and limitations. Understand your needs, use my three-questions matrix, and build a solution that truly empowers your business. If you're grappling with this choice or need expert guidance on your next Shopify project, I offer a free 30-minute consultation call. Let's discuss your specific requirements and chart the most effective path forward for your store at /consultation. --- ## Shopify App Bridge 4.x vs. Embedded Apps: The Authentication Shift You Can't Ignore (2026 Deadline) Published: 2026-06-29 · By Ashraful · Tags: shopify app bridge 4, shopify app development, authentication, embedded apps, session management URL: https://ezomfy.com/blog/shopify-app-bridge-vs-embedded-app *Shopify App Bridge 4.x completely redefined how embedded apps authenticate. If your app still uses the old session token method, it will break by mid-2026. Here's your migration path.* A lot of Shopify app developers are in for a rude awakening. If you've built an embedded app for Shopify anytime before late 2023, there's a high chance its authentication mechanism is living on borrowed time. Shopify App Bridge 4.x, specifically, has introduced a fundamental change in how embedded apps handle session tokens. Ignore it, and your app simply won't work within the Shopify admin by mid-2026.\n\nThis isn't just a minor update; it's a critical security and stability improvement that demands your attention now. As someone who's spent seven years building and auditing Shopify apps – shipping over 700 projects – I've seen the consequences of neglecting platform shifts. This isn't one you can patch later. You need to understand the new flow and migrate your app's session management before your merchants start reporting widespread login failures.\n\n## The New Reality of Shopify App Bridge 4.x Authentication\n\nFor years, embedded apps relied on a fairly straightforward, albeit somewhat insecure, method for authenticating users within the Shopify admin: fetching a session token client-side with `getSessionToken()` and sending it to your backend. Your backend would then validate this token using the `shopify-api-node` library's `validateShopifySessionToken` method or similar, verifying its authenticity and extracting session data.\n\nApp Bridge 4.x changes this. The core of the shift is a move away from relying solely on a client-side generated token for every request. Shopify is pushing for a more robust, server-side initiated authentication flow that ensures the app installation and session are always tied to a verified shop context. This isn't just about making things harder; it's about closing security loopholes and providing a more consistent experience for merchants.\n\nThe new approach integrates more deeply with your server-side framework, specifically through the `shopify-api-node` library. It ensures that when a merchant accesses your embedded app, the session is established securely and persistently, often using cookies and server-side state, rather than a transient client-side token for every single API call. This means less reliance on front-end JavaScript for core authentication and more on a proper server-side session management strategy.\n\n### What `shopify-api-node` Does Differently\n\nThe `shopify-api-node` library, a crucial component for any Shopify app developer, has been updated to reflect these changes. Its core `Auth` module now focuses on `ensureInstalledOnShop` or similar middleware-like functions. These functions handle the OAuth flow, ensure the app is installed, and establish a secure, long-lived session, often using cookie-based strategies. This is a departure from the previous pattern where `validateShopifySessionToken` was your go-to for almost every request to a protected endpoint.\n\n## Why Your Embedded App *Will* Break: The Session Token Time Bomb\n\nLet's be direct: if your app's backend still primarily relies on validating `getSessionToken()` for every authenticated request, it's operating on borrowed time. Shopify has set a clear deadline: **mid-2026**. After this point, the old session token mechanism will no longer function as expected, or the tokens themselves will cease to be valid under the old validation methods. When that happens, your app will simply stop working for your merchants.\n\nImagine this scenario: a merchant opens your embedded app in their Shopify admin, and instead of seeing their data, they're greeted with a continuous loading spinner or a generic error message. Your backend, expecting a valid session token, fails to authenticate their request because the token either isn't there, is malformed, or can't be validated by the deprecated methods. This isn't an edge case; it will be the default experience for unmigrated apps.\n\nI've seen similar shifts create chaos for developers who didn't keep up. For instance, I worked with a client who ran a popular subscription box service on Shopify. Their custom embedded app, built years ago, handled complex product configuration and order management. Suddenly, after a seemingly unrelated Shopify update, their merchants started complaining about intermittent failures to save configurations within the app. Upon auditing their system, I found their app was still relying on an older App Bridge version and a `validateShopifySessionToken` pattern that was becoming increasingly unreliable due to changes in how Shopify issued and validated those tokens. The fix involved upgrading `shopify-api-node` and refactoring their authentication middleware to use the newer `ensureInstalledOnShop` pattern, which stabilized their app immediately and prepared them for the upcoming 2026 deadline. Without that, their entire operational flow would have ground to a halt.\n\n### The Old Way (And Why It's Dying)\n\nTo illustrate, here's a simplified example of how many older embedded apps handle authentication on the client-side:\n\n```js\n// Client-side JavaScript in an older embedded app\nimport { getSessionToken } from '@shopify/app-bridge-utils';\n\nasync function makeAuthenticatedRequest(url, method, data) {\n const app = window.shopifyAppBridgeInstance; // Assuming App Bridge is initialized\n const token = await getSessionToken(app);\n\n const response = await fetch(url, {\n method: method,\n headers: {\n 'Authorization': `Bearer ${token}`,\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n });\n\n return response.json();\n}\n\n// Usage example:\n// makeAuthenticatedRequest('/api/data', 'GET').then(data => console.log(data));\n```\n\nAnd on the server-side, the backend would then use `shopify.api.utils.validateShopifySessionToken` to verify this token for every incoming request. While convenient, this approach has limitations and security implications that Shopify is now addressing with App Bridge 4.x.\n\n## The Right Way: Migrating to App Bridge 4.x for Secure Sessions\n\nThe migration path involves shifting your app's core authentication from a client-side initiated token validation model to a robust, server-side session management system, often using cookies. The `shopify-api-node` library provides the necessary tools to achieve this efficiently.\n\nThe primary concept is to establish a persistent session when the app is initially loaded or installed, and then use that session for subsequent requests. This is handled by a middleware or a similar mechanism in your server-side framework. If you're building a new app or upgrading an existing one, I recommend looking into `shopify-api-node`'s `shopify.api.utils.ensureInstalledOnShop` (for `Express` or similar frameworks) or similar constructs for other frameworks. This function performs the necessary checks, including OAuth redirection if the app isn't installed, and then establishes a secure session.\n\n### Server-Side Migration Example (Node.js with Express)\n\nHere’s a simplified example of what your server-side authentication middleware might look like using `shopify-api-node`:\n\n```js\n// Server-side (e.g., Express) authentication middleware\nimport Shopify from '@shopify/shopify-api';\nimport { shopifyApp } from '@shopify/shopify-app-express';\n\n// Initialize Shopify API (your API key, secret, scopes, host, etc.)\nconst shopify = shopifyApp({\n api: {\n apiKey: process.env.SHOPIFY_API_KEY,\n apiSecretKey: process.env.SHOPIFY_API_SECRET,\n scopes: ['read_products', 'write_products'],\n hostName: process.env.HOST.replace(/https?:\/\//, ''),\n apiVersion: Shopify.ApiVersion.2023_10, // Ensure you use a recent API version\n isOnline: true, // Crucial for embedded apps\n },\n auth: {\n path: '/api/auth',\n callbackPath: '/api/auth/callback',\n },\n webhooks: {\n path: '/api/webhooks',\n },\n sessionStorage: new Shopify.Session.CustomSessionStorage(), // Use a persistent session storage\n});\n\n// Protect your API routes with the new authentication middleware\napp.use('/api/*', shopify.validateAuthenticatedSession());\n\n// Example protected route\napp.get('/api/products', async (req, res) => {\n try {\n const session = res.locals.shopify.session; // Session is now available via res.locals\n const client = new Shopify.Clients.Rest(session.shop, session.accessToken);\n const products = await client.get({\n path: 'products',\n });\n res.status(200).send(products.body.products);\n } catch (error) {\n console.error('Failed to fetch products:', error);\n res.status(500).send({ message: 'Failed to fetch products' });\n }\n});\n\n// Client-side interactions no longer need to pass a session token explicitly for every request\n```\n\nThe `shopify.validateAuthenticatedSession()` middleware ensures that a valid and active Shopify session exists for the incoming request. If not, it initiates the appropriate redirects for authentication. Once authenticated, the session object is attached to `res.locals.shopify.session` (or a similar context depending on your framework), allowing you to make authenticated API calls directly using `session.shop` and `session.accessToken`.\n\n### Client-Side Adjustments\n\nWith the server-side handling the session, your client-side code becomes much simpler for authenticated requests. You no longer need to manually fetch `getSessionToken()` for every API call. Instead, you can simply make requests to your backend, and the server-side middleware will handle the session validation.\n\n```js\n// Client-side JavaScript for an App Bridge 4.x compatible embedded app\n// Assuming your App Bridge instance is initialized and available globally or via context\nimport { authenticatedFetch } from '@shopify/app-bridge-utils';\n\n// Use authenticatedFetch if you still need to make requests directly to Shopify APIs\n// otherwise, simple fetch to your backend is sufficient IF your backend handles authentication.\n\nasync function makeBackendRequest(url, method, data) {\n // No manual session token fetching needed if your backend manages the session\n const response = await fetch(url, {\n method: method,\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify(data),\n });\n\n if (!response.ok) {\n // Handle authentication failures, e.g., redirect to login if session expired\n // App Bridge often handles redirects for you if set up correctly.\n console.error(`Error: ${response.status} - ${response.statusText}`);\n throw new Error('Backend request failed');\n }\n return response.json();\n}\n\n// Example usage:\n// makeBackendRequest('/api/products', 'GET').then(data => console.log(data));\n```\n\nThe key takeaway is that the responsibility for managing the authenticated session shifts primarily to the server. The client still needs to interact with App Bridge for UI actions and navigation, but the authentication handshake for your app's internal API calls is now server-driven.\n\nIf you're struggling with this migration, or building a new app from scratch, this is precisely the kind of intricate Shopify app development work I specialize in. You can learn more about my dedicated [Shopify App Development services](/services/app-dev) and how I help clients navigate these complex platform changes.\n\n## What Most Agencies Get Wrong: Misunderstanding Session Lifecycles\n\nHere's what nobody talks about enough: most agencies and even in-house teams misunderstand the full lifecycle of a Shopify app session. They treat session tokens as stateless, ephemeral credentials that can be fetched and validated on demand for every single request. While true for the token's immediate validity, this ignores the underlying server-side session that App Bridge 4.x is designed to manage.\n\nThe mistake I see most often is developers attempting to "patch" the old system by just ensuring `getSessionToken()` is still called, without truly adopting a robust server-side session storage mechanism (like a database or Redis) as part of `shopify-api-node`'s configuration. They might upgrade the `shopify-api-node` library but fail to implement `CustomSessionStorage` or ensure `isOnline: true` is correctly configured, leading to flaky authentication or requiring merchants to re-authenticate constantly.\n\nShopify isn't just asking for a new token validation method; it's enforcing a new paradigm where your app's backend must maintain a secure, persistent session for the merchant. This means correctly configuring `sessionStorage` in `shopify-api-node` is no longer optional for reliability; it's essential. Without it, your app will struggle to maintain state and provide a seamless experience, even if your `getSessionToken()` calls somehow still work for a short period.\n\n## Beyond Authentication: Other Key App Bridge 4.x Improvements\n\nWhile authentication is the most critical shift, App Bridge 4.x also brings other improvements that enhance the developer and merchant experience. These include:\n\n* **Improved Performance**: The library itself is optimized for faster loading and smoother interactions within the Shopify admin.\n* **Richer UI Components**: New or enhanced components provide more native-feeling UI elements, allowing your app to blend seamlessly into the Shopify admin. This means less custom CSS and JavaScript to mimic Shopify's look and feel, and more focus on your app's core functionality.\n* **Better Error Handling**: More explicit error messages and structured error objects make debugging issues within the embedded context much easier.\n* **Consistent Experience**: By standardizing how apps integrate, Shopify ensures a more predictable and consistent user experience across different embedded apps.\n\nThese enhancements, combined with the authentication overhaul, make App Bridge 4.x a significant leap forward for embedded app development. They push developers towards building more secure, performant, and integrated experiences. When I build custom [Shopify Apps](/apps) for clients, these new capabilities are a major part of ensuring future-proof, high-quality solutions.\n\n## Prepare Your App, Secure Your Future\n\nThe 2026 deadline for Shopify App Bridge 4.x authentication changes isn't a suggestion; it's a hard stop. Ignoring this shift will lead to broken apps, frustrated merchants, and significant headaches down the line. I've guided countless clients through complex Shopify migrations and integrations, and my advice is always the same: address critical platform changes proactively. Understand the new session management paradigm, upgrade your `shopify-api-node` implementation, and refactor your authentication flow on the server-side.\n\nIf your embedded app relies on the old App Bridge `getSessionToken()` method, or if you're unsure whether your app is compliant with the latest security standards, don't wait until it breaks. Book a free 30-minute consultation with me at [/consultation] to discuss your specific app, assess its current state, and outline a clear path forward for secure and stable operation. --- ## Shopify Liquid Pagination: The SEO Pattern You're Missing Published: 2026-06-28 · By Ashraful · Tags: shopify seo, liquid, pagination, theme development, shopify expert, crawl budget URL: https://ezomfy.com/blog/liquid-pagination-pattern *Most Shopify developers use JavaScript for collection pagination, unknowingly damaging SEO. I'll show you why Liquid's built-in `{% paginate %}` with `rel=prev/next` is the only way to protect your crawl budget and get your products indexed.* When I audit Shopify stores, I've seen this 50+ times on client audits: a beautiful collection page that loads more products as you scroll, or a slick 'Load More' button. It looks modern, it feels fast to the user. But it's a disaster for SEO, and it’s killing your product visibility. The core issue? Most developers completely miss the proper Shopify Liquid pagination pattern, opting instead for JavaScript-based solutions that leave countless products in the dark, unindexed by search engines. If you're using anything other than native `{% paginate %}` with `rel=prev/next` for your collection, search, or blog pages, you're actively preventing Google from seeing your store's full inventory. ## Why Shopify Liquid Pagination Matters for SEO Search engines, at their core, are designed to follow links. When they crawl your Shopify store, they discover pages by traversing the links embedded in your HTML. Your collection pages, especially those with many products, are critical pathways for discovery. If these pathways are broken or obscured by JavaScript, Googlebot can't find and index your valuable product pages. Shopify provides a robust, built-in solution for pagination using its Liquid templating language: the `{% paginate %}` tag. This tag automatically generates server-side pagination links, complete with unique URLs for each page (e.g., `/collections/your-collection?page=2`). More importantly, it integrates seamlessly with the `rel="prev"` and `rel="next"` attributes, which are crucial signals for search engines to understand the sequential relationship between paginated pages. This tells Google: "Hey, this isn't duplicate content; it's part of a larger series." Without this, you're asking Google to guess, and search engines don't guess – they move on. ## The Mistake I See Most Often: Client-Side Pagination What most agencies get wrong is prioritizing perceived user experience over fundamental search engine crawlability. They implement custom JavaScript to fetch more products, often using `fetch` or `XMLHttpRequest` with an `offset` parameter to load products dynamically. This might make the page *feel* faster to a human user because they don't experience a full page reload, but it's a silent killer for your SEO. The problem is simple: Googlebot (and other search engine crawlers) executes JavaScript, but it doesn't always do so perfectly, and it certainly doesn't always wait for *all* dynamic content to load before moving on. Even if it did, the dynamically loaded content often lacks a unique, crawlable URL. When you use JavaScript to load more products without changing the URL, you're essentially showing Googlebot only the first page of products. All subsequent products, loaded via AJAX, exist in a crawlable black hole. I've seen stores with thousands of products where only the first 50-100 were indexed because the rest were hidden behind a 'Load More' button that Google couldn't effectively interact with or attribute to a distinct URL. This isn't just about missing out on new products; it's about wasting your crawl budget – the number of pages Google is willing to crawl on your site in a given period. Every uncrawlable product is a missed opportunity for organic traffic. Here's a simplified example of what I often see, which *looks* fine to the user but is an SEO dead-end: ```js // collection-load-more.js document.getElementById('load-more-button').addEventListener('click', function() { const currentPage = parseInt(this.dataset.currentPage); const nextPage = currentPage + 1; fetch(`/collections/my-collection.json?limit=24&page=${nextPage}`) .then(response => response.json()) .then(data => { // Append new products to the DOM // Update currentPage dataset }) .catch(error => console.error('Error loading products:', error)); }); ``` This JavaScript approach fetches product data, but it doesn't create new, unique URLs that Google can follow. The URL in the browser's address bar remains `/collections/my-collection`, regardless of how many times the user clicks 'Load More'. For Google, it's just one page. ## The Correct Shopify Liquid Pagination Pattern (with `rel=prev/next`) The solution is built right into Shopify: the `{% paginate %}` tag. It's designed to handle pagination natively and correctly. This ensures that each page of your collection has its own unique URL, which is the cornerstone of good SEO for large catalogs. Let's look at the standard implementation within `collection.liquid` (or `search.liquid`, `blog.liquid`, etc.): ```liquid {% comment %} Set the number of products per page. This is usually 12, 24, or 48. Keep this consistent across your store. {% endcomment %} {% assign products_per_page = 24 %} {% paginate collection.products by products_per_page %}
{% for product in collection.products %} {% render 'product-card', product: product %} {% endfor %}
{% if paginate.pages > 1 %} {% endif %} {% endpaginate %} ``` This Liquid code snippet does several things correctly: 1. **Unique URLs**: Each paginated page gets a distinct URL (e.g., `/collections/my-collection?page=2`). 2. **Server-Side Rendering**: The links are generated on the server, meaning they are present in the HTML that Googlebot first receives, without needing JavaScript execution. 3. **`rel=prev` and `rel=next`**: Shopify automatically injects these critical attributes into the `` of your document when `{% paginate %}` is used. This is often overlooked, but it's *the* signal that tells search engines: "These pages are part of a continuous sequence." This helps Google consolidate ranking signals across the series, preventing perceived duplicate content issues and ensuring all pages are properly understood. To confirm, check your `theme.liquid` (or a file included there that handles `` content). You should see something like this, which Shopify adds automatically when `{% paginate %}` is active on a page: ```liquid {% comment %} This is a simplified example. Shopify automatically adds rel=prev/next when {% paginate %} is used on the current page. You don't need to manually add these if you're using paginate correctly. This is just to illustrate what Shopify does for you. {% endcomment %} {% if paginate.previous %} {% endif %} {% if paginate.next %} {% endif %} ``` This automatic inclusion is why you *must* use `{% paginate %}`. It handles the nuances of sequential page signaling that custom JS implementations almost always miss. ## Protecting Your Crawl Budget: A Real-World Example I recently worked with a large fashion retailer, an established brand moving hundreds of SKUs daily, but their new arrivals and seasonal collections were consistently struggling to rank. Sales were plateauing despite high brand awareness. During my initial audit, I immediately spotted the problem: their collection pages used a custom 'infinite scroll' implemented with JavaScript. While visually appealing, only the first 30 products on any collection page were truly indexed by Google. Thousands of products deeper in their catalog were practically invisible to organic search. The fix was straightforward, though it required a complete overhaul of their collection template: we ripped out the custom JavaScript and reimplemented all collection, search, and vendor pages using the native `{% paginate %}` Liquid tag. We styled the pagination links to be clean and modern, ensuring a smooth user experience even with full page reloads. Within weeks, we saw a dramatic increase in indexed pages via Google Search Console. New product launches started gaining organic visibility much faster. Within three months, their organic traffic from collection pages climbed by 28%, directly correlating to improved product indexing. This wasn't a magic trick; it was simply aligning with how search engines are designed to crawl the web. If your current theme development isn't yielding these kinds of results, it's time to re-evaluate. You can find out more about my approach to robust theme development at [ezomfy.com/services/theme-dev](https://ezomfy.com/services/theme-dev). ## Implementing `{% paginate %}`: Practical Steps and Considerations Using `{% paginate %}` isn't just about dropping a tag into your code; it requires thoughtful placement and consideration to maximize its SEO benefits. ### Where to Place Your Pagination The `{% paginate %}` tag is most effective when applied to templates that display a list of items: * **`collection.liquid`**: For product collections. * **`search.liquid`**: For search results pages. * **`blog.liquid`**: For blog post listings. * **`customers/orders.liquid`**: Even for customer order history, if you want that indexed (though less common for SEO). Always wrap the *entire loop* of items you want to paginate within the `{% paginate %}` block. This ensures that the pagination object (`paginate`) has access to all the necessary information to generate correct links. ### Customizing Pagination Links Shopify's `link_to_page`, `link_to_previous_page`, and `link_to_next_page` filters provide basic HTML for your pagination. You'll want to apply your own CSS to make them fit your theme's aesthetic. Remember, the goal is functional, crawlable links, not just pretty ones. Ensure they are clearly visible and clickable. Avoid styling that makes them look like static text or hides them. ### Avoiding Common Pitfalls 1. **Don't Hide `rel=prev/next`**: Some developers try to hide the default Shopify-generated pagination links with `display: none` in CSS while simultaneously trying to implement their own JavaScript pagination. This completely defeats the purpose. If you use `{% paginate %}`, let it generate the links and the `rel=prev/next` tags, and style *those* links. 2. **Canonical URLs**: Always ensure your canonical tags are correct. Shopify generally handles this well, but custom themes or app integrations can sometimes introduce issues. The canonical URL for a paginated page should point to itself (e.g., `/collections/my-collection?page=2` should canonicalize to `/collections/my-collection?page=2`), *not* the first page of the collection, unless explicitly intended for a very specific SEO strategy (which is rare for pagination). 3. **Performance**: While `{% paginate %}` requires full page reloads, modern Shopify themes are optimized. Focus on overall page speed – image optimization, efficient Liquid code, minimal render-blocking resources – rather than trying to 'fix' pagination with JavaScript at the expense of SEO. I built ezomfy.com to help Shopify merchants navigate these kinds of technical SEO challenges. If your store's organic visibility isn't where it should be, or if you suspect your current pagination setup is holding you back, I offer a free 30-minute consultation. We can quickly pinpoint issues and map out a practical path forward. Visit [ezomfy.com/consultation](https://ezomfy.com/consultation) to book your call. --- ## Why Your Shopify Theme Needs Theme Check in CI Published: 2026-06-26 · By Ashraful · Tags: shopify development, theme check, ci/cd, liquid, github actions, shopify partner URL: https://ezomfy.com/blog/shopify-theme-check-ci-pipeline *Shopify Theme Check prevents Liquid errors before they hit production, yet most agencies skip it in CI. I'll show how to integrate it into GitHub Actions to block faulty PRs, saving countless emergencies.* I've shipped over 700 Shopify projects, and I've seen the same pattern repeat dozens of times: a seemingly minor Liquid change goes live, breaks a critical store function, and then it's a scramble to fix. This isn't just about bad code; it's about a lack of process. Specifically, it's about ignoring a foundational tool that Shopify themselves provides: Theme Check. Most development agencies, even the "good" ones, don't run Shopify Theme Check in their Continuous Integration (CI) pipelines. This is a massive oversight. Theme Check catches Liquid, JSON, and CSS/JS syntax errors, performance anti-patterns, and even security vulnerabilities *before* your code ever reaches production. My argument is simple: if you're building Shopify themes, Theme Check needs to be a mandatory gate in your CI. It takes 10 minutes to set up and can save you from 10 client emergencies. ## Why Shopify Theme Check in CI is Non-Negotiable Theme Check is Shopify's official linter for Liquid, JSON, and other theme files. It's designed to catch common errors, enforce best practices, and identify potential performance bottlenecks. Think of it as ESLint or Prettier, but for your Shopify theme code. I've been a Shopify Select Partner since 2021, and I've audited countless theme codebases. What I consistently find is that while developers might *know* about Theme Check, almost no one integrates it into their automated workflows. They might run it manually occasionally, or worse, not at all. This leaves a gaping hole in their quality assurance process. Here's why relying on manual checks is a terrible idea: * **Human Error:** Developers forget. They're busy. They're under pressure. Manual steps are skipped, leading to inconsistencies and missed issues. * **Inconsistency:** What one developer checks, another might not. Coding standards erode over time without automated enforcement. * **Late Detection:** Errors are found during manual QA, or worse, by the client or their customers in a live environment. This is expensive to fix, damages trust, and can lead to lost sales. By embedding Shopify Theme Check directly into your CI pipeline, you automate this crucial step. Every Pull Request (PR) is automatically scanned, and if it fails the checks, it simply cannot merge. This isn't about being draconian; it's about building robust, reliable stores from the ground up. It ensures a baseline level of quality and consistency across your entire team's contributions, making code reviews faster and more focused on logic rather than syntax. ## The Mistake Most Agencies Make: Trusting "Good Enough" The mistake I see most often is agencies relying on visual QA alone, or basic syntax highlighting in their IDE. They push code, someone looks at it on a staging environment, and if it "looks fine," it goes to production. This approach is fundamentally flawed for Shopify theme development. Liquid isn't like React or Vue where a compiler will throw a fit if you misspell a component or forget a closing tag. Liquid is interpreted at runtime. A simple typo like `assign product = prooducts.first` instead of `products` might not crash the page, but it will silently fail to assign the product, leading to incorrect data display, broken buy buttons, or missing content. These are the kinds of subtle, hard-to-debug issues that Theme Check excels at finding. It can pinpoint performance issues like excessive database queries or asset sizes, and even security risks such as unescaped output. ### A Client War Story: The Missing Product Loop I once took over a project for a client, a high-growth fashion brand, whose previous agency had delivered a custom theme. They were complaining about inconsistent product displays on collection pages. Sometimes, product images wouldn't load, or the "add to cart" button would be missing. It was intermittent and maddening for them. When I pulled down the theme and ran Theme Check, it immediately flagged a dozen warnings and errors. One critical error was a Liquid loop structured incorrectly, causing it to sometimes iterate over an empty array if a specific product tag wasn't present, even though the intent was to show *all* products. The previous agency's manual QA had missed this because it only manifested under specific data conditions not always present in their staging environment. Setting up Theme Check in CI for them would have caught this in minutes, not months after launch. This experience solidified my belief that automated checks are paramount for reliable theme development, like the specialized theme development services I offer at ezomfy.com/services/theme-dev. ## Setting Up Shopify Theme Check in GitHub Actions Okay, enough theory. Let's get practical. I'll show you how to set up Theme Check as a GitHub Action. This will run every time you push code to a PR, and it will fail the PR if any critical Theme Check errors are found. First, you need to have Theme Check installed. It's a Ruby gem, but the easiest way to use it in a CI context is often via `npx` if you have Node.js installed, as many front-end projects do. Shopify provides a CLI tool that wraps Theme Check, making it simple to integrate into an existing `npm` or `yarn` workflow. ### Step 1: Install Shopify CLI (if not already present) While you can install the Theme Check gem directly, using the Shopify CLI is generally more robust for CI environments as it handles dependencies and keeps everything updated. If you don't already have `@shopify/cli` installed as a dev dependency, add it to your `package.json`. Your `package.json` might look something like this, with a script for Theme Check: ```json { "name": "my-shopify-theme", "version": "1.0.0", "scripts": { "theme:check": "shopify theme check", "theme:dev": "shopify theme dev", "theme:push": "shopify theme push" }, "devDependencies": { "@shopify/cli": "^3.0.0" } } ``` After adding the dev dependency, run `npm install`. Then, you can run `npm run theme:check` locally to test it out. ### Step 2: Configure Theme Check Rules (Optional but Recommended) By default, Theme Check uses a sensible set of rules. However, you might want to customize them. You can create a `.theme-check.yml` file in the root of your theme directory. This allows you to disable certain checks that might not apply to your workflow, or elevate the severity of others. Here’s an example `.theme-check.yml` that disables a few checks I often find too strict for client projects and makes a critical HTML parsing error fail a build: ```yaml # .theme-check.yml # See https://shopify.dev/docs/themes/tools/theme-check/configuration root: "./" ignore: - "node_modules/**" - "assets/*.js" - "assets/*.css" - "sections/*.liquid" # Example: ignore a specific file if needed # Adjust check severities or disable them check_configs: HtmlParsingError: severity: error # Make sure HTML parsing errors are always errors MissingTemplate: severity: error DeprecatedFilter: severity: warning # Log deprecated filters as warnings, not errors MatchingTranslations: enabled: false # Disable this check if you're not using translation keys consistently RequiredLayoutThemeFile: enabled: false # If you have a custom layout strategy that doesn't use theme.liquid TranslationKeyExists: severity: warning # We want to know, but not fail PRs immediately ``` This file gives you fine-grained control. I prefer to make `HtmlParsingError` an `error` because broken HTML is almost always a bug. I might set `MatchingTranslations` to `false` on some projects where translation consistency isn't the highest priority during initial development. Tailor this to *your* project's needs and your team's coding standards. ### Step 3: Create the GitHub Actions Workflow Now, let's create the actual CI workflow. In your theme repository, create a file at `.github/workflows/theme-check.yml`. This file defines the steps GitHub Actions will take to run Theme Check. ```yaml name: Shopify Theme Check on: pull_request: branches: - main - master push: branches: - main - master jobs: theme_check: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' # Or your preferred Node.js version - name: Install dependencies run: npm install - name: Run Shopify Theme Check run: npm run theme:check -- --fail-on=error # Fails the action if any errors are found ``` Let's break down this GitHub Actions file: * `name: Shopify Theme Check`: A human-readable name for your workflow, which will appear in GitHub's checks UI. * `on: pull_request` and `on: push`: This workflow will trigger on every `pull_request` against `main` or `master` branches, and also on `push` to those branches. I recommend `pull_request` as the primary trigger so errors are caught *before* merging. * `jobs: theme_check`: Defines a single job within the workflow. You could have multiple jobs if you had other CI steps. * `runs-on: ubuntu-latest`: The job will run on a fresh Ubuntu virtual machine provided by GitHub Actions. * `steps`: * `actions/checkout@v4`: This action checks out your repository code into the runner's environment. * `actions/setup-node@v4`: This sets up Node.js, which is needed for `npm install` and the Shopify CLI. Choose a Node.js version compatible with your project. * `npm install`: Installs your `devDependencies`, including `@shopify/cli`, from your `package.json`. * `npm run theme:check -- --fail-on=error`: This is the crucial step. It executes the `theme:check` script we defined in `package.json`. The `-- --fail-on=error` argument tells Theme Check to exit with a non-zero status code (which fails the GitHub Action) if any errors are detected. You could change this to `--fail-on=warning` if you want to be even stricter, but I typically start with `error` to block critical issues without being overly prescriptive on minor warnings. ## Making Theme Check a PR Blocker Running the workflow is one thing; making it a required check is another. This is where you actually *block* merges, ensuring no code with detected errors makes it into your main branch. In your GitHub repository settings: 1. Go to `Settings` -> `Branches`. 2. Select the branch you want to protect (e.g., `main` or `master`). 3. Click `Add rule`. 4. Under `Require status checks to pass before merging`, search for "Shopify Theme Check" (or whatever you named your workflow). 5. Check the box next to it. This links your workflow's success to the PR's mergeability. 6. Ensure `Require branches to be up to date before merging` is also checked (good practice for avoiding merge conflicts). 7. Save changes. Now, any Pull Request targeting your protected branch will *require* the "Shopify Theme Check" workflow to pass before it can be merged. If Theme Check finds an error, the PR will show a red X, and the merge button will be disabled. Your developers will be forced to fix the Liquid before shipping it. This automated gate ensures that a baseline quality is met with every single code change. This process takes maybe 10 minutes to set up, and it immediately elevates your team's code quality and reduces the risk of production outages due to theme errors. It's a small investment for a massive payoff in stability, developer sanity, and ultimately, client satisfaction. ## Beyond Basic Checks: Custom Rules and Best Practices While Theme Check's built-in rules are excellent, for larger agencies or very specific project requirements, you might want to extend its capabilities with custom checks. This is where you can define rules specific to your internal coding standards or unique Liquid patterns that are critical for your projects. For instance, if your agency always uses a specific naming convention for sections or snippets, you can create a custom check to enforce it. Or, if you have a strict policy against using certain global objects directly in Liquid to maintain performance or prevent data exposure, you can write a check for that. This level of customization ensures that your themes not only meet Shopify's general standards but also your own bespoke requirements, making your codebase uniquely robust. I've worked with numerous clients to develop custom Theme Check configurations that align with their long-term maintenance goals and internal development guidelines. It's a key part of building sustainable and high-performing Shopify stores, and something I often help clients with through my Shopify theme development services. ### How Custom Checks Work Custom checks are typically written in Ruby and live within your theme's directory, often in a `lib/theme_check` folder. You then reference them in your `.theme-check.yml` to ensure they are run alongside Shopify's default checks. For example, a simple custom check might look for specific comments (e.g., a required copyright header) or ensure all `{% schema %}` tags have a `name` property, which is crucial for theme editor usability. ```ruby # lib/theme_check/checks/my_custom_check.rb module ThemeCheck class MyCustomCheck < Check def initialize(config) super @offenses = [] end def on_schema(node) # Ensure all schema tags have a 'name' property if node.schema["name"].nil? || node.schema["name"].empty? add_offense("All schema tags must have a 'name' property for editor visibility.", node) end end def offenses @offenses end private def add_offense(message, node) @offenses << Offense.new( check: self, message: message, template: node.template, line_number: node.start_line_number, markup: node.markup, severity: :error ) end end end ``` Then, in your `.theme-check.yml`, you'd include your custom check: ```yaml # .theme-check.yml root: "./" require: - "./lib/theme_check" # Path to your custom checks folder check_configs: MyCustomCheck: enabled: true severity: error ``` This ensures that your team adheres to specialized requirements, making your codebase more consistent and easier to maintain in the long run. It's a powerful way to codify your team's institutional knowledge and prevent common project-specific errors. This isn't about adding unnecessary overhead. It's about building a robust development process that catches problems early, saves time, and prevents client headaches. If you're serious about delivering high-quality Shopify themes, Theme Check in CI isn't optional; it's fundamental. If you're struggling with consistent theme quality, or if you're an agency looking to implement more robust CI/CD practices for your Shopify projects, I offer free 30-minute consultation calls. We can discuss your specific challenges and explore how you can streamline your development workflow to avoid preventable errors. Book a call at ezomfy.com/consultation. --- ## Metaobjects vs. Metafields in Shopify: The Decision Tree I Use to Avoid Costly Refactoring Published: 2026-06-25 · By Ashraful · Tags: shopify, metafields, metaobjects, shopify development, custom data URL: https://ezomfy.com/blog/metaobjects-vs-metafields-shopify *Confused about Shopify Metafields vs. Metaobjects? I'll show you my practical decision tree, with real-world examples, to pick the right tool for structured data and save yourself costly refactoring later.* As a Shopify Select Partner, I've seen hundreds of Shopify stores built, rebuilt, and patched up. One of the most common architecture mistakes, the kind that costs clients tens of thousands in refactoring down the line, revolves around custom data: specifically, the choice between Metafields and Metaobjects. This isn't a theoretical debate; it's a foundational decision that impacts your store's flexibility, maintainability, and ultimately, your bottom line. Pick wrong, and you'll find yourself wrestling with an unmanageable content strategy in three months. I'm going to lay out the decision tree I use, with real-world examples and code, to ensure you make the right call every time. Here's my thesis: both Metafields and Metaobjects exist for a reason. Metafields are for data tightly coupled to a single specific resource (a product, collection, customer). Metaobjects are for reusable, structured data that can be referenced from multiple places. Understanding this core distinction is critical. ## Shopify Metaobjects vs. Metafields: Understanding the Core Difference Before we jump into the decision tree, let's nail down what each of these tools actually *is*. I've found that a lot of the confusion comes from a fuzzy understanding of their fundamental purpose. Think of it like this: are you writing a sticky note on an item, or are you creating an entry in a structured database table? ### What are Metafields, Really? Metafields are essentially custom data fields you attach to standard Shopify resources. A resource could be a product, collection, order, customer, blog post, or even a page. They extend the default data model. If Shopify doesn't have a field for "care instructions" on a product, you can create a metafield for it. The key characteristic here is attachment. A product's care instructions metafield *belongs* only to that specific product. A collection's unique banner image metafield *belongs* only to that specific collection. They are not designed to be standalone, reusable data points that exist independently. I often explain metafields as "sticky notes." You write a specific piece of information and stick it directly onto the item it describes. That note doesn't make sense on its own; it needs the item. ```liquid {% if product.metafields.custom.care_instructions != blank %}

Care Instructions

{{ product.metafields.custom.care_instructions }}

{% endif %} ``` ### What are Metaobjects, Really? Metaobjects, on the other hand, are custom, reusable data structures. They are like creating your own mini-database table within Shopify. You define a template (the "definition") with specific fields (text, image, URL, product reference, etc.), and then you create individual entries (the "metaobjects") based on that template. Unlike metafields, metaobjects don't *attach* directly to a standard Shopify resource. They are standalone entities. You can then *reference* these metaobjects from other places – a product, a collection, a page, or even another metaobject. This referencing is what makes them so powerful for reusable content. Think of metaobjects as entries in a meticulously organized spreadsheet or a proper database table. Each row is a distinct piece of information, and you can point to that row from various other places without duplicating the data itself. ```liquid {% if product.metafields.custom.product_features.value != blank %}

Key Features

{% endif %} ``` ## The Decision Tree: When to Use Metafields My rule of thumb is simple: If the data point is unique to *one specific instance* of a Shopify resource and will likely *never* need to be referenced or reused elsewhere in a structured way, use a metafield. Here are three practical scenarios where metafields are the clear winner: ### 1. Product-Specific Details (Non-Reusable) Imagine you sell t-shirts. Each shirt has a unique "fabric composition" or "washing instructions." These details are specific to *that particular t-shirt* and don't need to be shared or maintained globally across other products or content types. You wouldn't want to create a separate database entry for "100% Cotton" if it's just a descriptive text for one product. **Example:** A `product.metafields.custom.fabric_composition` or `product.metafields.custom.country_of_origin` field. These are simple strings or numbers that describe *that product*. ### 2. Collection-Specific Banners or Introductory Text Let's say each of your collections needs a unique hero image or a short introductory paragraph that appears at the top of its collection page. This content is tied directly to *that specific collection*. You're not going to reuse this exact banner or text for another collection, nor for a product or page. **Example:** A `collection.metafields.custom.hero_image` (file reference) or `collection.metafields.custom.intro_text` (rich text) for a specific collection page header. ### 3. Customer-Specific Notes or Preferences If you need to store internal notes about a customer (e.g., "Prefers email communication," "VIP client since 2019") or their specific preferences for order fulfillment, these are tied to *that unique customer record*. You won't typically need to pull these into a structured list across your entire store or link them to products. **Example:** A `customer.metafields.custom.internal_notes` or `customer.metafields.custom.marketing_preference`. ## The Decision Tree: When to Use Metaobjects If the data needs to be structured, reusable, and potentially referenced by multiple different Shopify resources, or if it represents a distinct entity that exists independently, then metaobjects are your only sensible choice. This is where the power of structured content truly shines and prevents spaghetti code. Here are three scenarios where metaobjects are the solution I always reach for: ### 1. Reusable Brand Ambassadors or Influencers Many brands work with influencers or ambassadors. Each ambassador has a name, bio, social media links, an image, and perhaps a unique discount code. You might want to display a list of ambassadors on a dedicated page, link to them from specific products they've promoted, or feature them in blog posts. If you used metafields for this (e.g., `product.metafields.custom.ambassador_name_1`, `product.metafields.custom.ambassador_bio_1`), you'd duplicate the ambassador's data for *every product* they're associated with. Imagine updating their Instagram handle across 50 products! With metaobjects, you update it once in the ambassador metaobject, and it propagates everywhere it's referenced. **Metaobject Definition:** `Ambassador` (fields: `name` (text), `bio` (rich_text), `instagram_url` (url), `profile_image` (file)). **Usage:** Create individual `Ambassador` metaobjects. Reference them from a product metafield (type: `metaobject_reference`) or a page section setting. ```liquid
{% for ambassador in section.settings.featured_ambassadors.value %}
{{ ambassador.name }}

{{ ambassador.name }}

{{ ambassador.bio }}

{% if ambassador.instagram_url != blank %} Follow on Instagram {% endif %}
{% endfor %}
``` ### 2. Complex Product Specifications or Compatibility Guides Consider an electronics store selling camera gear. A lens might be compatible with multiple camera bodies. Each camera body has its own set of specs (sensor size, mount type, brand). If you use product metafields to list compatible camera bodies, you'll end up with massive, difficult-to-manage lists on each lens product, and vice-versa. Updating a camera's mount type would mean editing every lens that references it. Instead, define a `Camera_Body` metaobject (fields: `model_name`, `brand`, `mount_type`, `sensor_size`). Then, on your `Lens` product, use a metafield of type `list.metaobject_reference` to link to the relevant `Camera_Body` metaobjects. This is also excellent for `product.metafields.custom.ingredient_list` for food or cosmetics, where ingredients are separate entities with their own details. **Metaobject Definition:** `Ingredient` (fields: `name` (text), `description` (rich_text), `allergens` (list.text)). **Usage:** Create individual `Ingredient` metaobjects. Reference them from a product metafield (type: `list.metaobject_reference`). This makes `/services/app-dev` for ingredient filtering much easier. ### 3. Store Locations, FAQs, or Rich Testimonials If you have multiple physical store locations, each with an address, opening hours, phone number, and map link, you'll want to manage these as metaobjects. You can then display them on a "Store Locator" page, show the nearest store on a product page, or use them in your footer. Similarly, a robust FAQ section with structured questions and answers, or customer testimonials that include a customer name, rating, text, and avatar, are perfect candidates for metaobjects. These are all distinct content pieces that might be displayed in various contexts across your store. ```js // Example of fetching metaobject data via Storefront API for a dynamic FAQ section // This assumes you have enabled Storefront API access for your metaobject type async function fetchFAQs() { const query = ` query { metaobjects(type: "faq_item", first: 20) { edges { node { id field(key: "question") { value } field(key: "answer") { value } } } } } `; const response = await fetch("YOUR_SHOPIFY_STOREFRONT_API_ENDPOINT", { method: "POST", headers: { "Content-Type": "application/json", "X-Shopify-Storefront-Access-Token": "YOUR_STOREFRONT_API_TOKEN" }, body: JSON.stringify({ query }) }); const data = await response.json(); console.log(data.data.metaobjects.edges); // Render FAQs on the page } // Call the function when the page loads // fetchFAQs(); ``` ## The Mistake I See Most Often: Over-using Metafields for Reusable Data What most agencies get wrong is defaulting to metafields for *everything*. They see "custom data" and immediately think "metafield." This happens because metafields have been around longer and are conceptually simpler to grasp initially. But this short-sightedness leads to a tangled mess. I recently worked with a client who runs a high-end jewelry store. They had an extensive catalog of gemstones, each with properties like hardness (Mohs scale), clarity, origin, and specific care instructions. Initially, their previous developer used product metafields to store all this gemstone data. So, on a "Ruby Ring" product, they'd have metafields like `gemstone_type: Ruby`, `ruby_mohs_hardness: 9`, `ruby_origin: Myanmar`, etc. Then, on an "Emerald Necklace," they'd have `gemstone_type: Emerald`, `emerald_mohs_hardness: 7.5`, `emerald_origin: Colombia`. You see the problem. When they introduced a new type of sapphire with unique properties, they had to add 5-6 new metafields to *every single product* that featured a sapphire, then manually populate them. Updating the "care instructions" for all diamonds meant editing dozens of products one by one. It was a nightmare. The site was slow, maintenance was painful, and adding new products became a monumental task. My fix was to migrate all gemstone data into a `Gemstone` metaobject definition. Each individual gemstone (e.g., "Ruby (Burmese)", "Emerald (Colombian)", "Diamond (GIA Certified)") became a metaobject entry with its specific fields. Then, each product simply had a `product.metafields.custom.main_gemstone` metafield that *referenced* the appropriate `Gemstone` metaobject. Now, if they update the Mohs hardness for all rubies, they edit *one* metaobject. Adding a new gemstone type means creating one metaobject definition and then adding a new metaobject entry. This significantly improved site performance, drastically reduced content management time, and made the store much more scalable for future growth. It also made building a sophisticated gemstone filtering system on their [/services/theme-dev](https://ezomfy.com/services/theme-dev) much more straightforward. ## Advanced Use Cases and Performance Considerations Understanding the basic decision tree is crucial, but it's worth noting that metafields and metaobjects aren't mutually exclusive. They often work best together. You can, for instance, have a product metafield that references a metaobject. This is actually the most common and powerful way to use them in combination, as seen in my gemstone example. For instance, a `product.metafields.custom.author` might be a `metaobject_reference` to an `Author` metaobject. This allows you to attach a specific author's full profile (bio, image, social links – all stored in the `Author` metaobject) to many products (e.g., books) without duplicating author data. Performance is another angle. While both add database queries, correctly structured data using metaobjects can actually lead to *better* performance and developer experience over time, especially for complex stores. Why? Because you're making fewer, more targeted data calls and avoiding the overhead of managing hundreds of disparate metafields that are essentially doing the job of a structured table. When I'm building custom functionality or integrating with external systems, especially when it comes to [/services/app-dev](https://ezomfy.com/services/app-dev), having data neatly organized in metaobjects is a massive advantage. It provides a clean, predictable API for developers to work with, rather than parsing through a mishmash of loosely related metafields. Choosing between Shopify Metafields and Metaobjects isn't a trivial choice. It's a strategic architectural decision that impacts your store's long-term health and your team's efficiency. My approach is always practical: evaluate the reusability and structure of the data. If it's a unique attribute for a single item, use a metafield. If it's a structured, reusable entity, use a metaobject and reference it. Following this decision tree will save you headaches and refactoring costs down the line. Are you wrestling with complex data structures on your Shopify store? Or maybe you're planning a new build and want to ensure a solid foundation from day one? I offer a free 30-minute consultation where we can discuss your specific challenges and map out a robust data strategy. No obligation, just practical advice from seven years in the trenches. Book your slot at [/consultation](https://ezomfy.com/consultation). --- ## Shopify Section vs. Block: Master When and How to Use Them (with Code) Published: 2026-06-23 · By Ashraful · Tags: shopify development, shopify 2.0, theme architecture, liquid, web development URL: https://ezomfy.com/blog/shopify-section-vs-block-when-to-use *Shopify developers and merchants often misuse sections for everything. I'll show you why blocks are crucial for repeating content and how getting this wrong leads to unmaintainable themes in months.* I see it happen constantly. A new client comes to me with a Shopify theme that's barely 6 months old, and the theme editor is a nightmare. Endless scrolling, duplicate content, and a merchant who's frustrated because simple updates feel impossible. The root cause? A fundamental misunderstanding of Shopify sections vs. blocks. Here's my thesis, direct and unapologetic: Sections are for structuring your page layout. Blocks are for repeating content *within* those structures. Get this wrong, and I guarantee your theme will become unmaintainable, slow, and a source of constant headaches in short order. This isn't just theory; I've built 700+ Shopify stores and audited countless others. The code doesn't lie. ## The Core Difference: Shopify Section vs Block Explained Let's get straight to what a Shopify section is. Think of a section as a large, self-contained component that defines a distinct area of your page. It’s a hero banner, a product grid, a testimonials area, a footer. Each section is typically unique in its layout and purpose on a given page, though you can use the same section multiple times. It brings its own settings, styles, and logic. A section's primary job is to provide a structural container for content. ```liquid {% schema %} { "name": "Hero banner", "settings": [ { "type": "image_picker", "id": "image", "label": "Background image" }, { "type": "text", "id": "heading", "label": "Heading", "default": "Your Brand Story" } ], "presets": [ { "name": "Hero banner" } ] } {% endschema %}
{{ section.settings.heading }}

{{ section.settings.heading }}

``` Now, blocks. Blocks are the atomic, repeatable units of content *inside* a section. If your section is a `Hero Banner`, blocks might be individual slides in a carousel within that banner. If your section is `Testimonials`, blocks are individual testimonials. They inherit some context from their parent section but have their own distinct settings. Blocks allow merchants to add, remove, reorder, and edit multiple instances of similar content without duplicating entire sections. It’s like building a house. The `Kitchen` is a section, the `Living Room` is a section. Inside the `Kitchen` section, you have `Cabinet` blocks, `Appliance` blocks, and a `Countertop` block. You wouldn't make a whole new kitchen section just to add another cabinet, would you? You'd add another `Cabinet` block. This distinction is critical for scalable, maintainable themes. ## Why Most Developers Get Shopify Section vs Block Wrong Here's what nobody talks about: The mistake I see most often is developers, especially those new to Shopify 2.0 or migrating from older theme structures, using sections for *everything*. They need a row of three feature icons? Three separate sections. They need 10 testimonials? Ten separate testimonial sections. It "works" in the sense that the content appears on the storefront, but it utterly destroys the merchant's experience in the theme editor. Why does this happen? Sometimes it's a lack of understanding. More often, it's about speed. It's quicker to copy-paste an entire section and change a few settings than to architect a flexible section with blocks. But this short-term gain is a long-term poison. I’ve seen themes where a merchant had to scroll for literally minutes through a single page's section list in the editor, all because simple, repeatable content was implemented as unique sections. ### A Client War Story: The Testimonial Tangle I recently took over a project for a fast-growing DTC beauty brand. They had a Shopify store built by a less experienced agency. Their product pages needed a section for customer testimonials. Instead of using blocks, the previous developers had created a `Testimonial` section. Every single testimonial was its own section. The client initially had three, which was fine. But as their brand grew, they wanted to add more – they aimed for 15-20 testimonials per product. The result? To add a new testimonial, the client had to click "Add Section," find "Testimonial," add it, then manually drag it into the correct order. The theme editor became a nightmare of endless, identical sections, making updates agonizingly slow and error-prone. We rebuilt that component as a single `Testimonials` section with individual `Testimonial Item` blocks, cutting their content update time by 80% and making the editor a joy to use. This isn't theoretical; this is the reality of day-to-day store management. ## When to Use a Section: Structural Layouts Sections are your big-picture layout tools. They define the overall architecture of a page. If you're building something that typically appears once or has a very distinct, unique purpose, it's a section. Think about the fundamental components that make up a typical e-commerce page: * **Hero Banners**: The main visual at the top of a page. * **Featured Product Grids**: A section dedicated to showcasing a collection of products. * **Image with Text**: A versatile section for marketing messages. * **Contact Forms**: A specific functional area. * **Global Elements**: Header and Footer (though these are often rendered separately in `layout/theme.liquid`, they function conceptually as sections). When you're mapping out a new page template, the first thing you should be thinking about is, "What are the distinct, top-level content areas here?" Those are your sections. They usually have broad settings that affect the entire area, like background color, padding, or overall layout options (e.g., full-width vs. contained). ```liquid
{% if section.settings.image %} {{ section.settings.image.alt | escape }} {% else %} {{ 'image' | placeholder_svg_tag: 'placeholder-svg' }} {% endif %}
{% if section.settings.heading != blank %}

{{ section.settings.heading }}

{% endif %} {% if section.settings.text != blank %}

{{ section.settings.text }}

{% endif %} {% if section.settings.button_label != blank and section.settings.button_link != blank %} {{ section.settings.button_label }} {% endif %}
{% schema %} { "name": "Image with Text", "settings": [ { "type": "image_picker", "id": "image", "label": "Image" }, { "type": "text", "id": "heading", "label": "Heading", "default": "Image with text" }, { "type": "richtext", "id": "text", "label": "Text", "default": "

Pair large text with an image to tell a story.

" }, { "type": "text", "id": "button_label", "label": "Button label" }, { "type": "url", "id": "button_link", "label": "Button link" } ], "presets": [ { "name": "Image with Text" } ] } {% endschema %} ``` This `image-with-text` is a perfect example of a section. It has a distinct purpose, a clear layout, and its settings control the entire content block. You wouldn't typically have 10 identical 'Image with Text' blocks side-by-side; you'd have one, or maybe two, strategically placed sections on a page. ## When to Use a Block: Repeating Content Units Blocks are where the magic of flexibility happens. If you find yourself needing to display a list of similar items, where each item has the same structure but different content, that's a job for blocks. This is where your theme goes from rigid to dynamic, empowering merchants to manage their content efficiently. Consider these common use cases for blocks: * **Slides in a Carousel/Slider Section**: Each slide is a block. * **Individual Testimonials within a Testimonials Section**: Each testimonial entry is a block. * **Feature Cards/Icons**: A section for displaying key features, where each feature card is a block. * **Column Items**: A section with multiple columns, where each column's content is a block. * **Menu Items**: In a custom navigation section, each link could be a block. Blocks thrive on repetition. They allow you to define a single content structure (e.g., `testimonial_item` with `author`, `quote`, `image`) and then let the merchant add as many of those items as they need, reorder them, and configure each one individually. This is paramount for a good merchant experience. ```liquid

{{ section.settings.heading }}

{% for block in section.blocks %}
{% if block.settings.image %} {{ block.settings.author | escape }} {% endif %}
{{ block.settings.quote }}

- {{ block.settings.author }}

{% endfor %}
{% schema %} { "name": "Testimonials List", "settings": [ { "type": "text", "id": "heading", "label": "Heading", "default": "What Our Customers Say" } ], "blocks": [ { "type": "testimonial_item", "name": "Testimonial", "settings": [ { "type": "image_picker", "id": "image", "label": "Author Image" }, { "type": "text", "id": "author", "label": "Author Name", "default": "Jane Doe" }, { "type": "textarea", "id": "quote", "label": "Quote", "default": "This product changed my life! Highly recommend." } ] } ], "max_blocks": 10, "presets": [ { "name": "Testimonials List", "blocks": [ { "type": "testimonial_item" }, { "type": "testimonial_item" }, { "type": "testimonial_item" } ] } ] } {% endschema %} ``` In this example, the `Testimonials List` is the section. It has a `heading` setting for the overall section. Inside, it defines a `testimonial_item` block type. Each block has its own settings for `image`, `author`, and `quote`. The `max_blocks` setting is crucial here, preventing the merchant from adding an excessive number of blocks that might break the layout or editor performance. This separation is clean, efficient, and intuitive for anyone managing the store. ## Code in Practice: Building a Flexible Feature Section Let's apply this to another common scenario: a 'Features' section where you want to highlight several key benefits of your product or service. Each feature will have an icon, a title, and a short description. This is a perfect candidate for a section with blocks. First, the Liquid code for how the section renders the blocks: ```liquid
{% if section.settings.heading != blank %}

{{ section.settings.heading }}

{% endif %} {% if section.settings.subheading != blank %}

{{ section.settings.subheading }}

{% endif %}
{% for block in section.blocks %}
{% if block.settings.icon_name != blank %} {# Assuming you have a CSS icon library #} {% endif %} {% if block.settings.title != blank %}

{{ block.settings.title }}

{% endif %} {% if block.settings.description != blank %}

{{ block.settings.description }}

{% endif %} {% if block.settings.link_label != blank and block.settings.link_url != blank %} {{ block.settings.link_label }} {% endif %}
{% endfor %}
``` Next, the `schema` that defines the section's overall settings and the structure of each `feature_item` block. This is where you empower the merchant. ```liquid {% schema %} { "name": "Feature Highlight", "settings": [ { "type": "text", "id": "heading", "label": "Section Heading", "default": "Key Features" }, { "type": "textarea", "id": "subheading", "label": "Section Subheading" } ], "blocks": [ { "type": "feature_item", "name": "Feature Item", "settings": [ { "type": "text", "id": "icon_name", "label": "Icon Class (e.g., icon-star)", "info": "Use a CSS class name for your icon library" }, { "type": "text", "id": "title", "label": "Feature Title", "default": "Feature Title" }, { "type": "richtext", "id": "description", "label": "Description", "default": "

Briefly describe this amazing feature.

" }, { "type": "text", "id": "link_label", "label": "Link Label" }, { "type": "url", "id": "link_url", "label": "Link URL" } ] } ], "presets": [ { "name": "Feature Highlight", "blocks": [ { "type": "feature_item" }, { "type": "feature_item" }, { "type": "feature_item" } ] } ] } {% endschema %} ``` This architecture ensures that the merchant can easily add a new feature, reorder existing ones, or update content for any single feature without touching other parts of the section or creating redundant sections. It’s clean, efficient, and scales perfectly as their business evolves. ## Performance and Maintainability: The Real Cost of Misuse Implementing repeatable content with individual sections might seem harmless at first, but it quickly leads to serious issues. I've seen merchants struggle with themes that become virtually unmanageable because of this basic architectural flaw. The impact is felt in two critical areas: ### Theme Editor Performance Every time you load the Shopify theme editor, it has to process and render the settings for every single section on the page. If you've got 20 "testimonial" sections instead of one "testimonials" section with 20 blocks, the editor becomes sluggish. Imagine trying to drag and drop sections when there are hundreds of them on a single page. It's a frustrating, time-consuming experience that directly impacts a merchant's productivity and their perception of their store's usability. This isn't just about aesthetics; it's about real business efficiency. ### Long-Term Maintainability From a developer's perspective, a theme built with misused sections is a nightmare to maintain. If you need to update the styling or add a new setting to your "testimonial" component, you have to modify 20 different section files or risk inconsistent implementations. With blocks, you modify one section file and one block definition, and the changes propagate to all instances. This drastically reduces development time, prevents bugs, and makes future updates significantly easier. It’s why I always prioritize solid theme architecture in my [Shopify theme development services](/services/theme-dev) and for products like [Verso Theme](/themes/verso). When I audit themes, this is one of the first things I look for. It's a tell-tale sign of hurried development or a lack of understanding of Shopify's powerful architecture. A well-structured theme isn't just a nicety; it's a strategic asset for any e-commerce business. ## Avoiding the Pitfalls: Best Practices for Shopify Theme Architecture To ensure you're building robust, future-proof Shopify themes, adopt these principles: 1. **Component Thinking**: Break down your designs into truly reusable components. Ask yourself: "Does this content element repeat?" If yes, it's a block. If it defines a unique layout area, it's a section. 2. **Schema First**: Before writing a single line of Liquid for rendering, plan your `{% schema %}`. Define your section settings and block settings. This forces you to think about the merchant's experience and how they'll interact with the content. 3. **Prioritize Merchant Experience (MX)**: Always put yourself in the merchant's shoes. Will they easily understand how to update this content? Will the editor be responsive? A technically elegant solution that's a pain to use is a bad solution. 4. **Embrace `min_blocks` and `max_blocks`**: Use these settings in your section schema to provide guardrails. You can ensure a minimum number of elements are present (e.g., at least 3 features) or prevent an overwhelming number of blocks that could hurt performance or layout. 5. **Small and Focused**: Keep your sections and blocks focused on a single responsibility. Don't try to cram too many disparate functionalities or settings into one component. By following these practices, you'll build themes that are not only performant and maintainable but also genuinely enjoyable for your clients to use. This elevates the entire store management experience and ultimately contributes to their success. If your Shopify theme editor feels like a maze or you're constantly fighting with your content, it’s time for a change. Let's talk about how to optimize your store's architecture for long-term success. I offer free 30-minute consultation calls to diagnose common issues and chart a path forward for your Shopify store. --- ## The One Liquid Snippet That Drops LCP by 800ms on Most Shopify Stores Published: 2026-06-22 · By Ashraful · Tags: shopify speed, lcp optimization, liquid code, theme development, performance URL: https://ezomfy.com/blog/preload-hero-image-shopify *Discover a single Liquid snippet that can slash your Shopify store's Largest Contentful Paint (LCP) by hundreds of milliseconds, dramatically improving speed and user experience.* If you've spent any time looking at your Shopify store's performance metrics, you've probably stared at "Largest Contentful Paint" (LCP) scores with a mix of frustration and bewilderment. It’s often the hardest core web vital to fix, and on Shopify, it's almost always held back by one glaring, easily avoidable mistake: your hero image isn't preloaded. I've audited hundreds of Shopify stores, from small startups to multi-million dollar enterprises, and I can tell you that 90% of them are leaving 600-1200ms of LCP improvement on the table. The fix? A single, simple `` tag in your `theme.liquid` file. This isn't theoretical; it's a practical, high-impact adjustment that I've applied countless times with consistent, dramatic results. ## Why Shopify Preload Hero Image Matters for LCP (and Your Bottom Line) LCP measures when the largest image or text block on your page becomes visible. For most Shopify stores, this "largest contentful element" is the main hero banner image on the homepage. Google considers an LCP of 2.5 seconds or less "good." Anything above that, and you're penalizing your users, your search rankings, and ultimately, your conversions. Think about it: when a user lands on your store, what's the first thing they see? Usually a big, impactful hero image. If that image takes an extra second or two to load, it creates a moment of blank space or low-res flicker. That's a bad first impression. It screams "slow site," and slow sites bleed money. I've seen firsthand how an LCP improvement of just a few hundred milliseconds can translate into higher engagement and lower bounce rates. The problem is, browsers are smart, but they're not clairvoyant. They don't know which image is going to be your LCP element until they've parsed most of your HTML and CSS. By then, it's often too late. ## The Core Problem: Late Image Discovery When a browser loads a web page, it goes through a series of steps: 1. **HTML Parsing**: It reads the HTML document. 2. **Resource Discovery**: It finds `` and ` ``` This simple change to `async` or `defer` can help, but it's often not enough when you have 15+ scripts. It's like putting a bandage on a gushing wound if you don't address the core problem: too many scripts, period. ## The Mistake Most Shopify Stores Make: Prioritizing Features Over Performance Here's what nobody talks about: most agencies and store owners approach performance backwards. They start by adding apps and features – a review widget, a loyalty program, a pop-up for email capture, a social proof app, a currency converter, a back-in-stock notifier. Each of these typically comes with its own JavaScript payload. What most agencies get wrong is that they focus solely on adding functionality without considering the cumulative performance cost. "Oh, you want reviews? Install Judge.me. Want pop-ups? Install Klaviyo. Loyalty program? Install Smile.ai." It's always about adding. Never about cutting or consolidating. I've seen this 50+ times on client [audits](/audit). The shiny new feature always wins the argument, until the store owner sees their bounce rate climb and conversion rate tank. They then blame the theme, or Shopify, or their marketing, when the real culprit is often the sheer weight of their own app stack. This isn't to say these apps are bad. Many are excellent. The problem is when you have *all* of them. Your store isn't a museum for every cool app; it's a selling machine. And a selling machine needs to be lean and fast. ## How to Audit and Identify Your Shopify Script Overload Before you can cut, you need to know what you're dealing with. A proper script audit isn't rocket science, but it does require a systematic approach. Forget general speed tools for a moment; we're going granular. 1. **Browser DevTools (Network Tab):** This is your primary weapon. Load your most important pages (homepage, product page, collection page) in an incognito window with the DevTools open. Filter by `JS` in the network tab. Look at the `Domain` column. You'll quickly see all the external scripts loading. Note down the domains you don't recognize or that seem redundant. 2. **PageSpeed Insights / WebPageTest:** These tools provide a high-level view and highlight critical performance issues. Look for warnings about "Reduce JavaScript execution time" or "Eliminate render-blocking resources." They often tell you *which* scripts are the culprits. 3. **Theme Code Scan:** Many old, unused apps leave remnants in your `theme.liquid`, `snippets`, or `sections` files. If you're comfortable with code, download your theme and use a local text editor's search function (grep for specific script domains or keywords like `cdn.shopify.com/s/files/1/`) to find lingering code. ### Hunting for the Unnecessary Focus on these areas: * **Uninstalled Apps:** Did you uninstall an app but never clean up its script code? This happens all the time. * **Redundant Functionality:** Do you have two apps doing similar things (e.g., two review apps, two pop-up apps)? * **Page-Specific Needs:** Does a script need to load on *every* page, or just on specific ones (e.g., a review widget only on product pages, a chat widget only after a certain scroll depth)? * **A/B Testing Graveyard:** Old A/B testing scripts that are no longer running but still loading. Once you identify the culprits, you have a few options: remove, consolidate, or conditionally load. ```liquid {% comment %} Example: Conditionally load a review widget only on product pages {% endcomment %} {% if template contains 'product' %} {% endif %} {% comment %} Example: Load a chat widget only if the customer is logged in or on specific pages {% endcomment %} {% if customer or request.page_type == 'contact' %} {% endif %} {% comment %} Example: Delay loading a pop-up script until user interaction or after a delay {% endcomment %} ``` These code snippets show how to be smart about *when* and *where* scripts load. Don't just throw everything into `theme.liquid` and hope for the best. That's a recipe for a slow store. ## Client War Story: The Fashion Brand's Slow Conversion Crisis I remember a fashion brand, selling high-end streetwear. They came to me complaining about low conversions despite good traffic. Their analytics showed high bounce rates on product pages, and visitors were dropping off rapidly. A quick [audit](/audit) revealed 18 third-party scripts: multiple review apps, two loyalty programs, a defunct wishlist app, an abandoned cart app that duplicated Klaviyo's functionality, and a few A/B testing tools they weren't even actively using. It was a bloated mess. We systematically went through their `theme.liquid` and various snippets. We cut 10 scripts entirely – the redundant ones, the unused ones, and those with negligible impact. We consolidated others and implemented conditional loading for the remaining critical apps. For instance, the main review widget only loaded on product pages, and the loyalty program script only on the customer account page. Within a month, their product page load time dropped by over 1.5 seconds, and their conversion rate jumped by 18%. It wasn't about adding, it was about removing and being strategic. ## My Script Budget Rule: Max Four "Must-Haves" After years of optimizing Shopify stores, I've developed a hard rule: **your Shopify store should aim for a maximum of four truly essential, performance-critical third-party scripts.** Everything else needs to be aggressively justified or cut. What makes the cut? 1. **Analytics (e.g., Google Analytics 4):** Essential for understanding your traffic and customer behavior. 2. **Email Marketing (e.g., Klaviyo, Omnisend):** Crucial for customer retention, abandoned carts, and promotions. 3. **Core Payment Gateway Scripts (e.g., Shop Pay, PayPal):** Often integrated and unavoidable, but critical for transactions. 4. **A Key Subscription App (e.g., ReCharge, if applicable):** If subscriptions are a core part of your business model. That's it. Everything else – every pop-up, every social proof notification, every currency switcher (unless truly critical for your target market), every niche review app – needs a *very* strong case. Does it directly drive revenue *more* than it costs in performance? If not, it's a luxury you can't afford. You need to be ruthless. Is that pop-up app actually converting more than it's annoying customers and slowing down your site? Is that social proof notification truly influencing purchases, or just adding another network request and blocking script? Often, the answer is no. For most stores, consolidating functionality is key. Use your email marketing platform for pop-ups and forms. Use Shopify's built-in features where possible. If you want to dive deeper into how this impacts your store specifically, check out my [speed optimization services](/services/speed). ### The Nuclear Option: Manual Script Pruning Sometimes, simply uninstalling an app isn't enough. Many apps leave behind snippets of Liquid or JavaScript in your theme files. These can still trigger network requests or cause errors, even if the app itself is gone. This requires manual intervention. ```liquid {% comment %} Example: Searching for and removing an old script reference in theme.liquid or a snippet. Look for external URLs (e.g., cdn.shopify.com/s/files/1/, static.appname.com) and specific script IDs or class names. {% endcomment %} {% comment %} Another common leftover: conditional scripts that are no longer needed. If the app is gone, this 'if' block is dead weight. {% endcomment %} {% if shop.metafields.some_app.enabled %} {% endif %} {% comment %} Use your theme editor to search for specific domains or keywords. For example, search for "judgeme" if you removed Judge.me but suspect remnants. {% endcomment %} ``` ```bash # If you've downloaded your theme files locally, use grep to find script remnants # Navigate to your theme directory (e.g., your-theme-name/) # Search for a specific app's domain grep -r "static.old-review-app.com" . # Search for common Shopify app script patterns grep -r "cdn.shopify.com/s/files/1/" . # Search for the app's name in Liquid files grep -r "judgeme" ./*.liquid ``` This level of cleanup is often necessary to truly get your script budget under control. It's tedious, yes, but the performance gains are undeniable. If your Shopify store is struggling with slow load times and you suspect third-party scripts are the culprit, it's time for a serious intervention. I offer a free 30-minute [consultation](/consultation) where we can quickly review your store's current performance and identify immediate areas for improvement. No sales pitch, just direct, actionable advice based on years of optimizing Shopify stores. Let's get your store back on budget and converting more customers. --- ## The Three Shopify CLS Fixes That Actually Move the Needle Published: 2026-06-19 · By Ashraful · Tags: shopify speed, cls fix, core web vitals, layout shift, shopify optimization URL: https://ezomfy.com/blog/shopify-cls-fixes-that-actually-work *Stop chasing complex CLS fixes on Shopify. My 7+ years of experience show that almost all Cumulative Layout Shift issues stem from three predictable sources: late-loading reviews, currency switchers, and unsized hero images. Fix these, and your scores will soar.* I've audited hundreds of Shopify stores, from fledgling startups to multi-million dollar brands. Time and again, clients approach me with abysmal Core Web Vitals scores, especially a high Cumulative Layout Shift (CLS). They've often tried a dozen "fixes" recommended by generic blog posts or inexperienced agencies – deferring scripts, preloading fonts, the usual song and dance – all for minimal impact. My experience, honed over 7+ years and 700+ projects, tells a different story. The vast majority of Shopify CLS issues aren't some arcane mystery. They boil down to three predictable, fixable problems: the late-loading reviews badge, the dynamic currency switcher, and unsized hero images. Address these three, and I guarantee your CLS will plummet from a failing 0.4+ to a pristine sub-0.05. This isn't theory; it's a pattern I've seen play out on 90% of stores. ## Understanding Cumulative Layout Shift: Why Your Shopify Store Feels Jumpy Cumulative Layout Shift (CLS) measures the unexpected movement of visual elements on a webpage. Think about it: you're reading an article, and suddenly, an image loads, pushing all the text you were reading downwards. That's a layout shift. It's jarring, frustrating, and signals a poor user experience. Google hates it, and so do your customers. For Shopify stores, a high CLS score often translates directly to higher bounce rates and, over time, can impact your search engine rankings. Google prioritizes pages that offer a stable, smooth browsing experience. When I talk about a practical *shopify cls fix*, I'm not talking about abstract performance theories. I'm talking about tangible elements on your store that cause real, measurable shifts. Most CLS on Shopify isn't complex, it's predictable. It's not about deep code refactoring for the average store; it's about addressing these specific elements that consistently cause trouble. ## Culprit #1: The Reviews Badge That Loads Too Late (and Too Big) Nearly every Shopify store uses a reviews app, be it Yotpo, Loox, Judge.me, or another popular solution. These apps inject their star ratings and review counts into product pages (and sometimes collections) via JavaScript. The fundamental problem is this: the browser renders the page's HTML and CSS first. Then, the JavaScript for the reviews app loads, executes, and *then* injects the badge HTML into the page. Before the badge loads, the browser doesn't know how much space it will occupy. When it finally appears, it shoves all subsequent content down. This is a classic, predictable CLS hit. I've seen this 50+ times on client audits. A client, a popular gourmet coffee brand, had product pages consistently scoring 0.45 CLS. They were using Loox reviews. A quick audit showed the review badge loading well after the product title and price. This single element was responsible for the majority of their CLS problem. ### The Fix: Pre-allocate the Space The solution is to tell the browser exactly how much space that reviews badge will take up *before* it actually loads. You can do this by wrapping the review app's Liquid snippet in a container with a defined `min-height` and `display: block`. This reserves the space, so when the JavaScript finally injects the badge, the page doesn't have to reflow. Here's how you can implement this for a typical reviews app: ```liquid
{% comment %} Replace this with your actual reviews app snippet. Examples: - Yotpo: {%- render 'yotpo-product-bottomline' product: product -%} - Loox:
- Judge.me: {%- include 'judgeme_widgets', product: product, widget_type: 'judgeme_preview_badge' -%} {% endcomment %}
{# Generic placeholder for app JS injection #}
``` For the gourmet coffee brand, implementing this simple `min-height` placeholder, specifically targeting their Loox badge container, dropped their CLS to 0.02 almost overnight. It's a low-effort, high-impact *shopify cls fix*. ## Culprit #2: The Jumpy Currency Switcher and Geo-IP Popups If you run an international Shopify store, chances are you use a currency switcher or a geo-IP popup/banner. These elements, much like review apps, are almost always loaded via JavaScript after the initial HTML render. They frequently appear at the very top of the page, meaning when they finally load, they push *all* subsequent content downwards. This is particularly problematic because it affects every single page on your store, not just product pages. Here's what most agencies get wrong when dealing with this: they focus on deferring these scripts or loading them asynchronously. While that's good practice for other performance metrics like Largest Contentful Paint (LCP) or Time to Interactive (TTI), it often *exacerbates* CLS. The content is still injected late; it just happens later in the loading process. The real *shopify cls fix* here isn't about *when* it loads, but *how* it impacts the page's layout once it does. ### The Fix: Static Height Allocation or Early, Dimensioned Rendering The best approach is either to render the switcher directly in the Liquid template with fixed dimensions from the start or, if it must be JavaScript-driven, to allocate placeholder space for it. For geo-IP popups that appear at the top, ensure they use `position: fixed` or `position: absolute` so they float above the content without pushing it down. If you're using a third-party app for currency switching, you need to be aggressive with your CSS targeting to reserve the space. If you have control over the snippet, you can directly size its container. ```liquid ``` The key is to ensure that the space is reserved. If you're building your own currency switcher, embed it directly into the Liquid with `width` and `height` (or a `min-height` container). For third-party apps, you might need to inspect their final rendered HTML and apply CSS to their container to prevent layout shift. This simple step can dramatically reduce CLS across your entire site. ## Culprit #3: The Unsized Hero Images and Product Galleries This is a fundamental web development principle, yet it's astonishing how often I see it missed or mishandled on Shopify stores. When a browser loads a webpage, it attempts to lay out the content as quickly as possible. If it encounters an `` tag without explicit `width` and `height` attributes, it doesn't know how tall that image will be until the image file *actually downloads*. This means the browser renders all the content around and below the image, and then, when the image loads, it has to reflow the entire page, pushing everything down to accommodate the image's height. This is a guaranteed CLS hit. Hero images (the large banner images at the top of your homepage) are the worst offenders due to their sheer size. Product image galleries and collection page image grids are also common culprits, as multiple unsized images can cause a cascade of shifts. ### The Fix: Explicitly Define Image Dimensions Always include `width` and `height` attributes on your `` tags. Shopify's `img_url` filter, when used with `img_tag`, makes this incredibly straightforward as it automatically pulls the image's dimensions. For responsive images that need to scale, use CSS (`max-width: 100%; height: auto;`), but keep the `width` and `height` attributes on the `` tag. These attributes provide the browser with the aspect ratio information it needs to reserve the correct space. Here's an example using Liquid for a hero image: ```liquid {%- assign hero_image = section.settings.image -%} {% if hero_image != blank %}
{{ hero_image.alt | escape }}
{% endif %} ``` Notice how `width="{{ hero_image.width }}"` and `height="{{ hero_image.height }}"` are directly used. Even if the image is later scaled by CSS, the browser initially allocates space based on these attributes, preventing layout shift. For situations where `width` and `height` attributes might be less practical (e.g., highly dynamic image aspect ratios or backgrounds), an **aspect ratio padding hack** can be used with CSS: ```liquid
{{ hero_image.alt | escape }}
``` While the CSS aspect ratio method is powerful, I find that simply adding `width` and `height` attributes to the `` tag is often the easiest and most effective *shopify cls fix* for most stores. It's direct, browser-native, and doesn't require complex CSS calculations. ## My Process for Real Shopify CLS Fixes When a client approaches me about slow Shopify speed, CLS is almost always a major pain point. I don't start with deferring every script or micro-optimizing fonts – those are often secondary concerns for CLS. I go straight for these three: reviews, currency switchers, and hero images. They are the highest-impact, lowest-effort changes for CLS on 90% of stores. I've honed this practical approach over hundreds of speed audits, seeing consistent, dramatic improvements across diverse niches. This focused method is what sets my approach apart from generic advice. It's not about magic; it's about understanding the common culprits on the Shopify platform and applying targeted, proven solutions. These aren't just theoretical suggestions; they are the exact steps I take with my clients. If you're struggling with speed, my team and I specialize in these kinds of practical, impactful optimizations. Learn more about our [Shopify Speed Optimization Services](/services/speed). Want a quick estimate of your store's speed potential? Try our [free Shopify Speed Calculator](/tools/speed-calculator). ## Stop Guessing, Start Fixing: What to Do Next Don't get bogged down in endless performance audits that suggest dozens of minor tweaks. For Cumulative Layout Shift on Shopify, focus your energy on the big three: late-loading reviews badges, dynamic currency switchers, and unsized hero images. Implement the fixes I've outlined, then check your store's CLS via Lighthouse or PageSpeed Insights. You'll likely see a massive improvement. If you've tried these fixes and still see high CLS, or if you simply want an expert to ensure your Shopify store is performing at its peak, I offer a free 30-minute consultation. We'll review your specific situation and map out a clear path to a faster, higher-converting store. Book your free call at [/consultation](https://ezomfy.com/consultation). --- ## How to Hit Lighthouse 95 on Shopify Without Rebuilding Your Theme Published: 2026-06-17 · By Ashraful · Tags: shopify speed, lighthouse score, performance optimization, core web vitals, theme optimization URL: https://ezomfy.com/blog/lighthouse-95-shopify-without-rebuilding *Most Shopify stores can achieve 95+ mobile Lighthouse scores by implementing 7 key optimizations on their existing theme, avoiding expensive rebuilds.* Too many Shopify merchants get the same advice: "Your theme is slow, you need a rebuild." I've seen stores pay tens of thousands for this, only to gain a few points on their Lighthouse score. That's usually a waste of money. The truth is, most Shopify stores can hit a 95+ mobile Lighthouse score by fixing a handful of critical issues on their *existing* theme. You don't need a complete overhaul; you need targeted, impactful optimizations. Before you even think about a rebuild, tackle these seven areas. ## Stop Paying for Rebuilds: Your Shopify Theme Isn't the Problem (Yet) The common narrative is that default themes or older themes are inherently slow. While some themes are better optimized out of the box, the core architecture of your Shopify theme is rarely the sole reason for a low Lighthouse score. I've audited over 500 Shopify stores, and almost every single time, the biggest culprits are easily fixable elements: inefficient image loading, bloated third-party scripts, and poor font handling. Chasing a "perfect" theme without addressing these low-hanging fruit is like trying to drain a bathtub with the tap still running. My approach to achieving Shopify Lighthouse 95 mobile focuses on practical, real-world changes that deliver tangible results without breaking the bank. If you're struggling with speed, start with a targeted approach to identify bottlenecks. This is exactly what we focus on in our [Shopify speed optimization services](/services/speed). ## The Hero Section: Preloading Your LCP Image The Largest Contentful Paint (LCP) is often the most significant factor dragging down your Lighthouse score. It measures when the largest image or text block in the viewport becomes visible. For most Shopify stores, this is the main hero banner image. If your browser has to download other assets (CSS, JS) before it even knows it needs to fetch your hero image, your LCP will suffer. The fix is simple: tell the browser to fetch that critical image *immediately*. Here's how you do it by adding a `` tag in your `` section. You'll need to find the specific image URL for your hero image. This often involves inspecting your theme's `header.liquid` or `sections/hero.liquid` files. ```liquid ... {% comment %} Preload the LCP image {% endcomment %} {% assign hero_image = section.settings.image %} {% comment %} Adjust this based on your theme's image variable {% endcomment %} {% if hero_image != blank %} {% endif %} ... ``` **Explanation:** * `as="image"`: Tells the browser it's an image. * `href="..."`: The actual URL of your hero image. Use Shopify's `image_url` filter to get a responsive version. Adjust the `width` to match your hero image's typical display size. * `fetchpriority="high"`: A hint to the browser that this resource is critical. Implement this, and watch your LCP score jump. I've seen this alone improve scores by 10-20 points on mobile. ## Deferring Third-Party JavaScript: Kill the Render Blocking This is where most Shopify stores bleed performance. Every app you install, every tracking script, every marketing pixel adds JavaScript. And a lot of this JS is *render-blocking*. That means your browser pauses rendering the page until it's downloaded and executed those scripts. This directly impacts your First Contentful Paint (FCP) and LCP. The solution? Defer or asynchronously load non-critical JavaScript. Shopify's script tags don't always come with `defer` or `async` by default, especially for older or poorly built apps. You need to identify these scripts and modify how they're loaded. ### How to Identify Render-Blocking Scripts Run a Lighthouse audit. Look under "Eliminate render-blocking resources." You'll see a list of URLs. Pay close attention to scripts loaded from CDNs (`cdn.shopify.com/s/...`, `static.klaviyo.com`, etc.) or app-specific domains. ### Implementing Deferral (Carefully) The safest way to defer scripts is to add the `defer` attribute to their ` ``` **Important:** Test *rigorously* after making these changes. Deferring certain scripts (like those for critical UI elements or analytics that must fire immediately) can break functionality. What most agencies get wrong is blindly deferring everything. You need to understand what each script does. I've seen this 50+ times on client audits: a store installs 15-20 apps over a few years, and suddenly their load time is abysmal. Each app adds its own JS, often without optimization. We recently worked with a fashion store that had a Lighthouse score of 45. They had 18 active apps, but 5 of them hadn't been used in months. Simply uninstalling those apps and manually cleaning up their lingering script tags, combined with deferring the remaining non-critical JS, boosted their mobile score to 88 within a week. This was without touching a single line of their theme's core structure. It wasn't about a rebuild; it was about surgical cleanup. If you're unsure where to start, an in-depth [Shopify audit](/audit) can pinpoint these issues precisely. ## Font Loading: The `font-display` Swap Web fonts are beautiful, but they can significantly impact your Core Web Vitals, especially Cumulative Layout Shift (CLS) and LCP. When custom fonts load late, browsers often hide text or show a fallback font, then "swap" to the custom font once it's ready. This causes a sudden reflow of content, leading to a jarring user experience and a high CLS score. The solution is `font-display: swap;`. This CSS property tells the browser: "If the custom font isn't ready, display the text immediately using a fallback font. Once the custom font loads, swap it in." This eliminates the "invisible text" (FOIT - Flash of Invisible Text) problem and reduces layout shifts. ### Implementing `font-display: swap;` You need to add `font-display: swap;` to every `@font-face` rule in your theme's CSS. These rules are usually in files like `base.css`, `theme.css`, or `assets/theme.scss.liquid`. ```css /* Example @font-face rule in your theme's CSS */ @font-face { font-family: 'YourCustomFont'; src: url('your-custom-font.woff2') format('woff2'); font-weight: normal; font-style: normal; font-display: swap; /* Add this line */ } /* For Google Fonts, you might need to append &display=swap to the URL */ /* e.g., */ ``` After adding `font-display: swap;`, you'll notice text rendering much faster, even if it's initially in a fallback font. This is a huge win for perceived performance and CLS. ## Intelligent Image Loading: Lazy Load Offscreen & Modern Formats Images are often the heaviest assets on a Shopify store. Improper image handling is a primary cause of slow load times and poor LCP. There are two main strategies here: lazy loading and modern image formats. ### Lazy Loading Offscreen Images Lazy loading means images that are not immediately visible in the user's viewport (i.e., "offscreen") are only loaded when the user scrolls near them. This saves bandwidth and reduces initial page load time. Modern browsers support native lazy loading with the `loading="lazy"` attribute. Shopify themes generally include this for product images and other non-hero images by default. However, always double-check. You can often find and modify image rendering in files like `sections/main-product.liquid`, `snippets/product-card.liquid`, or `snippets/image-with-text.liquid`. ```liquid {{ image.alt }} ``` **Crucial Caveat:** Do *not* lazy load your LCP image (usually the hero banner). That's counterproductive, as you want it to load as fast as possible. Preload it as discussed in H2 2. ### Modern Image Formats (WebP, AVIF) Shopify's CDN automatically converts and serves images in modern formats like WebP or AVIF to supported browsers. This is a massive win, as these formats offer superior compression without significant quality loss compared to JPEG or PNG. However, it only works if you're using Shopify's `img_url` filter correctly. Avoid uploading images directly to your theme assets and then referencing them, as these won't be optimized by Shopify's CDN. Always upload through the Shopify admin and use the Liquid `image_url` filter. To verify, right-click an image on your live store and inspect it (or check the Network tab in DevTools). You should see `Content-Type: image/webp` or `image/avif` for modern browsers. If you're seeing `image/jpeg` or `image/png`, investigate how that specific image is being rendered. ## Minimizing Layout Shift (CLS): Dimensions and Aspect Ratios Cumulative Layout Shift (CLS) measures how much unexpected layout shift occurs during the loading of a page. It's incredibly frustrating for users when content jumps around while they're trying to read or click something. The primary culprit? Images, embeds, and ads that load without reserved space. When an image loads, if the browser doesn't know its dimensions, it allocates no space. Then, once the image data arrives, the browser suddenly pushes everything else down to make room. This is layout shift. ### The Simple Fix: Explicit `width` and `height` Attributes For every image in your theme, ensure it has `width` and `height` attributes. Shopify's `image_tag` Liquid filter usually handles this, but custom image implementations or third-party app images might miss it. ```liquid {{ product.featured_image.alt }} ``` This tells the browser exactly how much space to reserve before the image even loads. The browser can then render the page without content jumping. ### Aspect Ratio Boxes for Responsive Images For responsive images, where the actual display size changes, using `width` and `height` directly can be tricky without CSS to manage it. A robust technique is to use CSS to create an "aspect ratio box." This involves wrapping your image in a container and using `padding-bottom` (based on the image's aspect ratio) to reserve space. Example CSS: ```css /* For an image with a 16:9 aspect ratio (height = 9/16 * width) */ .aspect-ratio-box { position: relative; width: 100%; padding-bottom: 56.25%; /* 9 / 16 = 0.5625 */ overflow: hidden; } .aspect-ratio-box img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; /* or contain, depending on desired behavior */ } ``` You would then apply `.aspect-ratio-box` to a `div` wrapping your `img` tag. This ensures space is reserved for the image, regardless of its final rendered size, preventing CLS. This technique, combined with explicit `width` and `height` attributes, virtually eliminates image-related CLS. ## Kill Unused Apps and Unnecessary Code This might sound obvious, but it's astonishing how many stores accumulate apps they no longer use. Every app, even "inactive" ones, can leave behind JavaScript, CSS, or Liquid snippets that still load, slowing down your store. ### The Audit Process 1. **List all installed apps:** Go to your Shopify admin -> Apps. 2. **Identify unused apps:** Which ones haven't you touched in months? Which ones are redundant? 3. **Uninstall:** Use Shopify's uninstall process. This often removes most of the code. 4. **Manual Cleanup:** This is the critical step often missed. Even after uninstalling, apps can leave behind code snippets in `theme.liquid`, `assets` files, or `snippets`. * Search your theme code (using the theme editor or a local development environment) for app-specific keywords, script URLs, or Liquid tags related to uninstalled apps. * **Backup your theme** before doing any manual code removal. * Common places to check: `theme.liquid` (for script includes), `assets` (for app-specific CSS/JS files), `sections` and `snippets` (for Liquid renders). * If you're unsure, comment out the code first and test thoroughly before deleting. This isn't just about apps. I've seen stores with old tracking pixels from campaigns long-ended, or A/B testing scripts for tests that finished months ago. Regularly review any custom code or snippets. Every line of unnecessary code is dead weight. If you're overwhelmed by this, our [Shopify speed calculator](/tools/speed-calculator) can give you an initial estimate of your current performance, but a deep dive is always needed for cleanup. Achieving a 95+ mobile Lighthouse score on Shopify is absolutely attainable for most stores without resorting to a costly theme rebuild. By systematically tackling these seven areas – hero image preloading, deferring third-party JS, using `font-display: swap`, intelligent lazy loading, modern image formats, fixing CLS, and ruthless app cleanup – you can dramatically improve your store's performance. These aren't just theoretical fixes; they are practical, proven strategies I've applied across hundreds of client stores. If you've gone through these steps and are still struggling, or you simply need expert guidance to implement them correctly, I offer a free 30-minute consultation call to discuss your specific store's needs and how we can help. Book your call at [/consultation](/consultation). --- ## Why I Immediately Uninstall Every Shopify Speed App Published: 2026-06-17 · By Ashraful · Tags: shopify speed, shopify performance, app bloat, theme optimization, web vitals URL: https://ezomfy.com/blog/why-i-uninstall-shopify-speed-apps *As a Shopify Select Partner, I immediately uninstall every Shopify speed app on day one of a paid engagement. They promise magic but almost always add bloat and complexity. Real speed improvements come from deep, theme-level optimization, not quick fixes.* When a new client brings me in for a Shopify speed audit, the first thing I do, before even running a single performance test, is uninstall every 'speed optimization' app they have. Yes, you read that right. Every single one. I'm Ashraful Alam, and after 7+ years building Shopify stores and shipping over 700 projects, I've seen this play out too many times: these apps, designed to make your store faster, almost invariably add bloat, inject more code, and ultimately make things worse. My approach is fundamentally different. I believe true Shopify speed comes from meticulous, theme-level work – an image budget, deferred third-party scripts, proper font handling, and a ruthless audit of unused apps and code. It’s about building performance in, not bolting it on. So, my Day 1 protocol is simple: rip out the supposed 'speed boosters,' establish a true baseline, and then get to work on what actually moves the needle. ## Why Shopify Speed Apps Don't Work (and Often Make Things Worse) Let's be direct: most Shopify speed apps don't work. Not in the way you think, and certainly not without significant trade-offs. The promise is alluring – install an app, click a button, and watch your Core Web Vitals soar. The reality is usually a Frankenstein's monster of injected JavaScript, extra CSS, and often conflicting scripts that fight each other for resources. Think about it logically. For an app to 'optimize' your store, it has to inject its own code. It has to scan your pages, manipulate the DOM, or serve assets through its own CDN. All of this requires *more* code. Even if that code is well-written (and often it isn't), it still adds to your total byte weight, increases parse time, and can introduce layout shifts or render-blocking issues. I've seen this 50+ times on client audits: a store installs a 'lazy load' app, only for it to lazy load images *after* the browser has already painted the initial content, causing a huge Cumulative Layout Shift (CLS) score. It's an illusion of optimization. The goal of these apps is to provide a 'quick fix,' but performance isn't a quick fix. It's a fundamental aspect of your store's architecture. Trying to patch over a poor foundation with an app is like trying to fix a leaky roof with a sticker – it might look better for a moment, but the underlying problem persists, and often gets compounded by the 'fix' itself. This is why when clients ask me about 'shopify speed apps not working,' my answer is always the same: they're designed to address symptoms, not the disease. ## The Real Culprits: What's Actually Slowing Your Shopify Store? Once the speed apps are gone and we have a clean slate, the real work begins. And almost every time, the same issues emerge as the true performance bottlenecks. These aren't things an app can fix with a one-click toggle; they require a developer's touch. ### Image Bloat: The Elephant in the Room Massive, unoptimized images are the single biggest performance killer on Shopify. Merchants often upload images straight from a designer or camera, unaware of the huge file sizes. A 5MB product image might look great, but it's crushing your page load times. Shopify handles some image optimization automatically with its CDN, but it's not a magic bullet. You still need to serve appropriately sized images and use modern formats. Here’s how you ensure images are responsive and efficiently loaded in your theme. This isn't just about lazy loading; it's about serving the *right* image at the *right* size. ```liquid {{ product.featured_image.alt | escape }} ``` This Liquid snippet ensures the browser picks the best image size for the user's device and viewport, and `loading="lazy"` defers offscreen images. But critically, the `srcset` and `sizes` attributes empower the browser to make intelligent decisions, rather than forcing a heavy image on everyone. ### Third-Party Scripts: The Unseen Drag Review widgets, chatbots, analytics tools, advertising pixels – these are essential for many stores. But each one injects its own JavaScript and CSS, often without any thought for performance. A single poorly implemented script can hold up your entire page render. I audit every third-party script, determine its necessity, and implement strategies like deferred loading or conditional loading. ### Font Loading Strategy: The Invisible Layout Shifter Custom fonts look great, but if not handled correctly, they can cause a Flash of Unstyled Text (FOUT) or a significant Cumulative Layout Shift (CLS). The key is to tell the browser how to behave while the custom font is loading. ### Unused Apps and Theme Bloat Every app you install, even if uninstalled, can leave behind snippets of code, assets, or settings. Over time, this digital debris accumulates, slowing down your store. A clean, custom theme, or a heavily optimized premium theme, with minimal app reliance, is always faster. I frequently recommend clients consider a custom build for ultimate performance control, but even with off-the-shelf themes, aggressive cleanup is necessary. ## My Day 1 Protocol: Rip Them Out, Measure, Then Build When I take on a new client engagement focused on speed, my first step is always the same, and it’s non-negotiable: I uninstall every single Shopify speed optimization app. No exceptions. This includes image optimizers, lazy loaders, script deferrers, and anything else promising a 'magic bullet.' It sounds counter-intuitive to some clients, but it's the only way to get a true picture of the store's underlying performance. After the digital detox, I establish a baseline. I use tools like Google PageSpeed Insights, GTmetrix, and WebPageTest to capture metrics across different pages (homepage, product page, collection page). This gives us an honest, unvarnished look at where the store stands *without* any band-aid solutions. This is the starting point for all our optimization efforts at [ezomfy.com/services/speed](/services/speed). ### A Client's Story: The Widget Jungle I once took on a client, a mid-sized fashion retailer, whose Shopify store was suffering terribly. Their PageSpeed scores were in the teens, and conversion rates were plummeting. They had three different 'speed' apps installed, plus a review widget, a chatbot, a currency converter, and a custom product builder. Each one was fighting for resources. On day one, I removed all three speed apps. Immediately, their Lighthouse scores jumped 15-20 points just from the cleanup, and their CLS improved dramatically. The real problem wasn't a lack of optimization; it was *too much* conflicting optimization. From that cleaner baseline, we then tackled image compression, theme code cleanup, and selective deferral of critical third-party scripts, ultimately achieving consistent scores in the 70s-80s and a noticeable boost in user engagement. ## What Most Agencies Get Wrong: Chasing Scores, Not Experience Here's what nobody talks about enough: many agencies and developers chase Google PageSpeed Insights scores as the holy grail. They'll do whatever it takes to get that green number, even if it means sacrificing real user experience. They might aggressively defer critical CSS or JavaScript, leading to a flash of unstyled content or a janky interactive experience, just to shave off a few milliseconds on a synthetic test. My focus is always on the *real* user experience. A perfect 100 on PageSpeed Insights means nothing if users are frustrated by a flickering layout or unresponsive elements. We aim for excellent Core Web Vitals, yes, but we also monitor real user data (RUM) to ensure our optimizations translate into tangible improvements for actual visitors. The synthetic lab data is a guide, but the field data from your actual users is the ultimate truth. If you want to understand your current speed, check out our [speed calculator](/tools/speed-calculator). ## The Foundation of True Shopify Speed: Theme-Level Optimization Once we've stripped away the cruft and established a baseline, the true work begins: deep, theme-level optimization. This is where a developer with a deep understanding of Liquid, JavaScript, and CSS makes a difference. This isn't about installing another app; it's about carefully dissecting and rebuilding your theme's performance. ### Deferring Non-Critical JavaScript Many scripts don't need to load immediately. Think about a chatbot script or a complex animation that appears only when a user scrolls down. Loading these with `defer` or `async` attributes allows the browser to render critical content first. Here's a common pattern to defer non-critical scripts, often placed just before the closing `` tag: ```html ``` The `defer` attribute tells the browser to execute the script after the HTML document has been parsed. This prevents the script from blocking the initial render of your page. ### Critical CSS and Asynchronous Loading Only the CSS needed for the 'above the fold' content should be render-blocking. The rest can be loaded asynchronously. This is a more advanced technique but incredibly effective for improving First Contentful Paint (FCP). ### Font-Display: Swap for Faster Text Rendering Don't let custom fonts hold up your text. The `font-display: swap;` CSS property tells the browser to use a fallback font immediately and then swap in your custom font once it's loaded. This prevents invisible text (FOIT) and provides a better user experience. You implement this within your `@font-face` declarations in your theme's CSS: ```css @font-face { font-family: 'MyCustomFont'; src: url('path/to/mycustomfont.woff2') format('woff2'), url('path/to/mycustomfont.woff') format('woff'); font-weight: 400; font-style: normal; font-display: swap; /* This is the key */ } ``` This simple declaration makes a profound difference in perceived load speed and eliminates font-related CLS. ### Shopify Asset Minification and Concatenation Shopify automatically minifies Liquid output to some extent, but for CSS and JavaScript, you're responsible. Combining multiple small CSS or JS files into fewer, larger ones (concatenation) reduces HTTP requests, and then minifying those combined files removes unnecessary characters, further reducing file size. This is often part of a custom build process or a well-structured theme setup. While Shopify's build process handles some of this, for custom themes or advanced setups, a build tool might be used. Here's a conceptual command you might run locally using a tool like `uglify-js` or `cssnano` before uploading assets: ```bash # Example: Minify JavaScript uglifyjs src/js/*.js -o assets/theme.min.js -c -m # Example: Minify CSS (using cssnano via postcss-cli) postcss --use cssnano src/css/*.css -d assets/theme.min.css ``` This isn't something you'd run directly on Shopify's servers, but it demonstrates the *type* of work that goes into reducing asset sizes before they're ever served to a user. It's about optimizing the source, not just trying to patch the output. ## Beyond the Code: A Holistic Approach to Performance Speed isn't just about code; it's about strategy. My work with clients extends beyond technical fixes to a broader consultation on their app stack, content strategy, and long-term performance goals. ### Strategic App Audits and Removal I conduct thorough app audits, identifying redundant, underperforming, or simply unused apps. Each app should justify its presence with clear ROI that outweighs its performance cost. Sometimes, a custom solution for a specific feature is faster and more efficient than a bloated app. ### Content Delivery Network (CDN) and Cache Optimization Shopify uses a robust CDN by default, which is fantastic. But we ensure that caching headers are properly set where applicable, and that dynamic content is managed efficiently to maximize the benefits of the CDN. ### Prioritizing Core Web Vitals Performance work at ezomfy.com focuses heavily on Google's Core Web Vitals: Largest Contentful Paint (LCP), Cumulative Layout Shift (CLS), and First Input Delay (FID). These metrics directly correlate with user experience and search engine rankings. Our goal isn't just a 'faster' site, but a site that *feels* fast and is rewarded by search engines. You can find more detailed guides and thoughts on performance optimization in our [resources section](/resources). If your Shopify store is struggling with speed, and you're tired of chasing elusive fixes with apps that only complicate matters, it's time for a different approach. I invite you to book a [free 30-minute consultation call](/consultation) with me. We'll discuss your specific challenges, look at your current performance, and outline a clear, actionable path to a faster, more reliable store. No sales pitch, just honest, direct advice from someone who's built a career on making Shopify stores perform. --- ## Custom Shopify app vs. App Store: when to build vs. buy Published: 2026-05-10 · By Ashraful · Tags: shopify, apps, custom development URL: https://ezomfy.com/blog/shopify-custom-app-vs-install *Building a custom Shopify app costs $4k–$20k. Installing one from the App Store costs $9–$99/month. Here's the real decision framework.* The Shopify App Store has ~13,000 apps. Most stores install 15–25 of them. So why does anyone ever build a custom one? Three reasons, when they actually pay off. We'll walk through the math, then three rules of thumb for when each side wins. ## The math: monthly fee vs. one-time build A typical App Store app costs $9–$99/month. The median app charge across all installed Shopify apps is roughly $25/month. Let's say you need *one* specific capability. The options: | | Install an app | Build a custom one | |---|---|---| | Upfront cost | $0 | $4,000 – $20,000 | | Monthly cost | $25 – $99 | $0 | | Time to launch | Hours | 3–8 weeks | | Owns your data | They do | You do | | Locked to provider | Yes | No | | Maintenance | They handle | You do | The naive break-even: a $5,000 custom build vs. a $50/month app is 100 months — over 8 years. That math kills almost every custom-app conversation. But the math is wrong, because it ignores the four things that matter more than fee: ## The four things that actually matter ### 1. Time you save (or lose) on every order If an app saves you 2 minutes per order and you do 100 orders/day, that's 3.3 hours/day saved. At $25/hr fully loaded employee cost, that's $25,000/year in labor saved. Suddenly a $5k custom build that's 10% better than the app pays for itself in months. Or in reverse: if the app *adds* friction (e.g., requires syncing fields manually), you're paying both the app fee and the labor it costs. ### 2. Compounding data ownership Every customer interaction with a third-party app generates data the app provider keeps. Email opens, cart abandonment patterns, customer support tickets. After 18 months of using an app, the provider has more insight into your store than you do. For most apps this doesn't matter. For three categories it really does: - **Customer data apps** (CRM, segmentation, loyalty) - **Operations apps** (inventory, fulfillment, supplier management) - **Analytics / attribution apps** If you ever want to switch providers, this data is sticky. ### 3. The "fits 80% and pisses you off" problem Every store has at least one app that almost works. It does 80% of what you need but the missing 20% is something you think about every single day. Examples we hear constantly: - "Our loyalty app supports 3 tiers but not 4" - "Our subscription app doesn't let customers swap products mid-cycle" - "Our reviews app shows star ratings but won't filter by variant" The fix is usually feature-requesting the app provider and waiting 14 months. Or it's a custom replacement. ### 4. Multi-app sprawl A single feature that requires four apps to implement (because each app does part of it) becomes expensive fast. Real example: a store wanted "show in-stock store locations on the product page." That required: a store-locator app ($30/mo), an inventory-sync app ($50/mo), a customer location app ($20/mo), and a custom Liquid snippet. Total: $100/mo for something a 1-week custom build replaced. ## Three rules of thumb ### Rule 1: Install if it's a generic problem with a strong category leader There's a strongest-in-category app for almost every common need: - Reviews → Judge.me or Yotpo - Email → Klaviyo (just use Klaviyo) - Loyalty → Smile.io or LoyaltyLion - SEO → Searchanise or Boost - Subscriptions → Recharge or Bold You won't build a better Klaviyo. Don't try. Install and move on. ### Rule 2: Build if you're already paying for 3+ apps to do one workflow Three apps for one workflow is the signal. The labor cost of coordinating them, the data fragmentation, and the combined monthly fees usually justify a custom replacement. This is the most common reason we get custom-app inquiries. The merchant has duct-taped a workflow across multiple apps and finally hit the wall. ### Rule 3: Build if the app encodes your competitive advantage If your store does something that competitors don't, and that something is currently glued together by Zapier + 4 apps + a manual SOP — that's the thing you should own as custom software. The most successful custom Shopify apps we've built fall into this category: configurators for custom products, B2B catalogs with weird pricing rules, vertical-specific compliance tooling. ## The hybrid play The fastest path for most merchants isn't "build everything custom." It's: install most apps, build the 1–2 things that are uniquely yours. A typical $1M/year store might install 20 apps for the standard stuff and have 1 custom app for the workflow that makes them them. That's a ~$5,000–$15,000 investment that compounds for years. Our [Shopable Video app](/apps) is a good example of the inverse — we built it because we kept seeing merchants pay $40/mo for fragmented video tooling. Now it's $9/mo, native, and shopper-tested. ## When to skip the conversation entirely Three signals that you're not ready for a custom app yet: - **Revenue under $200k/year.** The app fees you'd replace probably total under $200/mo. Custom development won't pencil. - **No specific app frustration you can name.** "Apps are slowing me down" is too vague. "I need to do X and no app does it" is specific enough to scope a build. - **You haven't tried the top 3 apps in the category.** The cost of trying apps for a month each is low. Try before you build. If those three things don't apply — and you have a clear capability gap that's costing you time, money, or sales — that's the moment custom pays off. --- We build custom Shopify apps starting at $4,999 (private apps for a single store) and ranging up to $20k+ for public App Store apps with subscription billing. [Tell us about your use case](/contact) and we'll send a fixed quote. --- ## Custom Shopify theme vs. premium theme: which one should you actually buy? Published: 2026-05-06 · By Ashraful · Tags: shopify, themes, custom development URL: https://ezomfy.com/blog/custom-vs-premium-shopify-theme *An honest comparison of building a custom Shopify theme vs. buying a premium one. With real costs, real timelines, and the decision framework we use with clients.* Every Shopify merchant eventually has the same question: *do I just buy a premium theme, or do I get one built custom?* The honest answer depends on three things we'll walk through. Most merchants don't need custom. Some absolutely do. Here's how to tell which one you are. ## The cost reality | | Premium theme | Customized premium | Fully custom | |---|---|---|---| | Up-front cost | $150 – $350 | $500 – $2,500 | $3,000 – $15,000 | | Time to launch | Same day | 1–2 weeks | 3–6 weeks | | Future tweaks | Self-serve or $50/hr | $50–150/hr | $80–200/hr | | Updates | Auto (theme store) | Manual | Manual | | Re-platform friction | Low | Medium | High | Three things people get wrong about these numbers: - **Premium themes aren't free.** Even Dawn (Shopify's free theme) costs you in time-to-launch tweaks. Plan for $300–800 in customization labor on any premium theme. - **Custom themes don't depreciate.** A $5,000 custom theme used for 3 years is $1,667/year. A $300 premium theme replaced every 18 months because it doesn't fit anymore is $200/year — but ongoing customization labor often closes the gap. - **Re-platform friction matters.** If you ever migrate to Shopify Plus, switch to Hydrogen, or change agencies — a custom theme is portable expertise; a premium theme means starting over. ## When premium wins Pick a premium theme if at least three of these are true: - **You're under $50k/year in revenue.** The labor cost of custom doesn't pencil yet. Spend $300 on Dawn or a strong premium theme, get 90% of the way there, save the budget for ads. - **Your category has a strong premium theme.** Fashion has [Verso](/themes/verso). Beauty has a few. Tech accessories have Prestige. If a category-fit theme exists, it's usually cheaper to start there and tune. - **You don't have strong design opinions yet.** If you can't articulate what makes your brand visually different from a competitor, a custom theme will just be a more expensive version of what you'd build with a premium theme anyway. - **Your conversion rate is normal (~2–3% on a non-luxury category).** Premium themes have been A/B tested by their authors against thousands of stores. Custom themes are A/B tested by *you*. That's the wrong stage to be experimenting. ## When custom wins Pick custom if at least two of these apply: - **Your category has no good premium theme.** B2B, wholesale, food & beverage with specific compliance needs (alcohol age-gates, supplements with FDA disclaimers), restricted-substance niches. - **Your storefront is part of your brand differentiation.** Examples: highly art-directed lookbook layouts, complex configurators (build-your-own X), interactive product visualizers, multi-currency layouts with non-Latin scripts. - **You're doing $500k+/year.** The math flips here. A 1% conversion rate lift on a $1M/year store pays for a $10k custom theme in a month. - **You've hit the customization ceiling on a premium theme.** When your dev says "this is fighting the theme," you've already paid for custom twice over in patch labor. ## The middle path almost everyone should consider The third column above — *customized premium theme* — is what we actually recommend for ~60% of merchants. The pattern: 1. Buy a premium theme that's 70% right for your category (~$200) 2. Hire someone for a focused 1–2 week customization sprint ($1,000–$2,000) 3. Live on the customized theme for 18–36 months 4. Re-evaluate when you hit one of the "custom wins" triggers above This is what we offer in our [Pro plan](/pricing). You get a premium theme installed and meaningfully customized — brand colors, fonts, custom hero, product card variant — for $1,499 flat. ## What "custom" actually buys you Three things that *are* worth paying for custom labor on, even on a premium theme: 1. **Lighthouse 90+ from day one.** Most premium themes hit 70–85 out of the box. Getting to 90+ requires custom image handling, JS deferring, and CSS optimization — covered in our [speed-fix post](/blog/shopify-store-slow-4-fixes). 2. **A truly custom product page.** This is where conversion happens. Standard templates work; bespoke templates win. Expect $800–2,000 for a fully custom product page on top of an off-the-shelf theme. 3. **Accessibility (WCAG AA).** Most premium themes fail several WCAG criteria out of the box. If you sell in Europe, sell to government / education buyers, or care about accessibility lawsuits in the US, this isn't optional. ## Decision framework If you remember nothing else: > Under $100k revenue → premium theme + minor tweaks. > > $100k–$1M → customized premium theme (the middle option). > > Over $1M, or differentiated brand → custom theme. Outliers exist — a $50k niche brand with strong design opinions might justify custom; a $5M generic store might be fine with premium. But that's the heuristic for 90% of cases. --- If you want a sanity check on which side of the line you're on, [send us your store](/contact) and we'll tell you honestly which path makes sense. We sell both — premium themes ([Verso](/themes/verso)) and [custom builds](/services/theme-dev) — so we have no incentive to push you toward one or the other. --- ## Migrating from WooCommerce to Shopify in under 10 days: the real playbook Published: 2026-04-29 · By Ashraful · Tags: shopify, woocommerce, migration, seo URL: https://ezomfy.com/blog/woocommerce-to-shopify-migration-playbook *Step-by-step: how to move from WooCommerce to Shopify without losing orders, SEO, or customer trust. The exact playbook we use on every migration.* WooCommerce stores migrate to Shopify for the same reasons every year: hosting headaches, security patches, plugin conflicts, payment-gateway tax, and the slow realization that "self-hosted = cheaper" was never actually true. The migration itself is what scares people. Everyone has a horror story — lost orders, broken redirects, search rankings tanked for 6 months. We've done 30+ of these and the bad outcomes are 100% preventable with a real plan. Here's the actual 10-day playbook. ## The phases | Day | Phase | What ships | |---|---|---| | 1 | Discovery + inventory | Migration spec doc, gap list | | 2–3 | Data extraction + transformation | Clean CSV bundle | | 4–5 | Shopify setup + import | Live data on dev store | | 6–7 | Theme + checkout configuration | Visually complete dev store | | 8 | Redirect map + analytics | Redirect CSV, GA4 reconfigured | | 9 | Soft launch (staging) | Final QA pass | | 10 | Cutover (DNS) | Live on Shopify | ## Day 1: Discovery and inventory The single most expensive mistake is starting the migration without a written inventory of what exists. Make a doc with these sections: - **Products** — how many SKUs, variants, custom fields, downloadable products, subscriptions? - **Customers** — how many, GDPR-compliant export available? - **Orders** — how many to migrate? (Hint: usually only last 12 months matters) - **Pages** — every CMS page, blog post, landing page URL - **Custom code** — checkout customizations, custom plugins, custom checkout fields - **Integrations** — email (Klaviyo / Mailchimp), reviews (Judge.me / Yotpo), shipping, ERP, analytics, ads - **Payment gateway** — Stripe, PayPal, both? - **SEO state** — current rank for top 50 queries, current sitemap Without this doc, you'll discover surprises on day 8 that push launch by 2 weeks. ## Day 2–3: Data extraction + transformation WooCommerce stores data as WordPress post-types with serialized PHP arrays in `postmeta`. It's a mess. Three tools we use: 1. **WP All Export** for products, orders, customers — gives you CSV with the right columns 2. **Direct SQL** for things All Export struggles with (custom fields, ACF data, custom order line items) 3. **Custom Node scripts** to transform Woo's data shape into Shopify's import shape The transformations that always need custom code: - **Variants** — Woo stores them as child products; Shopify wants them on the parent - **Categories → Collections** — Woo's nested hierarchies don't map 1:1 to Shopify - **Custom fields** — ACF data goes into Shopify metafields; you have to define the metafield definitions first - **Tax classes** — Woo lets you mix shipping classes per item in ways Shopify can't replicate exactly Pro tip: dedupe customer emails before import. Woo allows multiple customer rows with the same email; Shopify doesn't. ## Day 4–5: Setup + import In Shopify: 1. Create the store, choose plan (Plus only if Plus features are required) 2. Define metafield definitions (these are easier to create *before* the import) 3. Import customers first, then products, then orders 4. Use Matrixify or a custom CSV importer — Shopify's built-in CSV import truncates long descriptions Validate after each import: ```bash # Quick sanity checks: # 1. Product count matches export # 2. Random sample of 10 products — open and verify all images, variants, descriptions # 3. Customer count matches; one random customer's order history is intact # 4. Total order count matches; one paid order's line items match Woo's ``` Catch problems here, not on day 10. ## Day 6–7: Theme + checkout If the merchant wants a same-look-as-before migration, we build a custom theme that mirrors the old WooCommerce design. This is usually a mistake — the migration is the cheapest moment to redesign. If you want a redesign instead, this is where you'd plug in [Verso](/themes/verso) or commission a [custom theme build](/services/theme-dev). Shopify checkout-specific config to verify: - Express payments (Apple Pay, Shop Pay, Google Pay) — enable all - Address autocomplete — on - Email vs. phone at checkout — match what customers expect - Custom checkout fields → Functions (Shopify Plus) or scripts - Discount stacking rules ## Day 8: Redirects and analytics **This is the single most important phase for SEO.** Mess this up and you'll lose 6 months of rankings. Build a redirect CSV from your old WooCommerce URL structure to Shopify's structure. The patterns differ: | WooCommerce | Shopify | |---|---| | `/product/awesome-tee/` | `/products/awesome-tee` | | `/product-category/men/` | `/collections/men` | | `/blog/post-slug/` | `/blogs/news/post-slug` | | `/page-name/` | `/pages/page-name` | Import into Shopify → Navigation → URL Redirects. Test 20 URLs manually before launch. Analytics: - Set up GA4 with new view; tag every Shopify event (purchase, add_to_cart, etc.) - Reconnect Facebook Pixel / Meta CAPI - Reconnect Google Ads conversion tracking — this *will* break for a week if you don't reconfigure conversion IDs ## Day 9: Soft launch (staging) Run the dev store under a password. Have your team place real test orders end-to-end: - One paid order (use Bogus Gateway or live with $0.50 product) - One refund - One discount-code order - One shipping-method test order - One email signup - One abandoned-cart recovery email check If all six work clean, you're ready for cutover. ## Day 10: Cutover The actual DNS swap takes 5 minutes; the work is everything around it. 1. Final inventory sync (orders placed on Woo in the last 24h) 2. Update DNS apex A record → Shopify's IP 3. Update `www` CNAME → `shops.myshopify.com` 4. Wait ~10 min for propagation 5. Validate: store loads on the canonical domain, checkout completes a real order 6. Switch off WooCommerce orders (set it to maintenance mode for 48h, then archive) We do every cutover during the merchant's slowest 2-hour window. For most stores that's Tuesday 3am UTC. Sales tank during that window anyway; if anything breaks, we have time to fix before the morning rush. ## What we don't migrate (on purpose) Three things we deliberately leave behind: - **Old reviews** if the original review widget data is malformed. Cleaner to start fresh with a real review-ask flow. - **Stale customers** — anyone who hasn't bought in 24 months. Importing them just bloats your Shopify customer count (and Plus bill). - **Cancelled / refunded historical orders** — only migrate orders that actually represent revenue. ## When 10 days isn't enough Three scenarios where the timeline stretches: - **Subscriptions** — Woo Subscriptions → Shopify Subscriptions API takes another 3–5 days - **B2B / wholesale** — custom catalogs, customer-specific pricing tiers - **Multi-currency** — if you're going Shopify Markets and need country-specific stores If any of these apply, we'd quote 14–20 days instead. --- We do this end-to-end as a fixed-quote service starting at $1,999. [Send us your store and we'll quote yours](/contact). --- ## Why your Shopify store is slow (and the 4 fixes that actually work) Published: 2026-04-22 · By Ashraful · Tags: shopify, performance, core web vitals, optimization URL: https://ezomfy.com/blog/shopify-store-slow-4-fixes *Most Shopify speed advice is fluff. Here are the 4 fixes that actually move Lighthouse scores — and the popular ones that don't.* Every Shopify merchant we talk to is convinced their store is slow. They're usually right — the average Shopify store on the bare Dawn theme scores around 60 on mobile Lighthouse, and that's *before* the bloat that arrives after a couple of years of installing apps. The frustrating part: most of the speed advice on the internet is wrong. "Install a speed-booster app" is the most common one, and ironically that's one of the things that *causes* slow stores. After auditing 100+ stores, the same four problems account for ~90% of slow Shopify performance. The good news: all four are fixable in a week, by a single developer, without a "speed optimization app." ## 1. Audit your apps — most are dragging you down Open any Shopify store and view source. Count the unique third-party domains in the ``. A typical merchant's store has 15–25. Every third-party domain is a DNS lookup, a TLS handshake, and a render-blocking request before your hero image starts to load. Most "speed-booster" apps add another 1–3 of these. The audit: 1. List every installed app 2. For each one, ask: *what would actually break if I uninstalled it?* 3. The answer is "nothing visible" more often than you'd expect We routinely uninstall 6–8 apps from a store and the merchant's reaction is, "I forgot I had that." A typical app cleanup recovers 15–30 Lighthouse points and saves $60–$200/mo in app fees. Apps to look at first: pop-up apps, review apps with widgets you've removed, "trust badge" apps, currency converters that just rewrite text, and anything claiming to "optimize" your site. ## 2. Use modern image formats — and stop using image-optimizer apps Shopify's CDN serves WebP automatically if you reference your images correctly. You don't need an app. The wrong way (still on most stores): ```liquid ``` The right way: ```liquid {{ product.featured_image.alt | escape }} ``` That snippet does five things image apps charge $9/mo for: WebP via the CDN, responsive sizes, lazy loading, async decoding, and CLS prevention via explicit dimensions. Drop it into your theme's product card / collection card / hero sections. Lighthouse will reward you immediately. ## 3. Audit your theme's JavaScript bundle Most premium themes ship 200–400KB of JavaScript that 80% of your customers never trigger. Sliders, lookbook modals, animated counters, cart drawers, predictive search. Open Chrome DevTools → Coverage tab. Hit "Start recording" and click around your store. The bytes in red are JavaScript that loaded but never executed. The fix isn't to remove features — it's to defer them. Move non-critical scripts to `defer` or load on interaction: ```html ``` The mobile customer who never clicks Quick View saves 40KB of JS download and 60ms of parse time. ## 4. Cache your CDN properly — and set width/height on every image The single biggest CLS (Cumulative Layout Shift) bug on Shopify themes is missing image dimensions. Lighthouse penalizes CLS more than almost any other Web Vital. Two rules: - **Every `` needs explicit `width` and `height` attributes.** Yes, even with responsive sizes. The aspect ratio prevents jumpy layouts as images load. - **Use `loading="lazy"` everywhere below the fold.** Hero images stay `loading="eager"`. Everything else is `lazy`. For CDN: Shopify uses Fastly under the hood and you don't need to configure anything — but if you're running an external image CDN (Cloudinary, Imgix, etc.) for some assets, double-check you're not double-caching. ## What we don't recommend Three things that get sold as "speed boosters" that don't move Lighthouse meaningfully: - **"Critical CSS" apps** — they inline a minified copy of your CSS, which helps in synthetic tests but real users were already getting that CSS cached on the second pageview. Net effect: ~2–3 points. - **AMP for product pages** — Google deprecated AMP's preferential ranking treatment in 2021. The "speed gain" is real but the maintenance burden isn't worth it. - **Custom infrastructure / "headless" Shopify** — moving to Hydrogen or Next.js doesn't make your store faster by default. It just shifts the bottleneck from Liquid to your React bundle. We've seen headless stores score worse than the original Liquid theme they replaced. ## When to call someone If you've done the four fixes above and you're still under 70 on mobile Lighthouse, the slowness is likely structural — a theme that was never built with performance in mind, or a stack of integrations that need re-architecting. That's the kind of work we do: one-week speed audits that ship as a PR you (or your team) can review and merge. Usually a 15–30 point Lighthouse gain. If you want a quote, [tell us about your store](/contact). Or, if you're rebuilding from scratch, [Verso](/themes/verso) ships with all four of these patterns built in by default. --- # Contact - Email: info@ezomfy.com - Shopify Partner: https://www.shopify.com/partners/directory/partner/ezomfy - Upwork: https://www.upwork.com/freelancers/ashraful41 - Quote form: https://ezomfy.com/contact