CINXE.COM

Building a ShopifyQL Code Editor (2023) - Shopify

<!DOCTYPE html><html lang="en"><head><title>Building a ShopifyQL Code Editor (2023) - Shopify</title><meta charSet="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="description" content="In October 2022, Shopify released ShopifyQL Notebooks, a first-party app that lets merchants analyze their shop data to make better decisions. It puts the power of ShopifyQL into merchants’ hands with a guided code editing experience. In order to provide a first-class editing experience, we turned to CodeMirror, a code editor framework built for the web. Out of the box, CodeMirror didn’t have support for ShopifyQL–here’s how we built it. ShopifyQL Everywhere ShopifyQL is an accessible, commerce-focused querying language used on both the client and server. The language is defined by an ANTLR grammar and is used to generate code for multiple targets (currently, Go and Typescript). This lets us share the same grammar definition between both the client and server despite differences in runtime language. As an added benefit, we have types written in Protobuf so that types can be shared between targets as well. All the ShopifyQL language features on the front end are encapsulated into a typescript language server, which is built on top of the ANTLR typescript target. It conforms to Microsoft&#x27;s language server protocol (LSP) in order to keep a clear separation of concerns between the language server and a code editor. LSP defines the shape of common language features like tokenization, parsing, completion, hover tooltips, and linting. When code editors and language servers both conform to LSP, they become interoperable because they speak a common language. For more information about LSP, read the VSCode Language Server Extension Guide. Connecting The ShopifyQL Language Server To CodeMirror CodeMirror has its own grammar &amp; parser engine called Lezer. Lezer is used within CodeMirror to generate parse trees, and those trees power many of the editor features. Lezer has support for common languages, but no Lezer grammar exists for ShopifyQL. Lezer also doesn’t conform to LSP. Because ShopifyQL’s grammar and language server had already been written in ANTLR, it didn’t make sense to rewrite what we had as a Lezer grammar. Instead, we"/><meta property="fb:pages" content="20409006880"/><meta property="fb:app_id" content="847460188612391"/><meta property="og:type" content="website"/><meta property="og:site_name" content="Shopify"/><meta property="og:title" content="Building a ShopifyQL Code Editor (2023) - Shopify"/><meta property="og:description" content="In October 2022, Shopify released ShopifyQL Notebooks, a first-party app that lets merchants analyze their shop data to make better decisions. It puts the power of ShopifyQL into merchants’ hands with a guided code editing experience. In order to provide a first-class editing experience, we turned to CodeMirror, a code editor framework built for the web. Out of the box, CodeMirror didn’t have support for ShopifyQL–here’s how we built it. ShopifyQL Everywhere ShopifyQL is an accessible, commerce-focused querying language used on both the client and server. The language is defined by an ANTLR grammar and is used to generate code for multiple targets (currently, Go and Typescript). This lets us share the same grammar definition between both the client and server despite differences in runtime language. As an added benefit, we have types written in Protobuf so that types can be shared between targets as well. All the ShopifyQL language features on the front end are encapsulated into a typescript language server, which is built on top of the ANTLR typescript target. It conforms to Microsoft&#x27;s language server protocol (LSP) in order to keep a clear separation of concerns between the language server and a code editor. LSP defines the shape of common language features like tokenization, parsing, completion, hover tooltips, and linting. When code editors and language servers both conform to LSP, they become interoperable because they speak a common language. For more information about LSP, read the VSCode Language Server Extension Guide. Connecting The ShopifyQL Language Server To CodeMirror CodeMirror has its own grammar &amp; parser engine called Lezer. Lezer is used within CodeMirror to generate parse trees, and those trees power many of the editor features. Lezer has support for common languages, but no Lezer grammar exists for ShopifyQL. Lezer also doesn’t conform to LSP. Because ShopifyQL’s grammar and language server had already been written in ANTLR, it didn’t make sense to rewrite what we had as a Lezer grammar. Instead, we"/><meta property="og:image" content="https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014"/><meta property="twitter:image" content="https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014"/><meta property="og:url" content="https://shopify.engineering/building-a-shopifyql-code-editor"/><meta property="twitter:card" content="summary_large_image"/><meta property="twitter:site" content="Shopify"/><meta property="twitter:account_id" content="17136315"/><meta property="twitter:title" content="Building a ShopifyQL Code Editor (2023) - Shopify"/><meta property="twitter:description" content="In October 2022, Shopify released ShopifyQL Notebooks, a first-party app that lets merchants analyze their shop data to make better decisions. It puts the power of ShopifyQL into merchants’ hands with a guided code editing experience. In order to provide a first-class editing experience, we turned to CodeMirror, a code editor framework built for the web. Out of the box, CodeMirror didn’t have support for ShopifyQL–here’s how we built it. ShopifyQL Everywhere ShopifyQL is an accessible, commerce-focused querying language used on both the client and server. The language is defined by an ANTLR grammar and is used to generate code for multiple targets (currently, Go and Typescript). This lets us share the same grammar definition between both the client and server despite differences in runtime language. As an added benefit, we have types written in Protobuf so that types can be shared between targets as well. All the ShopifyQL language features on the front end are encapsulated into a typescript language server, which is built on top of the ANTLR typescript target. It conforms to Microsoft&#x27;s language server protocol (LSP) in order to keep a clear separation of concerns between the language server and a code editor. LSP defines the shape of common language features like tokenization, parsing, completion, hover tooltips, and linting. When code editors and language servers both conform to LSP, they become interoperable because they speak a common language. For more information about LSP, read the VSCode Language Server Extension Guide. Connecting The ShopifyQL Language Server To CodeMirror CodeMirror has its own grammar &amp; parser engine called Lezer. Lezer is used within CodeMirror to generate parse trees, and those trees power many of the editor features. Lezer has support for common languages, but no Lezer grammar exists for ShopifyQL. Lezer also doesn’t conform to LSP. Because ShopifyQL’s grammar and language server had already been written in ANTLR, it didn’t make sense to rewrite what we had as a Lezer grammar. Instead, we"/><link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://cdn.shopify.com/static/fonts/ShopifySans--medium.woff2"/><link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://cdn.shopify.com/static/fonts/ShopifySans--bold.woff2"/><link rel="preload" as="font" crossorigin="anonymous" type="font/woff2" href="https://cdn.shopify.com/static/fonts/ShopifySans--extrabold.woff2"/><link rel="stylesheet" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/blog-TmMMQoVD.css"/><link rel="icon" href="https://cdn.shopify.com/shopifycloud/web/assets/v1/favicon-default-6cbad9de243dbae3.ico" type="image/x-icon"/><link rel="apple-touch-icon" href="https://cdn.shopify.com/b/shopify-brochure2-assets/c97c60ca19c64a8b5378d9f9e971f7bd.png"/><link rel="apple-touch-icon" sizes="120x120" href="https://cdn.shopify.com/b/shopify-brochure2-assets/c97c60ca19c64a8b5378d9f9e971f7bd.png"/><link rel="apple-touch-icon" sizes="114x114" href="https://cdn.shopify.com/b/shopify-brochure2-assets/b13486e5693b246af63c66ab047a6b6b.png"/><link rel="apple-touch-icon" sizes="72x72" href="https://cdn.shopify.com/b/shopify-brochure2-assets/8734d76c98437c8ae8a628bbeed3750a.png"/><link rel="apple-touch-icon" sizes="57x57" href="https://cdn.shopify.com/b/shopify-brochure2-assets/193f18e4855704ef1716dc0cd750c1ee.png"/><link rel="canonical" href="https://shopify.engineering/building-a-shopifyql-code-editor"/><link href="https://cdn.shopify.com" rel="preconnect"/><link href="https://gtm.shopify.com" rel="preconnect"/><link href="https://www.googletagmanager.com" rel="preconnect"/><link href="https://shopify.engineering/building-a-shopifyql-code-editor" hrefLang="en" rel="alternate"/></head><body class="overscroll-y-none"><div class="absolute top-2 left-2 z-[100] pointer-events-none"><a href="#main" class="inline-block self-center overflow-hidden max-w-full px-button-px py-button-py ring-inset rounded-button text-button-size font-button-font font-button-weight tracking-button-tracking focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-state-focus focus-visible:outline border-2 text-button-dark-primary-text bg-button-dark-primary-bg border-button-dark-primary-border ring-button-dark-primary-border hover:text-button-dark-primary-text-hover hover:bg-button-dark-primary-bg-hover hover:border-button-dark-primary-border-hover hover:ring-button-dark-primary-border-hover focus:text-button-dark-primary-text-focus focus:bg-button-dark-primary-bg-focus focus:border-button-dark-primary-border-focus focus:ring-button-dark-primary-border-focus active:text-button-dark-primary-text-active active:bg-button-dark-primary-bg-active active:border-button-dark-primary-border-active active:ring-button-dark-primary-border-active disabled:text-button-dark-primary-text-disabled disabled:bg-button-dark-primary-bg-disabled disabled:border-button-dark-primary-border-disabled disabled:ring-button-dark-primary-border-disabled transition-transform duration-300 translate-y-[-200%] motion-reduce:transition-none focus:translate-y-0" data-component-name="button" data-mode="dark" target="">Skip to Content</a></div><div class="relative bg-engineering-dark-bg text-engineering-dark-text [&amp;_.app-signup-links]:hidden"><div class="bg-enginneering-dark bg-top bg-auto bg-no-repeat"><header class="font-shopifysans absolute h-full w-full pointer-events-none z-50 overflow-x-clip" data-component-name="global-nav" data-viewable-component="true" data-has-secondary-navigation="true"><div class="after:absolute after:top-0 after:left-0 after:will-change-[opacity] after:pointer-events-none after:h-full after:w-full after:z-10 after:transition-opacity after:duration-200 w-full z-50 after:bg-black text-white after:opacity-0 -top-global-header pointer-events-auto sticky"><div class="h-global-header px-margin xl:px-auto-xl flex items-center z-20 relative"><div class="lg:w-[125px] mr-12 w-[98px] shrink-0"><a href="https://www.shopify.com" data-component-name="logo-home"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 608 173.7" role="img"><title>Shopify</title><path fill="#95BF47" d="M130.7 32.9c-.1-.9-.9-1.3-1.5-1.4-.6-.1-12.6-.2-12.6-.2s-10.1-9.8-11.1-10.8-2.9-.7-3.7-.5c0 0-1.9.6-5.1 1.6-.5-1.7-1.3-3.8-2.4-5.9-3.6-6.9-8.8-10.5-15.2-10.5-.4 0-.9 0-1.3.1-.2-.2-.4-.4-.6-.7-2.8-3-6.3-4.4-10.5-4.3-8.2.2-16.3 6.1-23 16.7-4.7 7.4-8.2 16.7-9.2 23.9-9.4 2.9-16 4.9-16.1 5-4.7 1.5-4.9 1.6-5.5 6.1C12.4 55.3 0 151.4 0 151.4l104.1 18 45.1-11.2S130.8 33.7 130.7 32.9zm-39.2-9.7c-2.4.7-5.1 1.6-8.1 2.5-.1-4.1-.6-9.9-2.5-14.9 6.3 1.2 9.3 8.2 10.6 12.4zM78 27.4c-5.5 1.7-11.4 3.5-17.4 5.4 1.7-6.4 4.9-12.8 8.8-17 1.5-1.6 3.5-3.3 5.9-4.3 2.3 4.7 2.7 11.4 2.7 15.9zM66.8 5.8c1.9 0 3.5.4 4.9 1.3-2.2 1.1-4.4 2.8-6.4 5-5.2 5.6-9.2 14.2-10.8 22.6-5 1.5-9.8 3-14.3 4.4 3-13.2 14-32.9 26.6-33.3z"></path><path fill="#5E8E3E" d="M129.2 31.5c-.6-.1-12.6-.2-12.6-.2s-10.1-9.8-11.1-10.8c-.4-.4-.9-.6-1.4-.6v149.5l45.1-11.2S130.8 33.8 130.7 32.9c-.2-.9-.9-1.3-1.5-1.4z"></path><path fill="#FFF" d="M79.1 54.7l-5.2 19.6s-5.8-2.7-12.8-2.2c-10.2.6-10.3 7-10.2 8.7.6 8.8 23.6 10.7 24.9 31.2 1 16.2-8.6 27.2-22.4 28.1-16.6 1-25.7-8.7-25.7-8.7l3.5-14.9s9.2 6.9 16.5 6.5c4.8-.3 6.5-4.2 6.3-7-.7-11.4-19.5-10.8-20.7-29.5-1-15.8 9.4-31.8 32.3-33.3 9-.8 13.5 1.5 13.5 1.5z"></path><path fill="#FFF" d="M210.3 96.5c-5.2-2.8-7.9-5.2-7.9-8.5 0-4.2 3.7-6.9 9.6-6.9 6.8 0 12.8 2.8 12.8 2.8l4.8-14.6s-4.4-3.4-17.3-3.4c-18 0-30.5 10.3-30.5 24.8 0 8.2 5.8 14.5 13.6 19 6.3 3.6 8.5 6.1 8.5 9.9 0 3.9-3.1 7-9 7-8.7 0-16.9-4.5-16.9-4.5l-5.1 14.6s7.6 5.1 20.3 5.1c18.5 0 31.8-9.1 31.8-25.5.1-8.9-6.6-15.2-14.7-19.8zm73.8-30.8c-9.1 0-16.3 4.3-21.8 10.9l-.3-.1 7.9-41.4h-20.6l-20 105.3h20.6l6.9-36c2.7-13.6 9.7-22 16.3-22 4.6 0 6.4 3.1 6.4 7.6 0 2.8-.3 6.3-.9 9.1l-7.8 41.2h20.6l8.1-42.6c.9-4.5 1.5-9.9 1.5-13.4 0-11.5-6.2-18.6-16.9-18.6zm63.5 0c-24.8 0-41.2 22.4-41.2 47.4 0 16 9.9 28.8 28.4 28.8 24.3 0 40.8-21.8 40.8-47.4-.1-14.7-8.8-28.8-28-28.8zm-10.2 60.4c-7 0-10-6-10-13.4 0-11.8 6.1-31.1 17.3-31.1 7.3 0 9.7 6.3 9.7 12.4 0 12.7-6.1 32.1-17 32.1zm90.8-60.4c-13.9 0-21.8 12.2-21.8 12.2h-.3l1.2-11.1h-18.2c-.9 7.5-2.5 18.8-4.2 27.3l-14.3 75.4h20.6l5.7-30.5h.4s4.2 2.7 12.1 2.7c24.2 0 40-24.8 40-49.9.1-13.7-6.1-26.1-21.2-26.1zm-19.7 60.7c-5.4 0-8.5-3-8.5-3l3.4-19.3c2.4-12.8 9.1-21.4 16.3-21.4 6.3 0 8.2 5.8 8.2 11.4 0 13.3-7.9 32.3-19.4 32.3zm70.4-90.2c-6.6 0-11.8 5.2-11.8 12 0 6.1 3.9 10.3 9.7 10.3h.3c6.4 0 12-4.3 12.1-12 0-6-4-10.3-10.3-10.3zm-28.8 104.2h20.6l14-73h-20.8zm87-73.2h-14.3l.7-3.4c1.2-7 5.4-13.3 12.2-13.3 3.7 0 6.6 1 6.6 1l4-16.1s-3.6-1.8-11.2-1.8c-7.3 0-14.6 2.1-20.2 6.9-7 6-10.3 14.6-12 23.3l-.6 3.4h-9.6l-3 15.5h9.6l-10.9 57.7H509l10.9-57.7h14.2l3-15.5zm49.6.2s-12.9 32.5-18.7 50.2h-.3c-.4-5.7-5.1-50.2-5.1-50.2H541l12.4 67.1c.3 1.5.1 2.4-.4 3.4-2.4 4.6-6.4 9.1-11.2 12.4-3.9 2.8-8.2 4.6-11.7 5.8l5.7 17.5c4.2-.9 12.8-4.3 20.2-11.2 9.4-8.8 18.1-22.4 27-40.9l25.2-54.1h-21.5z"></path></svg></a></div><nav class="lg:flex hidden h-full" aria-label="Main" itemscope="" itemType="https://schema.org/SiteNavigationElement" data-click-outside="dismiss" data-component-name="desktop-main-navigation" data-viewable-component="true"><ul class="flex h-full"><li class="mr-8 text-base"><div class="relative flex h-full after:content[&quot;&quot;] after:block after:h-[3px] after:w-full after:absolute after:bottom-0 after:scale-0 after:origin-left after:transition-transform after:duration-[350ms] after:ease-[bezier(0.66, 0.66, 0.34, 1.00)] after:motion-reduce:transition-none hover:underline after:bg-white text-white"><button type="button" class="bg-transparent whitespace-nowrap" data-component-name="Solutions-toggle-open" aria-controls="SolutionsDesktopMenu" aria-expanded="false" aria-haspopup="true"><span class="mr-3">Solutions</span><div aria-hidden="true" class="relative inline-block h-[7px] w-3 cursor-pointer"><div class="inline-block rounded border-b-8 h-2 w-[2px] origin-center transition-all ease-[bezier(0.66, 0.66, 0.34, 1.00)] duration-[350ms] motion-reduce:transition-none border-white bg-white -translate-x-[3px] rotate-[135deg]"></div><div class="inline-block rounded border-b-8 h-2 w-[2px] origin-center transition-all ease-[bezier(0.66, 0.66, 0.34, 1.00)] duration-[350ms] motion-reduce:transition-none border-white bg-white -rotate-[135deg]"></div></div></button></div><div id="SolutionsDesktopMenu" class="absolute inset-x-0 top-global-header z-[21] transition-transform duration-[452ms] ease-[cubic-bezier(0.26,1.00,0.48,1.00)] motion-reduce:transition-none before:absolute before:inset-x-0 before:top-0 before:w-full before:h-full before:left-0 before:shadow-xl before:pointer-events-none before:z-10 bg-black text-white opacity-0 pointer-events-none -translate-y-[10px]"><div class="relative no-scrollbar overflow-y-auto overflow-x-clip max-h-[calc(100vh-72px)] z-20 pb-28"><div class="container grid grid-cols-4 sm:grid-cols-8 md:grid-cols-12 gap-x-gutter gap-y-0"><div class="col-span-3 col-start-1 pt-8 text-white -translate-y-[30px]" data-background="transparent_dark"><div class="flex items-center border-shade-30 mb-6 border-b pb-4 text-lg font-bold border-shade-70"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="40" height="40" fill="none"><g clip-path="url(#white_circle_svg__a)"><path d="M19 21.5v-3h-2.5v1a1 1 0 0 1-1 1H14a1 1 0 0 1-1-1V14a1 1 0 0 1 1-1h1.5a1 1 0 0 1 1 1v1h6.75s4.25 0 4.25 4.5c0 0-2.25-1-5-1v3m-3.5 0h3.5m-3.5 0v6m3.5-6v6M39.25 20c0 10.631-8.619 19.25-19.25 19.25S.75 30.631.75 20 9.369.75 20 .75 39.25 9.369 39.25 20Z" stroke="url(#start_svg__b)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></g></svg></span><span id="subNavItem-Start" class="font-bold text-t7 inline-block text-white">Start</span></div><ul class="pb-2" aria-labelledby="subNavItem-Start"><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/start" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Start your business<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Build your brand</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/website/builder" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Create your website<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Online store editor</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://themes.shopify.com/" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Customize your store<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Store themes</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://apps.shopify.com/" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Find business apps<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Shopify app store</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/domains" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Own your site domain<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Domains &amp; hosting</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/tools" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Explore free business tools<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Tools to run your business</div></a></li></ul></div><div class="col-span-3 col-start-4 pt-8 text-white -translate-y-[30px]" data-background="transparent_dark"><div class="flex items-center border-shade-30 mb-6 border-b pb-4 text-lg font-bold border-shade-70"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="40" height="40" fill="none"><g clip-path="url(#white_circle_svg__a)"><path d="M12.75 21.5v4c0 .966 2.35 1.75 5.25 1.75s5.25-.784 5.25-1.75v-4m4-7c0 .966-2.35 1.75-5.25 1.75s-5.25-.784-5.25-1.75m10.5 0c0-.966-2.35-1.75-5.25-1.75s-5.25.784-5.25 1.75m10.5 0v4.25c0 .46-.533.88-1.405 1.192M16.75 14.5V17m6.5 4.25c0 .966-2.35 1.75-5.25 1.75s-5.25-.784-5.25-1.75S15.1 19.5 18 19.5s5.25.784 5.25 1.75Zm16-1.25c0 10.631-8.619 19.25-19.25 19.25S.75 30.631.75 20 9.369.75 20 .75 39.25 9.369 39.25 20Z" stroke="url(#sell_svg__a)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></g></svg></span><span id="subNavItem-Sell" class="font-bold text-t7 inline-block text-white">Sell</span></div><ul class="pb-2" aria-labelledby="subNavItem-Sell"><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/sell" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Sell your products<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Sell online or in person</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/checkout" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Check out customers<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">World-class checkout</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/online" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Sell online<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Grow your business online</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/channels" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Sell across channels<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Reach millions of shoppers and boost sales</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/international" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Sell globally<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">International sales</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/plus/solutions/b2b-ecommerce" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Sell wholesale &amp; direct<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Business-to-business (B2B)</div></a></li></ul></div><div class="col-span-3 col-start-7 pt-8 text-white -translate-y-[30px]" data-background="transparent_dark"><div class="flex items-center border-shade-30 mb-6 border-b pb-4 text-lg font-bold border-shade-70"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="40" height="40" fill="none"><g clip-path="url(#white_circle_svg__a)"><path d="m12.75 19.25 5.5-5.5m1 2.5v-3.5h-3.5m-2 14.5h.5a1 1 0 0 0 1-1v-2.5a1 1 0 0 0-1-1h-.5a1 1 0 0 0-1 1v2.5a1 1 0 0 0 1 1Zm6 0h.5a1 1 0 0 0 1-1v-5.5a1 1 0 0 0-1-1h-.5a1 1 0 0 0-1 1v5.5a1 1 0 0 0 1 1Zm6 0h.5a1 1 0 0 0 1-1v-12.5a1 1 0 0 0-1-1h-.5a1 1 0 0 0-1 1v12.5a1 1 0 0 0 1 1ZM39.25 20c0 10.631-8.619 19.25-19.25 19.25S.75 30.631.75 20 9.369.75 20 .75 39.25 9.369 39.25 20Z" stroke="url(#market_svg__a)" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></g></svg></span><span id="subNavItem-Market" class="font-bold text-t7 inline-block text-white">Market</span></div><ul class="pb-2" aria-labelledby="subNavItem-Market"><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/market" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Market your business<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Reach &amp; retain customers</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/facebook-instagram" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Market across social<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Social media integrations</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/inbox" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Chat with customers<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Shopify Inbox</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/email-marketing" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Nurture customers<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Shopify Email</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/segmentation" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Know your audience<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Gain customer insights</div></a></li></ul></div><div class="col-span-3 col-start-10 pt-8 text-white -translate-y-[30px]" data-background="transparent_dark"><div class="flex items-center border-shade-30 mb-6 border-b pb-4 text-lg font-bold border-shade-70"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="40" height="40" fill="none"><g clip-path="url(#white_circle_svg__a)"><path d="M12.75 15.75c0 1.243.75 2.5 2.25 2.5s2.5-1.257 2.5-2.5c0 1.243 1 2.5 2.5 2.5s2.5-1.257 2.5-2.5c0 1.243 1 2.5 2.5 2.5s2.25-1.257 2.25-2.5m-12.5 11.5h10.5a2 2 0 0 0 2-2v-9.067a2 2 0 0 0-.179-.827l-.538-1.184A2 2 0 0 0 24.713 13h-9.425a2 2 0 0 0-1.82 1.172l-.538 1.184a2 2 0 0 0-.18.827v9.067a2 2 0 0 0 2 2Zm3-3.5a2 2 0 0 1 2-2h.5a2 2 0 0 1 2 2v3.5h-4.5v-3.5ZM39.25 20c0 10.631-8.619 19.25-19.25 19.25S.75 30.631.75 20 9.369.75 20 .75 39.25 9.369 39.25 20Z" stroke="url(#manage_svg__a)" stroke-width="1.5" stroke-linecap="round"></path></g></svg></span><span id="subNavItem-Manage" class="font-bold text-t7 inline-block text-white">Manage</span></div><ul class="pb-2" aria-labelledby="subNavItem-Manage"><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/manage" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Manage your business<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Track sales, orders &amp; analytics</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/analytics" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Measure your performance<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Analytics and Reporting</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/orders" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Manage your stock &amp; orders<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Inventory &amp; order management</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/flow" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Automate your business<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Shopify Flow</div></a></li></ul></div></div></div><div class="absolute bottom-0 inset-x-0 overflow-hidden z-20"><div class="container grid grid-cols-4 sm:grid-cols-8 md:grid-cols-12 gap-x-gutter gap-y-0"><div class="col-span-12 col-start-1 scheme-white:bg-shade-10 scheme-transparent-light:bg-shade-10 scheme-black:bg-shade-90 scheme-transparent-dark:bg-shade-90 relative after:absolute after:left-full after:top-[-1px] after:w-[100vw] after:h-[calc(100%+1px)] scheme-white:after:bg-shade-10 scheme-transparent-light:after:bg-shade-10 scheme-black:after:bg-shade-90 scheme-transparent-dark:after:bg-shade-90 scheme-black:text-white scheme-transparent-dark:text-white -ml-[var(--margin)] -mt-8 md:mt-0 pl-[var(--margin)] pt-6 before:content-[&#x27;&#x27;] before:absolute before:right-full before:top-[-1px] before:w-[100vw] before:h-[calc(100%+1px)] scheme-white:before:bg-shade-10 scheme-transparent-light:before:bg-shade-10 scheme-black:before:bg-shade-90 scheme-transparent-dark:before:bg-shade-90 border-t scheme-white:border-shade-20 scheme-transparent-light:border-shade-20 scheme-black:border-shade-70 scheme-transparent-dark:border-shade-70 before:border-t scheme-white:before:border-shade-20 scheme-transparent-light:before:border-shade-20 scheme-black:before:border-shade-70 scheme-transparent-dark:before:border-shade-70 after:border-t scheme-white:after:border-shade-20 scheme-transparent-light:after:border-shade-20 scheme-black:after:border-shade-70 scheme-transparent-dark:after:border-shade-70 text-black -translate-y-[30px]" data-background="transparent_dark"><ul class="grid grid-cols-12 gap-x-gutter gap-y-0 pb-0"><li class="flex mb-6 pr-4 border-r border-shade-30 last:border-0 col-span-3"><a class="group inline-block w-full text-white hover:text-white" href="https://shopify.dev" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Shopify Developers<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Build with Shopify&#x27;s powerful APIs</div></a></li><li class="flex mb-6 pr-4 border-r border-shade-30 last:border-0 col-span-3"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/plus" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Plus<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">A commerce solution for growing digital brands</div></a></li><li class="flex mb-6 pr-4 border-r border-shade-30 last:border-0 col-span-3"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/products" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">All Products<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Explore all Shopify products &amp; features</div></a></li></ul></div></div></div></div></li><li class="mr-8 text-base"><div class="flex h-full"><a class="flex items-center hover:underline text-white hover:text-white" href="https://www.shopify.com/pricing" aria-label="">Pricing</a></div></li><li class="mr-8 text-base"><div class="relative flex h-full after:content[&quot;&quot;] after:block after:h-[3px] after:w-full after:absolute after:bottom-0 after:scale-0 after:origin-left after:transition-transform after:duration-[350ms] after:ease-[bezier(0.66, 0.66, 0.34, 1.00)] after:motion-reduce:transition-none hover:underline after:bg-white text-white"><button type="button" class="bg-transparent whitespace-nowrap" data-component-name="Resources-toggle-open" aria-controls="ResourcesDesktopMenu" aria-expanded="false" aria-haspopup="true"><span class="mr-3">Resources</span><div aria-hidden="true" class="relative inline-block h-[7px] w-3 cursor-pointer"><div class="inline-block rounded border-b-8 h-2 w-[2px] origin-center transition-all ease-[bezier(0.66, 0.66, 0.34, 1.00)] duration-[350ms] motion-reduce:transition-none border-white bg-white -translate-x-[3px] rotate-[135deg]"></div><div class="inline-block rounded border-b-8 h-2 w-[2px] origin-center transition-all ease-[bezier(0.66, 0.66, 0.34, 1.00)] duration-[350ms] motion-reduce:transition-none border-white bg-white -rotate-[135deg]"></div></div></button></div><div id="ResourcesDesktopMenu" class="absolute inset-x-0 top-global-header z-[21] transition-transform duration-[452ms] ease-[cubic-bezier(0.26,1.00,0.48,1.00)] motion-reduce:transition-none before:absolute before:inset-x-0 before:top-0 before:w-full before:h-full before:left-0 before:shadow-xl before:pointer-events-none before:z-10 bg-black text-white opacity-0 pointer-events-none -translate-y-[10px]"><div class="relative no-scrollbar overflow-y-auto overflow-x-clip max-h-[calc(100vh-72px)] z-20"><div class="container grid grid-cols-4 sm:grid-cols-8 md:grid-cols-12 gap-x-gutter gap-y-0"><div class="col-span-3 col-start-1 pt-8 text-white -translate-y-[30px]" data-background="transparent_dark"><div class="flex items-center border-shade-30 mb-6 border-b pb-4 text-lg font-bold border-shade-70"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="40" height="40" fill="none"><g clip-path="url(#help-and-support_svg__a)"><circle cx="20" cy="20" r="19.25" stroke="currentColor" stroke-width="1.5"></circle><path d="M20 20v6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path><path d="M20 16.5a1.5 1.5 0 1 0 0-3 1.5 1.5 0 0 0 0 3Z" fill="currentColor"></path></g></svg></span><span id="subNavItem-Help and support" class="font-bold text-t7 inline-block text-white">Help and support</span></div><ul class="pb-2" aria-labelledby="subNavItem-Help and support"><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://help.shopify.com/en/" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Help and support<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Get 24/7 support</div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://academy.shopify.com" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Business courses<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Learn from proven experts</div></a></li></ul></div><div class="col-span-6 col-start-4 pt-8 text-white -translate-y-[30px]" data-background="transparent_dark"><div class="flex items-center border-shade-30 mb-6 border-b pb-4 text-lg font-bold border-shade-70"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="40" height="40" fill="none"><g clip-path="url(#white_circle_svg__a)" stroke="currentColor" stroke-width="1.5"><circle cx="20" cy="20" r="19.25"></circle><path d="m22.023 15.04 3 3M12.75 27.25l4.25-1 9.95-9.95a1 1 0 0 0 0-1.413l-1.837-1.836a1 1 0 0 0-1.414 0L13.75 23l-1 4.25Z" stroke-linecap="round" stroke-linejoin="round"></path></g></svg></span><span id="subNavItem-Popular topics" class="font-bold text-t7 inline-block text-white">Popular topics</span></div><ul class="pb-2 columns-2" aria-labelledby="subNavItem-Popular topics"><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/blog/what-is-shopify" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">What is Shopify?<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">How our commerce platform works</div></a></li></ul></div><div class="col-span-3 col-start-10 pt-8 scheme-white:bg-shade-10 scheme-transparent-light:bg-shade-10 scheme-black:bg-shade-90 scheme-transparent-dark:bg-shade-90 pl-10 relative after:absolute after:left-full after:top-[-1px] after:w-[100vw] after:h-[calc(100%+1px)] scheme-white:after:bg-shade-10 scheme-transparent-light:after:bg-shade-10 scheme-black:after:bg-shade-90 scheme-transparent-dark:after:bg-shade-90 scheme-black:text-white scheme-transparent-dark:text-white border-l scheme-white:border-shade-20 scheme-transparent-light:border-shade-20 scheme-black:border-shade-70 scheme-transparent-dark:border-shade-70 text-black -translate-y-[30px]" data-background="transparent_dark"><div class="flex items-center border-shade-30 mb-6 border-b pb-4 text-lg font-bold border-shade-70"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="40" height="40" fill="none"><g clip-path="url(#white_circle_svg__a)" stroke="currentColor" stroke-width="1.5"><circle cx="20" cy="20" r="19.25"></circle><path d="M16.75 16.75v-2a2 2 0 0 1 2-2h2.5a2 2 0 0 1 2 2v2m-2 4v1.5a1 1 0 0 1-1 1h-.5a1 1 0 0 1-1-1v-1.5m2.5 0a1 1 0 0 0-1-1h-.5a1 1 0 0 0-1 1m2.5 0h6m-8.5 0h-6m2 6.5h10.5a2 2 0 0 0 2-2v-6.5a2 2 0 0 0-2-2h-10.5a2 2 0 0 0-2 2v6.5a2 2 0 0 0 2 2Z"></path></g></svg></span><span id="subNavItem-Essential tools" class="font-bold text-t7 inline-block text-white">Essential tools</span></div><ul class="pb-2" aria-labelledby="subNavItem-Essential tools"><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/tools/business-name-generator" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Business name generator<span class="sr-only">.</span></span></div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/tools/logo-maker" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Logo maker<span class="sr-only">.</span></span></div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/stock-photos" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Stock photography<span class="sr-only">.</span></span></div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.linkpop.com/" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Link in bio tool<span class="sr-only">.</span></span></div></a></li><li class="flex mb-6 pr-4"><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/tools/qr-code-generator" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">QR code generator<span class="sr-only">.</span></span></div></a></li></ul></div></div></div></div></li><li class="mr-8 text-base"><div class="relative flex h-full after:content[&quot;&quot;] after:block after:h-[3px] after:w-full after:absolute after:bottom-0 after:scale-0 after:origin-left after:transition-transform after:duration-[350ms] after:ease-[bezier(0.66, 0.66, 0.34, 1.00)] after:motion-reduce:transition-none hover:underline after:bg-white text-white"><button type="button" class="bg-transparent whitespace-nowrap" data-component-name="What&#x27;s new-toggle-open" aria-controls="What&#x27;s newDesktopMenu" aria-expanded="false" aria-haspopup="true"><span class="mr-3">What&#x27;s new</span><div aria-hidden="true" class="relative inline-block h-[7px] w-3 cursor-pointer"><div class="inline-block rounded border-b-8 h-2 w-[2px] origin-center transition-all ease-[bezier(0.66, 0.66, 0.34, 1.00)] duration-[350ms] motion-reduce:transition-none border-white bg-white -translate-x-[3px] rotate-[135deg]"></div><div class="inline-block rounded border-b-8 h-2 w-[2px] origin-center transition-all ease-[bezier(0.66, 0.66, 0.34, 1.00)] duration-[350ms] motion-reduce:transition-none border-white bg-white -rotate-[135deg]"></div></div></button></div><div id="What&#x27;s newDesktopMenu" class="absolute inset-x-0 top-global-header z-[21] transition-transform duration-[452ms] ease-[cubic-bezier(0.26,1.00,0.48,1.00)] motion-reduce:transition-none before:absolute before:inset-x-0 before:top-0 before:w-full before:h-full before:left-0 before:shadow-xl before:pointer-events-none before:z-10 bg-black text-white opacity-0 pointer-events-none -translate-y-[10px]"><div class="relative no-scrollbar overflow-y-auto overflow-x-clip max-h-[calc(100vh-72px)] z-20"><div class="container grid grid-cols-4 sm:grid-cols-8 md:grid-cols-12 gap-x-gutter gap-y-0"><div class="col-span-12 col-start-1 pt-8 text-white -translate-y-[30px]" data-background="transparent_dark"><ul class="grid grid-cols-12 gap-x-gutter gap-y-0 pb-0"><li class="flex mb-6 pr-4 border-r border-shade-30 last:border-0 col-span-3"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="41" height="42" viewBox="0 0 41 42" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0_213_1921)"><path d="M39.5 21C39.5 31.6315 30.8815 40.25 20.25 40.25C9.61852 40.25 1 31.6315 1 21C1 10.3685 9.61852 1.75 20.25 1.75C30.8815 1.75 39.5 10.3685 39.5 21Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><rect x="13" y="14" width="15" height="14" rx="2" stroke="currentColor" stroke-width="1.5"></rect><path d="M25 24L19 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M25 21L19 21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M25 18L19 18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M17 18L16 18" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M17 21L16 21" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M17 24L16 24" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></g><defs><clipPath id="clip0_213_1921"><rect width="40.5" height="40.5" fill="white" transform="translate(0 0.75)"></rect></clipPath></defs></svg></span><a class="group inline-block w-full text-white hover:text-white" href="https://changelog.shopify.com" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Changelog<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">Your source for recent updates</div></a></li><li class="flex mb-6 pr-4 border-r border-shade-30 last:border-0 col-span-3"><span aria-hidden="true" class="-ml-2 lg:ml-0 mr-3 md:mr-3 inline-block h-10 w-10"><svg width="42" height="42" viewBox="0 0 42 42" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M40.25 21C40.25 31.6315 31.6315 40.25 21 40.25C10.3685 40.25 1.75 31.6315 1.75 21C1.75 10.3685 10.3685 1.75 21 1.75C31.6315 1.75 40.25 10.3685 40.25 21Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M17.341 24.6079L18.8363 26.9574" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M26.9609 14.7523C28.2811 15.4085 29.324 16.6069 29.7356 18.1429C30.1472 19.679 29.8432 21.2382 29.028 22.4667" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M26.9769 20.7052C27.2556 20.1447 27.3388 19.4842 27.164 18.832C26.9893 18.1799 26.587 17.6495 26.0654 17.3034" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path><path d="M14.5357 20.5604C14.4233 20.1409 14.5945 19.6968 14.9595 19.4613L21.4187 15.2941C21.9885 14.9265 22.7512 15.2206 22.9267 15.8756L24.9726 23.5109C25.1481 24.1659 24.6346 24.802 23.9574 24.7685L16.2799 24.3892C15.8461 24.3678 15.4758 24.0688 15.3633 23.6493L14.5357 20.5604Z" stroke="currentColor" stroke-width="1.5"></path><path d="M12.0335 21.5081L12.7226 24.0796" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"></path></svg></span><a class="group inline-block w-full text-white hover:text-white" href="https://www.shopify.com/news" aria-label=""><div class="text-base font-medium"><span class="inline-block arrow-animation">Newsroom<span class="sr-only">.</span></span></div><div class="text-sm text-shade-30 group-hover:text-white">All company news and press releases</div></a></li></ul></div></div></div></div></li></ul></nav><div class="ml-auto transition-opacity duration-200 opacity-100 delay-100"><ul class="flex items-center lg:hidden mr-[-15px]"><li><button class="flex h-12 w-12 cursor-pointer select-none flex-col items-center justify-center gap-1" aria-label="Menu" aria-expanded="false" aria-controls="MobileDrawer" aria-haspopup="true" type="button" data-component-name="navigation-toggle-open"><div class="h-0.5 w-[1.125rem] transition-all ease-out-in duration-500 motion-reduce:transition-none bg-white"></div><div class="h-0.5 w-[1.125rem] transition-all ease-out-in duration-500 motion-reduce:transition-none bg-white"></div><div class="h-0.5 w-[1.125rem] transition-all ease-out-in duration-500 motion-reduce:transition-none bg-white"></div></button></li></ul></div></div></div><div class="top-0 w-full z-20 before:absolute before:top-0 before:left-0 before:pointer-events-none before:w-full before:h-global-header-with-border before:z-20 after:absolute after:top-0 after:left-0 after:will-change-[opacity] after:pointer-events-none after:h-full after:w-full after:z-10 after:transition-opacity after:duration-200 after:bg-black text-white before:border-y before:border-white/20 after:opacity-0 pointer-events-auto before:opacity-0 sticky"><div class="h-global-header container flex items-center relative z-20"><div data-component-name="secondary-navigation" class="flex items-center gap-x-8 w-full h-14 sm:h-global-header text-white"><a class="absolute left-0 top-1/2 -translate-y-1/2 transform-opacity will-change-opacity duration-200 z-20 opacity-0 pointer-events-none" href="/" data-component-name="logo-home"><svg viewBox="0 0 32 36" role="img" aria-labelledby="ShoppingBagOnlyLogoTitle" class="w-8 h-9"><title id="ShoppingBagOnlyLogoTitle">Shopify</title><path fill="#95BF47" d="M27.1851 6.83166C27.1598 6.65519 27.0079 6.55435 26.8813 6.55435C26.7548 6.55435 24.2489 6.50393 24.2489 6.50393C24.2489 6.50393 22.148 4.48717 21.9455 4.26028C21.743 4.0586 21.338 4.10902 21.1861 4.15944C21.1861 4.15944 20.7812 4.28549 20.123 4.48717C20.0218 4.13423 19.8446 3.70567 19.6168 3.2519C18.8828 1.84016 17.769 1.08387 16.4528 1.08387C16.3516 1.08387 16.2756 1.08387 16.1744 1.10908C16.1237 1.05866 16.0984 1.00824 16.0478 0.983034C15.4656 0.378004 14.7316 0.0754894 13.8457 0.100699C12.1498 0.151118 10.4539 1.38639 9.0617 3.55441C8.09984 5.0922 7.36579 7.00813 7.13798 8.49549C5.18896 9.10052 3.82211 9.52908 3.77149 9.52908C2.78432 9.8316 2.75901 9.85681 2.63245 10.7896C2.58182 11.4954 0 31.3858 0 31.3858L21.6671 35.1168L31.0578 32.7975C31.0325 32.7975 27.2104 7.00813 27.1851 6.83166ZM19.0346 4.8401C18.5284 4.99136 17.9715 5.16783 17.364 5.3695C17.364 4.51238 17.2375 3.30232 16.8578 2.29393C18.1487 2.49561 18.7815 3.95777 19.0346 4.8401ZM16.225 5.69723C15.086 6.05016 13.8457 6.42831 12.6054 6.80645C12.9597 5.47034 13.6179 4.15944 14.4278 3.27711C14.7316 2.94938 15.1619 2.59645 15.6428 2.39477C16.1491 3.37794 16.2503 4.76447 16.225 5.69723ZM13.8963 1.23513C14.3013 1.23513 14.6303 1.31076 14.9088 1.51244C14.4532 1.73932 13.9975 2.09226 13.5925 2.54603C12.5041 3.70567 11.6688 5.49555 11.3398 7.23501C10.302 7.56274 9.2895 7.86525 8.37827 8.14256C8.98576 5.39471 11.2892 1.31076 13.8963 1.23513Z"></path><path fill="#5E8E3E" d="M26.8814 6.55452C26.7548 6.55452 24.2489 6.5041 24.2489 6.5041C24.2489 6.5041 22.148 4.48733 21.9455 4.26045C21.8696 4.18482 21.7684 4.1344 21.6671 4.1344V35.117L31.0579 32.7977C31.0579 32.7977 27.2358 7.00829 27.2104 6.83183C27.1598 6.65536 27.0079 6.55452 26.8814 6.55452Z"></path><path fill="white" d="M16.4528 11.3696L15.3644 15.4283C15.3644 15.4283 14.1494 14.8737 12.7066 14.9745C10.5804 15.1006 10.5804 16.4367 10.5804 16.7644C10.707 18.5795 15.4909 18.9828 15.7694 23.2433C15.9719 26.5961 13.9975 28.8902 11.112 29.0667C7.66952 29.2432 5.77112 27.2264 5.77112 27.2264L6.50517 24.1256C6.50517 24.1256 8.42888 25.5625 9.9476 25.4617C10.9348 25.4113 11.3144 24.5794 11.2638 24.0248C11.112 21.6551 7.2139 21.8063 6.96078 17.8988C6.75829 14.6216 8.90981 11.3191 13.6685 11.0166C15.5162 10.8906 16.4528 11.3696 16.4528 11.3696Z"></path></svg></a><nav aria-label="Secondary" class="hidden lg:block h-full transition-transform will-change-transform duration-200 translate-x-0" data-click-outside="dismiss"><ul class="flex items-center gap-x-8 h-full"><li class="relative flex flex-col items-center h-full border-white/30"><a href="/" class="font-bold flex items-center h-full hover:underline outline-4 outline-state-focus focus-visible:outline">Engineering Blog</a></li><li class="relative flex flex-col items-center h-full after:absolute after:w-full after:h-[3px] after:bottom-0 hover:underline after:hidden after:bg-white"><a href="/topics/development" class="flex items-center h-full outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="1">Development</a></li><li class="relative flex flex-col items-center h-full after:absolute after:w-full after:h-[3px] after:bottom-0 hover:underline after:hidden after:bg-white"><a href="/topics/infrastructure" class="flex items-center h-full outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="1">Infrastructure</a></li><li class="relative flex flex-col items-center h-full after:absolute after:w-full after:h-[3px] after:bottom-0 hover:underline after:hidden after:bg-white"><a href="/topics/mobile" class="flex items-center h-full outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="1">Mobile</a></li><li class="relative flex flex-col items-center h-full after:absolute after:w-full after:h-[3px] after:bottom-0 hover:underline after:hidden after:bg-white"><a href="/topics/developer-tooling" class="flex items-center h-full outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="1">Developer Tooling</a></li><li class="relative flex flex-col items-center h-full after:absolute after:w-full after:h-[3px] after:bottom-0 hover:underline after:hidden after:bg-white"><a href="/latest" class="flex items-center h-full outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="1">Latest</a></li><li class="relative flex flex-col items-center h-full after:absolute after:w-full after:h-[3px] after:bottom-0 hover:underline after:hidden after:bg-white"><button type="button" aria-expanded="false" aria-haspopup="true" aria-controls="id_More topics_5" class="h-full outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="1"><span class="flex items-center">More topics<svg fill="none" viewBox="0 0 16 16" aria-hidden="true" icon="chevron" height="64" width="64" class="rotate-180 shrink-0 w-5 h-7 ml-2"><path fill="currentColor" d="M12 10.4a.798.798 0 0 1-.566-.234L8 6.73l-3.434 3.435a.8.8 0 1 1-1.132-1.132l4-4a.8.8 0 0 1 1.132 0l4 4A.8.8 0 0 1 12 10.4Z"></path></svg></span></button><div id="id_More topics_5" class="absolute top-[80px] duration-500 left-0 rounded-lg transition-all box-content opacity-0 -translate-y-6 pointer-events-none w-[256px] bg-black text-white shadow-dark"><ul class="p-4 w-[224px] box-content"><li class="py-3 px-4 rounded-lg"><a href="/topics/security" class="hover:underline outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="2">Security</a></li><li class="py-3 px-4 rounded-lg"><a href="/topics/data-science-engineering" class="hover:underline outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="2">Data Science Engineering</a></li><li class="py-3 px-4 rounded-lg"><a href="/topics/culture" class="hover:underline outline-state-focus outline-4 focus-visible:outline" data-secondary-nav-tier="2">Culture</a></li></ul></div></li></ul></nav><div class="hidden lg:block ml-auto"><div class="hidden lg:flex flex-row items-center text-base cursor-pointer blog-navbar-navigation">Search<svg viewBox="0 0 20 20" class="[&amp;&gt;path]:fill-white w-4 h-4 ml-2"><path fill-rule="evenodd" d="M12.323 13.383a5.5 5.5 0 1 1 1.06-1.06l2.897 2.897a.75.75 0 1 1-1.06 1.06l-2.897-2.897Zm.677-4.383a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"></path></svg></div><div class="mb-6 lg:mb-0 lg:absolute lg:bottom-0 lg:left-0 w-full z-50 lg:hidden bg-transparent lg:before:bg-engineering-dark-bg lg:before:bg-top lg:before:bg-no-repeat lg:before:h-full lg:before:absolute lg:before:top-0 lg:before:left-0 lg:before:w-[200vw] lg:before:-translate-x-1/2"><div class="lg:container lg:h-[71px] flex items-center"><div class="flex flex-row items-center text-base relative w-full border-b border-[#ccc]"><form method="get" action="/building-a-shopifyql-code-editor" class="flex lg:block w-full -mt-4 lg:mt-0" novalidate="" data-component-extra-search-value=""><input class="outline-0 text-base relative z-20 bg-transparent w-full lg:placeholder:text-transparent py-2 text-white placeholder-white" type="text" name="header-search" autoComplete="off" placeholder="Search" value=""/><button class="lg:hidden p-6 -mr-6" aria-label="Search icon"><svg viewBox="0 0 20 20" class="[&amp;&gt;path]:fill-white w-4 h-4 ml-2"><path fill-rule="evenodd" d="M12.323 13.383a5.5 5.5 0 1 1 1.06-1.06l2.897 2.897a.75.75 0 1 1-1.06 1.06l-2.897-2.897Zm.677-4.383a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"></path></svg></button></form><div class="hidden lg:flex absolute z-10 top-1/2 left-0 -translate-y-1/2 flex-row items-center text-base text-white">Type something you&#x27;re looking for<svg viewBox="0 0 20 20" class="[&amp;&gt;path]:fill-white w-4 h-4 ml-2"><path fill-rule="evenodd" d="M12.323 13.383a5.5 5.5 0 1 1 1.06-1.06l2.897 2.897a.75.75 0 1 1-1.06 1.06l-2.897-2.897Zm.677-4.383a4 4 0 1 1-8 0 4 4 0 0 1 8 0Z"></path></svg></div><div class="hidden lg:block p-2 cursor-pointer ml-auto"><svg viewBox="0 0 20 20" class="size-5 [&amp;&gt;path]:fill-white"><path d="M12.72 13.78a.75.75 0 1 0 1.06-1.06l-2.72-2.72 2.72-2.72a.75.75 0 0 0-1.06-1.06l-2.72 2.72-2.72-2.72a.75.75 0 0 0-1.06 1.06l2.72 2.72-2.72 2.72a.75.75 0 1 0 1.06 1.06l2.72-2.72 2.72 2.72Z"></path></svg></div></div></div></div></div><div class="pl-8 app-signup-links transform-opacity duration-200 relative before:h-[22px] before:bg-shade-30 before:w-[1px] before:absolute before:left-0 before:top-[1/2] before:translate-[-1/2] opacity-0 lg:hidden"><a href="/login?ui_locales=en" class="whitespace-nowrap">Log in</a><div data-component-name="button-group" data-mode="dark"><div class="flex gap-y-sm flex-wrap gap-x-2 justify-start"><a href="https://accounts.shopify.com/store-create?language=en&amp;locale=en&amp;signup_page=https%3A%2F%2Fshopify.engineering%2Fbuilding-a-shopifyql-code-editor&amp;signup_types[]=paid_trial_experience" class="inline-block self-center overflow-hidden max-w-full px-button-px py-button-py ring-inset rounded-button text-button-size font-button-font font-button-weight tracking-button-tracking transition-all duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-state-focus focus-visible:outline border-2 text-button-dark-primary-text bg-button-dark-primary-bg border-button-dark-primary-border ring-button-dark-primary-border hover:text-button-dark-primary-text-hover hover:bg-button-dark-primary-bg-hover hover:border-button-dark-primary-border-hover hover:ring-button-dark-primary-border-hover focus:text-button-dark-primary-text-focus focus:bg-button-dark-primary-bg-focus focus:border-button-dark-primary-border-focus focus:ring-button-dark-primary-border-focus active:text-button-dark-primary-text-active active:bg-button-dark-primary-bg-active active:border-button-dark-primary-border-active active:ring-button-dark-primary-border-active disabled:text-button-dark-primary-text-disabled disabled:bg-button-dark-primary-bg-disabled disabled:border-button-dark-primary-border-disabled disabled:ring-button-dark-primary-border-disabled" data-component-name="button" data-mode="dark" target="">Start free trial</a></div></div></div></div></div></div><svg class="sr-only" aria-hidden="true"><defs><linearGradient id="start_svg__a" x1="4" y1="2.5" x2="37.5" y2="36" gradientUnits="userSpaceOnUse"><stop stop-color="#00E392"></stop><stop offset="1" stop-color="#00B4CD"></stop></linearGradient><linearGradient id="manage_svg__a" x1="1" y1="1" x2="39" y2="39" gradientUnits="userSpaceOnUse"><stop stop-color="#1CD9D9"></stop><stop offset="1" stop-color="#70D50E"></stop></linearGradient><linearGradient id="market_svg__a" x1="1" y1="1" x2="39" y2="39" gradientUnits="userSpaceOnUse"><stop stop-color="#EA4EF2"></stop><stop offset="1" stop-color="#29C9FF"></stop></linearGradient><linearGradient id="start_svg__b" x1="4" y1="2.5" x2="37.5" y2="36" gradientUnits="userSpaceOnUse"><stop stop-color="#00E392"></stop><stop offset="1" stop-color="#00B4CD"></stop></linearGradient><linearGradient id="sell_svg__a" x1="1" y1="1" x2="39" y2="39" gradientUnits="userSpaceOnUse"><stop stop-color="#33EDE2"></stop><stop offset="1" stop-color="#6754FF"></stop></linearGradient><clipPath id="white_circle_svg__a"><path fill="#fff" d="M0 0h40v40H0z"></path></clipPath><linearGradient id="paint0_linear_1_569" x1="6.12412" y1="7.32529" x2="16.6923" y2="17.7506" gradientUnits="userSpaceOnUse"><stop offset="0.161933" stop-color="#27293B"></stop><stop offset="0.760531" stop-color="#27293B"></stop></linearGradient><linearGradient id="paint1_linear_1_569" x1="18.4621" y1="6.91041" x2="12.5203" y2="17.2518" gradientUnits="userSpaceOnUse"><stop stop-color="#27293B"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient><linearGradient id="paint0_linear_41_589" x1="7.59808" y1="8.96" x2="28.4853" y2="30.2112" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#32386C"></stop></linearGradient><linearGradient id="paint1_linear_41_589" x1="18.4622" y1="6.91041" x2="12.7852" y2="12.9621" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient><linearGradient id="paint0_linear_1_568" x1="85.4375" y1="44" x2="-160.562" y2="-57" gradientUnits="userSpaceOnUse"><stop stop-color="white"></stop><stop offset="0.829504" stop-color="white" stop-opacity="0"></stop></linearGradient><radialGradient id="paint1_radial_1_568" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(73.4375 49) rotate(-96.3402) scale(54.3323 72.1138)"><stop stop-color="#3C00BB" stop-opacity="0.72"></stop><stop offset="1" stop-color="white" stop-opacity="0"></stop></radialGradient><radialGradient id="paint2_radial_1_568" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(107.438 44) rotate(-15.5615) scale(82.0061 39.4114)"><stop stop-color="#423EFF" stop-opacity="0.6"></stop><stop offset="0.754349" stop-color="#12C06D" stop-opacity="0"></stop></radialGradient><linearGradient id="paint3_linear_1_568" x1="15.1241" y1="17.3253" x2="25.6923" y2="27.7506" gradientUnits="userSpaceOnUse"><stop offset="0.161933" stop-color="#27293B"></stop><stop offset="0.760531" stop-color="#27293B"></stop></linearGradient><linearGradient id="paint4_linear_1_568" x1="27.4621" y1="16.9104" x2="21.5203" y2="27.2518" gradientUnits="userSpaceOnUse"><stop stop-color="#27293B"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient><linearGradient id="paint0_linear_1_612" x1="85.4375" y1="44" x2="-160.562" y2="-57" gradientUnits="userSpaceOnUse"><stop stop-color="white"></stop><stop offset="0.829504" stop-color="white"></stop></linearGradient><radialGradient id="paint1_radial_1_612" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(73.4375 49) rotate(-99.5942) scale(44.6242 90.7846)"><stop stop-color="#3C00BB" stop-opacity="0.72"></stop><stop offset="1" stop-color="white" stop-opacity="0"></stop></radialGradient><radialGradient id="paint2_radial_1_612" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(107.438 44) rotate(-47.641) scale(98.7905 47.4778)"><stop stop-color="#423EFF" stop-opacity="0.6"></stop><stop offset="0.754349" stop-color="#12C06D" stop-opacity="0"></stop></radialGradient><linearGradient id="paint3_linear_1_612" x1="15.1242" y1="17.3253" x2="25.6923" y2="27.7506" gradientUnits="userSpaceOnUse"><stop offset="0.161933" stop-color="#27293B"></stop><stop offset="0.760531" stop-color="#27293B"></stop></linearGradient><linearGradient id="paint4_linear_1_612" x1="27.4621" y1="16.9104" x2="21.5203" y2="27.2518" gradientUnits="userSpaceOnUse"><stop stop-color="#27293B"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient><linearGradient id="paint0_linear_1_651" x1="85.4375" y1="44" x2="-160.562" y2="-57" gradientUnits="userSpaceOnUse"><stop stop-color="white"></stop><stop offset="0.829504" stop-color="white"></stop></linearGradient><radialGradient id="paint1_radial_1_651" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(73.4375 49) rotate(-99.5942) scale(44.6242 90.7846)"><stop stop-color="#3C00BB" stop-opacity="0.72"></stop><stop offset="1" stop-color="white" stop-opacity="0"></stop></radialGradient><radialGradient id="paint2_radial_1_651" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(107.438 44) rotate(-47.641) scale(98.7905 47.4778)"><stop stop-color="#423EFF" stop-opacity="0.6"></stop><stop offset="0.754349" stop-color="#12C06D" stop-opacity="0"></stop></radialGradient><linearGradient id="paint3_linear_1_651" x1="15.1242" y1="17.3253" x2="25.6923" y2="27.7506" gradientUnits="userSpaceOnUse"><stop offset="0.161933" stop-color="#27293B"></stop><stop offset="0.760531" stop-color="#27293B"></stop></linearGradient><linearGradient id="paint4_linear_1_651" x1="27.4621" y1="16.9104" x2="21.5203" y2="27.2518" gradientUnits="userSpaceOnUse"><stop stop-color="#27293B"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient><linearGradient id="paint0_linear_41_588" x1="-59.2497" y1="-1.6632e-07" x2="-32.4337" y2="99.4491" gradientUnits="userSpaceOnUse"><stop offset="0.000294443" stop-color="#15161B"></stop><stop offset="0.606057" stop-color="#1C1F31"></stop></linearGradient><radialGradient id="paint1_radial_41_588" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(146.438 64) rotate(-172.051) scale(224.154 105.307)"><stop stop-color="#333FB2"></stop><stop offset="0.75924" stop-color="#27293B" stop-opacity="0"></stop></radialGradient><linearGradient id="paint2_linear_41_588" x1="16.5981" y1="18.96" x2="37.4853" y2="40.2112" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#32386C"></stop></linearGradient><linearGradient id="paint3_linear_41_588" x1="27.4622" y1="16.9104" x2="21.7852" y2="22.9621" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient><linearGradient id="paint0_linear_41_625" x1="-59.2497" y1="-1.6632e-07" x2="-32.4337" y2="99.4491" gradientUnits="userSpaceOnUse"><stop offset="0.000294443" stop-color="#15161B"></stop><stop offset="0.606057" stop-color="#1C1F31"></stop></linearGradient><radialGradient id="paint1_radial_41_625" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(146.438 64) rotate(-172.051) scale(224.154 105.307)"><stop stop-color="#333FB2"></stop><stop offset="0.75924" stop-color="#27293B" stop-opacity="0"></stop></radialGradient><linearGradient id="paint2_linear_41_625" x1="16.5981" y1="18.96" x2="37.4853" y2="40.2112" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#32386C"></stop></linearGradient><linearGradient id="paint3_linear_41_625" x1="27.4622" y1="16.9104" x2="21.7852" y2="22.9621" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient><linearGradient id="paint0_linear_41_662" x1="-59.2497" y1="-1.6632e-07" x2="-32.4337" y2="99.4491" gradientUnits="userSpaceOnUse"><stop offset="0.000294443" stop-color="#15161B"></stop><stop offset="0.606057" stop-color="#1C1F31"></stop></linearGradient><radialGradient id="paint1_radial_41_662" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(146.438 64) rotate(-172.051) scale(224.154 105.307)"><stop stop-color="#333FB2"></stop><stop offset="0.75924" stop-color="#27293B" stop-opacity="0"></stop></radialGradient><linearGradient id="paint2_linear_41_662" x1="16.5981" y1="18.96" x2="37.4853" y2="40.2112" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#32386C"></stop></linearGradient><linearGradient id="paint3_linear_41_662" x1="27.4622" y1="16.9104" x2="21.7852" y2="22.9621" gradientUnits="userSpaceOnUse"><stop stop-color="#A8B1EB"></stop><stop offset="1" stop-color="#4F58AA"></stop></linearGradient></defs></svg></header><main role="main" id="main" tabindex="-1"><div class="fixed top-0 left-0 h-[6px] z-50 bg-engineering-highlight" style="width:null%"></div><section class="grid grid-cols-full text-section-light-text featured-blogs gap-y-0 pt-36 pb-0 bg-transparent" data-section-name="" data-component-name="building-a-shopifyql-code-editor" data-viewable-component="true" data-mode="light"><article id="article" itemscope="" itemType="https://schema.org/Article" class="featured-blogs__section"><meta itemProp="mainEntityOfPage" content="//building-a-shopifyql-code-editor"/><meta itemProp="dateModified" content="2023-09-11T14:02:54.000Z"/><div class="relative"><div class="py-10 tablet:text-center desktop:pt-16 desktop:pb-16" data-mode="light"><div class="container desktop:w-9/12 desktop:mx-auto"><div class="uppercase font-aktivgroteskextended text-[12px] leading-[14px] tracking-[0.01em] font-semibold tablet-xl:font-shopifysans tablet-xl:text-[11px] tablet-xl:leading-[13px] tablet:tracking-[0.5px] tablet:font-bold pb-2 text-engineering-dark-text"><a href="/" class="no-underline topic-link hover:text-inherit">blog</a><span class="richtext px-2.5 text-xs">|</span><a href="/topics/developer-tooling" class="no-underline topic-link hover:text-inherit">Developer Tooling</a></div><h1 class="richtext font-medium tracking-[-0.02em] text-[28px] leading-9 tablet-xl:text-[40px] tablet-xl:leading-[48px] text-engineering-dark-text" itemProp="headline">Building a ShopifyQL Code Editor</h1><div class="lg:hidden mt-4 tablet-xl:mt-12"><div class="gap-x-1 flex flex-wrap font-bold sm:justify-center !text-lg text-engineering-dark-text"> </div><div class="text-body-sm font-normal text-engineering-dark-accent">Last updated<!-- --> <time>Sep 11, 2023</time></div></div></div></div><div class="tablet:container desktop:w-9/12 desktop:mx-auto overflow-hidden flex justify-center"><img class="" src="https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782" alt="" loading="eager" decoding="async" width="1848" height="782" srcSet="https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=200 200w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=400 400w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=600 600w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=800 800w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=1000 1000w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=1200 1200w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=1400 1400w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=1600 1600w, https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014&amp;originalWidth=1848&amp;originalHeight=782&amp;width=1800 1800w"/></div></div><div class="container grid-cols-4 sm:grid-cols-8 md:grid-cols-12 block tablet-xl:grid gap-y-0 gap-x-12"><div class="col-span-4 xs:col-span-12 md:col-span-4 lg:col-span-3 col-start-1 nav-drawer md:pb-20 pt-10 desktop:pt-16" data-component-name="blog-left-sidebar"><nav class="md:top-[calc(var(--header-height)*2)] md:max-h-[calc(100vh-var(--header-height))] md:overflow-auto md:sticky md:pb-6 pr-2 text-engineering-dark-text"><div class="hidden md:block bg-engineering-dark-accent text-engineering-light-text"><div class="p-6"><p class="richtext text-2xl font-aktivgroteskextended leading-4 desktop:leading-7 color-black tracking-[-0.02em]">Engineering at Shopify</p><p class="richtext font-aktivgroteskextended desktop:leading-7 color-black tracking-[-0.02em] text-2xl mb-9 font-bold">We’re hiring</p><a href="https://www.shopify.com/careers#Engineering" class="flex justify-between align-middle font-aktivgroteskextended leading-6 tracking-[-0.0175rem] !no-underline border-b-[1px] border-black group" target="_blank" rel="noopener noreferrer">See open roles<span class="flex justify-center items-center w-6 h-6 ml-4 my-auto [&amp;_svg]:w-6 reduced-motion:group-hover:translate-x-0 transition-all duration-500 will-change-transform group-hover:translate-x-full group-hover:opacity-0"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20" fill="none"><path fill-rule="evenodd" clip-rule="evenodd" d="M8.00025 15.9998C7.74425 15.9998 7.48825 15.9018 7.29325 15.7068C6.90225 15.3158 6.90225 14.6838 7.29325 14.2928L11.5862 9.99976L7.29325 5.70676C6.90225 5.31576 6.90225 4.68376 7.29325 4.29276C7.68425 3.90176 8.31625 3.90176 8.70725 4.29276L13.7073 9.29276C14.0982 9.68376 14.0982 10.3158 13.7073 10.7068L8.70725 15.7068C8.51225 15.9018 8.25625 15.9998 8.00025 15.9998Z" fill="black"></path></svg></span></a></div></div></nav></div><div class="col-span-4 xs:col-span-12 md:col-span-8 lg:col-span-6 col-start-1"><div id="article-content" class="text-body-base md:pt-10 desktop:pt-16 [&amp;_h2]:text-t4 [&amp;_h2]:mt-12 [&amp;_h2]:mb-6 [&amp;_h2]:font-medium [&amp;_.heading--2]:text-t5 [&amp;_.heading--2]:text-black [&amp;_.heading--2]:font-aktivgroteskextended [&amp;_.heading--2]:mb-6 [&amp;_.heading--2]:font-medium [&amp;_h3]:text-t5 [&amp;_h3]:mt-12 [&amp;_h3]:mb-4 [&amp;_h4]:text-t6 [&amp;_h4]:mt-12 [&amp;_h4]:mb-4 [&amp;_h5]:text-t7 [&amp;_p]:mb-6 [&amp;_hr]:mb-6 [&amp;_a]:font-medium [&amp;_a]:underline [&amp;_a:hover]:no-underline [&amp;_sup_a]:no-underline [&amp;_sup_a:hover]:underline [&amp;_.marketing-button]:text-white [&amp;_.marketing-button]:no-underline [&amp;_a&gt;img]:mt-3 [&amp;_a&gt;img]:mb-8 [&amp;_a&gt;img]:h-[revert-layer] [&amp;_img]:my-12 [&amp;_a&gt;img]:inline-block [&amp;_figure&gt;img]:mb-0 [&amp;_figure]:mb-12 [&amp;_img]:inline-block [&amp;_ul]:list-disc [&amp;_ul]:space-y-3 [&amp;_ul]:pl-6 [&amp;_ul]:mb-6 [&amp;_ul_li]:pl-4 [&amp;_ol]:list-decimal [&amp;_ol]:space-y-3 [&amp;_ol]:pl-6 [&amp;_ol]:mb-6 [&amp;_ol_li]:pl-4 [&amp;_ol_li::marker]:font-bold [&amp;_iframe]:max-w-full [&amp;_figcaption]:mb-12 [&amp;_figcaption]:text-center [&amp;_figcaption]:text-xs [&amp;_figcaption]:sm:text-sm [&amp;_figcaption]:mt-1 [&amp;_strong]:font-medium [&amp;_.heading--4]:font-aktivgroteskextended [&amp;_.heading--4]:text-2xl [&amp;_.heading--4]:font-medium [&amp;_.heading--4]:tracking-[-.02em] [&amp;_iframe]:mt-3 [&amp;_iframe]:mb-9 [&amp;_.container]:m-0 [&amp;_.container]:flex [&amp;_.container]:gap-5 [&amp;_.container]:flex-wrap [&amp;_.green]:text-[green] [&amp;_.red]:text-[red] [&amp;_.pros]:bg-[#eafaea] [&amp;_.pros]:rounded-[10px] [&amp;_.pros]:p-5 [&amp;_.pros_h3]:mt-2 [&amp;_.cons]:bg-[#ffebe6] [&amp;_.cons]:rounded-[10px] [&amp;_.cons]:p-5 [&amp;_.cons_h3]:mt-2 [&amp;_.pros]:tablet:flex-[1_0_calc(50%-10px)] [&amp;_.pros]:flex-[1_0_100%] [&amp;_.cons]:tablet:flex-[1_0_calc(50%-10px)] [&amp;_.cons]:flex-[1_0_100%] [&amp;_.pros_.grid-container]:grid [&amp;_.cons_.grid-container]:grid [&amp;_.grid-container]:gap-[5px] [&amp;_.grid-container]:grid-cols-[15px_auto] [&amp;_.pros_.grid-container]:p-[5px] [&amp;_.cons_.grid-container]:p-[5px] [&amp;_.aspect-video_iframe]:mt-0 article-content [&amp;_.truncated-text]:relative [&amp;_.truncated-text]:max-h-[300px] [&amp;_.truncated-text]:overflow-hidden [&amp;_.truncated-text]:mb-6 [&amp;_.truncated-text::before]:absolute [&amp;_.truncated-text::before]:content-[&quot;&quot;] [&amp;_.truncated-text::before]:bottom-0 [&amp;_.truncated-text::before]:w-full [&amp;_.truncated-text::before]:h-[150px] [&amp;_.truncated-text::before]:bg-gradient-to-t [&amp;_.truncated-text::before]:from-white [&amp;_.truncated-text::before]:from-50% [&amp;_.truncated-text::before]:to-transparent [&amp;_.truncated-text\_\_toggle]:absolute [&amp;_.truncated-text\_\_toggle]:bottom-0 [&amp;_.truncated-text\_\_toggle]:w-full [&amp;_.truncated-text\_\_toggle]:text-center [&amp;_.marketing-code]:bg-[#f0f1f2] [&amp;_.marketing-code]:font-bold [&amp;_.marketing-code]:p-1 [&amp;_.marketing-code.marketing-code--block]:p-4 [&amp;_.marketing-code--block]:block [&amp;_.marketing-code--block]:max-w-full [&amp;_.marketing-code--block]:overflow-x-scroll [&amp;_.marketing-code--block]:mb-5 [&amp;_.partners-signup]:mt-6 [&amp;_.partners-signup]:mb-12 [&amp;_.partners-signup]:bg-gray-200 [&amp;_.partners-signup]:p-3 [&amp;_.partners-signup]:md:p-6 [&amp;_.table]:block [&amp;_.table]:overflow-x-scroll [&amp;_.table]:max-w-full text-engineering-dark-text [&amp;_h2]:text-inherit [&amp;_h3]:text-inherit [&amp;_h4]:text-inherit [&amp;_.heading--4]:text-inherit [&amp;_a]:text-[#E7ECFB] [&amp;_strong]:text-engineering-dark-text" itemProp="articleBody"><div class="hidden leadpage-container leadpage__image leadpage__content marketing-button hide--mobile block--bordered"></div><div><p>In October 2022, Shopify released ShopifyQL Notebooks, a first-party app that lets merchants analyze their shop data to make better decisions. It puts the power of ShopifyQL into merchants’ hands with a guided code editing experience. In order to provide a first-class editing experience, we turned to <a href="https://codemirror.net/" target="_blank" title="CodeMirror" rel="nofollow noopener noreferrer">CodeMirror</a>, a code editor framework built for the web. Out of the box, CodeMirror didn’t have support for ShopifyQL–here’s how we built it.</p> <h2><strong>ShopifyQL Everywhere</strong></h2> <p><a href="/shopify-commerce-data-querying-language-shopifyql" target="_blank" title="Shopify Engineering"> ShopifyQL</a> is an accessible, commerce-focused querying language used on both the client and server. The language is defined by an <a href="http://antlr.org" target="_blank" title="ANTLR">ANTLR</a> grammar and is used to generate code for multiple targets (currently, Go and Typescript). This lets us share the same grammar definition between both the client and server despite differences in runtime language. As an added benefit, we have types written in Protobuf so that types can be shared between targets as well.</p> <p>All the ShopifyQL language features on the front end are encapsulated into a typescript language server, which is built on top of the ANTLR typescript target. It conforms to Microsoft's<a href="https://microsoft.github.io/language-server-protocol/" target="_blank" title="Microsoft GitHub" rel="nofollow noopener noreferrer"> language server protocol</a> (LSP) in order to keep a clear separation of concerns between the language server and a code editor. LSP defines the shape of common language features like tokenization, parsing, completion, hover tooltips, and linting.</p> <p><img alt="Flow from ShopifyQL Language Server from LSP to Editor" src="https://cdn.shopify.com/s/files/1/0779/4361/files/ShopifyQLEverywhere.png?v=1694031194"></p> <p>When code editors and language servers both conform to LSP, they become interoperable because they speak a common language. For more information about LSP, read the <a href="https://code.visualstudio.com/api/language-extensions/language-server-extension-guide" rel="nofollow noopener noreferrer" target="_blank">VSCode Language Server Extension Guide</a>.</p> <h2><strong>Connecting The ShopifyQL Language Server To CodeMirror</strong></h2> <p>CodeMirror has its own grammar &amp; parser engine called <a href="https://lezer.codemirror.net" target="_blank" title="Lezer" rel="nofollow noopener noreferrer">Lezer</a>. Lezer is used within CodeMirror to generate parse trees, and those trees power many of the editor features. Lezer has support for common languages, but no Lezer grammar exists for ShopifyQL. Lezer also doesn’t conform to LSP. Because ShopifyQL’s grammar and language server had already been written in ANTLR, it didn’t make sense to rewrite what we had as a Lezer grammar. Instead, we decided to create an adapter that would conform to LSP and integrate with Lezer. This allowed us to pass a ShopifyQL query to the language server, adapt the response, and return a Lezer parse tree.</p> <p><img alt="Flow chart of ShopifyQL language server from LSP to custom adapter to/from Lezer" src="https://cdn.shopify.com/s/files/1/0779/4361/files/Connecting_The_ShopifyQL_Language_Server_To_CodeMirror.png?v=1694031395" loading="lazy"></p> <p>Lezer supports creating a tree in one of two ways:</p> <ol> <li>Manually creating a tree by creating nodes and attaching them in the correct tree shape</li> <li>Generating a tree from a buffer of tokens</li> </ol> <p>The ShopifyQL language server can create a stream of tokens from a document, so it made sense to re-shape that stream into a buffer that Lezer understands.</p> <h2><strong>Converting A ShopifyQL Query Into A Lezer Tree</strong></h2> <p>In order to transform a ShopifyQL query into a Lezer parse tree, the following steps occur:</p> <ol> <li>Lezer initiates the creation of a parse tree. This happens when the document is first loaded and any time the document changes.</li> <li>Our custom adapter takes the ShopifyQL query and passes it to the language server.</li> <li>The language server returns a stream of tokens that describe the ShopifyQL query.</li> <li>The adapter takes those tokens and transforms them into Lezer node types.</li> <li>The Lezer node types are used to create a buffer that describes the document.</li> <li>The buffer is used to build a Lezer tree.</li> <li>Finally, it returns the tree back to Lezer and completes the parse cycle.</li> </ol> <img src="https://cdn.shopify.com/s/files/1/0779/4361/files/converting_a_shopifyql_query_into_a_lezer_tree_18358861-5ee6-41c7-9fbc-5a4c8be04d9c.png?v=1694202679" alt="Flow chart of above steps converting a shopifyql query into a lezer tree" data-mce-fragment="1" data-mce-src="https://cdn.shopify.com/s/files/1/0779/4361/files/converting_a_shopifyql_query_into_a_lezer_tree_18358861-5ee6-41c7-9fbc-5a4c8be04d9c.png?v=1694202679" loading="lazy"><br> <ol></ol> <h2><strong>Understanding ShopifyQL’s Token Offset</strong></h2> <p>One of the biggest obstacles to transforming the language server’s token stream into a Lezer buffer was the format of the tokens. Within the ShopifyQL Language Server, the tokens come back as integers in chunks of 5, with the position of each integer having distinct meaning:</p><p><script src="https://gist.github.com/ShopifyEng/fee6f634f71db29c8d71b46b19650f7b.js"></script></p> <p>In this context, length, token type, and token modifier were fairly straightforward to use. However, the behavior of line and start character were more difficult to understand. Imagine a simple ShopifyQL query like this:</p><p><script src="https://gist.github.com/ShopifyEng/46cb2d42ed1ba0baa23cf7bdbcf1233c.js"></script></p> <p>This query would be tokenized like this:</p><p><script src="https://gist.github.com/ShopifyEng/6570d96362bce6b38ff4ca527b5f45cc.js"></script></p> <p>In the stream of tokens, even though <code>product_title</code> is on line 1 (using zero-based indexes), the value for its line integer is zero! This is because the tokenization happens incrementally and each computed offset value is always relative to the previous token. This becomes more confusing when you factor in whitespace-let’s say that we add five spaces before the word <code>SHOW</code>:</p><p><script src="https://gist.github.com/ShopifyEng/3534fdbedd19b4ab0874b5248930adf4.js"></script></p> <p>The tokens for this query are:</p><p><script src="https://gist.github.com/ShopifyEng/5a9ac4131334568473cbbadcbb2514f6.js"></script></p> <p>Notice that only the start character for <code>SHOW</code> changed! It changed from <code>0</code> to <code>5</code> after adding five spaces before the <code>SHOW</code> keyword. However, <code>product_title</code>’s values remain unchanged. This is because the values are relative to the previous token, and the space between <code>SHOW</code> and <code>product_title</code> didn’t change.</p> <p>This becomes especially confusing when you use certain language features that are parsed out of order. For example, in some ANTLR grammars, comments are not parsed as part of the default <a href="https://datacadamia.com/antlr/channel" target="_blank" title="Datacadamia" rel="nofollow noopener noreferrer">channel</a>–they are parsed after everything in the main channel is parsed. Let’s add a comment to the first line:</p><p><script src="https://gist.github.com/ShopifyEng/f59af161c23171559c683ff725dd284f.js"></script></p> <p>The tokens for this query look like this (and are in this order):</p><p><script src="https://gist.github.com/ShopifyEng/4ec2a5cedc25734cc2889cb7cf3845a4.js"></script></p> <p>Before the parser parses the comment, it points at <code>product_title</code>, which is two lines after the comment. When the parser finishes with the main channel and begins parsing the channel that contains the comment, the pointer needs to move two lines up to tokenize the comment–hence the value of -2 for the comment’s line integer.</p> <h2><strong>Adapting ShopifyQL’s Token Offset To Work With CodeMirror</strong></h2> <p>CodeMirror treats offset values much simpler than ANTLR. In CodeMirror, everything is relative to the top of the document–the document is treated as one long string of text. This means that newlines and whitespace are meaningful to CodeMirror and affect the start offset of a token.</p> <p>So to adapt the values from ANTLR to work with CodeMirror, we need to take these values:</p><p><script src="https://gist.github.com/ShopifyEng/af642957edf58a56951743c1ab5cc94b.js"></script></p> <p>And convert them into this:</p><p><script src="https://gist.github.com/ShopifyEng/26ae6dbc46c0e89b452c9f0a007c8730.js"></script></p> <p>The solution? A custom <code>TokenIterator</code> that could follow the “directions” of the Language Server’s offsets and convert them along the way. The final implementation of this class was fairly simple, but arriving at this solution was the hard part.</p> <p>At a high level, the <code>TokenIterator</code> class:</p> <ol> <li>Takes in the document and derives the length of each line. This means that trailing whitespace is properly represented.</li> <li>Internally tracks the current line and character that the iterator points to.</li> <li>Ingests the ANTLR-style line, character, and token length descriptors and moves the current line and character to the appropriate place.</li> <li>Uses the current line, current character, and line lengths to compute the CodeMirror-style start offset.</li> <li>Uses the start offset combined with the token length to compute the end offset.</li> </ol> <p>Here’s what the code looks like:</p><p><script src="https://gist.github.com/ShopifyEng/6082c4ce8d18711ac1df0f47cd8eadff.js"></script></p> <h2><strong>Building A Parse Tree</strong></h2> <p>Now that we have a clear way to convert an ANTLR token stream into a Lezer buffer, we’re ready to build our tree! To build it, we follow the steps mentioned previously–we take in a ShopifyQL query, use the language server to convert it to a token stream, transform that stream into a buffer of nodes, and then build a tree from that buffer.</p> <p>Once the parse tree is generated, CodeMirror then “understands” ShopifyQL and provides useful language features such as syntax highlighting.</p> <p><img alt="Syntax highlight line 1 FROM products line 2 SHOW product_title" src="https://cdn.shopify.com/s/files/1/0779/4361/files/Building_A_Parse_Tree.png?v=1694031668" loading="lazy"></p> <h2><strong>Providing Additional Language Features</strong></h2> <p>By this point, CodeMirror can talk to the ShopifyQL Language Server and build a parse tree that describes the ShopifyQL code. However, the language server offers other useful features like code completion, linting, and tooltips. As mentioned above, Lezer/CodeMirror doesn’t conform to LSP–but it does offer many plugins that let us provide a connector between our language server and CodeMirror. In order to provide these features, we adapted the language server’s <code>doValidate</code> with CodeMirror’s <code>linting</code> plugin, the language server’s <code>doComplete</code> with CodeMirror’s <code>autocomplete</code> plugin, and the language server’s <code>doHover</code> with CodeMirror’s <code>requestHoverTooltips</code> plugin.</p> <p><img alt="CodeMirror to Custom Adapter to Language Server" src="https://cdn.shopify.com/s/files/1/0779/4361/files/Providing_Additional_Language_Features.png?v=1694031422" loading="lazy"></p> <p>Once we connect those features, our ShopifyQL code editor is fully powered up, and we get an assistive, delightful code editing experience.</p> <p><img alt="Gif show editing experience through a drop down in syntax highlighting" src="https://cdn.shopify.com/s/files/1/0779/4361/files/Providing_Additional_Language_Features.gif?v=1694031415" loading="lazy"></p> <h2><strong>Conclusion</strong></h2> <p>This approach enabled us to provide ShopifyQL features to CodeMirror while continuing to maintain a grammar that serves both client and server. The custom adapter we created allows us to pass a ShopifyQL query to the language server, adapt the response, and return a Lezer parse tree to CodeMirror, making it possible to provide features like syntax highlighting, code completion, linting, and tooltips. Because our solution utilizes CodeMirror’s internal parse tree, we are able to make better decisions in the code and craft a stronger editing experience. The ShopifyQL code editor helps merchants write ShopifyQL and get access to their data in new and delightful ways.</p> <div class="marketing-block marketing-block--light marketing-block--padded my-12 border-l-2 pl-4 [&amp;_h2]:!mt-0 [&amp;_h2]:mb-6 [&amp;_h2]:text-t5 [&amp;_h3]:!mt-0 [&amp;_h3]:mb-6 [&amp;_h3]:text-t7 [&amp;_h4]:!mt-0 [&amp;_h4]:mb-6 [&amp;_h4]:text-t8 [&amp;_ul]:!mt-0 [&amp;_ul]:mb-6 [&amp;_ul:last-child]:mb-0 [&amp;_ol]:!mt-0 [&amp;_ol]:mb-6 [&amp;_ol:last-child]:mb-0 [&amp;_p]:!mt-0 [&amp;_p]:mb-6 [&amp;_p:last-child]:mb-0 text-engineering-dark-text [&amp;_*]:!text-engineering-dark-text bg-transparent border-engineering-highlight text-body-sm my-8 tablet:my-16 p-8 bg-marketingBg !border-l-0 border-t-2 pl-8 [&amp;_[itemscope]]:mb-6 [&amp;_[itemscope]:last-child]:mb-0 !bg-[#0C052B]"> <p>This post was written by <strong> Trevor Harmon</strong>, a Senior Developer working to make reporting and analytics experiences richer and more informative for merchants. When he isn't writing code, he spends time writing music, volunteering at his church, and hanging out with his wife and daughter. You can find more articles on topics like this one on his blog at <a title="The Trevor Harmon" href="https://thetrevorharmon.com/" data-sanitized-target="_blank" rel="nofollow noopener noreferrer" target="_blank">thetrevorharmon.com</a>, or follow him on <a title="GitHub - Trevor Harmon" href="https://github.com/thetrevorharmon" data-sanitized-target="_blank" rel="nofollow noopener noreferrer" target="_blank">GitHub</a> and <a title="Twitter - Trevor Harmon" href="https://twitter.com/thetrevorharmon" data-sanitized-target="_blank" rel="nofollow noopener noreferrer" target="_blank">Twitter</a>.</p> </div></div></div><div class="border-t tablet-xl:border-[#d4d4d8] my-16 py-6 flex tablet-xl:flex-row justify-between flex-col"><div class="flex items-center tablet-xl:justify-start tablet-xl:flex-row flex-row-reverse justify-between"><div class="self-center flex flex-col"><div class="gap-x-1 flex flex-wrap text-lg font-aktivgroteskextended leading-[18px] font-bold mb-2 text-engineering-dark-text"> <!-- -->by <span itemProp="author" itemscope="" itemType="https://schema.org/Person"><a href="/authors/trevor-harmon" class="no-underline" itemProp="url"><span itemProp="name">Trevor Harmon</span></a></span></div><div class="text-body-sm font-normal text-engineering-dark-accent">Last updated<!-- --> <time>Sep 11, 2023</time></div></div></div><div class="xs:border-b tablet-xl:border-0 pb-4"><div class="font-bold text-xs font-aktivgroteskextended uppercase mt-4 tablet-xl:mt-0 tablet-xl:text-right text-engineering-dark-accent">Share article</div><ul id=":R1bj95n5:" class="flex gap-4 mt-4" data-component-name="social"><li><a class="block h-9 w-9 hover:fill-shade-7 fill-[#E7ECFB]" aria-describedby="blog-social-icon-facebook-:R1bj95n5:" href="https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fshopify.engineering%2Fbuilding-a-shopifyql-code-editor" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-facebook" aria-label=""><svg xmlns="http://www.w3.org/2000/svg" width="36" height="37" viewBox="0 0 36 37"><circle cx="18" cy="18.6143" r="18" fill="#E7ECFB"></circle><path fill-rule="evenodd" clip-rule="evenodd" d="M16.4664 25.3643V19.2063H14.2495V16.8064H16.4664V15.0366C16.4664 12.9826 17.8084 11.8643 19.7685 11.8643C20.7074 11.8643 21.5144 11.9296 21.7495 11.9588V14.1053L20.3901 14.1059C19.3241 14.1059 19.1177 14.5793 19.1177 15.2742V16.8064H21.66L21.329 19.2063H19.1177V25.3643H16.4664Z" fill="#120937"></path></svg><span id="blog-social-icon-facebook-:R1bj95n5:" class="sr-only">Facebook</span></a></li><li><a class="block h-9 w-9 hover:fill-shade-7 fill-[#E7ECFB]" aria-describedby="blog-social-icon-twitter-:R1bj95n5:" href="https://twitter.com/intent/tweet?text=Building+a+ShopifyQL+Code+Editor&amp;url=https%3A%2F%2Fshopify.engineering%2Fbuilding-a-shopifyql-code-editor&amp;via=Shopify" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-twitter" aria-label=""><svg xmlns="http://www.w3.org/2000/svg" width="36" height="37" viewBox="0 0 36 37" fill="none"><circle cx="18" cy="18.6143" r="18" fill="#E7ECFB"></circle><path fill-rule="evenodd" clip-rule="evenodd" d="M23.3786 17.0672C23.3786 17.9589 23.2031 18.8473 22.851 19.7304C22.5 20.6135 22.0027 21.4212 21.3615 22.1535C20.7202 22.8858 19.8844 23.4781 18.8561 23.9326C17.8279 24.387 16.7074 24.6143 15.4946 24.6143C13.9657 24.6143 12.5505 24.219 11.25 23.4275C11.4784 23.4447 11.6977 23.4533 11.9081 23.4533C13.2097 23.4533 14.3606 23.0743 15.363 22.3172C14.7656 22.3172 14.2335 22.1449 13.7677 21.8003C13.302 21.4546 12.9814 21.013 12.8047 20.4746C12.9814 20.509 13.1659 20.5252 13.3594 20.5252C13.6575 20.5252 13.9219 20.5004 14.1502 20.4498C13.5349 20.3152 13.0376 20.0083 12.6596 19.5279C12.2827 19.0487 12.0937 18.4898 12.0937 17.8501V17.7995C12.0937 17.9007 12.2119 17.9804 12.4492 18.0396C12.6866 18.0978 12.9375 18.1279 13.2007 18.1279C12.7969 17.8749 12.4627 17.555 12.1984 17.1684C11.9351 16.7818 11.8035 16.3693 11.8035 15.931C11.8035 15.4604 11.9261 15.0146 12.1725 14.5935C12.8756 15.4184 13.7239 16.0743 14.7172 16.5621C15.7095 17.051 16.7692 17.3203 17.8942 17.3698C17.8414 17.1684 17.8144 16.966 17.8144 16.7646C17.8144 16.0409 18.0877 15.4184 18.6322 14.8972C19.1767 14.3749 19.8281 14.1143 20.583 14.1143C21.3919 14.1143 22.0691 14.3921 22.6136 14.9467C23.229 14.8293 23.8174 14.6193 24.3799 14.3156C24.1695 14.9392 23.7566 15.427 23.1412 15.7803C23.7037 15.7124 24.2392 15.5703 24.75 15.3506C24.363 15.889 23.8972 16.3521 23.3516 16.7387C23.3696 16.8572 23.3786 16.966 23.3786 17.0672Z" fill="#120937"></path></svg><span id="blog-social-icon-twitter-:R1bj95n5:" class="sr-only">Twitter</span></a></li><li><a class="block h-9 w-9 hover:fill-shade-7 fill-[#E7ECFB]" aria-describedby="blog-social-icon-linkedin-:R1bj95n5:" href="https://www.linkedin.com/shareArticle?mini=true&amp;source=Shopify&amp;title=Building+a+ShopifyQL+Code+Editor&amp;url=https%3A%2F%2Fshopify.engineering%2Fbuilding-a-shopifyql-code-editor" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-linkedin" aria-label=""><svg xmlns="http://www.w3.org/2000/svg" width="36" height="37" viewBox="0 0 36 37" fill="none"><circle cx="18" cy="18.6143" r="18" fill="#E7ECFB"></circle><path fill-rule="evenodd" clip-rule="evenodd" d="M13.4102 12.6143C14.1884 12.6143 14.8191 13.2463 14.8191 14.0236C14.8191 14.8018 14.1884 15.4334 13.4102 15.4334C12.6293 15.4334 11.9995 14.8018 11.9995 14.0236C11.9995 13.2463 12.6293 12.6143 13.4102 12.6143ZM16.1521 16.5032H18.4826V17.572H18.5163C18.8405 16.9568 19.6337 16.3083 20.8163 16.3083C23.2766 16.3083 23.7315 17.9281 23.7315 20.035V24.3258H21.3026V20.5218C21.3026 19.6147 21.2849 18.4476 20.039 18.4476C18.7736 18.4476 18.5805 19.4353 18.5805 20.4562V24.3258H16.1521V16.5032ZM12.1929 24.326H14.6268V16.5034H12.1929V24.326Z" fill="#120937"></path></svg><span id="blog-social-icon-linkedin-:R1bj95n5:" class="sr-only">LinkedIn</span></a></li></ul></div></div></div><div class="col-span-4 xs:col-span-12 lg:col-span-3 col-start-1 hidden lg:block relative" data-component-name="blog-right-sidebar"><div class="tablet-xl:pt-10 desktop:pt-16 pb-6 absolute h-full max-h-full tot-0 left-0"><div class="h-full overflow-hidden" id="blog-side-banners-col"><div class="gap-x-1 flex flex-wrap font-bold text-lg !font-shopifysans text-engineering-dark-text"> <!-- -->by <span itemProp="author" itemscope="" itemType="https://schema.org/Person"><a href="/authors/trevor-harmon" class="no-underline" itemProp="url"><span itemProp="name">Trevor Harmon</span></a></span></div><div class="text-body-sm font-normal text-engineering-dark-accent">Last updated<!-- --> <time>Sep 11, 2023</time></div><span class="text-body-sm font-normal text-engineering-dark-accent"> • <!-- -->7 minute read</span><div id="blog-side-banners"><div class="font-aktivgroteskextended text-[#E7ECFB] mt-[640px]" data-priority="0"><p class="richtext text-base uppercase mb-6 font-medium"></p><span class="mb-4 pl-2 border-l border-[#CAD4D7] inline-block border-engineering-highlight"><span class="uppercase text-xs mb-2 block pt-2 font-bold [&amp;_a]:no-underline [&amp;_a]:hover:text-inherit"><a href="/topics/development" data-component-name="blog-popular-post-Development">Development</a></span><a href="/introducing-ruvy" data-component-name="blog-popular-post-introducing-ruvy"><span class="richtext text-base block hover:underline hover:underline-offset-4 font-normal text-engineering-dark-text">Introducing Ruvy</span></a></span><span class="mb-4 pl-2 border-l border-[#CAD4D7] inline-block border-engineering-highlight"><span class="uppercase text-xs mb-2 block pt-2 font-bold [&amp;_a]:no-underline [&amp;_a]:hover:text-inherit"><a href="/topics/developer-tooling" data-component-name="blog-popular-post-Developer Tooling">Developer Tooling</a></span><a href="/building-a-shopifyql-code-editor" data-component-name="blog-popular-post-building-a-shopifyql-code-editor"><span class="richtext text-base block hover:underline hover:underline-offset-4 font-normal text-engineering-dark-text">Building a ShopifyQL Code Editor</span></a></span></div><div class="font-aktivgroteskextended text-[#E7ECFB] mt-[640px]" data-priority="0"><p class="richtext text-base uppercase mb-6 font-medium"></p><span class="mb-4 pl-2 border-l border-[#CAD4D7] inline-block border-engineering-highlight"><span class="uppercase text-xs mb-2 block pt-2 font-bold [&amp;_a]:no-underline [&amp;_a]:hover:text-inherit"><a href="/topics/apps" data-component-name="blog-popular-post-Apps">Apps</a></span><a href="/shopifys-platform-is-the-web-platform" data-component-name="blog-popular-post-shopifys-platform-is-the-web-platform"><span class="richtext text-base block hover:underline hover:underline-offset-4 font-normal text-engineering-dark-text">Shopify’s platform is the Web platform</span></a></span><span class="mb-4 pl-2 border-l border-[#CAD4D7] inline-block border-engineering-highlight"><span class="uppercase text-xs mb-2 block pt-2 font-bold [&amp;_a]:no-underline [&amp;_a]:hover:text-inherit"><a href="/topics/development" data-component-name="blog-popular-post-Development">Development</a></span><a href="/building-flex-comp" data-component-name="blog-popular-post-building-flex-comp"><span class="richtext text-base block hover:underline hover:underline-offset-4 font-normal text-engineering-dark-text">The Engineering Story Behind Flex Comp</span></a></span></div></div></div></div></div></div><div itemProp="publisher" itemscope="" itemType="https://schema.org/Organization"><meta itemProp="name" content="Shopify"/><div itemProp="logo" itemscope="" itemType="https://schema.org/ImageObject"><meta itemProp="url" content="https://cdn.shopify.com/assets/images/logos/shopify_logo_black.png"/><meta itemProp="width" content="210"/><meta itemProp="height" content="60"/></div></div></article><div class="py-10 tablet-xl:py-16 overflow-hidden lg:hidden tablet-xl:pt-0 pt-0 [&amp;_h5]:uppercase text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal [&amp;_.blogPost&gt;div:first-child&gt;a:not(:hover)]:text-engineering-highlight"><div class="container"><div class="flex justify-between items-center mb-9"><h5 class="richtext tracking-[-.02em] blog-section-header text-[26px] leading-[30px] tablet:text-[24px] tablet:leading-[32px] font-bold tablet:font-medium"></h5></div></div><div class="container max-lg:!mr-0"><div class="flex justify-start flex-nowrap max-w-full overflow-x-auto snap-x snap-mandatory gap-x-gutter pr-[var(--margin)] lg:flex-wrap lg:justify-center lg:overflow-hidden lg:gap-y-gutter lg:mb-gutter lg:mx-auto lg:py-4 lg:pr-0"><div class="popular-posts support-card shrink-0 snap-center w-[296px] lg:basis-1/4-gutter"><article class="article--index"><div class="blogPost pt-4"><div class="uppercase font-shopifysans font-medium pb-2 text-[12px] leading-[20px] tracking-[0.02em] text-shade-100 hover:text-link-light-hover"><a href="/topics/development" class="no-underline text-engineering-dark-text font-shopifysans font-normal hover:text-eyebrow-dark-text">Development</a></div><div class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal"><a class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal" href="/introducing-ruvy" rel="" target="_self">Introducing Ruvy</a></div><p class="richtext text-body-sm font-normal text-engineering-dark-author-text font-shopifysans">2023-10-18</p></div></article></div><div class="popular-posts support-card shrink-0 snap-center w-[296px] lg:basis-1/4-gutter"><article class="article--index"><div class="blogPost pt-4"><div class="uppercase font-shopifysans font-medium pb-2 text-[12px] leading-[20px] tracking-[0.02em] text-shade-100 hover:text-link-light-hover"><a href="/topics/developer-tooling" class="no-underline text-engineering-dark-text font-shopifysans font-normal hover:text-eyebrow-dark-text">Developer Tooling</a></div><div class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal"><a class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal" href="/building-a-shopifyql-code-editor" rel="" target="_self">Building a ShopifyQL Code Editor</a></div><p class="richtext text-body-sm font-normal text-engineering-dark-author-text font-shopifysans">2023-09-11</p></div></article></div><div class="popular-posts support-card shrink-0 snap-center w-[296px] lg:basis-1/4-gutter"><article class="article--index"><div class="blogPost pt-4"><div class="uppercase font-shopifysans font-medium pb-2 text-[12px] leading-[20px] tracking-[0.02em] text-shade-100 hover:text-link-light-hover"><a href="/topics/apps" class="no-underline text-engineering-dark-text font-shopifysans font-normal hover:text-eyebrow-dark-text">Apps</a></div><div class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal"><a class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal" href="/shopifys-platform-is-the-web-platform" rel="" target="_self">Shopify’s platform is the Web platform</a></div><p class="richtext text-body-sm font-normal text-engineering-dark-author-text font-shopifysans">2023-07-26</p></div></article></div><div class="popular-posts support-card shrink-0 snap-center w-[296px] lg:basis-1/4-gutter"><article class="article--index"><div class="blogPost pt-4"><div class="uppercase font-shopifysans font-medium pb-2 text-[12px] leading-[20px] tracking-[0.02em] text-shade-100 hover:text-link-light-hover"><a href="/topics/development" class="no-underline text-engineering-dark-text font-shopifysans font-normal hover:text-eyebrow-dark-text">Development</a></div><div class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal"><a class="tracking-[-.02em] pb-4 hover:underline text-base desktop:text-xl text-engineering-dark-text font-aktivgroteskextended font-normal [&amp;_h4]:font-normal" href="/building-flex-comp" rel="" target="_self">The Engineering Story Behind Flex Comp</a></div><p class="richtext text-body-sm font-normal text-engineering-dark-author-text font-shopifysans">2022-10-05</p></div></article></div></div><div class="mobile-indicators mt-6 flex justify-center gap-x-2 lg:hidden"><div class="rounded-full box-content w-2 h-2 bg-black w-2 h-2 !bg-[#E7ECFB]"></div><div class="rounded-full box-content w-2 h-2 bg-[#d9d9d9] w-2 h-2 !bg-[#9AA7C8]"></div><div class="rounded-full box-content w-2 h-2 bg-[#d9d9d9] w-2 h-2 !bg-[#9AA7C8]"></div><div class="rounded-full box-content w-2 h-2 bg-[#d9d9d9] w-2 h-2 !bg-[#9AA7C8]"></div></div></div></div></section><div class="relative overflow-hidden bg-engineering-banner-bg py-10 tablet-xl:py-20 bg-[#61d095]" data-component-name="cta-footer-banner"><div class="container relative flex flex-col items-center text-center py-10 tablet-xl:py-16"><span class="richtext uppercase font-medium font-aktivgroteskextended mb-2 text-[28px] tablet-xl:text-[42px] desktop:text-[48px] max-w-[800px] tablet-xl:mb-6 leading-[36px] tablet-xl:leading-[46px] desktop:leading-[56px] text-black">Work from anywhere <br/> with Shopify</span><p class="richtext max-w-[680px] text-body-base tablet-xl:text-body-lg mb-2 tablet-xl:mb-9 text-black">See our open roles and learn more about our digital by design culture.</p><div data-component-name="button-group" class="text-base relative z-20 w-[273px] h-14" data-mode="light"><div class="flex gap-y-sm flex-wrap gap-x-sm justify-center"><a href="https://www.shopify.com/careers#Engineering" class="inline-block self-center overflow-hidden max-w-full px-button-px py-button-py ring-inset rounded-button text-button-size font-button-font font-button-weight tracking-button-tracking transition-all duration-150 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-state-focus focus-visible:outline md:px-button-lg-px md:py-button-lg-py md:text-button-lg-size border-2 text-button-light-primary-text bg-button-light-primary-bg border-button-light-primary-border ring-button-light-primary-border hover:text-button-light-primary-text-hover hover:bg-button-light-primary-bg-hover hover:border-button-light-primary-border-hover hover:ring-button-light-primary-border-hover focus:text-button-light-primary-text-focus focus:bg-button-light-primary-bg-focus focus:border-button-light-primary-border-focus focus:ring-button-light-primary-border-focus active:text-button-light-primary-text-active active:bg-button-light-primary-bg-active active:border-button-light-primary-border-active active:ring disabled:text-button-light-primary-text-disabled disabled:bg-button-light-primary-bg-disabled disabled:border-button-light-primary-border-disabled disabled:ring-button-light-primary-border-disabled w-[273px] sm:w-full" data-component-name="button" data-mode="light" target="_blank" rel="noopener noreferrer">See open roles</a></div></div></div></div></main><footer class="relative flex flex-col font-shopifysans bg-black text-white"><div id="NewWindowExternalSite" class="hidden">Opens an external site in a new window</div><section class="container flex flex-col gap-16 py-16 my-0 sm:justify-start sm:pb-16 md:flex-row md:flex-nowrap md:justify-between md:pb-20 md:py-20 md:gap-20 xl:w-full bg-black text-white" data-section-name="footer" data-section-index="1" data-component-name="footer" data-viewable-component="true"><div class="min-w-[100px]"><svg fill="none" role="img" viewBox="0 0 56 64" class="h-[44px]" height="64" data-component-name="shopify-logo"><title></title><g clip-path="url(#clip0_198_267)"><path d="M37.7102 7.42882C37.7102 7.42882 37.0044 7.6296 35.8448 7.98098C35.6432 7.32843 35.3407 6.57549 34.9374 5.77235C33.6266 3.26255 31.6604 1.90725 29.3414 1.90725C29.1901 1.90725 29.0389 1.90725 28.8372 1.95745C28.7868 1.85706 28.686 1.80686 28.6356 1.70647C27.6273 0.602155 26.3165 0.100194 24.7537 0.15039C21.7288 0.250783 18.7039 2.40921 16.284 6.27431C14.5699 8.9849 13.2591 12.3982 12.9062 15.0586C9.42764 16.1127 7.00774 16.8657 6.95733 16.9159C5.19282 17.468 5.1424 17.5182 4.94075 19.1747C4.7895 20.4296 0.151367 55.7676 0.151367 55.7676L38.2647 62.3433V7.37862C37.9623 7.37862 37.811 7.42882 37.7102 7.42882ZM28.8876 10.1394C26.8711 10.7418 24.6528 11.4445 22.485 12.0971C23.09 9.73784 24.2999 7.37862 25.7115 5.82255C26.2661 5.27039 27.0223 4.61784 27.8794 4.21627C28.7364 6.02333 28.9381 8.48294 28.8876 10.1394ZM24.7537 2.20843C25.4595 2.20843 26.0644 2.35902 26.5686 2.66019C25.7619 3.06176 24.9553 3.71431 24.1991 4.46725C22.2833 6.52529 20.8213 9.73784 20.2163 12.7998C18.4014 13.352 16.5865 13.9041 14.9228 14.4061C16.0319 9.58725 20.1155 2.30882 24.7537 2.20843ZM18.9056 29.8163C19.1072 33.0288 27.6273 33.7316 28.1314 41.3112C28.4843 47.2845 24.9553 51.3504 19.8634 51.6516C13.7129 52.0531 10.3351 48.439 10.3351 48.439L11.6459 42.9174C11.6459 42.9174 15.0237 45.4774 17.746 45.2767C19.5105 45.1763 20.1659 43.7206 20.1155 42.7167C19.8634 38.5002 12.9062 38.7512 12.4525 31.8241C12.0492 26.0014 15.8807 20.1284 24.3503 19.5763C27.6273 19.3755 29.291 20.1786 29.291 20.1786L27.3752 27.4069C27.3752 27.4069 25.2074 26.4029 22.6362 26.6037C18.9056 26.8547 18.8552 29.2139 18.9056 29.8163ZM30.9042 9.53706C30.9042 8.03117 30.7026 5.87274 29.9968 4.06568C32.3158 4.51745 33.4249 7.07745 33.9291 8.63353C33.0216 8.88451 32.0133 9.18568 30.9042 9.53706Z" fill="white"></path><path d="M39.4238 62.2429L55.254 58.3276C55.254 58.3276 48.448 12.4986 48.3976 12.1974C48.3472 11.8963 48.0951 11.6955 47.8431 11.6955C47.591 11.6955 43.1545 11.5951 43.1545 11.5951C43.1545 11.5951 40.4321 8.98488 39.4238 7.98096V62.2429Z" fill="white"></path><path d="M29.2498 20.2129L27.3148 27.4286C27.3148 27.4286 25.1548 26.4426 22.5898 26.6218C18.8098 26.8459 18.8098 29.2213 18.8098 29.8039C19.0348 33.0308 27.5398 33.7479 28.0348 41.3221C28.3948 47.2829 24.8848 51.3613 19.7548 51.6751C13.6348 51.9888 10.2598 48.4034 10.2598 48.4034L11.5648 42.8907C11.5648 42.8907 14.9848 45.4454 17.6848 45.2661C19.4398 45.1765 20.1148 43.6975 20.0248 42.7115C19.7548 38.4986 12.8248 38.7675 12.3748 31.8207C12.0148 25.9944 15.8398 20.1232 24.2998 19.5854C27.5848 19.3613 29.2498 20.2129 29.2498 20.2129Z" fill="black"></path></g><defs><clipPath id="clip0_198_267"><rect width="225" height="64" fill="white"></rect></clipPath></defs></svg></div><div class="md:block md:justify-end"><div class="flex flex-col flex-wrap gap-12 gap-x-4 md:gap-20 md:gap-x-16 sm:grid sm:max-h-fit sm:grid-cols-3 lg:grid-cols-4 max-h-[92rem] sm:max-h-[75rem]"><div class="w-[calc(50%_-_1rem)] sm:w-fit" data-component-name="shopify"><h3 class="text-base font-bold text-white">Shopify</h3><ul class="mt-4 md:mt-6"><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/about" data-component-name="about" aria-label="">About</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/careers" data-component-name="careers" aria-label="">Careers</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://shopifyinvestors.com/home/default.aspx" data-component-name="investors" aria-label="">Investors</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/news" data-component-name="press-and-media" aria-label="">Press and Media</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/partners" data-component-name="partners" aria-label="">Partners</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/affiliates" data-component-name="affiliates" aria-label="">Affiliates</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/legal" data-component-name="legal" aria-label="">Legal</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopifystatus.com/" data-component-name="service-status" aria-label="">Service status</a></li></ul></div><div class="w-[calc(50%_-_1rem)] sm:w-fit" data-component-name="support"><h3 class="text-base font-bold text-white">Support</h3><ul class="mt-4 md:mt-6"><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://help.shopify.com/en/questions" data-component-name="merchant-support" aria-label="">Merchant support</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://help.shopify.com/en/" data-component-name="help-center" aria-label="">Help center</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/partners/directory" data-component-name="hire-a-partner" aria-label="">Hire a Partner</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://academy.shopify.com?itcat=brochure&amp;itterm=global-footer" data-component-name="" aria-label=""></a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://community.shopify.com/c/Shopify-Community/ct-p/en?utm_campaign=footer&amp;utm_content=en&amp;utm_medium=web&amp;utm_source=shopify" data-component-name="shopify-community" aria-label="">Shopify Community</a></li></ul></div><div class="w-[calc(50%_-_1rem)] sm:w-fit" data-component-name="developers"><h3 class="text-base font-bold text-white">Developers</h3><ul class="mt-4 md:mt-6"><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://shopify.dev" data-component-name="shopify-dev" aria-label="">Shopify.dev</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://shopify.dev/api" data-component-name="a-p-i-documentation" aria-label="">API documentation</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://devdegree.ca" data-component-name="dev-degree" aria-label="">Dev Degree</a></li></ul></div><div class="w-[calc(50%_-_1rem)] sm:w-fit" data-component-name="products"><h3 class="text-base font-bold text-white">Products</h3><ul class="mt-4 md:mt-6"><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://shop.app" data-component-name="shop" aria-label="">Shop</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/shop-pay" data-component-name="shop-pay" aria-label="">Shop Pay</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.linkpop.com/" data-component-name="linkpop" aria-label="">Linkpop</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/enterprise" data-component-name="shopify-for-enterprise" aria-label="">Shopify for enterprise</a></li></ul></div><div class="w-[calc(50%_-_1rem)] sm:w-fit" data-component-name="global-impact"><h3 class="text-base font-bold text-white">Global impact</h3><ul class="mt-4 md:mt-6"><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/climate" data-component-name="sustainability" aria-label="">Sustainability</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/about/social-impact" data-component-name="social-impact" aria-label="">Social impact</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/1mbb" data-component-name="build-black" aria-label="">Build Black</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://buildnative.shop" data-component-name="build-native" aria-label="">Build Native</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/plus/commerce-trends" data-component-name="research" aria-label="">Research</a></li></ul></div><div class="w-[calc(50%_-_1rem)] sm:w-fit" data-component-name="solutions"><h3 class="text-base font-bold text-white">Solutions</h3><ul class="mt-4 md:mt-6"><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/online" data-component-name="online-store-builder" aria-label="">Online store builder</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/website/builder" data-component-name="website-builder" aria-label="">Website builder</a></li><li><a class="mt-2 block py-3 text-base font-normal hover:underline md:py-0.5 text-[#E0E0E0] hover:text-white" href="https://www.shopify.com/tour/ecommerce-website" data-component-name="ecommerce-website" aria-label="">Ecommerce website</a></li></ul></div></div></div></section><section class="border-t my-0 container max-sm:mx-0 xl:w-full border-white border-opacity-20 bg-inherit" data-section-name="footer" data-section-index="2" data-component-name="footer" data-viewable-component="true"><div class="flex flex-col items-center justify-center gap-4 py-8 sm:items-start sm:gap-8 lg:flex-row lg:justify-between lg:gap-10 bg-black text-white"><div class="mt-1 flex w-full flex-col items-center justify-center sm:flex-row sm:items-start sm:justify-start sm:gap-8 lg:gap-14"><ul class="flex flex-col items-center flex-wrap sm:flex-row sm:items-start gap-x-8 md:gap-x-10" data-component-name="bottom-nav"><li class="mt-2 block py-3 sm:mt-0 text-[#E0E0E0] hover:text-white"><a class="hover:underline" href="https://www.shopify.com/legal/terms" data-component-name="terms-of-service" aria-label="">Terms of Service</a></li><li class="mt-2 block py-3 sm:mt-0 text-[#E0E0E0] hover:text-white"><a class="hover:underline" href="https://www.shopify.com/legal/privacy" data-component-name="privacy-policy" aria-label="">Privacy Policy</a></li><li class="mt-2 block py-3 sm:mt-0 text-[#E0E0E0] hover:text-white"><a class="hover:underline" href="https://www.shopify.com/sitemap" data-component-name="sitemap" aria-label="">Sitemap</a></li><li class="mt-2 block py-3 sm:mt-0 text-[#E0E0E0] hover:text-white"><a class="hover:underline" href="https://privacy.shopify.com/en" data-component-name="privacy-choices" aria-label="">Privacy Choices</a></li></ul></div><ul class="flex gap-4 md:gap-6" data-component-name="social"><li><a class="block h-8 w-8 fill-white hover:fill-shade-30" href="https://www.facebook.com/shopify" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-facebook" aria-label=""><svg viewBox="0 0 30 30"><path d="M15.6 30V19.4h5V15h-5v-3.1c0-1 .6-1.9 1.3-1.9h3.8V5.6h-3.8c-3.1 0-5.6 2.8-5.6 6.3V15H7.5v4.4h3.8v10.1C4.8 27.9 0 22 0 15 0 6.7 6.7 0 15 0s15 6.7 15 15c0 8.1-6.4 14.7-14.4 15z"></path></svg></a></li><li><a class="block h-8 w-8 fill-white hover:fill-shade-30" href="https://x.com/shopifyeng" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-twitter" aria-label=""><svg viewBox="0 0 19 19"><path fill-rule="evenodd" clip-rule="evenodd" d="M19 9.5C19 14.7467 14.7467 19 9.5 19C4.25329 19 0 14.7467 0 9.5C0 4.25329 4.25329 0 9.5 0C14.7467 0 19 4.25329 19 9.5ZM8.18721 10.0675L3.64633 3.99603H7.14609L10.123 7.97639L13.8073 3.99603H14.8358L10.5825 8.59099L15.379 15.0039H11.8792L8.64689 10.6819L4.6462 15.0039H3.61771L8.18721 10.0675ZM6.76655 4.75353H5.15877L12.2586 14.2464H13.8663L6.76655 4.75353Z"></path></svg></a></li><li><a class="block h-8 w-8 fill-white hover:fill-shade-30" href="https://www.youtube.com/user/shopify" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-youtube" aria-label=""><svg viewBox="0 0 30 30"><path d="M30 15c0 8.3-6.7 15-15 15S0 23.3 0 15 6.7 0 15 0s15 6.7 15 15zm-5.6 0c0-6.9 0-6.9-9.4-6.9s-9.4 0-9.4 6.9 0 6.9 9.4 6.9 9.4 0 9.4-6.9zm-11.9-3.7 6.3 3.8-6.3 3.8v-7.6z"></path></svg></a></li><li><a class="block h-8 w-8 fill-white hover:fill-shade-30" href="https://www.instagram.com/shopify/" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-instagram" aria-label=""><svg viewBox="0 0 18 18"><path clip-rule="evenodd" d="M9 0c5 0 9 4 9 9s-4 9-9 9-9-4-9-9 4-9 9-9zM6.7 3.5h4.7c1.8 0 3.2 1.4 3.2 3.1v4.7c0 1.7-1.4 3.1-3.2 3.1H6.7c-1.8 0-3.2-1.4-3.2-3.1V6.7c0-1.8 1.4-3.2 3.2-3.2zM9 6.2c1.6 0 2.9 1.3 2.9 2.9S10.6 12 9 12s-3-1.3-3-2.9 1.3-2.9 3-2.9zm0 1.2c.9 0 1.7.8 1.7 1.7 0 .9-.8 1.7-1.7 1.7-.9 0-1.7-.8-1.7-1.7 0-.9.7-1.7 1.7-1.7zm2.8-1.7c.3 0 .6.2.6.6 0 .3-.2.6-.6.6-.3 0-.6-.2-.6-.6 0-.4.3-.6.6-.6zm-4.6-1h3.7c1.4 0 2.5 1.1 2.5 2.5v3.7c0 1.4-1.1 2.5-2.5 2.5H7.2c-1.4 0-2.5-1.1-2.5-2.5V7.2c-.1-1.4 1.1-2.5 2.5-2.5z" fill-rule="evenodd"></path></svg></a></li><li><a class="block h-8 w-8 fill-white hover:fill-shade-30" href="https://www.tiktok.com/@shopify" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-tiktok" aria-label=""><svg viewBox="0 0 30 30"><path fill-rule="evenodd" clip-rule="evenodd" d="M15 30C23.2843 30 30 23.2843 30 15C30 6.71573 23.2843 0 15 0C6.71573 0 0 6.71573 0 15C0 23.2843 6.71573 30 15 30ZM17.9679 11.937C19.1423 12.7643 20.551 13.2076 21.9952 13.2041V10.3838C21.2138 10.3841 20.4497 10.1579 19.7979 9.73365C19.3398 9.44298 18.9467 9.06336 18.6427 8.61802C18.3387 8.17268 18.1302 7.67098 18.0299 7.1436C17.9873 6.90737 17.9669 6.66779 17.9689 6.42789H15.0829V17.7069C15.0829 18.2146 14.92 18.7094 14.6173 19.1206C14.3146 19.5317 13.8877 19.8382 13.3975 19.9962C12.9073 20.1543 12.3789 20.1558 11.8878 20.0006C11.3966 19.8454 10.9679 19.5414 10.6627 19.1321C10.3576 18.7227 10.1916 18.2289 10.1886 17.7211C10.1856 17.2134 10.3456 16.7177 10.6458 16.3048C10.946 15.8919 11.3711 15.583 11.8604 15.4221C12.3496 15.2612 12.878 15.2566 13.37 15.409V12.5068C12.2944 12.3595 11.1987 12.539 10.2295 13.0213C9.26031 13.5037 8.46383 14.2658 7.94658 15.2058C7.42933 16.1458 7.21597 17.2188 7.33504 18.2814C7.4541 19.344 7.89992 20.3455 8.61286 21.1519C9.3258 21.9583 10.2719 22.5312 11.3246 22.794C12.3772 23.0568 13.4863 22.997 14.5035 22.6225C15.5207 22.2479 16.3975 21.5766 17.0166 20.6983C17.6357 19.82 17.9676 18.7766 17.9679 17.7075V11.937Z"></path></svg></a></li><li><a class="block h-8 w-8 fill-white hover:fill-shade-30" href="https://www.linkedin.com/company/shopify" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-linkedin" aria-label=""><svg viewBox="0 0 30 30"><path d="M30 15c0 8.3-6.7 15-15 15S0 23.3 0 15 6.7 0 15 0s15 6.7 15 15zM11.3 7.8c0-1.2-1-2.2-2.5-2.2s-2.5.9-2.5 2.2c0 1.2 1 2.2 2.5 2.2s2.5-1 2.5-2.2zm-.7 4.1H6.9v10.6h3.8V11.9zm13.8 5c0-3.4-1.7-5.6-4.4-5.6-1.5 0-2.6.9-3.1 2.3l-.1-1.6H13c0 .4.1 2.5.1 2.5v8.1h3.8V17c0-1.5.7-2.5 1.8-2.5s1.9.6 1.9 2.5v5.6h3.8v-5.7z"></path></svg></a></li><li><a class="block h-8 w-8 fill-white hover:fill-shade-30" href="https://www.pinterest.com/shopify/" rel="me nofollow noopener noreferrer" target="_blank" data-component-name="social-pinterest" aria-label=""><svg viewBox="0 0 30 30"><path d="M15 30c-1.5 0-2.9-.2-4.3-.6.6-.9 1.2-2 1.5-3.2.2-.7 1-4.1 1-4.1.5 1 2 1.9 3.7 1.9 4.8 0 8.1-4.4 8.1-10.3 0-4.4-3.8-8.6-9.5-8.6-7.1-.1-10.6 5-10.6 9.3 0 2.6 1 4.9 3.1 5.7.3.1.7 0 .8-.4.1-.2.2-.9.3-1.2.1-.4 0-.5-.2-.8-.6-.7-1-1.6-1-3 0-3.8 2.8-7.2 7.4-7.2 4 0 6.2 2.5 6.2 5.8 0 4.3-1.9 8-4.8 8-1.6 0-2.7-1.3-2.4-2.9.5-1.9 1.3-4 1.3-5.3 0-1.2-.7-2.3-2-2.3-1.6 0-2.9 1.7-2.9 3.9 0 1.4.5 2.4.5 2.4S9.5 24 9.3 25.3c-.3 1.1-.4 2.4-.3 3.5-5.3-2.4-9-7.7-9-13.8C0 6.7 6.7 0 15 0s15 6.7 15 15-6.7 15-15 15z"></path></svg></a></li></ul></div></section></footer></div></div><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/entry.client-DwbT9GtS.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/components-Cjscd6JA.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/enums-CqpDel02.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/I18N-DkwmfuKm.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Wrapper-DhZo7Nml.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Section-Bl3w6paD.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/SideBySide-BIF2EZB1.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Provider-CQVAfcXd.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/HeadingGroup-1EyidEKf.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/root-DSZyfDOi.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/blog-CclTzyK5.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/layout-CR0rDhCR.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/EngineeringBlogFooter-C9aEb07f.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/XIcon.svg-1TYfFUVM.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/EngineeringPageLayout-BenRbSvA.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Checkmark-DIG1PPwH.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/ArticleCarousel-C5IDQsOb.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/useBlogPageTitle-B_wJ7raq.js"/><link rel="modulepreload" href="https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/route-BuTjHipM.js"/><script>window.__remixContext = {"url":"/building-a-shopifyql-code-editor","basename":"/","future":{"v3_fetcherPersist":false,"v3_relativeSplatPath":false,"v3_throwAbortReason":false,"unstable_singleFetch":false,"unstable_fogOfWar":true},"isSpaMode":false,"state":{"loaderData":{"pages/shopify.engineering/$article":{"article":{"__typename":"Article","authorV2":{"__typename":"ArticleAuthor","name":"Trevor Harmon","avatarUrl":"https://www.gravatar.com/avatar/0babda2d09f79e01febb2fd5be4ec6fa?s=200\u0026d=404","isDisabledAuthor":false},"id":"gid://shopify/Article/557957480504","handle":"building-a-shopifyql-code-editor","title":"Building a ShopifyQL Code Editor","publishedAt":"2023-09-11T14:02:54Z","image":{"__typename":"Image","url":"https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014","altText":null,"width":1215,"height":510},"tags":["Developer Tooling","Popular"],"contentHtml":"\u003cp\u003eIn October 2022, Shopify released ShopifyQL Notebooks, a first-party app that lets merchants analyze their shop data to make better decisions. It puts the power of ShopifyQL into merchants’ hands with a guided code editing experience. In order to provide a first-class editing experience, we turned to \u003ca href=\"https://codemirror.net/\" target=\"_blank\" title=\"CodeMirror\" rel=\"noopener noreferrer\"\u003eCodeMirror\u003c/a\u003e, a code editor framework built for the web. Out of the box, CodeMirror didn’t have support for ShopifyQL–here’s how we built it.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eShopifyQL Everywhere\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://shopify.engineering/shopify-commerce-data-querying-language-shopifyql\" target=\"_blank\" title=\"Shopify Engineering\" rel=\"noopener noreferrer\"\u003e ShopifyQL\u003c/a\u003e is an accessible, commerce-focused querying language used on both the client and server. The language is defined by an \u003ca href=\"http://antlr.org\" target=\"_blank\" title=\"ANTLR\" rel=\"noopener noreferrer\"\u003eANTLR\u003c/a\u003e grammar and is used to generate code for multiple targets (currently, Go and Typescript). This lets us share the same grammar definition between both the client and server despite differences in runtime language. As an added benefit, we have types written in Protobuf so that types can be shared between targets as well.\u003c/p\u003e\n\u003cp\u003eAll the ShopifyQL language features on the front end are encapsulated into a typescript language server, which is built on top of the ANTLR typescript target. It conforms to Microsoft's\u003ca href=\"https://microsoft.github.io/language-server-protocol/\" target=\"_blank\" title=\"Microsoft GitHub\" rel=\"noopener noreferrer\"\u003e language server protocol\u003c/a\u003e (LSP) in order to keep a clear separation of concerns between the language server and a code editor. LSP defines the shape of common language features like tokenization, parsing, completion, hover tooltips, and linting.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Flow from ShopifyQL Language Server from LSP to Editor\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/ShopifyQLEverywhere.png?v=1694031194\"\u003e\u003c/p\u003e\n\u003cp\u003eWhen code editors and language servers both conform to LSP, they become interoperable because they speak a common language. For more information about LSP, read the \u003ca href=\"https://code.visualstudio.com/api/language-extensions/language-server-extension-guide\"\u003eVSCode Language Server Extension Guide\u003c/a\u003e.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eConnecting The ShopifyQL Language Server To CodeMirror\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eCodeMirror has its own grammar \u0026amp; parser engine called \u003ca href=\"https://lezer.codemirror.net\" target=\"_blank\" title=\"Lezer\" rel=\"noopener noreferrer\"\u003eLezer\u003c/a\u003e. Lezer is used within CodeMirror to generate parse trees, and those trees power many of the editor features. Lezer has support for common languages, but no Lezer grammar exists for ShopifyQL. Lezer also doesn’t conform to LSP. Because ShopifyQL’s grammar and language server had already been written in ANTLR, it didn’t make sense to rewrite what we had as a Lezer grammar. Instead, we decided to create an adapter that would conform to LSP and integrate with Lezer. This allowed us to pass a ShopifyQL query to the language server, adapt the response, and return a Lezer parse tree.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Flow chart of ShopifyQL language server from LSP to custom adapter to/from Lezer\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Connecting_The_ShopifyQL_Language_Server_To_CodeMirror.png?v=1694031395\"\u003e\u003c/p\u003e\n\u003cp\u003eLezer supports creating a tree in one of two ways:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eManually creating a tree by creating nodes and attaching them in the correct tree shape\u003c/li\u003e\n\u003cli\u003eGenerating a tree from a buffer of tokens\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThe ShopifyQL language server can create a stream of tokens from a document, so it made sense to re-shape that stream into a buffer that Lezer understands.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eConverting A ShopifyQL Query Into A Lezer Tree\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eIn order to transform a ShopifyQL query into a Lezer parse tree, the following steps occur:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eLezer initiates the creation of a parse tree. This happens when the document is first loaded and any time the document changes.\u003c/li\u003e\n\u003cli\u003eOur custom adapter takes the ShopifyQL query and passes it to the language server.\u003c/li\u003e\n\u003cli\u003eThe language server returns a stream of tokens that describe the ShopifyQL query.\u003c/li\u003e\n\u003cli\u003eThe adapter takes those tokens and transforms them into Lezer node types.\u003c/li\u003e\n\u003cli\u003eThe Lezer node types are used to create a buffer that describes the document.\u003c/li\u003e\n\u003cli\u003eThe buffer is used to build a Lezer tree.\u003c/li\u003e\n\u003cli\u003eFinally, it returns the tree back to Lezer and completes the parse cycle.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cimg src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/converting_a_shopifyql_query_into_a_lezer_tree_18358861-5ee6-41c7-9fbc-5a4c8be04d9c.png?v=1694202679\" alt=\"Flow chart of above steps converting a shopifyql query into a lezer tree\" data-mce-fragment=\"1\" data-mce-src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/converting_a_shopifyql_query_into_a_lezer_tree_18358861-5ee6-41c7-9fbc-5a4c8be04d9c.png?v=1694202679\"\u003e\u003cbr\u003e\n\u003col\u003e\u003c/ol\u003e\n\u003ch2\u003e\u003cstrong\u003eUnderstanding ShopifyQL’s Token Offset\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eOne of the biggest obstacles to transforming the language server’s token stream into a Lezer buffer was the format of the tokens. Within the ShopifyQL Language Server, the tokens come back as integers in chunks of 5, with the position of each integer having distinct meaning:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/fee6f634f71db29c8d71b46b19650f7b.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\n\u003cp\u003eIn this context, length, token type, and token modifier were fairly straightforward to use. However, the behavior of line and start character were more difficult to understand. Imagine a simple ShopifyQL query like this:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/46cb2d42ed1ba0baa23cf7bdbcf1233c.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\n\u003cp\u003eThis query would be tokenized like this:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/6570d96362bce6b38ff4ca527b5f45cc.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eIn the stream of tokens, even though \u003ccode\u003eproduct_title\u003c/code\u003e is on line 1 (using zero-based indexes), the value for its line integer is zero! This is because the tokenization happens incrementally and each computed offset value is always relative to the previous token. This becomes more confusing when you factor in whitespace-let’s say that we add five spaces before the word \u003ccode\u003eSHOW\u003c/code\u003e:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/3534fdbedd19b4ab0874b5248930adf4.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eThe tokens for this query are:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/5a9ac4131334568473cbbadcbb2514f6.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eNotice that only the start character for \u003ccode\u003eSHOW\u003c/code\u003e changed! It changed from \u003ccode\u003e0\u003c/code\u003e to \u003ccode\u003e5\u003c/code\u003e after adding five spaces before the \u003ccode\u003eSHOW\u003c/code\u003e keyword. However, \u003ccode\u003eproduct_title\u003c/code\u003e’s values remain unchanged. This is because the values are relative to the previous token, and the space between \u003ccode\u003eSHOW\u003c/code\u003e and \u003ccode\u003eproduct_title\u003c/code\u003e didn’t change.\u003c/p\u003e\n\u003cp\u003eThis becomes especially confusing when you use certain language features that are parsed out of order. For example, in some ANTLR grammars, comments are not parsed as part of the default \u003ca href=\"https://datacadamia.com/antlr/channel\" target=\"_blank\" title=\"Datacadamia\" rel=\"noopener noreferrer\"\u003echannel\u003c/a\u003e–they are parsed after everything in the main channel is parsed. Let’s add a comment to the first line:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/f59af161c23171559c683ff725dd284f.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eThe tokens for this query look like this (and are in this order):\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/4ec2a5cedc25734cc2889cb7cf3845a4.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eBefore the parser parses the comment, it points at \u003ccode\u003eproduct_title\u003c/code\u003e, which is two lines after the comment. When the parser finishes with the main channel and begins parsing the channel that contains the comment, the pointer needs to move two lines up to tokenize the comment–hence the value of -2 for the comment’s line integer.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eAdapting ShopifyQL’s Token Offset To Work With CodeMirror\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eCodeMirror treats offset values much simpler than ANTLR. In CodeMirror, everything is relative to the top of the document–the document is treated as one long string of text. This means that newlines and whitespace are meaningful to CodeMirror and affect the start offset of a token.\u003c/p\u003e\n\u003cp\u003eSo to adapt the values from ANTLR to work with CodeMirror, we need to take these values:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/af642957edf58a56951743c1ab5cc94b.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eAnd convert them into this:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/26ae6dbc46c0e89b452c9f0a007c8730.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eThe solution? A custom \u003ccode\u003eTokenIterator\u003c/code\u003e that could follow the “directions” of the Language Server’s offsets and convert them along the way. The final implementation of this class was fairly simple, but arriving at this solution was the hard part.\u003c/p\u003e\n\u003cp\u003eAt a high level, the \u003ccode\u003eTokenIterator\u003c/code\u003e class:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eTakes in the document and derives the length of each line. This means that trailing whitespace is properly represented.\u003c/li\u003e\n\u003cli\u003eInternally tracks the current line and character that the iterator points to.\u003c/li\u003e\n\u003cli\u003eIngests the ANTLR-style line, character, and token length descriptors and moves the current line and character to the appropriate place.\u003c/li\u003e\n\u003cli\u003eUses the current line, current character, and line lengths to compute the CodeMirror-style start offset.\u003c/li\u003e\n\u003cli\u003eUses the start offset combined with the token length to compute the end offset.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eHere’s what the code looks like:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/6082c4ce8d18711ac1df0f47cd8eadff.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003ch2\u003e\u003cstrong\u003eBuilding A Parse Tree\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eNow that we have a clear way to convert an ANTLR token stream into a Lezer buffer, we’re ready to build our tree! To build it, we follow the steps mentioned previously–we take in a ShopifyQL query, use the language server to convert it to a token stream, transform that stream into a buffer of nodes, and then build a tree from that buffer.\u003c/p\u003e\n\u003cp\u003eOnce the parse tree is generated, CodeMirror then “understands” ShopifyQL and provides useful language features such as syntax highlighting.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Syntax highlight line 1 FROM products line 2 SHOW product_title\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Building_A_Parse_Tree.png?v=1694031668\"\u003e\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eProviding Additional Language Features\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eBy this point, CodeMirror can talk to the ShopifyQL Language Server and build a parse tree that describes the ShopifyQL code. However, the language server offers other useful features like code completion, linting, and tooltips. As mentioned above, Lezer/CodeMirror doesn’t conform to LSP–but it does offer many plugins that let us provide a connector between our language server and CodeMirror. In order to provide these features, we adapted the language server’s \u003ccode\u003edoValidate\u003c/code\u003e with CodeMirror’s \u003ccode\u003elinting\u003c/code\u003e plugin, the language server’s \u003ccode\u003edoComplete\u003c/code\u003e with CodeMirror’s \u003ccode\u003eautocomplete\u003c/code\u003e plugin, and the language server’s \u003ccode\u003edoHover\u003c/code\u003e with CodeMirror’s \u003ccode\u003erequestHoverTooltips\u003c/code\u003e plugin.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"CodeMirror to Custom Adapter to Language Server\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Providing_Additional_Language_Features.png?v=1694031422\"\u003e\u003c/p\u003e\n\u003cp\u003eOnce we connect those features, our ShopifyQL code editor is fully powered up, and we get an assistive, delightful code editing experience.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Gif show editing experience through a drop down in syntax highlighting\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Providing_Additional_Language_Features.gif?v=1694031415\"\u003e\u003c/p\u003e\n\n\u003ch2\u003e\u003cstrong\u003eConclusion\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eThis approach enabled us to provide ShopifyQL features to CodeMirror while continuing to maintain a grammar that serves both client and server. The custom adapter we created allows us to pass a ShopifyQL query to the language server, adapt the response, and return a Lezer parse tree to CodeMirror, making it possible to provide features like syntax highlighting, code completion, linting, and tooltips. Because our solution utilizes CodeMirror’s internal parse tree, we are able to make better decisions in the code and craft a stronger editing experience. The ShopifyQL code editor helps merchants write ShopifyQL and get access to their data in new and delightful ways.\u003c/p\u003e\n\u003cdiv class=\"marketing-block marketing-block--light marketing-block--padded\"\u003e\n\u003cp\u003eThis post was written by \u003cstrong\u003e Trevor Harmon\u003c/strong\u003e, a Senior Developer working to make reporting and analytics experiences richer and more informative for merchants. When he isn't writing code, he spends time writing music, volunteering at his church, and hanging out with his wife and daughter. You can find more articles on topics like this one on his blog at \u003ca title=\"The Trevor Harmon\" href=\"https://thetrevorharmon.com/\" data-sanitized-target=\"_blank\"\u003ethetrevorharmon.com\u003c/a\u003e, or follow him on \u003ca title=\"GitHub - Trevor Harmon\" href=\"https://github.com/thetrevorharmon\" data-sanitized-target=\"_blank\"\u003eGitHub\u003c/a\u003e and \u003ca title=\"Twitter - Trevor Harmon\" href=\"https://twitter.com/thetrevorharmon\" data-sanitized-target=\"_blank\"\u003eTwitter\u003c/a\u003e.\u003c/p\u003e\n\u003c/div\u003e","excerpt":"","excerptHtml":"","seo":{"__typename":"SEO","title":null,"description":null},"metafields":[null,null,null,null,null,null,null],"imageUrl":"https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014","imageAltText":null,"imageClass":"","modifiedAt":"Sep 11, 2023","modifiedAtRaw":"2023-09-11T14:02:54Z"},"metaDescription":"In October 2022, Shopify released ShopifyQL Notebooks, a first-party app that lets merchants analyze their shop data to make better decisions. It puts the power of ShopifyQL into merchants’ hands with a guided code editing experience. In order to provide a first-class editing experience, we turned to CodeMirror, a code editor framework built for the web. Out of the box, CodeMirror didn’t have support for ShopifyQL–here’s how we built it.\nShopifyQL Everywhere\n ShopifyQL is an accessible, commerce-focused querying language used on both the client and server. The language is defined by an ANTLR grammar and is used to generate code for multiple targets (currently, Go and Typescript). This lets us share the same grammar definition between both the client and server despite differences in runtime language. As an added benefit, we have types written in Protobuf so that types can be shared between targets as well.\nAll the ShopifyQL language features on the front end are encapsulated into a typescript language server, which is built on top of the ANTLR typescript target. It conforms to Microsoft's language server protocol (LSP) in order to keep a clear separation of concerns between the language server and a code editor. LSP defines the shape of common language features like tokenization, parsing, completion, hover tooltips, and linting.\n\nWhen code editors and language servers both conform to LSP, they become interoperable because they speak a common language. For more information about LSP, read the VSCode Language Server Extension Guide.\nConnecting The ShopifyQL Language Server To CodeMirror\nCodeMirror has its own grammar \u0026 parser engine called Lezer. Lezer is used within CodeMirror to generate parse trees, and those trees power many of the editor features. Lezer has support for common languages, but no Lezer grammar exists for ShopifyQL. Lezer also doesn’t conform to LSP. Because ShopifyQL’s grammar and language server had already been written in ANTLR, it didn’t make sense to rewrite what we had as a Lezer grammar. Instead, we","contentHtml":"\u003cp\u003eIn October 2022, Shopify released ShopifyQL Notebooks, a first-party app that lets merchants analyze their shop data to make better decisions. It puts the power of ShopifyQL into merchants’ hands with a guided code editing experience. In order to provide a first-class editing experience, we turned to \u003ca href=\"https://codemirror.net/\" target=\"_blank\" title=\"CodeMirror\" rel=\"nofollow noopener noreferrer\"\u003eCodeMirror\u003c/a\u003e, a code editor framework built for the web. Out of the box, CodeMirror didn’t have support for ShopifyQL–here’s how we built it.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eShopifyQL Everywhere\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"/shopify-commerce-data-querying-language-shopifyql\" target=\"_blank\" title=\"Shopify Engineering\"\u003e ShopifyQL\u003c/a\u003e is an accessible, commerce-focused querying language used on both the client and server. The language is defined by an \u003ca href=\"http://antlr.org\" target=\"_blank\" title=\"ANTLR\"\u003eANTLR\u003c/a\u003e grammar and is used to generate code for multiple targets (currently, Go and Typescript). This lets us share the same grammar definition between both the client and server despite differences in runtime language. As an added benefit, we have types written in Protobuf so that types can be shared between targets as well.\u003c/p\u003e\n\u003cp\u003eAll the ShopifyQL language features on the front end are encapsulated into a typescript language server, which is built on top of the ANTLR typescript target. It conforms to Microsoft's\u003ca href=\"https://microsoft.github.io/language-server-protocol/\" target=\"_blank\" title=\"Microsoft GitHub\" rel=\"nofollow noopener noreferrer\"\u003e language server protocol\u003c/a\u003e (LSP) in order to keep a clear separation of concerns between the language server and a code editor. LSP defines the shape of common language features like tokenization, parsing, completion, hover tooltips, and linting.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Flow from ShopifyQL Language Server from LSP to Editor\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/ShopifyQLEverywhere.png?v=1694031194\"\u003e\u003c/p\u003e\n\u003cp\u003eWhen code editors and language servers both conform to LSP, they become interoperable because they speak a common language. For more information about LSP, read the \u003ca href=\"https://code.visualstudio.com/api/language-extensions/language-server-extension-guide\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003eVSCode Language Server Extension Guide\u003c/a\u003e.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eConnecting The ShopifyQL Language Server To CodeMirror\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eCodeMirror has its own grammar \u0026amp; parser engine called \u003ca href=\"https://lezer.codemirror.net\" target=\"_blank\" title=\"Lezer\" rel=\"nofollow noopener noreferrer\"\u003eLezer\u003c/a\u003e. Lezer is used within CodeMirror to generate parse trees, and those trees power many of the editor features. Lezer has support for common languages, but no Lezer grammar exists for ShopifyQL. Lezer also doesn’t conform to LSP. Because ShopifyQL’s grammar and language server had already been written in ANTLR, it didn’t make sense to rewrite what we had as a Lezer grammar. Instead, we decided to create an adapter that would conform to LSP and integrate with Lezer. This allowed us to pass a ShopifyQL query to the language server, adapt the response, and return a Lezer parse tree.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Flow chart of ShopifyQL language server from LSP to custom adapter to/from Lezer\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Connecting_The_ShopifyQL_Language_Server_To_CodeMirror.png?v=1694031395\" loading=\"lazy\"\u003e\u003c/p\u003e\n\u003cp\u003eLezer supports creating a tree in one of two ways:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eManually creating a tree by creating nodes and attaching them in the correct tree shape\u003c/li\u003e\n\u003cli\u003eGenerating a tree from a buffer of tokens\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThe ShopifyQL language server can create a stream of tokens from a document, so it made sense to re-shape that stream into a buffer that Lezer understands.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eConverting A ShopifyQL Query Into A Lezer Tree\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eIn order to transform a ShopifyQL query into a Lezer parse tree, the following steps occur:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eLezer initiates the creation of a parse tree. This happens when the document is first loaded and any time the document changes.\u003c/li\u003e\n\u003cli\u003eOur custom adapter takes the ShopifyQL query and passes it to the language server.\u003c/li\u003e\n\u003cli\u003eThe language server returns a stream of tokens that describe the ShopifyQL query.\u003c/li\u003e\n\u003cli\u003eThe adapter takes those tokens and transforms them into Lezer node types.\u003c/li\u003e\n\u003cli\u003eThe Lezer node types are used to create a buffer that describes the document.\u003c/li\u003e\n\u003cli\u003eThe buffer is used to build a Lezer tree.\u003c/li\u003e\n\u003cli\u003eFinally, it returns the tree back to Lezer and completes the parse cycle.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cimg src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/converting_a_shopifyql_query_into_a_lezer_tree_18358861-5ee6-41c7-9fbc-5a4c8be04d9c.png?v=1694202679\" alt=\"Flow chart of above steps converting a shopifyql query into a lezer tree\" data-mce-fragment=\"1\" data-mce-src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/converting_a_shopifyql_query_into_a_lezer_tree_18358861-5ee6-41c7-9fbc-5a4c8be04d9c.png?v=1694202679\" loading=\"lazy\"\u003e\u003cbr\u003e\n\u003col\u003e\u003c/ol\u003e\n\u003ch2\u003e\u003cstrong\u003eUnderstanding ShopifyQL’s Token Offset\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eOne of the biggest obstacles to transforming the language server’s token stream into a Lezer buffer was the format of the tokens. Within the ShopifyQL Language Server, the tokens come back as integers in chunks of 5, with the position of each integer having distinct meaning:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/fee6f634f71db29c8d71b46b19650f7b.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\n\u003cp\u003eIn this context, length, token type, and token modifier were fairly straightforward to use. However, the behavior of line and start character were more difficult to understand. Imagine a simple ShopifyQL query like this:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/46cb2d42ed1ba0baa23cf7bdbcf1233c.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\n\u003cp\u003eThis query would be tokenized like this:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/6570d96362bce6b38ff4ca527b5f45cc.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eIn the stream of tokens, even though \u003ccode\u003eproduct_title\u003c/code\u003e is on line 1 (using zero-based indexes), the value for its line integer is zero! This is because the tokenization happens incrementally and each computed offset value is always relative to the previous token. This becomes more confusing when you factor in whitespace-let’s say that we add five spaces before the word \u003ccode\u003eSHOW\u003c/code\u003e:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/3534fdbedd19b4ab0874b5248930adf4.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eThe tokens for this query are:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/5a9ac4131334568473cbbadcbb2514f6.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eNotice that only the start character for \u003ccode\u003eSHOW\u003c/code\u003e changed! It changed from \u003ccode\u003e0\u003c/code\u003e to \u003ccode\u003e5\u003c/code\u003e after adding five spaces before the \u003ccode\u003eSHOW\u003c/code\u003e keyword. However, \u003ccode\u003eproduct_title\u003c/code\u003e’s values remain unchanged. This is because the values are relative to the previous token, and the space between \u003ccode\u003eSHOW\u003c/code\u003e and \u003ccode\u003eproduct_title\u003c/code\u003e didn’t change.\u003c/p\u003e\n\u003cp\u003eThis becomes especially confusing when you use certain language features that are parsed out of order. For example, in some ANTLR grammars, comments are not parsed as part of the default \u003ca href=\"https://datacadamia.com/antlr/channel\" target=\"_blank\" title=\"Datacadamia\" rel=\"nofollow noopener noreferrer\"\u003echannel\u003c/a\u003e–they are parsed after everything in the main channel is parsed. Let’s add a comment to the first line:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/f59af161c23171559c683ff725dd284f.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eThe tokens for this query look like this (and are in this order):\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/4ec2a5cedc25734cc2889cb7cf3845a4.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eBefore the parser parses the comment, it points at \u003ccode\u003eproduct_title\u003c/code\u003e, which is two lines after the comment. When the parser finishes with the main channel and begins parsing the channel that contains the comment, the pointer needs to move two lines up to tokenize the comment–hence the value of -2 for the comment’s line integer.\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eAdapting ShopifyQL’s Token Offset To Work With CodeMirror\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eCodeMirror treats offset values much simpler than ANTLR. In CodeMirror, everything is relative to the top of the document–the document is treated as one long string of text. This means that newlines and whitespace are meaningful to CodeMirror and affect the start offset of a token.\u003c/p\u003e\n\u003cp\u003eSo to adapt the values from ANTLR to work with CodeMirror, we need to take these values:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/af642957edf58a56951743c1ab5cc94b.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eAnd convert them into this:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/26ae6dbc46c0e89b452c9f0a007c8730.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003cp\u003eThe solution? A custom \u003ccode\u003eTokenIterator\u003c/code\u003e that could follow the “directions” of the Language Server’s offsets and convert them along the way. The final implementation of this class was fairly simple, but arriving at this solution was the hard part.\u003c/p\u003e\n\u003cp\u003eAt a high level, the \u003ccode\u003eTokenIterator\u003c/code\u003e class:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eTakes in the document and derives the length of each line. This means that trailing whitespace is properly represented.\u003c/li\u003e\n\u003cli\u003eInternally tracks the current line and character that the iterator points to.\u003c/li\u003e\n\u003cli\u003eIngests the ANTLR-style line, character, and token length descriptors and moves the current line and character to the appropriate place.\u003c/li\u003e\n\u003cli\u003eUses the current line, current character, and line lengths to compute the CodeMirror-style start offset.\u003c/li\u003e\n\u003cli\u003eUses the start offset combined with the token length to compute the end offset.\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eHere’s what the code looks like:\u003c/p\u003e\u003cp\u003e\u003cscript src=\"https://gist.github.com/ShopifyEng/6082c4ce8d18711ac1df0f47cd8eadff.js\"\u003e\u003c/script\u003e\u003c/p\u003e\n\n\u003ch2\u003e\u003cstrong\u003eBuilding A Parse Tree\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eNow that we have a clear way to convert an ANTLR token stream into a Lezer buffer, we’re ready to build our tree! To build it, we follow the steps mentioned previously–we take in a ShopifyQL query, use the language server to convert it to a token stream, transform that stream into a buffer of nodes, and then build a tree from that buffer.\u003c/p\u003e\n\u003cp\u003eOnce the parse tree is generated, CodeMirror then “understands” ShopifyQL and provides useful language features such as syntax highlighting.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Syntax highlight line 1 FROM products line 2 SHOW product_title\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Building_A_Parse_Tree.png?v=1694031668\" loading=\"lazy\"\u003e\u003c/p\u003e\n\u003ch2\u003e\u003cstrong\u003eProviding Additional Language Features\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eBy this point, CodeMirror can talk to the ShopifyQL Language Server and build a parse tree that describes the ShopifyQL code. However, the language server offers other useful features like code completion, linting, and tooltips. As mentioned above, Lezer/CodeMirror doesn’t conform to LSP–but it does offer many plugins that let us provide a connector between our language server and CodeMirror. In order to provide these features, we adapted the language server’s \u003ccode\u003edoValidate\u003c/code\u003e with CodeMirror’s \u003ccode\u003elinting\u003c/code\u003e plugin, the language server’s \u003ccode\u003edoComplete\u003c/code\u003e with CodeMirror’s \u003ccode\u003eautocomplete\u003c/code\u003e plugin, and the language server’s \u003ccode\u003edoHover\u003c/code\u003e with CodeMirror’s \u003ccode\u003erequestHoverTooltips\u003c/code\u003e plugin.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"CodeMirror to Custom Adapter to Language Server\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Providing_Additional_Language_Features.png?v=1694031422\" loading=\"lazy\"\u003e\u003c/p\u003e\n\u003cp\u003eOnce we connect those features, our ShopifyQL code editor is fully powered up, and we get an assistive, delightful code editing experience.\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"Gif show editing experience through a drop down in syntax highlighting\" src=\"https://cdn.shopify.com/s/files/1/0779/4361/files/Providing_Additional_Language_Features.gif?v=1694031415\" loading=\"lazy\"\u003e\u003c/p\u003e\n\n\u003ch2\u003e\u003cstrong\u003eConclusion\u003c/strong\u003e\u003c/h2\u003e\n\u003cp\u003eThis approach enabled us to provide ShopifyQL features to CodeMirror while continuing to maintain a grammar that serves both client and server. The custom adapter we created allows us to pass a ShopifyQL query to the language server, adapt the response, and return a Lezer parse tree to CodeMirror, making it possible to provide features like syntax highlighting, code completion, linting, and tooltips. Because our solution utilizes CodeMirror’s internal parse tree, we are able to make better decisions in the code and craft a stronger editing experience. The ShopifyQL code editor helps merchants write ShopifyQL and get access to their data in new and delightful ways.\u003c/p\u003e\n\u003cdiv class=\"marketing-block marketing-block--light marketing-block--padded my-12 border-l-2 pl-4 [\u0026amp;_h2]:!mt-0 [\u0026amp;_h2]:mb-6 [\u0026amp;_h2]:text-t5 [\u0026amp;_h3]:!mt-0 [\u0026amp;_h3]:mb-6 [\u0026amp;_h3]:text-t7 [\u0026amp;_h4]:!mt-0 [\u0026amp;_h4]:mb-6 [\u0026amp;_h4]:text-t8 [\u0026amp;_ul]:!mt-0 [\u0026amp;_ul]:mb-6 [\u0026amp;_ul:last-child]:mb-0 [\u0026amp;_ol]:!mt-0 [\u0026amp;_ol]:mb-6 [\u0026amp;_ol:last-child]:mb-0 [\u0026amp;_p]:!mt-0 [\u0026amp;_p]:mb-6 [\u0026amp;_p:last-child]:mb-0 text-engineering-dark-text [\u0026amp;_*]:!text-engineering-dark-text bg-transparent border-engineering-highlight text-body-sm my-8 tablet:my-16 p-8 bg-marketingBg !border-l-0 border-t-2 pl-8 [\u0026amp;_[itemscope]]:mb-6 [\u0026amp;_[itemscope]:last-child]:mb-0 !bg-[#0C052B]\"\u003e\n\u003cp\u003eThis post was written by \u003cstrong\u003e Trevor Harmon\u003c/strong\u003e, a Senior Developer working to make reporting and analytics experiences richer and more informative for merchants. When he isn't writing code, he spends time writing music, volunteering at his church, and hanging out with his wife and daughter. You can find more articles on topics like this one on his blog at \u003ca title=\"The Trevor Harmon\" href=\"https://thetrevorharmon.com/\" data-sanitized-target=\"_blank\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003ethetrevorharmon.com\u003c/a\u003e, or follow him on \u003ca title=\"GitHub - Trevor Harmon\" href=\"https://github.com/thetrevorharmon\" data-sanitized-target=\"_blank\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003eGitHub\u003c/a\u003e and \u003ca title=\"Twitter - Trevor Harmon\" href=\"https://twitter.com/thetrevorharmon\" data-sanitized-target=\"_blank\" rel=\"nofollow noopener noreferrer\" target=\"_blank\"\u003eTwitter\u003c/a\u003e.\u003c/p\u003e\n\u003c/div\u003e","latestArticles":[],"contentNavigationLinks":[],"blogHandle":"engineering","settings":{"topSectionImages":[{"src":"https://cdn.shopify.com/b/shopify-brochure2-assets/2ecdaa09da44408d3c2a2b9cba3a2c07.png?originalWidth=1005\u0026originalHeight=1288"},{"src":"https://cdn.shopify.com/b/shopify-brochure2-assets/8262a99b5702f964162106b94a23ad2d.png?originalWidth=1005\u0026originalHeight=1288"},{"src":"https://cdn.shopify.com/b/shopify-brochure2-assets/16db235a9390238e6f9b8fd2a5c29680.png?originalWidth=1005\u0026originalHeight=1288"}],"images":{"guides-modal-default":"https://cdn.shopify.com/shopifycloud/brochure/assets/content-marketing/blog/guides/default-popup-small-507879111d55acdd759b202ab869ea0b8bd0f4af9f9aaa7c540efe59b8e046db.jpg","guides-modal-photography":"https://cdn.shopify.com/b/shopify-brochure2-assets/1bd4b072dc187a774c9af650d0f16d0b.jpg","guides-modal-seo":"https://cdn.shopify.com/b/shopify-brochure2-assets/63781344c2932de6e553a54b02291692.jpg","subscription":{"image":{"en":"https://cdn.shopify.com/b/shopify-brochure2-assets/c46f986d892538f4b0a15f25692330f7.png?originalWidth=1420\u0026originalHeight=1040"}},"rightColSideBannerImg":"https://cdn.shopify.com/b/shopify-brochure2-assets/c8790c0f3c1fa91ccc845e45ce067bbe.png","articleNavigation":{"banner":{"en":"https://cdn.shopify.com/b/shopify-brochure2-assets/e570727d45209fec56c410f802faa0fe.png"}}},"subscription":{"settings":{"subscriptionId":"DE84EF61-2A02-4778-8807-F01B108DE974"}},"citation":{}},"shareImage":"https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014","availableRegions":["en","en-ca","en-gb","en-au","en-id","en-nz","en-za","en-ng","en-ph","en-sg","en-hk","en-ie","en-my"],"popularArticles":[{"title":"Introducing Ruvy","tags":["Development","Popular"],"handle":"introducing-ruvy","imageUrl":"https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_211008_72ppi_01_YJIT-BuildingaNewJITCompilerforCRuby_0a00a8cf-3951-4556-9b2c-3f88e9de76ff.jpg?v=1697134143","imageAltText":null,"modifiedAt":"2023-10-18"},{"title":"Building a ShopifyQL Code Editor","tags":["Developer Tooling","Popular"],"handle":"building-a-shopifyql-code-editor","imageUrl":"https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_210719_72ppi_05_FiveStepsforBuildingMachineLearning_886f2915-8848-4dfe-9278-f2575912073a.jpg?v=1694216014","imageAltText":null,"modifiedAt":"2023-09-11"},{"title":"Shopify’s platform is the Web platform","tags":["Apps","Popular"],"handle":"shopifys-platform-is-the-web-platform","imageUrl":"https://cdn.shopify.com/s/files/1/0779/4361/articles/remix-app-bridge.png?v=1690230786","imageAltText":null,"modifiedAt":"2023-07-26"},{"title":"The Engineering Story Behind Flex Comp","tags":["Development","Hero","Popular"],"handle":"building-flex-comp","imageUrl":"https://cdn.shopify.com/s/files/1/0779/4361/articles/ShopifyEng_BlogIllustrations_220927_72ppi_03_BuildingShopifysNewFlexCompSystem.jpg?v=1666044315","imageAltText":null,"modifiedAt":"2022-10-05"}],"hreflangs":[{"tagName":"link","rel":"alternate","hreflang":"en","href":"https://shopify.engineering/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-ca","href":"https://shopify.engineering/ca/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-gb","href":"https://shopify.engineering/uk/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-au","href":"https://shopify.engineering/au/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-id","href":"https://shopify.engineering/id/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-nz","href":"https://shopify.engineering/nz/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-za","href":"https://shopify.engineering/za/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-ng","href":"https://shopify.engineering/ng/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-ph","href":"https://shopify.engineering/ph/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-sg","href":"https://shopify.engineering/sg/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-hk","href":"https://shopify.engineering/hk/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-ie","href":"https://shopify.engineering/ie/building-a-shopifyql-code-editor"},{"tagName":"link","rel":"alternate","hreflang":"en-my","href":"https://shopify.engineering/my/building-a-shopifyql-code-editor"}],"url":"https://shopify.engineering/building-a-shopifyql-code-editor","canonicalUrl":"https://shopify.engineering/building-a-shopifyql-code-editor","rootDomain":"shopify.engineering","experimentVariationId":"","realCountryCode":"SG","geoCountryCode":"","regionCode":"","site":{"domain":"www.shopify.engineering","countryCode":"US","hreflang":"en","locale":"en","baseEnDomain":true,"currencyCode":"USD","features":[],"languageCode":"en"},"fileRoutePath":"/$article","regions":[{"domain":"www.shopify.engineering","href":"https://shopify.engineering/building-a-shopifyql-code-editor","hreflang":"en","label":"USA","lang":"en","base":false,"selector":true,"isActive":true,"published":true}],"enPath":"/building-a-shopifyql-code-editor","handle":"engineering-article","geoPricingIsEnabled":false,"editMode":false,"translations":{"en":{"pages/blogSubNav":{"argoPlaceholder":"0","subNav":{"title":"Engineering Blog","pageTitle":"Menu","overviewText":"Homepage","links":{"development":"Development","infrastructure":"Infrastructure","mobile":"Mobile","developer-tooling":"Developer Tooling","latest":"Latest","moreTopics":"More topics"},"subLinks":{"security":"Security","data-science-engineering":"Data Science Engineering","culture":"Culture"}}},"global":{"argoPlaceholder":"2","ariaLabels":{"close":"Close","carousel":{"name":"carousel","slide":"slide","xOfY":"{x} of {y}","play":"Play carousel","pause":"Pause carousel"},"footer":{"regionsNav":"Region Navigation","regionsClose":"Close Region Navigation","newWindow":"Opens an external site in a new window"},"header":{"mobileNavMenu":"Menu","mainNav":"Main","secondaryNav":"Related pages","skipToContent":"Skip to Content"},"headerLight":{"dismissIcon":"Dismiss banner"},"learnMore":"Learn more about {title}","modal":{"close":"Close modal","pip":"Picture in picture"},"next":"Next","previous":"Previous","testimonial":{"plural":"Testimonials","next":"Next Testimonial","previous":"Previous Testimonial","play":"Play Testimonial","pause":"Pause Testimonial"},"videoModal":"video","video":{"play":"Play video","pause":"Pause video"}},"incentivesPromoBanner":{"app_store_credit":"\u003ca href=\"{{signupUrl}}\"\u003eJoin Shopify and get $50 USD to use in the Shopify App Store. \u003c/a\u003e\u003ca id=\"incentivesTermsLink\" target=\"_blank\" data-component-name=\"acquisition-incentive-terms\" href=\"{{incentiveTerms}}\"/\u003eTerms apply\u003c/a\u003e","gpv_acquisition_incentive_flag":"Join today and get up to \u003ca id=\"showTermsLink\" target=\"_blank\" data-component-name=\"sales-bonus-terms\" href=\"{{incentiveTerms}}\"/ style=\"color: inherit; font-weight: normal\"\u003e$5,000 in cash rebates\u003c/a\u003e on your first six months of sales","retail":{"spRateTermsLink":"\u0026nbsp\u003ca id=\"spIncentivesTermsLink\" target=\"_blank\" class=\"!font-normal\" target=\"_blank\" data-component-name=\"sp-incentive-terms\" href=\"{{incentiveTerms}}\"/\u003eTerms apply\u003c/a\u003e","subscriptionIncentive":"Special offer: get 6 months of POS Pro for {{incentivePrice}}/month","spRateIncentive":"Get {{rate}}% card rates on up to {{gpvLimit}} USD of in-person sales during your first {{length}} months.","spRateIncentiveTerms":"Terms apply"}},"cookiesNotice":{"acceptAllButtonText":"Accept all","activeConsentContentHtml":"We use cookies (and other similar technologies) for many purposes, including to improve your experience on our site and for ads and analytics. Click \"Accept all\" to accept these uses. Read more in our \u003ca href=\"{{legalCookiesPath}}\"\u003eCookie Policy\u003c/a\u003e.","buttonText":"OK","contentHtml":"Shopify uses cookies to provide necessary site functionality and improve your experience. By using our website, you agree to our \u003ca href=\"{{legalPrivacyPath}}\"\u003ePrivacy Policy\u003c/a\u003e and our \u003ca href=\"{{legalCookiesPath}}\"\u003eCookie Policy\u003c/a\u003e.","rejectAllButtonText":"Reject all"},"countries":{"au":"Australia","be":"Belgium","br":"Brazil","ca":"Canada","cn":"China","co":"Colombia","dk":"Denmark","en":"USA","es":"Spain","de":"Germany","fr":"France","gb":"UK","hk":"Hong Kong SAR","id":"Indonesia","ie":"Ireland","in":"India","it":"Italy","jp":"Japan","kr":"Korea","my":"Malaysia","mx":"Mexico","ng":"Nigeria","nl":"Netherlands","no":"Norway","nz":"New Zealand","ph":"Philippines","se":"Sweden","sg":"Singapore","tw":"Taiwan","vn":"Vietnam","za":"South Africa"},"downloadApp":{"androidAlt":"Get the Shopify app on Google Play","androidUrl":"https://shopify.com/install/android","iosAlt":"Download the Shopify app on the Apple App Store","iosUrl":"https://shopify.com/install/mobile","qrCodeAlt":"QR code to download from","qrTitle":"Scan to install"},"downloadposApp":{"androidAlt":"Get the Shopify POS Mobile app on Google Play","androidUrl":"https://play.google.com/store/apps/details?id=com.shopify.pos","iosAlt":"Download the Shopify POS Mobile app on the Apple App Store","iosUrl":"https://apps.apple.com/us/app/shopify-point-of-sale-pos/id686830644","qrCodeAlt":"QR code to download from","qrTitle":"Scan or click below to install"},"forms":{"fields":{"country":{"label":"Country","placeholder":"Select one","error":"Please select a country"}}},"globalNav":{"applyText":"Apply for early access","applyTextPlain":"Apply for access","getStarted":"Get started","homeUrl":"{{home}}","loginGetStartedText":"Log in to get started","loginText":"Log in","loginUrl":"/login","signinText":"Sign in","signupText":"Start free trial","signupTextFree":"Sign up free","signupTextPlain":"Sign up","searchText":"Search","searchPlaceholder":"Type something you're looking for","searchPlaceholderMobile":"Search"},"showLess":"Show less","showMore":"Show more","home":"{{home}}","learnMore":"Learn more","plans":{"pricingFaqAnswerWithDiscount":"We offer a 25% discount for yearly subscriptions if you choose the Basic, Shopify, or Advanced plans. If you choose a 3-year Shopify Plus plan, we offer a monthly discount and lower online variable platform fees."},"sell":{"header":"Explore your options","title":"Apps that help you sell {{vertical}}","subheading":"Some popular apps other merchants use","seeRecommendedApps":"See more recommended apps","by":"By {{partner}}"},"shopify":"Shopify","pagination":{"page":"Page"},"signup":{"buttonText":"Start free trial","plusButtonText":"Get in touch","buttonText_fr":"Launch your store","label":"Email address","placeholder":"Enter your email address","disclaimerHtml":"Try Shopify free for {{trialLength}} days, no credit card\u0026nbsp;required. By entering your email, you agree to receive marketing emails\u0026nbsp;from\u0026nbsp;Shopify.","disclaimerNoLengthHtml":"No credit card required. By entering your email, you agree to receive marketing emails\u0026nbsp;from\u0026nbsp;Shopify.","testimonialButtonText":"Try Shopify free for {{trialLength}} days","freeTrialCtaHtml":"Try Shopify for free, and explore all the tools and services you need to start, run, and grow your\u0026nbsp;business.","mobileDisclaimerHtml":"Try Shopify free for {{trialLength}} days, no credit card\u0026nbsp;required.","signupPaidTrialPromo":{"default_one":"Get {{trialLength}} days free then 1 month for {{paidTrialAmount}}.","default_other":"Start for free, then get your first {{paidTrialMonths}} months for {{paidTrialAmount}}/mo.","disclaimer":"By entering your email, you agree to receive marketing emails\u0026nbsp;from\u0026nbsp;Shopify.","disclaimerNoEmail_one":"Don’t have a Shopify store? \u003ca href=\"{{deeplinkFreeTrial}}\" data-component-name=\"{{deeplinkFreeTrialName}}\"\u003eStart free, then get your first month for {{paidTrialAmount}}.\u003c/a\u003e","disclaimerNoEmail_other":"Don’t have a Shopify store? \u003ca href=\"{{deeplinkFreeTrial}}\" data-component-name=\"{{deeplinkFreeTrialName}}\"\u003eStart for free, then get your first {{paidTrialMonths}} months for {{paidTrialAmount}}/mo.\u003c/a\u003e"}},"social":{"facebook":{"text":"Facebook","url":"https://www.facebook.com/shopify","url_de":"https://www.facebook.com/shopifyDE","url_jp":"https://www.facebook.com/ShopifyJP"},"twitter":{"text":"Twitter","url":"https://twitter.com/shopify","url_jp":"https://twitter.com/ShopifyJP"},"youtube":{"text":"YouTube","url":"https://www.youtube.com/user/shopify","url_de":"https://www.youtube.com/c/ShopifyDeutsch","url_fr":"https://www.youtube.com/c/ShopifyFrançais","url_it":"https://www.youtube.com/c/ShopifyItaliano","url_jp":"https://www.youtube.com/c/ShopifyJapan","url_nl":"https://www.youtube.com/c/ShopifyNederlands","url_br":"https://www.youtube.com/c/ShopifyBrasil"},"instagram":{"text":"Instagram","url":"https://www.instagram.com/shopify/"},"tiktok":{"text":"TikTok","url":"https://www.tiktok.com/@shopify"},"linkedin":{"text":"LinkedIn","url":"https://www.linkedin.com/company/shopify"},"pinterest":{"text":"Pinterest","url":"https://www.pinterest.com/shopify/","url_jp":"https://www.pinterest.com/shopifyjp/"},"email":{"text":"Email","url":"mailto:"}},"tableOfContents":"Table of Contents","totalApps":6000,"totalThemes":70,"supportedLanguages":50,"totalGateways":100,"totalStores":1700000,"uptime":99.98,"waitlistForm":{"errors":{"general":"Something went wrong, try again later","emailFormat":"Email is formatted incorrectly"},"successMessage":"Done","placeholder":"Enter your email address","buttonText":"Signup"},"editionsEyebrow":{"text":"Shopify Editions | Winter ’24 is live"},"nav":{"about":"About Shopify","academy":"Shopify Academy","allFeatures":"All features","allProducts":"All Products","allProductsDescription":"Explore all Shopify products \u0026 features","appStore":"Find business apps","appStoreDescription":"Shopify app store","b2b":"Sell wholesale \u0026 direct","b2bDescription":"Business-to-business (B2B)","balance":"Balance","banking":"Banking","billPay":"Bill Pay","blog":"Blog","blogEnterprise":"Enterprise Blog","blogMerchants":"Merchant Blog","blogRetail":"Retail Blog","borrowing":"Borrowing","branding":"Branding","brandingDescriptionV2":"Build your brand from scratch","brandingFlyoutDescription":"Look professional and help customers connect with your business","build":"Build","burst":"Free stock photos","businessEncyclopedia":"Business encyclopedia","businessFunding":"Secure business funding","businessFinancing":"Secure business financing","businessFundingDescription":"Shopify Capital","businessFinancingDescription":"Shopify Lending","businessGrowth":"Business growth","businessGrowthDescription":"Scale your business","businessNameGenerator":"Business name generator","businessPlanTemplate":"Business plan template","businessTypes":"Business Types","buyButton":"Buy Button","buyButtonDescription":"Transform an existing website or blog into an online store","buyButtonSubnav":"Explore Buy Button","capital":"Capital for small business","capitalLoansAndCashAdvances":"Capital for large retailers","cdiscount":"Cdiscount","changelog":"Changelog","changelogDescription":"Your source for recent updates","checkout":"Checkout","checkoutDescription":"Provide fast, smooth checkout experiences","checkoutDescriptionV2":"World-class checkout","checkoutV2":"Check out customers","commerceComponents":"Enterprise","commerceComponentsDescription":"Solutions for the world's largest brands","community":"Community","communityEvents":"Community Events","compass":"Business courses","compassDescription":"Learn from proven experts","calculator":"Cost calculator","credit":"Credit","ctaAlt":"Free Trial","docs":"Docs","customerInsights":"Know your audience","customerInsightsDescription":"Gain customer insights","domains":"Domains","domainsAndHosting":"Own your site domain","domainsAndHostingDescription":"Domains \u0026 hosting","dropshipping":"Dropshipping","ecommerce":"Online store","ecommerceDescription":"Online store editor","ecommerceSeo":"Ecommerce SEO","ecommerceSeoDescription":"Improve your search ranking","ecommerceV2":"Create your website","editions":"Editions","editionsAll":"All Editions","editionsAllDescription":"Archive of past Shopify Editions","editionsLast":"Winter ’24 Edition","editionsLastDescription":"The latest 100+ product updates","emailMarketing":"Email marketing","encyclopedia":"Encyclopedia","essentialTools":"Essential tools","examples":"Examples","exchange":"Websites for sale","experts":"Experts","facebook":"Facebook Shops","facebookAds":"Facebook Ads","facebookInstagram":"Facebook \u0026 Instagram","faq":"FAQ","features":"Features","financialServices":"Financial Services","flow":"Ecommerce automation","flowDescription":"Shopify Flow","flowV2":"Automate your business","forums":"Shopify Community","founderStories":"Founder stories","founderStoriesDescription":"Learn from successful merchants","fraudProtection":"Fraud Protection","freeBusinessTools":"Explore free business tools","freeBusinessToolsDescription":"Tools to run your business","freeTools":"Free tools","fulfillment":"Fulfillment","fulfillmentDescription":"Shopify Fulfillment Network","fulfillmentV2":"Outsource fulfillment \u0026 returns","getStarted":"Start free trial","getInTouch":"Get in touch","google":"Google","googleSmart":"Google","googleYoutube":"Google \u0026 Youtube","guides":"Guides","hardware":"Hardware","hardwareStore":"Hardware store","helpAndSupport":"Help and support","helpAndSupportDescription":"Get 24/7 support","helpCenter":"Help Center","howToGuides":"How-to guides","howToGuidesDescription":"Read in-depth business guides","impressumGenerator":"Impressum-Generator","inbox":"Business chat","inboxDescription":"Turn browsers into buyers","inboxDescriptionV2":"Shopify Inbox","inboxV2":"Chat with customers","instagram":"Instagram","installments":"Installments","internationalSales":"International sales","internationalSalesDescription":"Sell globally","inventoryOrderManagement":"Manage your stock \u0026 orders","inventoryOrderManagementDescription":"Inventory \u0026 order management","joinNow":"Join Now","learn":"Learn","lending":"Lending","lendingOverview":"Lending overview","lineOfCredit":"Line of Credit","linkInBioTool":"Link in bio tool","login":"Log in","logoMaker":"Logo maker","manage":"Manage","manageEverything":"Manage everything","manageYourBusiness":"Manage your business","manageYourBusinessDescription":"Track sales, orders \u0026 analytics","market":"Market","marketDescription":"Market your business","marketDescriptionV2":"Reach \u0026 retain customers","marketing":"Marketing","marketingAutomation":"Marketing automation","marketingDescription":"Build a marketing plan","marketingTools":"Marketing tools","marketingToolsDescription":"Ads, email campaigns, and more","marketplaces":"Online marketplaces","markets":"International commerce","marketsDescription":"Reach buyers in new markets with international selling tools","measureYourPerformance":"Measure your performance","measureYourPerformanceDescription":"Analytics and Reporting","mobileApp":"Mobile app","mobileAppDescription":"Respond in real time","moneyManagement":"Get paid faster","moneyManagementDescription":"Shopify Balance","more":"More","newsroom":"Newsroom","newsroomDescription":"All company news and press releases","onlineFlyoutDescription":"Find a domain, explore stock images, and amplify your brand","onlinePresence":"Online presence","onlineStoreDescription":"Sell online with an ecommerce website","omnichannel":"Omnichannel selling","orderManagementAndFulfillment":"Order management and delivery","overview":"Overview","partners":"Partners","payments":"Payment processing","paymentsDescription":"Set up forms of payment","paymentsOverview":"Overview","paymentsV2":"Accept online payments","pillarDropdownCta":"Get started","ping":"Shopify Ping","plus":"Plus","podcasts":"Podcasts","pointOfSale":"Point of Sale","pointOfSaleDescription":"Point of Sale (POS)","pointOfSaleV2":"Sell in person","popularTopics":"Popular topics","pos":"Shopify POS","posPricing":"POS Pricing","posSystemSmallBusiness":"Small business POS","posMultiStore":"Multi-store POS","posInventorySystem":"POS inventory system","posAndroid":"Android POS","posIpad":"iPad POS","posApp":"POS App","posSoftware":"POS Software","pricing":"Pricing","pricingOverview":"Pricing overview","productSourcing":"Product sourcing","productSourcingDescription":"Find products to sell","productsToSell":"Products to sell","qrCodeGenerator":"QR code generator","rakuten":"Rakuten","research":"Research","resources":"Resources","retail":"Retail POS","retailDescription":"Sell at retail locations, pop-ups, and beyond","salesChannels":"Sales channels","salesChannelsDescription":"Reach millions of shoppers and boost sales","salesChannelsDescriptionV2":"Channels for social \u0026 marketplaces","salesChannelsV2":"Sell across channels","segmentation":"Customer groups","sell":"Sell","sellEverywhere":"Sell everywhere","sellOnline":"Sell online","sellOnlineDescription":"Grow your business online","sellYourProducts":"Sell your products","sellYourProductsDescription":"Sell online or in person","shipping":"Shipping","shippingDescription":"Fulfill orders faster","shippingDescriptionV2":"Shopify Shipping","shippingV2":"Ship orders faster","shop":"Shop","shopPay":"Shop Pay","shopifyBlog":"Shopify blog","shopifyBlogDescription":"Business strategy tips","shopifyDevelopers":"Shopify Developers","shopifyDevelopersDescription":"Build with Shopify's powerful APIs","shopifyEditions":"Shopify Editions","shopifyEditionsDescription":"New, innovative Shopify products","shopifyEmail":"Nurture customers","shopifyEmailDescription":"Shopify Email","shopifyGold":"Enterprise","shopifyOnlineStore":"Shopify Online store","shopifyPlus":"Enterprise","shopifyPlusDescription":"A commerce solution for growing digital brands","shopifyPointOfSale":"Shopify Point of Sale","shopifySite":"shopify.com","signup":"Sign up","social":"Social media","socialDescription":"Social media integrations","socialMediaStrategy":"Social media strategy","socialMediaStrategyDescription":"Turn social into sales","socialV2":"Market across social","solutions":"Solutions","staffManagement":"Staff management","start":"Start","startYourBusiness":"Start your business","startYourBusinessDescription":"Build your brand","stockPhotography":"Stock photography","storeSetup":"Store set up","storeSetupDescription":"Use Shopify’s powerful features to start selling","storeThemes":"Store themes","storeThemesDescription":"Customize your store","successStories":"Success stories","termLoans":"Term Loans","themeStore":"Theme store","themes":"Themes","tiktok":"TikTok","videos":"Videos","walmart":"Walmart Marketplace","waysToSell":"Ways to sell","webAddress":"Domain name","websiteBuilder":"Website Builder","whatIsShopify":"What is Shopify?","whatIsShopifyDescription":"How our commerce platform works","whatsNew":"What's new","youtube":"YouTube","blogEngineering":"Engineering Blog","workingShopify":"Working at Shopify","openSource":"Open Source at Shopify","devDegree":"Dev Degree"},"footer":{"about":"About","aboutShopify":"About Shopify","accessibility":"Accessibility","affiliateProgram":"Affiliate program","affiliates":"Affiliates","apiDocs":"API documentation","appDemo":"App demo","appDeveloperProgram":"App developers","appStore":"App Store","ar":"Shopify AR","blog":"Blog","blogTopics":"Blog topics","brand":"Brand","buildBlack":"Build Black","buildNative":"Build Native","businessNameGenerator":"Business name generator","burst":"Free stock photos","buyButton":"Buy Button","capital":"Capital","careers":"Careers","commerceComponents":"Commerce Components","communityEvents":"Community Events","company":"Company","companyInfo":"Company info","contact":"Contact","contactShopify":"Contact Shopify","countrySelect":"Change your country or region.","countrySelectHeading":"Country/region","devDegree":"Dev Degree","developers":"Developers","documentation":"Shopify Help Center","domainNames":"Domain names","domains":"Domains","dropshipping":"Dropshipping Business","ecommerce":"Online store","ecommerceHosting":"Ecommerce hosting","ecommerceSoftware":"Ecommerce software","economicGrowth":"Economic growth","email":"Email","examples":"Examples","experts":"Shopify Partners","featureTour":"Website builder","features":"Features","forums":"Shopify Community","fulfillment":"Fulfillment","gaming":"Gaming","globalImpact":"Global impact","hardware":"Hardware","hatchful":"Hatchful","heading":"More resources","helpCenter":"Help center","hireAnExpert":"Hire a Partner","investors":"Investors","leadership":"Leadership","legal":"Legal","linkpop":"Linkpop","logoGenerator":"Logo Maker","managePrivacy":"Manage Privacy","merchantSupport":"Merchant support","mobileCommerce":"Mobile commerce","onlineStore":"Ecommerce website","onlineStoreBuilder":"Online store builder","partnerProgram":"Partner program","partners":"Partners","payments":"Payments","phoneNumber":"1-888-329-0139","platform":"Platform","pointOfSale":"Point of sale","posFeatures":"Features","posSoftware":"POS software","pressAndMedia":"Press and Media","pressReleases":"Press releases","privacyChoices":"Privacy Choices","privacyPolicy":"Privacy Policy","products":"Products","research":"Research","resources":"Support","sellInStore":"Point of sale","sellOnline":"Sell online","sellOnlineTour":"Online retail","serviceStatus":"Service status","setUp":"Setup","sfn":"Shopify Fulfillment Network","shipping":"Shipping","shop":"Shop","shopPay":"Shop Pay","shopify":"Shopify","shopifyEvents":"Shopify Events","shopifyExperts":"Shopify Partners","shopifyForEnterprise":"Shopify for enterprise","shopifyGold":"Enterprise","shopifyLite":"Shopify Lite","shopifyPlus":"Shopify Plus","shoppingCart":"Shopping cart","sitemap":"Sitemap","social":"Social","socialImpact":"Social impact","solutions":"Solutions","storeDesign":"Themes","storeThemes":"Store themes","support":"24/7 support","supportLimited":"Customer Support","sustainability":"Sustainability","termsOfService":"Terms of Service","themeStore":"Theme Store","themeSupport":"Theme support","themes":"Themes","tools":"Free tools","topics":"Topics","trustSealsAccessibilityText":"Use Shopify with confidence. Our platform has been assessed and certified by:","video":"Video","videoTutorials":"Video Tutorials"}},"pages/$article/settings":{"images":{"guides-modal-default":"https://cdn.shopify.com/shopifycloud/brochure/assets/content-marketing/blog/guides/default-popup-small-507879111d55acdd759b202ab869ea0b8bd0f4af9f9aaa7c540efe59b8e046db.jpg","guides-modal-photography":"https://cdn.shopify.com/b/shopify-brochure2-assets/1bd4b072dc187a774c9af650d0f16d0b.jpg","guides-modal-seo":"https://cdn.shopify.com/b/shopify-brochure2-assets/63781344c2932de6e553a54b02291692.jpg","subscription":{"image":{"en":"https://cdn.shopify.com/b/shopify-brochure2-assets/c46f986d892538f4b0a15f25692330f7.png?originalWidth=1420\u0026originalHeight=1040"}},"articleNavigation":{"banner":{"en":"https://cdn.shopify.com/b/shopify-brochure2-assets/e570727d45209fec56c410f802faa0fe.png"}}}},"pages/$article/content":{"argoPlaceholder":"0","articleMetaTitle":"{{title}} ({{year}})","articleNavigation":{"title":"On this page","bannerTitle":"Engineering at Shopify","bannerSubTitle":"We’re hiring","buttonText":"See open roles"},"subscribeBanner":{"title":"Keep up with the latest from Shopify","description":"Get free ecommerce tips, inspiration, and resources delivered directly to your inbox.","placeholder":"Enter email","buttonText":"Subscribe","hint":"By entering your email, you agree to receive marketing emails from Shopify.","successMessage":{"heading":"Thank you for subscription!","contentHtml":"Check your email for instructions."}},"popularPostsTitle":"Most Read","subscribe":{"errors":{"email":"Please enter a valid email address."},"guidesCta":{"submit":"Get updates","success":{"heading":"Thanks for subscribing.","subhead":"You’ll start receiving free tips and resources soon. In the meantime, start building your store with a free trial of Shopify.","cta":"Get started"},"error":"Something went wrong, please try again later!"}},"readMoreLinks":{"title":"Read more"},"partnersSignup":{"service_partner":{"heading":"Grow your business with the Shopify Partner Program","content":"Whether you offer marketing, customization, or web design and development services, the Shopify Partner Program will set you up for success. Join for free and access revenue share opportunities, tools to grow your business, and a passionate commerce community.","cta":"Sign up"},"app_developer":{"heading":"Build apps for Shopify merchants","content":"Whether you want to build apps for the Shopify App Store, offer custom app development services, or are looking for ways to grow your user base, the Shopify Partner Program will set you up for success. Join for free and access educational resources, developer preview environments, and recurring revenue share opportunities.","cta":"Sign up"},"affiliate":{"heading":"Become a Shopify Affiliate","content":"Use your voice to inspire entrepreneurship around the world. Whether you’re an existing partner or new to the Shopify Partner Program, apply to become a Shopify Affiliate and earn commission by referring your audience to Shopify.","cta":"Apply now"},"general":{"heading":"Grow your business with the Shopify Partner Program","content":"Whether you offer web design and development services or want to build apps for the Shopify App Store, the Shopify Partner Program will set you up for success. Join for free and access revenue share opportunities, developer preview environments, and educational resources.","cta":"Sign up"}},"authorSection":{"headingHtml":"Share article","updated":"Last updated","byAuthor":"by \u003cwrapper\u003e\u003cname\u003e{{authorName}}\u003c/name\u003e\u003c/wrapper\u003e","byAuthor_withLink":"by \u003cwrapper\u003e\u003curl\u003e\u003cname\u003e{{authorName}}\u003c/name\u003e\u003c/url\u003e\u003c/wrapper\u003e","reviewed":"Reviewed","written":"Written","timeToRead":"{{minutes}} minute read"},"articleHeader":{"blogLink":{"text":"blog"}},"hero":{"heading":"Start selling with Shopify today","subhead":"Start your free trial with Shopify today—then use these resources to guide you through every step of the process.","image":{"alt":"My alt."},"button":{"href":"{{deeplinkFreeTrial}}","text":"Start free trial"},"lightButton":{"href":"/","text":"Learn more"},"treatment":{"heading":"The next big entrepreneur: you","subhead":"Try Shopify for free, and explore all the tools and services you need to start, run, and grow your business."},"treatment_2":{"heading":"LET’S MAKE YOUR BUSINESS HAPPEN","subhead":"Shopify has everything you need to build, run, and grow a business. All you've got to do is start."}},"subscription":{"heading":"Keep up with the latest from Shopify","subhead":"Subscribe to our blog and get free ecommerce tips, inspiration, and resources delivered directly to your inbox.","disclaimer":"Unsubscribe anytime. By entering your email, you agree to receive marketing emails from Shopify.","conversionCta":{"text":"Subscribe","emailPlaceholder":"Email here","successMessage":{"heading":"Thank you for subscription!","contentHtml":"Check your email for instructions."}}},"latestArticles":{"heading":"Latest from Shopify","link":{"text":"See all"}},"workAnywhere":{"headingHtml":"Work from anywhere \u003cbr/\u003e with Shopify","subheadHtml":"See our open roles and learn more about our digital by design culture.","button":{"text":"See open roles"}},"textCta":{"href":"{{deeplinkFreeTrial}}","text":"Click here to start selling online now with Shopify"},"rightColSideBannerTitle":"The point of sale for every sale.","citation":{"header":"Sources"}},"pages/$article/guide_ctas":{"guidesCta":{"modal":{"subhead":"Almost there: please enter your email below to gain instant access.","disclaimer":"We'll also send you updates on new educational guides and success stories from the Shopify newsletter. We hate SPAM and promise to keep your email address safe."},"analytics":{"heading":"Free Ebook: Ecommerce Analytics for Beginners","content":"Find out which metrics are the key to establishing and growing your online business. This free guide is the perfect first step in learning about ecommerce analytics.","cta":"Get the free ebook now","modal_headline":"Get Ecommerce Analytics for Beginners delivered right to your inbox."},"back_to_school":{"heading":"The Complete Guide to Back to School Marketing for Retail Stores","content":"Get an update on this year's back to school marketing trends, compare strategies, and build out your plan with this short guide.","cta":"Get your free guide now","modal_headline":"Get the Back to School Marketing Guide delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"brand_storytelling":{"heading":"Free Worksheet: Brand Storytelling","content":"Use this handy exercise as a guide to help you craft a compelling brand story and build a loyal audience through the power of storytelling.","cta":"Get the worksheet","modal_headline":"Get our free Brand Storytelling Worksheet delivered right to your inbox."},"branding":{"heading":"Free Reading List: How to Brand Your Business","content":"A great brand can help your products stand out from the crowd. Get a crash course in small business branding with our free, curated list of high-impact articles.","cta":"Get the free reading list","modal_headline":"Get our Branding reading list delivered right to your inbox."},"businessplan":{"heading":"Free: Business Plan Template","content":"Business planning is often used to secure funding, but plenty of business owners find writing a plan valuable, even if they never work with an investor. That’s why we put together a free business plan template to help you get started.","cta":"Get the template now","modal_headline":"Get the business plan template delivered right to your inbox."},"businessideas":{"heading":"Free: The Big List of Business Ideas","content":"To help you find the inspiration to start, we compiled a list of 100+ in-demand business ideas, broken down into categories like fitness, apparel, and gaming.","cta":"Get the list now","modal_headline":"Get the big list of business ideas delivered right to your inbox."},"case_studies":{"heading":"Free Video Series: Ecommerce Inspiration","content":"Feeling uninspired? Watch some of the world's most successful entrepreneurs share their best advice for new business owners.","cta":"Get the free video series","modal_headline":"Get our Ecommerce Inspiration video series delivered right to your inbox."},"competitive":{"heading":"Free: Competitive Analysis Template","content":"By evaluating the strengths and weaknesses of your competition, you can begin to formulate how to give your company an advantage. Download our free competitive analysis template and gain an edge over the competition.","cta":"Get the template now","modal_headline":"Get the competitive analysis template delivered right to your inbox."},"copywriting":{"heading":"Free Reading List: Copywriting Tactics for Entrepreneurs","content":"Is your website content costing you sales? Learn how to improve your website copy with our free, curated list of high-impact articles.","cta":"Get the free reading list","modal_headline":"Get our Copywriting Tactics reading list delivered right to your inbox."},"customerservice":{"heading":"Free Reading List: Customer Service Strategies","content":"Focusing on customer service can turn negative interactions into positive reviews (and repeat customers). Learn how with our free, curated list of high-impact articles.","cta":"Get the free reading list","modal_headline":"Get the reading list delivered right to your inbox."},"design":{"heading":"Free Reading List: Online Store Design Tips","content":"Your online store's appearance can have a big impact sales. Unleash your inner designer with our free, curated list of high-impact articles.","cta":"Get the free reading list","modal_headline":"Get our Store Design reading list delivered right to your inbox."},"email":{"heading":"Free Ebook: How to Grow Your Ecommerce Business with Email Marketing","content":"Whether you're just getting started or dreaming up your next big campaign, this email marketing guide will provide you with insights and ideas to help your business grow.","cta":"Get the free ebook now","modal_headline":"Get our Email Marketing guide delivered right to your inbox."},"googleshopping":{"heading":"Free Ebook: Google Shopping for Small Businesses","content":"Google Shopping ads are one of the leading traffic sources that can give you the ultimate edge. Discover this hidden gem often overlooked by your competition.","cta":"Get the free ebook now","modal_headline":"Get Google Shopping for Small Businesses delivered right to your inbox."},"hiring_questions":{"heading":"Free Guide: Interview Questions for Hiring Retail Employees","content":"Hiring competent retail employees is becoming increasingly challenging. Use this guide to ask the right questions during the interview process to ensure you hire the right people for your store.","cta":"Get the free guide now","modal_headline":"Get our guide Interview Questions for Hiring Retail Employees delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"holiday_selling_2021":{"heading":"Free Holiday Selling Guide: Top 10 Holiday Selling Tips for Retail Stores","content":"A sale can happen anywhere, at any time. Use our top 10 strategies to unify your customer touch points and set yourself up for holiday success.","cta":"Get the free guide now","modal_headline":"Get our Top 10 Holiday Selling Tips for Retail Stores delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"inventory_templates":{"heading":"5 Free Templates to Better Understand Your Inventory","content":"Calculate your businesses cost of goods sold, sell through rate, inventory turnover, saftey stock, economic order quantity, or reorder point with ease using these custom templates. (No math required!)","cta":"Get your free templates","modal_headline":"Get your Inventory Templates delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free templates soon. In the meantime, start building your store with a free trial of Shopify."},"opening_closing_checklist":{"heading":"Free Checklist for Opening and Closing Your Store","content":"Ensure all protocols and procedures are completed each time you open and close your store with this handy, easy-to-follow checklist.","cta":"Get the free checklist","modal_headline":"Get our Opening and Closing Checklist delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"optimization":{"heading":"Free Reading List: Conversion Optimization for Beginners","content":"Turn more website visitors into customers by getting a crash course in conversion optimization. Access our free, curated list of high-impact articles below.","cta":"Get the free reading list","modal_headline":"Get our Conversion Optimization reading list delivered right to your inbox."},"parentpreneur":{"heading":"Playbook for the Aspiring Parentpreneur","content":"Unleash your creativity and turn your passion into a thriving business with our Parentprenuer brainstorming guide. Discover new ideas, set realistic goals, and map out a plan for success.","cta":"Download now","modal_headline":"Check your inbox for your copy of the Parentpreneur Playbook."},"phonecases":{"heading":"Get a Free Phone Case Business in a Box","content":"Free high quality phone case stock photos and a list of some places you can look to find phone case suppliers to work with.","cta":"Download startup bundle","modal_headline":"Get our startup bundle delivered right to your inbox."},"photography":{"heading":"Free Guide: DIY Product Photography","content":"Learn how to take beautiful product photos on a budget with our free, comprehensive video guide.","cta":"Get your free guide","modal_headline":"Get our DIY Guide to Beautiful Product Photography delivered right to your inbox."},"popup_guide":{"heading":"Pop-Up Shop Quickstart Guide","content":"Thinking about hosting a Pop-Up Shop? This free guide includes 3 checklists that help frame what you're looking to accomplish through your Pop-Up Shop, different types of Pop-Up Shops, and design ideas to help get you started.","cta":"Get your free guide","modal_headline":"Get your Pop-Up Shop Quickstart Guide delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"pressrelease":{"heading":"Free: Press Release Template","content":"To help you get started, we put together this template to help you structure your story using a common press release format. You can replace each component with your own information and adapt it according to your needs.","cta":"Get the template now","modal_headline":"Get the press release template delivered right to your inbox."},"productivity":{"heading":"Free Reading List: Ecommerce Motivation","content":"Having trouble focusing on growing your small business? Get access to our free, curated list of high-impact productivity articles.","cta":"Get the free reading list","modal_headline":"Get our Ecommerce Motivation reading list delivered right to your inbox."},"products":{"heading":"Free Guide: How to Find a Profitable Product to Sell Online","content":"Excited about starting a business, but not sure where to start? This free, comprehensive guide will teach you how to find great, newly trending products with high sales potential.","cta":"Get the free guide","modal_headline":"Get How To Find A Product To Sell Online: The Definitive Guide PDF delivered right to your inbox."},"retail_marketing":{"heading":"Free Download: From Clicks to Customers: How to Measure Your Retail Store's Marketing Success","content":"Every marketing campaign is an investment of your time, energy, and money. Do you ever wonder if your efforts were worth it? Read this guide and build a framework to plan, track, and measure the success of retail marketing campaigns.","cta":"Get your free guide","modal_headline":"Get your free Retail Marketing Success PDF delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"retention_audit":{"heading":"Free Download: Retention Audit Checklist","content":"Struggling with customer retention? This free checklist will help you build a retention mindset throughout your company by auditing retention strategies at every level of your business.","cta":"Get your free checklist","modal_headline":"Get this free Retention Audit Checklist PDF delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"rotating_schedules":{"heading":"Free Download: Rotating Schedules Templates","content":"Struggling to keep your stores fully staffed and running smoothly? Consider implementing a rotating shift program. Download these free templates to find the right schedule for you and your employees.","cta":"Get your free templates","modal_headline":"Get these free Rotating Schedules Templates delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"seo":{"heading":"Free Download: SEO Checklist","content":"Want to rank higher in search results? Get access to our free, checklist on search engine optimization.","cta":"Get the free checklist","modal_headline":"Get our SEO Checklist delivered right to your inbox."},"sfn_interview":{"heading":"Free Checklist: How to Interview a Third Party Logistics Provider","content":"Outsourcing fulfillment is a significant investment. Here’s a checklist of questions to ask in preparation of choosing the right fulfillment provider and transitioning your business.","cta":"Get the free checklist","modal_headline":"Get the checklist delivered right to your inbox."},"sfn_outsource":{"heading":"Free Checklist: Are You Ready to Outsource Your Fulfillment?","content":"Trusting a third party logistics (3PL) provider with your inventory and your customer experience is a significant investment. These questions will help determine if it's time to outsource your fulfillment and take the next step in growing your business.","cta":"Get the free checklist","modal_headline":"Get the checklist delivered right to your inbox."},"shipping":{"heading":"The Shopify guide to shipping and fulfillment","content":"Boost customer satisfaction while driving sales growth for your ecommerce business with an effective shipping and fulfillment strategy. Use this guide to create a plan that covers all aspects of shipping and fulfillment, from how much to charge your customers to choosing the right fulfillment method.","cta":"Download now and start optimizing your operations","modal_headline":"Get our shipping and fulfillment guide delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free copy soon. In the meantime, start building your store with a free trial of Shopify."},"social":{"heading":"Social media strategy and planning templates","content":"Ready to get started with your social media strategy? These free, customizable templates give you tools to plan and execute a strategy that connects you with your target audience while keeping your content calendar organized.","cta":"Download now and start seeing results","modal_headline":"Get your social media strategy and planning templates delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free copy soon. In the meantime, start building your store with a free trial of Shopify."},"social_template":{"heading":"Social media strategy and planning templates","content":"Ready to get started with your social media strategy? These free, customizable templates give you tools to plan and execute a strategy that connects you with your target audience while keeping your content calendar organized.","cta":"Download now and start seeing results","modal_headline":"Get social media strategy and planning templates delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"store_layouts":{"heading":"Free Download: Store Layout Templates","content":"Trying to decide which layout is right for your store? Download these free templates to learn which types of layouts work best for different industries and draw inspiration for your own design.","cta":"Get your free templates","modal_headline":"Get these free templates delivered right to your inbox.","modal_success_heading":"Thanks for downloading. You'll receive your free download soon. In the meantime, start building your store with a free trial of Shopify."},"swot":{"heading":"Free: SWOT Analysis Template","content":"Get your free SWOT Analysis Template. Use this free PDF to future-proof your business by identifying your strengths, weaknesses, opportunities, and threats.","cta":"Get the template now","modal_headline":"Get the SWOT analysis template delivered right to your inbox."},"tax_season":{"heading":"Free Download: 6 Steps to Get Your Business Ready for Tax Season","content":"Tax season is stressful for any business owner. This guide will go through the process of filing income taxes in America and provide you with checklists to keep you organized and prepared.","cta":"Get your free guide now","modal_headline":"Get your free guide","modal_success_heading":"Thanks for downloading. You'll receive your free copy soon. In the meantime, start building your store with a free trial of Shopify."},"trust":{"heading":"Free: Shopify Store Trust Checklist","content":"Shopify’s research team conducted a series of in-depth interviews with North American shoppers to learn how customer trust is formed in online stores. This checklist is a summary of their findings, created to help business owners understand what essential aspects of their online store experience creates trust among customers, along with the trust-busting mistakes to avoid.","cta":"Get the checklist now","modal_headline":"Get the checklist delivered right to your inbox."},"ttp_guide":{"heading":"Free Download: The Online Sellers Guide to In-Person Sales","content":"In-person selling is more approachable than ever. This guide covers why selling in person can reduce customer acquisition costs, how to build a plan that will scale with your business, and tactics from ecommerce sellers that made the move successfully.","cta":"Get your free guide now","modal_headline":"Get your free guide","modal_success_heading":"Thanks for downloading. You'll receive your free copy soon. In the meantime, start building your store with a free trial of Shopify."},"twitter":{"heading":"Free Ebook: Twitter Marketing for Small Businesses","content":"This guide will teach you everything from earning your first followers to getting the most out of Twitter’s advertising platform. Claim your free download to get instant access.","cta":"Get the free ebook now","modal_headline":"Get Twitter Marketing for Small Businesses delivered right to your inbox."},"video":{"heading":"Free Reading List: Video Marketing Tips and Tricks","content":"Video marketing is a powerful tool you can’t afford to ignore. Get a crash course with our free, curated list of high-impact articles.","cta":"Get the free reading list","modal_headline":"Get the reading list delivered right to your inbox."}}}}},"pricing":{"annualDiscountPercentage":25,"basicAnnualPrice":"\u003cspan\u003e$29.00 USD\u003c/span\u003e","googleCountryOffer":"$500 USD","minimumMonthlyPrice":"$5","minimumMonthlyPriceCurrency":"USD","paidTrialAmount":"$1","paidTrialMonths":3,"posProPriceUsd":"$89","posRetailLocations":"1,000","promoAmount":{"amount":1,"currencyCode":"USD","currencySymbol":"$"},"signupTypes":["paid_trial_experience"],"trialLength":3,"usd":"USD"}},"root":null,"pages/shopify.engineering~~layout":null},"actionData":null,"errors":null}};</script><script type="module" async="">; import * as route0 from "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/root-DSZyfDOi.js"; import * as route1 from "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/layout-CR0rDhCR.js"; import * as route2 from "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/route-BuTjHipM.js"; window.__remixManifest = { "entry": { "module": "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/entry.client-DwbT9GtS.js", "imports": [ "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/components-Cjscd6JA.js" ], "css": [] }, "routes": { "root": { "id": "root", "path": "", "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": true, "module": "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/root-DSZyfDOi.js", "imports": [ "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/components-Cjscd6JA.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/enums-CqpDel02.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/I18N-DkwmfuKm.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Wrapper-DhZo7Nml.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Section-Bl3w6paD.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/SideBySide-BIF2EZB1.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Provider-CQVAfcXd.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/HeadingGroup-1EyidEKf.js" ], "css": [] }, "pages/shopify.engineering~~layout": { "id": "pages/shopify.engineering~~layout", "parentId": "root", "path": "/", "hasAction": false, "hasLoader": false, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/layout-CR0rDhCR.js", "imports": [ "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/components-Cjscd6JA.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/enums-CqpDel02.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/I18N-DkwmfuKm.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Provider-CQVAfcXd.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/blog-CclTzyK5.js" ], "css": [] }, "pages/shopify.engineering/$article": { "id": "pages/shopify.engineering/$article", "parentId": "pages/shopify.engineering~~layout", "path": ":article", "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/route-BuTjHipM.js", "imports": [ "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/components-Cjscd6JA.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/enums-CqpDel02.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/I18N-DkwmfuKm.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Wrapper-DhZo7Nml.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/EngineeringBlogFooter-C9aEb07f.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/XIcon.svg-1TYfFUVM.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/EngineeringPageLayout-BenRbSvA.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Section-Bl3w6paD.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Checkmark-DIG1PPwH.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/HeadingGroup-1EyidEKf.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/ArticleCarousel-C5IDQsOb.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/blog-CclTzyK5.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/useBlogPageTitle-B_wJ7raq.js" ], "css": [] }, "pages/shopify.engineering/_index": { "id": "pages/shopify.engineering/_index", "parentId": "pages/shopify.engineering~~layout", "index": true, "hasAction": false, "hasLoader": true, "hasClientAction": false, "hasClientLoader": false, "hasErrorBoundary": false, "module": "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/route-B7ttWBQl.js", "imports": [ "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/components-Cjscd6JA.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/enums-CqpDel02.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/I18N-DkwmfuKm.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/Wrapper-DhZo7Nml.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/EngineeringBlogFooter-C9aEb07f.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/XIcon.svg-1TYfFUVM.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/EngineeringPageLayout-BenRbSvA.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/blog-CclTzyK5.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/ArticleCarousel-C5IDQsOb.js", "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/HeadingGroup-1EyidEKf.js" ], "css": [] } }, "url": "https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/manifest-bee77d95.js", "version": "bee77d95" }; window.__remixRouteModules = {"root":route0,"pages/shopify.engineering~~layout":route1,"pages/shopify.engineering/$article":route2}; import("https://cdn.shopify.com/shopifycloud/brochure-iii/production-engineering/assets/entry.client-DwbT9GtS.js");</script></body></html>

Pages: 1 2 3 4 5 6 7 8 9 10