<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Jordan Finneran</title>
    <description>The latest articles on Forem by Jordan Finneran (@jordanfinners).</description>
    <link>https://forem.com/jordanfinners</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F159833%2F2c60ae3e-d259-4e13-917e-f393c0e60aa5.jpg</url>
      <title>Forem: Jordan Finneran</title>
      <link>https://forem.com/jordanfinners</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/jordanfinners"/>
    <language>en</language>
    <item>
      <title>Shopify App: October 2025 Updates</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Mon, 06 Oct 2025 11:34:55 +0000</pubDate>
      <link>https://forem.com/jordanfinners/shopify-app-october-2025-updates-4c5a</link>
      <guid>https://forem.com/jordanfinners/shopify-app-october-2025-updates-4c5a</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;I've been a little quiet blogging recently, I've been busy working on &lt;a href="https://www.pimsical.app/apps/stock-take/blogs/replenishment-limits/" rel="noopener noreferrer"&gt;Transfers and Replenishment Limits for my app&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But there has been a lot of updates in the Shopify app world released recently, so time to catch up on the latest changes!&lt;/p&gt;

&lt;h2&gt;
  
  
  App Bridge
&lt;/h2&gt;

&lt;p&gt;Shopify App Bridge has changed a lot over recent years. For those not familiar it is a JavaScript library that helps you build apps that integrate with Shopify Admin and the Shopify APIs.&lt;/p&gt;

&lt;p&gt;A little timeline of the changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;2018: App Bridge was first released as a npm package. 🤯&lt;/li&gt;
&lt;li&gt;2022: App Bridge 3 was released, the last major version on npm.&lt;/li&gt;
&lt;li&gt;2024: App Bridge 4 was released; this was a move off npm to a unversioned CDN script tag.&lt;/li&gt;
&lt;li&gt;2025: App Bridge and Polaris are now both moving to a unversioned CDN script tags, unifying the development experience further.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  NPM
&lt;/h3&gt;

&lt;p&gt;If you're on an older app bridge version or doing &lt;code&gt;npm i @shopify/app-bridge&lt;/code&gt; then you will need to update your code to move off this and instead use the script tag.&lt;/p&gt;

&lt;p&gt;Your app will need to include the following in the head of your HTML, and it should be the first script tag to load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"shopify-api-key"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"%SHOPIFY_API_KEY%"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.shopify.com/shopifycloud/app-bridge.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now when your app loads Shopify will take care of loading the latest version of App Bridge for you and setting up a shopify global variable for you to use.&lt;br&gt;
&lt;a href="https://shopify.dev/docs/api/app-bridge/migration-guide" rel="noopener noreferrer"&gt;Shopify have got some helpful migration guides available too.&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  UI components
&lt;/h3&gt;

&lt;p&gt;If you were using the script tag, you might have used the UI components that were available on the global &lt;code&gt;shopify&lt;/code&gt; variable.&lt;br&gt;
For example, &lt;code&gt;ui-modal&lt;/code&gt;, &lt;code&gt;ui-nav-menu&lt;/code&gt;, &lt;code&gt;ui-save-bar&lt;/code&gt; and &lt;code&gt;ui-title-bar&lt;/code&gt; or the React wrappers around these components.&lt;/p&gt;

&lt;p&gt;These were separate to Polaris components and lead to a bit of a confusing experience, with these being added via the script tag and Polaris being added via npm.&lt;/p&gt;

&lt;p&gt;Shopify has been doing work to unify the experience of using Polaris and App Bridge together. You can now use the new Shopify Web Components which are more consistent with Polaris components.&lt;br&gt;
These are available via the same script tag as App Bridge, so you do not need to change anything there.&lt;/p&gt;

&lt;p&gt;These new components are prefixed with &lt;code&gt;s-&lt;/code&gt; and have a slight difference in usage, which you can read more here for the App Bridge components: &lt;a href="https://shopify.dev/docs/api/app-home/app-bridge-web-components" rel="noopener noreferrer"&gt;App Bridge Web Components&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Unversioned
&lt;/h3&gt;

&lt;p&gt;Now this is a controversial move, but Shopify have decided to move to an unversioned script tag for both App Bridge and Polaris. This means they will update and serve the latest version of these libraries to you automatically.&lt;/p&gt;

&lt;p&gt;This is a big change from the previous approach of using versioned npm packages.&lt;/p&gt;

&lt;p&gt;Now the benefits of this are you always get the latest features and bug fixes without having to do anything; however, it does mean you have less control over when you get these updates.&lt;/p&gt;

&lt;p&gt;However, seeing the &lt;a href="https://www.npmjs.com/package/@shopify/app-bridge?activeTab=versions" rel="noopener noreferrer"&gt;long long tail of apps on older version of App Bridge&lt;/a&gt;, I'm looking at you the 500ish downloads of a 7-year-old version of the app bridge package in the last 6 months! I can see why managing it this way makes sense for Shopify, even if it feels a little riskier for app developers.&lt;/p&gt;
&lt;h2&gt;
  
  
  Polaris
&lt;/h2&gt;

&lt;p&gt;Polaris which is Shopify design system has also moved to an unversioned script tag and seen a lot of changes in October 2025.&lt;/p&gt;

&lt;p&gt;Previously Polaris was only available as React components via npm package. This was really helpful as React was very popular in the web and JavaScript world at the time.&lt;/p&gt;

&lt;p&gt;However it meant you had to use React to use Polaris, which was not ideal for everyone especially if your app backend wasn't in JavaScript.&lt;/p&gt;

&lt;p&gt;Now Polaris is available via a script tag, which loads in the new Shopify Web Components! These are framework agnostic, so you can use them in any web app regardless of the backend or frontend framework you are using.&lt;/p&gt;

&lt;p&gt;I know web components can be a bit of a scary topic for some, especially given the historic of support in Javascript frameworks.&lt;br&gt;
But it is now widely supported across all modern browsers and frameworks have got much better support for them.&lt;/p&gt;

&lt;p&gt;Again, I can see why Shopify has made this change:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More consistent experience for merchants when using apps, remember when Shopify changed the primary button from green to black? Now all apps using Polaris will get this change automatically rather than a slow move over by apps.&lt;/li&gt;
&lt;li&gt;Easier to use Polaris in any app regardless of the backend or frontend framework, as these are web standards you can use the tooling you want and optimise your app with your expertise without learning React.&lt;/li&gt;
&lt;li&gt;Better caching and admin loading performance, as Shopify can do smarter caching of this across apps rather than each app loading the same or similar libraries.&lt;/li&gt;
&lt;li&gt;Better performance for apps, as there will be less Javascript and bundle to run compared to React.&lt;/li&gt;
&lt;li&gt;Consistent experience because these can be run anywhere it means Admin components, UI extensions across of all of Shopify can use the same components and reducing the maintenance burden for Shopify and you as app devs!&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Migration
&lt;/h3&gt;

&lt;p&gt;If you were using Polaris via npm, you will need to update your code to move off this and instead use the script tag and new web components.&lt;/p&gt;

&lt;p&gt;Your app will need to include the following in the head of your HTML, and it should be after the App Bridge script tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"shopify-api-key"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"%SHOPIFY_API_KEY%"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.shopify.com/shopifycloud/app-bridge.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://cdn.shopify.com/shopifycloud/polaris.js"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I spoke to Shopify about doing this migration and they recommended using the &lt;a href="https://shopify.dev/docs/apps/build/devmcp" rel="noopener noreferrer"&gt;Shopify Dev MCP (Model Context Protocol) server&lt;/a&gt; to help AI tools to better understand the documentation and help you migrate.&lt;br&gt;
I will be giving this a try later this month and share how I get on!&lt;/p&gt;

&lt;p&gt;This also is a good opportunity to review your app design and see if you can improve the user experience with the latest Polaris components and design patterns.&lt;/p&gt;

&lt;p&gt;It also gives you the opportunity to review your app framework, for example if you were using React before you could consider moving to something like Preact or Astro for better performance, developer and merchant experience.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://shopify.dev/docs/api/app-home?" rel="noopener noreferrer"&gt;Here is a link to the updated Polaris and App Bridge documentation.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Remix
&lt;/h2&gt;

&lt;p&gt;I'm just going to touch on this one quickly, the previous Shopify CLI created apps using Remix.&lt;/p&gt;

&lt;p&gt;This has now been changed to React Router, however, don't fear this as it is a bad naming change rather than a huge underlying change.&lt;/p&gt;

&lt;p&gt;Remix was built on top of React Router, and then they basically merged to have the same features. I won't go into the history of why, but you can read more here: &lt;a href="https://v2.remix.run/docs/guides/migrating-react-router-app" rel="noopener noreferrer"&gt;Remix and React Router&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Just wanted to highlight this if you are creating a new app via the Shopify CLI and see this change.&lt;/p&gt;

&lt;h2&gt;
  
  
  UI Extensions
&lt;/h2&gt;

&lt;p&gt;UI Extensions have also seen some updates recently too! Previously you could build in React or Vanilla JS.&lt;/p&gt;

&lt;p&gt;React gave us a familiar way to build extensions but lead to large bundle sizes when Shopify needed to load these in the Checkout, POS, Admin etc.&lt;br&gt;
Vanilla JS was a bit more bare bones and lead to a lot of boilerplate code to get things working.&lt;/p&gt;

&lt;p&gt;Also, each team had a slightly different set of components and APIs to use, which made it a bit inconsistent to build with.&lt;/p&gt;

&lt;p&gt;Because of the unifying of Polaris to web components, you can now use the unified Polaris web components in your UI extensions!&lt;/p&gt;

&lt;p&gt;This should mean you don't have to repeat very similar but slightly different code across different extensions, which I cannot wait for. POS UI extensions were always more different than the others so very happy to see this be unified.&lt;/p&gt;

&lt;p&gt;And because of this change Shopify have also moved to Preact as the main framework for building UI extensions.&lt;br&gt;
&lt;a href="https://preactjs.com/" rel="noopener noreferrer"&gt;Preact if you haven't heard of it is a smaller and faster alternative to React but is compatible with React.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I think this is a great choice as it gives a good developer experience, but also much better performance for merchants. This also means if you want to, you can use &lt;a href="https://preactjs.com/guide/v10/signals/" rel="noopener noreferrer"&gt;Preact Signals&lt;/a&gt; which is a great performance optimisation for state management over hooks.&lt;/p&gt;

&lt;p&gt;I was already using Preact for the Admin side of my app, because of the performance and bundle size benefits. So, I'm very excited to be able to use it across the app and UI extensions.&lt;br&gt;
Each of the UI extensions have their own guide to migrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://shopify.dev/docs/api/checkout-ui-extensions/latest/upgrading-to-2025-10" rel="noopener noreferrer"&gt;Checkout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shopify.dev/docs/api/customer-account-ui-extensions/latest/upgrading-to-2025-10" rel="noopener noreferrer"&gt;Customer Account&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shopify.dev/docs/api/pos-ui-extensions/latest/upgrading-to-2025-10" rel="noopener noreferrer"&gt;POS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Admin UI Extensions don't have migration guide yet, but it will be similar to the other extension types linked about. You can read about the new setup here: &lt;a href="https://shopify.dev/docs/api/admin-extensions/latest#getting-started" rel="noopener noreferrer"&gt;Admin UI Extensions&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Lastly to note there is a new 64 KB file size limit for UI extensions, so just be aware of this when building your extensions, as you won't be able to exceed this limit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In summary, there has been a &lt;strong&gt;lot&lt;/strong&gt; of changes in the Shopify app world recently, I know it can feel a bit overwhelming and keeping up with everything can be a challenge.&lt;/p&gt;

&lt;p&gt;Firstly, don't panic, your existing apps will continue to work as normal, and you can migrate at your own pace.&lt;/p&gt;

&lt;p&gt;Secondly, take the time to read through the documentation and understand the changes and how they impact your app. I would plan out the migration across your apps and extensions and take the opportunity to review your app design and framework choices to improve the experience for both you and merchants.&lt;/p&gt;

&lt;p&gt;Finally, if you do come across any issues or rough edges, make sure to report and share it in the &lt;a href="https://community.shopify.dev/" rel="noopener noreferrer"&gt;Shopify Dev forum&lt;/a&gt;, as the Shopify teams have got really active there which has been great personally.&lt;/p&gt;

&lt;p&gt;I'm planning on tackling this towards the end of the month and November, so I'll share how I get on and any tips and tricks I find along the way.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pimsical.app/" rel="noopener noreferrer"&gt;Pimsical is my business, where you can find more Shopify content, my Shopify apps or help to optimise your operations and streamline your business backend.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>apps</category>
    </item>
    <item>
      <title>Shopify: Getting to grips with GraphQL</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Tue, 13 May 2025 09:08:22 +0000</pubDate>
      <link>https://forem.com/jordanfinners/shopify-getting-to-grips-with-graphql-fpp</link>
      <guid>https://forem.com/jordanfinners/shopify-getting-to-grips-with-graphql-fpp</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;The Shopify GraphQL documentation can be a lot to get to grips with, even if you &lt;a href="https://jordanfinners.dev/blogs/shopify-product-apis/#example-use-case" rel="noopener noreferrer"&gt;adopt an approach I recommend&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So what can make it easier for us, especially keeping up to date with the new versions and changes...&lt;/p&gt;

&lt;p&gt;&lt;a href="https://stackoverflow.com/questions/8503559/what-is-linting" rel="noopener noreferrer"&gt;Linting&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This linting is designed to work with &lt;a href="https://eslint.org/" rel="noopener noreferrer"&gt;eslint&lt;/a&gt;, which is very commonly used in the JavaScript world.&lt;/p&gt;

&lt;p&gt;To use this you should have an &lt;a href="https://eslint.org/docs/latest/use/configure/configuration-files#configuration-file" rel="noopener noreferrer"&gt;&lt;code&gt;eslint.config.js&lt;/code&gt; file&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Building ontop of the great work by &lt;a href="https://the-guild.dev/graphql/eslint/docs" rel="noopener noreferrer"&gt;&lt;code&gt;@graphql-eslint/eslint-plugin&lt;/code&gt;&lt;/a&gt;, I've created a &lt;a href="https://www.npmjs.com/package/eslint-plugin-pimsical-shopify-graphql" rel="noopener noreferrer"&gt;new eslint plugin to add some Shopify GraphQL specific best practices&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;This means you can check your GraphQL for unknown fields, deprecations when migrating between versions and best practices around mutations and pagination for example.&lt;/p&gt;

&lt;h2&gt;
  
  
  With Shopify Codegen
&lt;/h2&gt;

&lt;p&gt;The linters work &lt;strong&gt;best&lt;/strong&gt; combined with knowledge about Shopify GraphQL Schema.&lt;/p&gt;

&lt;p&gt;If you are already using &lt;a href="https://www.npmjs.com/package/@shopify/api-codegen-preset" rel="noopener noreferrer"&gt;Shopify GraphQL Codegen&lt;/a&gt;, this does a lot of the work for us and is our recommended approach.&lt;/p&gt;

&lt;p&gt;In your &lt;code&gt;.graphqlrc.ts&lt;/code&gt; or equivalent JS file, ensure that &lt;code&gt;pluckConfig&lt;/code&gt; is exposed in the default &lt;code&gt;extensions&lt;/code&gt; object.&lt;/p&gt;

&lt;p&gt;This will allow the linters to pick up the GraphQL code from your codebase using the same prefix as the codegen tools, it also uses the schemas from here as well.&lt;/p&gt;

&lt;p&gt;Here is an example &lt;code&gt;.graphqlrc.ts&lt;/code&gt; file, of how to expose this pluckConfig.&lt;/p&gt;

&lt;p&gt;However as long as the path &lt;code&gt;projects.default.extensions.pluckConfig&lt;/code&gt; exists in your config it will be used, so you can config this how you want to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;shopifyApiProject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ApiType&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@shopify/api-codegen-preset&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2025-04&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;shopifyApiProject&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;apiType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ApiType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Admin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./**/*.{js,ts,jsx,tsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;outputDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./shopify/types&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://shopify.dev/admin-graphql-direct-proxy/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./**/*.{js,ts,jsx,tsx}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;projects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;pluckConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;codegen&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;pluckConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Without Codegen
&lt;/h2&gt;

&lt;p&gt;If you are not using the codegen tools, you can manually define the schema in your &lt;code&gt;eslint.config.js&lt;/code&gt; under &lt;code&gt;parserOptions&lt;/code&gt; or add an equivalent &lt;a href="https://the-guild.dev/graphql/config/docs/user/usage" rel="noopener noreferrer"&gt;Graphql Config file&lt;/a&gt; which will automatically get picked up.&lt;/p&gt;

&lt;p&gt;Here is an example where I'm passing the schema information in parserOptions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;js&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@eslint/js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint/eslint-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;languageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;parserOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;graphQLConfig&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`https://shopify.dev/admin-graphql-direct-proxy/2025-04`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./**/*.{js,ts,jsx,tsx,graphql}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pimsical-shopify-graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flat/operations-recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint/require-selections&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;off&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flat/recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  eslint config
&lt;/h2&gt;

&lt;p&gt;Now we've got our codegen and schema information prepared we can configure the linting!&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation
&lt;/h3&gt;

&lt;p&gt;Get all the packages installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-D&lt;/span&gt; eslint @eslint/js @graphql-eslint/eslint-plugin eslint-plugin-pimsical-shopify-graphql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Queries in code
&lt;/h3&gt;

&lt;p&gt;If your GraphQL queries are located in your code in JavaScript or TypeScript files, the linter can parse them like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint/eslint-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint-plugin-pimsical-shopify-graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// any other linting config you wanted to have&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.{ts,js}&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;processor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;processor&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;languageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parser&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pimsical-shopify-graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flat/operations-recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint/require-selections&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;off&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flat/recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will use the &lt;code&gt;pluckConfig&lt;/code&gt; we defined earlier to extract the GraphQL queries out of the strings and into temporary &lt;code&gt;.graphql&lt;/code&gt; files so they can be linted and checked, using the processor in the config, like magic!&lt;/p&gt;

&lt;p&gt;This will then parse them with the &lt;code&gt;graphqlPlugin&lt;/code&gt; parse to understand the format and begin linting them with both the plugins and the rules listed below.&lt;/p&gt;

&lt;p&gt;You can always configure these rules of either plugin how you'd like it to work, but above is our recommended approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queries in &lt;code&gt;.graphql&lt;/code&gt; files
&lt;/h3&gt;

&lt;p&gt;If your queries are already in &lt;code&gt;.graphql&lt;/code&gt; files then you don't need the processor from above. So your config can look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint/eslint-plugin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;eslint-plugin-pimsical-shopify-graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// any other linting config you wanted to have&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**/*.graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;languageOptions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parser&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;pimsical-shopify-graphql&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;graphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flat/operations-recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@graphql-eslint/require-selections&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;off&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;shopifyGraphqlPlugin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;flat/recommended&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Run
&lt;/h3&gt;

&lt;p&gt;Now you can run your linting! Use whatever command you already have or &lt;code&gt;npx eslint .&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Any deprecated fields will be listed along with any other schema issues and best practices. Helping keep on top of Shopify GraphQL!&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/shopify-graphql-linter.png" class="article-body-image-wrapper"&gt;&lt;img src="/images/shopify-graphql-linter.png" title="Example of eslint result with errors in GraphQL queries" alt="Example of eslint result with errors in GraphQL queries"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope that this can help keep on top of GraphQL changes, updates and best practices. Making it a little easier to work with GraphQL going forwards.&lt;/p&gt;

&lt;p&gt;If you have any suggestions for best practices or if there is something tripping you up in GraphQL, I'd love to hear about them and you can &lt;a href="https://github.com/jordanfinners/eslint-plugin-pimsical-shopify-graphql" rel="noopener noreferrer"&gt;raise an issue on Github&lt;/a&gt; as this project is open source!&lt;/p&gt;

&lt;h2&gt;
  
  
  Learn More
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@shopify/api-codegen-preset" rel="noopener noreferrer"&gt;Shopify Codegen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://the-guild.dev/graphql/eslint/docs" rel="noopener noreferrer"&gt;GraphQL Eslint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/eslint-plugin-pimsical-shopify-graphql" rel="noopener noreferrer"&gt;Pimsical Shopify GraphQL Plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>shopify</category>
      <category>apps</category>
      <category>products</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Shopify: Unlocking the power of Shopify's new Product Model &amp; APIs</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Wed, 16 Apr 2025 09:01:13 +0000</pubDate>
      <link>https://forem.com/jordanfinners/shopify-unlocking-the-power-of-shopifys-new-product-model-apis-5fag</link>
      <guid>https://forem.com/jordanfinners/shopify-unlocking-the-power-of-shopifys-new-product-model-apis-5fag</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;There have been a lot of recent changes related to managing product information with Shopify's APIs, particularly in how app developers now interact with product data. These include changes to the product model, how products themselves work, and migrating from REST to GraphQL.&lt;/p&gt;

&lt;p&gt;As an developer who has experience building with the new Product APIs model, I'd like to share my experiences, and unpack some real-world examples that will help with understanding the updates and migration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Product Model
&lt;/h2&gt;

&lt;p&gt;Let's go over the product model changes first, as that will help us understand why GraphQL operates the way it does.&lt;/p&gt;

&lt;p&gt;Previously the product model was compact with Product, Images, Product Variants, and Inventory Items all co-located together. The images were tied to Products and Variants so could not be reused between products in any file/media gallery.&lt;/p&gt;

&lt;p&gt;Inventory Item information such as SKU and Weight were all combined with Product Variants. And of course, Product Variants were always attached to Products meaning we could only ever have 200 variants.&lt;/p&gt;

&lt;p&gt;This made it easy to work with from a developer perspective, as you got all data in one API call, but you can see how it wasn’t easily extensible or very future proof.&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/shopify-product-data-image-1.svg" class="article-body-image-wrapper"&gt;&lt;img src="/images/shopify-product-data-image-1.svg" title="Illusation of legacy shopify product model, where all parts are deeply coupled and co-located" alt="Illusation of legacy shopify product model, where all parts are deeply coupled and co-located"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The new product model separates these key components of a product into something that looks like the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="/images/shopify-product-data-image-2.svg" class="article-body-image-wrapper"&gt;&lt;img src="/images/shopify-product-data-image-2.svg" title="Illusation of new shopify product model, where components are more seperated and flexible" alt="Illusation of new shopify product model, where components are more seperated and flexible"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you'll notice is that this also matches the Shopify Admin layout of a product, with a separate page for product variants, and separate boxes for media and inventory within that. Remembering the Shopify Admin Product layout will be useful when we come to looking at the GraphQL Product APIs.&lt;/p&gt;

&lt;p&gt;The changes to the model give us some great capabilities:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Up to 2048 variants.&lt;/li&gt;
&lt;li&gt;Media can be shared between products without copying the file multiple times, which makes it easier for merchants to manage&lt;/li&gt;
&lt;li&gt;Support for larger videos and 3D Models as well as images.&lt;/li&gt;
&lt;li&gt;Separation of Product and Product Variant merchandising information from Inventory and Operational data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can learn more about how to use the &lt;a href="https://shopify.dev/docs/apps/build/graphql/migrate/new-product-model" rel="noopener noreferrer"&gt;latest GraphQL product APIs in the developer documentation&lt;/a&gt;, which describes the model and components and some of the reasons behind the changes, and hopefully you can see some of the opportunities this update unlocks for us as developers to do a lot more with products.&lt;/p&gt;

&lt;h2&gt;
  
  
  GraphQL
&lt;/h2&gt;

&lt;p&gt;Let's dive into some GraphQL now. If you’re new to GraphQL I'd recommend checking out the &lt;a href="https://shopify.dev/docs/apps/build/graphql" rel="noopener noreferrer"&gt;Shopify developer documentation&lt;/a&gt; first, which also includes a detailed migration guide too.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rate Limits
&lt;/h3&gt;

&lt;p&gt;One important aspect to understand when migrating from REST to GraphQL is that the rate limits are quite different. You may well need to make more calls in GraphQL than REST, but the rate limits are calculated based on a points system.&lt;/p&gt;

&lt;p&gt;Points are determined either by how much each query costs, or with mutations that will always cost 10 points, regardless of the changes being made. You have a lot more points in GraphQL to use, compared to REST limits, so don't worry too much if you need to make additional GraphQL requests compared to REST.&lt;/p&gt;

&lt;p&gt;You can also use &lt;a href="https://shopify.dev/docs/api/usage/bulk-operations/imports" rel="noopener noreferrer"&gt;Bulk Operations in GraphQL&lt;/a&gt; to create or update multiple products and variants in one go or process larger volumes of data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creation
&lt;/h3&gt;

&lt;p&gt;The first thing we'll want to do with any product is create it. How exactly you do this will depend on how you want to work.&lt;/p&gt;

&lt;p&gt;You can first create just the top level product information using &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/productCreate" rel="noopener noreferrer"&gt;productCreate&lt;/a&gt;, and if you're starting from scratch I would recommend this simpler option. You can see an example of what this would look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mutation createProduct($product: ProductCreateInput!) {
  productCreate(product: $product) {
    product {
      id
    }
    userErrors {
      field
      message
    }
  }
}`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;product&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Winter hat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;productOptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Grey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Black&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively you could use &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/productSet" rel="noopener noreferrer"&gt;productSet&lt;/a&gt; which allows you to create a more complete product with variants etc., which is easier if you are migrating from an existing app or REST integration. I’ll give an example of this below given the complexity involved here, where we create a product, variants and set weights and inventory information.&lt;/p&gt;

&lt;p&gt;One important thing to note here is that productSet is an &lt;a href="https://en.wiktionary.org/wiki/upsert" rel="noopener noreferrer"&gt;upsert&lt;/a&gt; endpoint, it will create or update a product so just be aware of this, particularly if you are only updating variant information.&lt;/p&gt;

&lt;p&gt;The first thing to call out from the example is we have a synchronous input option here, if you are creating a large product with lots of files and variants, it is best to run this as &lt;strong&gt;not&lt;/strong&gt; synchronous otherwise it may timeout. You can then query the productSetOperation to check when this has completed.&lt;/p&gt;

&lt;p&gt;As always with GraphQL ensure you check the userErrors in your response to catch any issues with your input. You can of course run this as a &lt;a href="https://shopify.dev/docs/api/usage/bulk-operations/imports" rel="noopener noreferrer"&gt;bulk job if you want to create a lot of products in one go&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the input here is an example creating a product with a variant, media item (file) and inventory information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
mutation createProductAsynchronous($productSet: ProductSetInput!, $synchronous: Boolean!) {
  productSet(synchronous: $synchronous, input: $productSet) {
    product {
      id
    }
    productSetOperation {
      id
      status
      userErrors {
        code
        field
        message
      }
    }
    userErrors {
      code
      field
      message
    }
  }
}`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;synchronous&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;productSet&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;title&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Winter hat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;metafields&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-description&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;key&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-key&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;namespace&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-namespace&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-type&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;your-value&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;productOptions&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;position&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Grey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Black&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variants&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optionValues&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optionName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Grey&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;79.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inventoryItem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRAMS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SKU-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tracked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optionValues&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optionName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Color&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Black&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;69.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;inventoryItem&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRAMS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;},&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SKU-456&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tracked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Variants
&lt;/h3&gt;

&lt;p&gt;If you’ve used productCreate and want to then create some variants and media, you can then use the &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/productVariantsBulkCreate" rel="noopener noreferrer"&gt;productVariantsBulkCreate&lt;/a&gt; which will allow you to create the variants and media items on a product.&lt;/p&gt;

&lt;p&gt;Personally I would recommend using productCreate and this method, instead of productSet, to manage the product and variants as it keeps everything a little easier to understand. The input data is less complex, there is less risk as these are not upsert endpoints and you have to store less identifiers than needed with productSet. I believe it also sets you up nicely for updates as they follow a similar format.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mutation productVariantsCreate($productId: ID!, $variants: [ProductVariantsBulkInput!]!, $media: [CreateMediaInput!]) {
  productVariantsBulkCreate(productId: $productId, variants: $variants, media: $media) {
    productVariants {
      id
      title
      selectedOptions {
        name
        value
      }
    }
    userErrors {
      field
      message
    }
  }
}`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;productId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid://shopify/Product/20995642&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variants&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;price&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;15.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;compareAtPrice&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;19.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optionValues&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Golden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;optionId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid://shopify/ProductOption/328272167&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;media&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mediaContentType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IMAGE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;originalSource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;someExternalURL.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mediaContentType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIDEO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;originalSource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stagedMediaResourceURL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Updates
&lt;/h3&gt;

&lt;p&gt;Once you have a product, you’re going to want to update it or the variants. I'll cover updating media in the next section.&lt;/p&gt;

&lt;p&gt;Again, you can use productSet here, as it will update the product. Make sure if you are doing this you provide IDs for all the existing variants, files etc. otherwise you will end up with duplicates of variants or other product content.&lt;/p&gt;

&lt;p&gt;Alternatively you can use the specific methods, again this is what I would recommend, we’ve got &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/productUpdate" rel="noopener noreferrer"&gt;productUpdate&lt;/a&gt;, &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/productVariantsBulkUpdate" rel="noopener noreferrer"&gt;productVariantsBulkUpdate&lt;/a&gt; which can allow you to really easily update certain properties on the product.&lt;/p&gt;

&lt;p&gt;An example of updating a product and adding media.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mutation updateProductData($input: ProductInput!, $media: [CreateMediaInput!]) {
  productUpdate(input: $input, media: $media) {
    product {
      id
    }
    userErrors {
      field
      message
    }
  }
}`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid://shopify/Product/912855135&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;descriptionHtml&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Updated description&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;media&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mediaContentType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IMAGE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;originalSource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;someExternalURL.jpg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mediaContentType&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;VIDEO&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;originalSource&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stagedMediaResourceURL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are also some more specific named methods, if you are looking to change a product's  options or published status for example.&lt;/p&gt;

&lt;p&gt;An example of an existing product with colour options, now adding size options and automatically creating the new variants. If the product didn’t have the size options when initially created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mutation createAdditionalOptions($productId: ID!, $options: [OptionCreateInput!]!, $variantStrategy: ProductOptionCreateVariantStrategy) {
  productOptionsCreate(productId: $productId, options: $options, variantStrategy: $variantStrategy) {
    userErrors {
      field
      message
      code
    }
  }
}`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;productId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid://shopify/Product/20995642&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;variantStrategy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CREATE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;options&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Size&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;values&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Small&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Medium&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;name&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Large&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Managing Media
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://shopify.dev/docs/apps/build/online-store/product-media" rel="noopener noreferrer"&gt;developer documentation comprehensively describes&lt;/a&gt; how to manage media for a product, so I won't repeat what is outlined there, but there are a couple of things I think are important to highlight:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Previously, REST calls only supported one image at a time. With the GraphQL methods, you can upload multiple images and other media in the same productUpdate call.&lt;/li&gt;
&lt;li&gt;You can also stage your media so multiple images, videos and 3D models can be added at the same time, making it more efficient for adding multiple media items at once.&lt;/li&gt;
&lt;li&gt;You can also use &lt;a href="https://shopify.dev/docs/api/usage/bulk-operations/imports" rel="noopener noreferrer"&gt;bulk imports with productUpdate, if you want to update multiple products and media.&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;If you're adding multiple media/images at once in a single GraphQL mutation, this will still only cost 10 points towards rate limits.&lt;/li&gt;
&lt;li&gt;You can still include an external link to an image, when adding it to a product or variant. However, this normally means you have to host it somewhere first. Now you can use &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/stagedUploadsCreate" rel="noopener noreferrer"&gt;stagedUploadsCreate&lt;/a&gt; so you don’t need to do that anymore, and you can upload multiple images/videos or 3D items in one go!&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Inventory Item
&lt;/h2&gt;

&lt;p&gt;Up until now we've been dealing with the product or product variant information, which is mostly around merchandising the product and preparing it for sale. This is where the &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/objects/InventoryItem" rel="noopener noreferrer"&gt;inventory item&lt;/a&gt; comes into play, it represents the actual physical item that will be sold.&lt;/p&gt;

&lt;p&gt;Here is where the Stock Keeping Unit (SKU), weight or other logistical information all exist and is connected to the various inventory levels at each location. You can set some of these properties when creating a variant as we discussed previously. However it does also have its own methods as well, for example &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/inventoryItemUpdate" rel="noopener noreferrer"&gt;updating an inventory item&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;An example of updating the information.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mutation inventoryItemUpdate($id: ID!, $input: InventoryItemInput!) {
  inventoryItemUpdate(id: $id, input: $input) {
    inventoryItem {
      id
    }
    userErrors {
      field
      message
    }
  }
}`&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variables&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gid://shopify/InventoryItem/43729076&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cost&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;145.89&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tracked&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;countryCodeOfOrigin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;US&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;provinceCodeOfOrigin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;OR&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;harmonizedSystemCode&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;621710&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sku&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SKU-123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;measurement&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weight&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;value&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;GRAMS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;InventoryItem also has its own webhooks, so if you only need these pieces of information, for example for a warehouse, you can subscribe to the inventory webhook instead of relying on the product webhooks, which should be a bit more efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Webhooks
&lt;/h2&gt;

&lt;p&gt;If you're not using webhooks yet, they're a useful tool to get information out of Shopify asynchronously without rate limits. There are some useful docs here on them if you’d like further background &lt;a href="https://shopify.dev/docs/apps/build/webhooks" rel="noopener noreferrer"&gt;https://shopify.dev/docs/apps/build/webhooks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Product Webhooks will now ONLY include the first 100 variants, all additional variants will be listed in the “variant_ids” property, this will also be backdated onto all product webhook versions. This will inform you which variants have been updated, you can then use the ID to more easily get the product variant information from Shopify API.&lt;/p&gt;

&lt;p&gt;Also, there is the opportunity here to move beyond just product webhooks as there are also webhooks for the inventory item updates, inventory level updates, variants in and out of stock. So, make sure you &lt;a href="https://shopify.dev/docs/api/webhooks?reference=toml" rel="noopener noreferrer"&gt;check the documentation&lt;/a&gt; if there is an alternative that works for you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Use Case
&lt;/h2&gt;

&lt;p&gt;Let's unpack a full real world scenario. For example, let's imagine you have a products' SKU, and you want to update its inventory information and publication status, which is a common process.&lt;/p&gt;

&lt;p&gt;This is how I would approach the problem and determine which GraphQL calls I may need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;It’s a SKU so it's Inventory Item which is related to a product variant (we know that from where the input is located in the Shopify Admin), so find we can that query and see if I can search by SKU, which we can &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/queries/productVariants" rel="noopener noreferrer"&gt;https://shopify.dev/docs/api/admin-graphql/2025-01/queries/productVariants&lt;/a&gt; or &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/queries/inventoryItems" rel="noopener noreferrer"&gt;https://shopify.dev/docs/api/admin-graphql/2025-01/queries/inventoryItems&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From either query we can return a list of product variants on the nodes, so we need to look at that to see how we can get to the inventory and publication &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/objects/ProductVariant" rel="noopener noreferrer"&gt;https://shopify.dev/docs/api/admin-graphql/2025-01/objects/ProductVariant&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We can see on the variant it has Inventory Item which it says is for managing inventory, so we’ll probably need that ID later &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/objects/ProductVariant#field-inventoryitem" rel="noopener noreferrer"&gt;https://shopify.dev/docs/api/admin-graphql/2025-01/objects/ProductVariant#field-inventoryitem&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;But there’s nothing on there about the publication, so that must be on the product also that is where publications are on the Shopify Admin, there’s a load of fields on there about publication so grab the product ID which we'll probably need later as well &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/objects/ProductVariant#field-product" rel="noopener noreferrer"&gt;https://shopify.dev/docs/api/admin-graphql/2025-01/objects/ProductVariant#field-product&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;From there we know we are now looking to make inventory changes, which will be a mutation and related to an inventory item. In the inventory section of the GraphQL docs mutations there is inventoryItemUpdate but that does have any quantities on, so let’s go with the one that has quantities in the name, which looks right and needs the ID we grabbed earlier &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/inventoryAdjustQuantities" rel="noopener noreferrer"&gt;https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/inventoryAdjustQuantities&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We saw before that the publication is on the product, so look through the product mutations for anything referencing it. There’s product publish, which sounds right and needs the product id from before, it is however deprecated but does direct you to the correct method of publishablePublish (my personal favourite mutation name) &lt;a href="https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/publishablePublish" rel="noopener noreferrer"&gt;https://shopify.dev/docs/api/admin-graphql/2025-01/mutations/publishablePublish&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We need to do multiple calls here which is fine, or if you want to you can combine mutations if you are doing them regularly. An example of how you can do this below, make sure each mutation has a unique alias. For example I'm calling mine &lt;em&gt;detaching&lt;/em&gt; and &lt;em&gt;appending&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`mutation productVariantManageMedia(
  $productId: ID!,
  $detach: [ProductVariantDetachMediaInput!]!,
  $append: [ProductVariantAppendMediaInput!]!) {

  detaching: productVariantDetachMedia(productId: $productId, variantMedia: $variantMedia) {
    userErrors {
      code
      field
      message
    }
  }

  appending: productVariantAppendMedia(productId: $productId, variantMedia: $variantMedia) {
    userErrors {
      code
      field
      message
    }
  }
}`&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;If you're using NodeJS you're in luck because Shopify libraries are great.&lt;br&gt;
This is a super lightweight GraphQL client for it &lt;a href="https://www.npmjs.com/package/@shopify/admin-api-client" rel="noopener noreferrer"&gt;@shopify/admin-api-client&lt;/a&gt;&lt;br&gt;
If you're moving between REST IDs and GraphQL IDs then you can always get the legacyResourceId from the Admin API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lastly use the &lt;a href="https://www.npmjs.com/package/@shopify/cli" rel="noopener noreferrer"&gt;Shopify CLI&lt;/a&gt; and &lt;a href="https://www.npmjs.com/package/@shopify/api-codegen-preset" rel="noopener noreferrer"&gt;@shopify/api-codegen-preset&lt;/a&gt; to generate types from your GraphQL queries which makes working with the data much much easier.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I hope that this has helped add some context with real world examples and approaches to tackling the recent changes to product models and API’s. Once you’ve migrated to GraphQL, you're set up for the future, as this is Shopify’s primary API format now.&lt;/p&gt;

&lt;p&gt;Which means upgrading and keeping track of any changes should be simpler for you and your apps in the future. Hopefully you can also see the opportunities that the product changes unlock for us to develop even more powerful product apps.&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>apps</category>
      <category>products</category>
      <category>graphql</category>
    </item>
    <item>
      <title>Shopify App: Where to store my app data?</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Wed, 12 Mar 2025 18:56:59 +0000</pubDate>
      <link>https://forem.com/jordanfinners/shopify-app-where-to-store-my-app-data-cck</link>
      <guid>https://forem.com/jordanfinners/shopify-app-where-to-store-my-app-data-cck</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Building Shopify apps used to have only one option for storing your data, your own database. But Shopify platform has come a long way since then now we have got metafields and metaobjects, allowing us to store data in many ways across Shopify platform. Not only can we store data but also have control over the ownership and permissions models too!&lt;br&gt;
But where is the best place to store your applications data? This blog post will explore the options available to you, and the best practices for storing your app data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Questions
&lt;/h2&gt;

&lt;p&gt;Theres some key questions about what your app is going to store and be used for, that can help you decide the best option of where to store your data.&lt;/p&gt;

&lt;p&gt;You only need a rough idea of the answers to these questions.&lt;/p&gt;

&lt;p&gt;1) What's the context of the data? i.e. does it align to a Shopify model would it make sense to store it on order/product etc or not&lt;br&gt;
2) What's the volume of data like? i.e. are you storing a couple of key data points and config or thousands of records&lt;br&gt;
3) Do you need to aggregate the information? i.e. compute totals across orders for example&lt;br&gt;
4) Are there webhooks for the Shopify model? i.e. can you easily get a copy of data out of Shopify&lt;br&gt;
5) Where will you need to access the data? i.e. only inside your app, on the storefront, on headless sites etc.&lt;br&gt;
6) What's the access of that data look like? i.e. will there be constant or sporadic access of the data&lt;/p&gt;

&lt;h2&gt;
  
  
  Database
&lt;/h2&gt;

&lt;p&gt;You will likely want a database to store some data regardless of the other information you'll want to store. As at the very minimum you will want to keep the store information, billing status, offline access token and contact info for your use and ease of access. This allows for a quick overview of the stores using the app, the billing plans in use, and provides the ability to contact them if necessary.&lt;/p&gt;

&lt;p&gt;Always ensure you have secured this information, particularly the offline access token. You will want to encrypt the data at rest and in transit and ensure you are following best practices for securing your database.&lt;/p&gt;

&lt;p&gt;Now we have covered the basics of what you will want to store in a database. Let us look at your answers to the questions above and where a database fits.&lt;/p&gt;

&lt;p&gt;If you have a large volume of data to store, hundreds, thousands or more of items, you will likely hit limits on using metafields or metaobjects in Shopify so probably not best approach. A database using SQL or NoSQL would work best here as then the limits are all in your control.&lt;/p&gt;

&lt;p&gt;If you need to aggregate the data regularly whether it be for dashboard, reports, or core app functionality, then a SQL based database is likely the right option. It will be much more efficient to do these aggregations than NoSQL. At the time of writing, you cannot aggregate across metafields or metaobjects in Shopify.&lt;/p&gt;

&lt;p&gt;If you need to allow access to the data in your app from all over Shopify, whether it be your app, in liquid, headless etc. Then using your own database here would add complications for both you and the merchant in exposing the data, so a database might not be the right option here.&lt;/p&gt;

&lt;p&gt;What is the access of the data you would store in a database? With constant requests, will you hit Shopify rate limits trying to access the metafields?&lt;br&gt;
If it is going to be hit with substantial number of requests consistently then NoSQL database likely makes sense, just for scale.&lt;/p&gt;

&lt;p&gt;Lastly how confident are you in what the data will look like, and will it need to change a lot over time? If not, then NoSQL might give you more flexibility as it does not need a specific schema and easier to change.&lt;br&gt;
But SQL you will have to migrate the tables, and this could be annoying if you have to do that frequently.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metafields
&lt;/h2&gt;

&lt;p&gt;So, let us look at some of the options Shopify give us if we want to look beyond storing the data ourselves.&lt;/p&gt;

&lt;p&gt;If the context of the data aligns to a Shopify model. Then you have some options, can you use the Shopify thing itself which saves you having to think of the format of the data and its lifecycle, like using draft orders or products etc and customising them.&lt;br&gt;
But consider if other apps or the merchant might be using these for something else, will there be any impact and can you differentiate your versions of these, so it is clear to other apps and merchants what is create by your app or not.&lt;br&gt;
Or does it make sense to extend the model using metafields, for example adding additional fields to a product or order. This is a great way to add additional information a Shopify model, in a structured way, instead of adding information to notes or tags.&lt;/p&gt;

&lt;p&gt;Metafields with a definition can be viewed by the merchant in Shopify Admin, so you can easily add more information and context to Orders or Products for example. They can also be easily accessed from Liquid, Storefront, or other APIs inside of Shopify, making them ideal to access information across different areas.&lt;/p&gt;

&lt;p&gt;Shopify have introduced some ownership and permissions for metafields now, so you can better control who can access that data and edit it. Giving you much better control over the data use inside of Shopify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Metaobjects
&lt;/h2&gt;

&lt;p&gt;If the data you want to store does not fit to the Shopify model, i.e. it is not directly related to an existing Shopify model, like products or orders. Then a metaobject might make sense, as here you can shape the data you want to store and allow customers to manage and edit that information as well.&lt;/p&gt;

&lt;p&gt;These can also be easily viewed by the merchant in Shopify Admin under Content, so you can easily add more information. They can also be easily accessed from Liquid, Storefront, or other APIs inside of Shopify, making them ideal to access information across different areas.&lt;/p&gt;

&lt;p&gt;Shopify have introduced some ownership and permissions for metaobjects now, so you can better control who can access that data and edit it. Giving you much better control over the data use inside of Shopify.&lt;/p&gt;

&lt;p&gt;Not only that but the metaobjects can be translated as well, so you can support a more localised experience for the merchants if you are using the data on the shop front.&lt;/p&gt;

&lt;h2&gt;
  
  
  One size does not fit all
&lt;/h2&gt;

&lt;p&gt;It may be the case that just one option does not fit all of what you need and that is also fine!&lt;/p&gt;

&lt;p&gt;You could use a database for keeping the store information, and some data you need to aggregate.&lt;br&gt;
If you can get data out of Shopify, leveraging webhooks for example. This could work really well, leveraging metafields, metaobjects in Shopify for your primary data storage and then keeping a copy of the data, you'd like to aggregate in a SQL Database. Meaning you get the best of both worlds!&lt;br&gt;
Of course, you can also always use metaobjects and metafields together, to add even more context and information than using just one of these independently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://shopify.dev/docs/apps/build/custom-data" rel="noopener noreferrer"&gt;If you'd like to read more about metafields and metaobjects, here is a link to the Shopify documentation.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In summary, you can use Shopify Platform to easily store data for your application. You have got plenty of options to choose from, whether you use just one or multiple in combination make sure you pick the option that works best for you and your customers and merchants.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pimsical.app/" rel="noopener noreferrer"&gt;Pimsical is my business, where you can find more Shopify content, my Shopify apps or help to optimise your operations and streamline your business backend.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>apps</category>
    </item>
    <item>
      <title>Shopify App Translations</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Sun, 29 Dec 2024 17:30:19 +0000</pubDate>
      <link>https://forem.com/jordanfinners/shopify-app-translations-52c</link>
      <guid>https://forem.com/jordanfinners/shopify-app-translations-52c</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;I've seen a lot of questions about the best way to integrate your Shopify app content with &lt;a href="https://apps.shopify.com/translate-and-adapt" rel="noopener noreferrer"&gt;Shopify's Translate &amp;amp; Adapt app&lt;/a&gt; and allowing merchants to translate your apps content.&lt;br&gt;
For example, if you are extending Shopify Checkout and would like merchants to be able to customise the translated content.&lt;/p&gt;

&lt;p&gt;I've worked on this for a number of Shopify apps, so thought I'd share an approach.&lt;/p&gt;
&lt;h2&gt;
  
  
  Content
&lt;/h2&gt;

&lt;p&gt;I believe the best way to allow custom translations of your apps content is to store the content in &lt;a href="https://shopify.dev/docs/apps/build/custom-data" rel="noopener noreferrer"&gt;Shopify Metaobjects&lt;/a&gt;.&lt;br&gt;
Metaobjects are pieces of content, which can easily be used across the Shopify Platform, either in Liquid or Storefront API. &lt;a href="https://shopify.dev/docs/apps/build/custom-data/metaobjects/use-metaobject-capabilities#make-your-metaobjects-translatable" rel="noopener noreferrer"&gt;You can enable them to be translatable as well&lt;/a&gt;, which is what we will use later!&lt;/p&gt;

&lt;p&gt;Ensure when you create the definitions they are not using &lt;a href="https://shopify.dev/docs/apps/build/custom-data/ownership#reserved-prefixes" rel="noopener noreferrer"&gt;app reserved prefix&lt;/a&gt;, e.g. beginning with &lt;code&gt;$app&lt;/code&gt;, as otherwise Translate &amp;amp; Adapt app won't be able to access it.&lt;/p&gt;

&lt;p&gt;Depending on how much content you want to translate you've got a couple of options here on designing your metaobject definition(s).&lt;/p&gt;

&lt;p&gt;You may already have metaobjects or metafields with config for your app, I believe it's best to separate the content and config.&lt;/p&gt;
&lt;h3&gt;
  
  
  Specific Definitions
&lt;/h3&gt;

&lt;p&gt;You could &lt;a href="https://shopify.dev/docs/apps/build/custom-data/metaobjects/manage-metaobjects" rel="noopener noreferrer"&gt;create one or many metaobject definitions&lt;/a&gt;, where each piece of content is a &lt;code&gt;single_line_text_field&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The advantages of this are that if you have different content for different sections of your app, its easily understandable from the merchant's point of view.&lt;/p&gt;

&lt;p&gt;For example, if you have content for the checkout, and their website. You could create two separate metaobject definitions, when this is displayed in Translate &amp;amp; Adapt app, it will be clear to the merchant, from the definition name and description, what the content will be used for.&lt;/p&gt;

&lt;p&gt;However, there are some limits here. Firstly, you will be using the &lt;a href="https://shopify.dev/docs/apps/build/custom-data/metaobjects/metaobject-limits" rel="noopener noreferrer"&gt;metaobject definition limits&lt;/a&gt;, which are fairly limited at the time of writing. Its best to be conscious that the merchant will be using other apps, which also could use definitions, so best to not be greedy with the number of definitions you create.&lt;/p&gt;

&lt;p&gt;Also, there is a limit of 40 field definitions per metaobject definition, so if you have a limited amount of content the merchants can translate or can consolidate it down to 40 then this will work for you. Otherwise, you may want to create multiple metaobject definitions or consider the Extensible approach below.&lt;/p&gt;

&lt;p&gt;Here is an example of what that looks like in &lt;a href="https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metaobjectDefinitionCreate" rel="noopener noreferrer"&gt;Admin GraphQL API 2024-10&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CreateMetaobjectDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MetaobjectDefinitionCreateInput&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;metaobjectDefinitionCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;metaobjectDefinition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;fieldDefinitions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;userErrors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"definition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My App Checkout Translations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Translations for the checkout section of my app."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-app-checkout-translations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fieldDefinitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The title of the checkout section"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"single_line_text_field"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subtitle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Subtitle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The subtitle of the checkout section"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"single_line_text_field"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"translatable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extensible
&lt;/h3&gt;

&lt;p&gt;If you have a lot of content the merchant can translate and don't want to register specific definitions. What you can do is register a single metaobject definition.&lt;br&gt;
Where you would create a metaobject definition, with a key and value. You may also want to register a description field, just to help the merchant understand where the content will be used.&lt;/p&gt;

&lt;p&gt;One thing to note here, is that you'll need to be careful as the key can be translated by the merchant, just something to watch out for when retrieving the data.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CreateMetaobjectDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MetaobjectDefinitionCreateInput&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;metaobjectDefinitionCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;definition&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$definition&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;metaobjectDefinition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;fieldDefinitions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;userErrors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"definition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My App Translations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Translations for my app."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-app-translations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fieldDefinitions"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"single_line_text_field"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Value"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"required"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"single_line_text_field"&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"capabilities"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"translatable"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"enabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Translations
&lt;/h2&gt;

&lt;p&gt;Now we have our metaobject definitions, it's time to add content and translate it.&lt;br&gt;
Fortunately, Shopify provides this out of the box!&lt;/p&gt;

&lt;p&gt;Firstly, &lt;a href="https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/metaobjectCreate" rel="noopener noreferrer"&gt;create your metaobject&lt;/a&gt;, based on the definition you created earlier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CreateMetaobject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$metaobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MetaobjectCreateInput&lt;/span&gt;&lt;span class="p"&gt;!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;metaobjectCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;metaobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$metaobject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;userErrors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;code&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"metaobject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"my-app-checkout-translations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"fields"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My App Checkout"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subtitle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Something to do with your order at checkout."&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next you need to get the &lt;a href="https://shopify.dev/docs/api/admin-graphql/2024-10/queries/translatableResource" rel="noopener noreferrer"&gt;translatable content for your metaobject&lt;/a&gt;, this will give you the digest and key that you can use when registering translations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;translatableResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gid://shopify/Metaobject/1234567"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;resourceId&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;translatableContent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;digest&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then you can register your translations using the &lt;a href="https://shopify.dev/docs/api/admin-graphql/2024-10/mutations/translationsRegister" rel="noopener noreferrer"&gt;translationsRegister mutation&lt;/a&gt;. You can also remove translations via a similar mutation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight graphql"&gt;&lt;code&gt;&lt;span class="k"&gt;mutation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CreateTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;!,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$translations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;TranslationInput&lt;/span&gt;&lt;span class="p"&gt;!]!)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;translationsRegister&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$translations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;userErrors&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;field&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;translations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;locale&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gid://shopify/Metaobject/1234567"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"translations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Paiement de mon application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"translatableContentDigest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dcf8d211f6633dac78dbd15c219a81b8931e4141204d18fba8c477afd19b75f9"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"key"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"subtitle"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Quelque chose à faire avec votre commande à la caisse."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"locale"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fr"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"translatableContentDigest"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"a18b34037fda5b1afd720d4b85b86a8a75b5e389452f84f5b6d2b8e210869fd7"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you specify the language and the translated content. This allows you to provide translations if you want to. If you also want to allow merchants to translate content in your own app and interface, you can use this to store the translations in Shopify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotcha
&lt;/h2&gt;

&lt;p&gt;There is a gotcha with this approach. You &lt;strong&gt;cannot&lt;/strong&gt; register translations in the stores default language.&lt;/p&gt;

&lt;p&gt;This means you do have to do some management here, its best explained with an example.&lt;/p&gt;

&lt;p&gt;If your default app content is in English, you can create your metaobject definitions.&lt;br&gt;
You create the metaobject with the fields you defined, if the merchants default language is English, the content here can be English.&lt;br&gt;
You can then register translations of this content in other languages, or the merchant can via Translate &amp;amp; Adapt app.&lt;/p&gt;

&lt;p&gt;However, if the merchants default language is Japanese, then you would need to create the metaobject content in either Japanese, if you have that available or ensure the merchant knows they will need to change the default content to Japanese. Which they can do inside of Shopify Metaobjects section.&lt;br&gt;
As you won't be able to register translations in Japanese.&lt;/p&gt;

&lt;p&gt;It’s just a gotcha when registering translations. I’m hoping the Shopify team will remove this limitation so content can be registered in any language as it would make management of this much easier.&lt;/p&gt;
&lt;h2&gt;
  
  
  Displaying Content
&lt;/h2&gt;

&lt;p&gt;Once you've set this all up. Merchants will be able to translate your content in Translate &amp;amp; Adapt app, and/or you can programmatically register them using Shopify APIs.&lt;/p&gt;

&lt;p&gt;You can then access these translations. In Liquid the translated content will automatically be displayed based on the user's language.&lt;/p&gt;

&lt;p&gt;If you are using the Storefront API, you can use the &lt;a href="https://shopify.dev/docs/api/storefront#directives" rel="noopener noreferrer"&gt;&lt;code&gt;@inContext&lt;/code&gt; directive&lt;/a&gt;. You set the language you'd like the content in and Shopify will automatically look it up for you, or return the default content if not available in that specific language.&lt;/p&gt;

&lt;p&gt;You can also always get the translations from the Admin GraphQL API, using the &lt;a href="https://shopify.dev/docs/api/admin-graphql/2024-10/queries/translatableResource" rel="noopener noreferrer"&gt;translatableResource&lt;/a&gt; and the translations list returned.&lt;/p&gt;
&lt;h2&gt;
  
  
  Translate &amp;amp; Adapt
&lt;/h2&gt;

&lt;p&gt;You can then deep link into the Shopify Translate &amp;amp; Adapt app using the Metaobject ID. For example, if your app is embedded in Shopify you can use this style of link:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;shopify://admin/apps/translate-and-adapt/localize/metaobject?shopLocale=fr&amp;amp;id=1234567
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Changing the shopLocale, market and id query parameters to the relevant ones for your metaobjects.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjordanfinners.dev%2Fimages%2Fshopify-app-translations-metaobject.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjordanfinners.dev%2Fimages%2Fshopify-app-translations-metaobject.webp" title="View of our Metaobject in Shopify Admin, from the merchants perspective" alt="View of our Metaobject in Shopify Admin, from the merchants perspective" width="800" height="410"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjordanfinners.dev%2Fimages%2Fshopify-app-translations-translate-adapt.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fjordanfinners.dev%2Fimages%2Fshopify-app-translations-translate-adapt.webp" title="View of our Metaobject in Shopify Translate &amp;amp; Adapt app, from the merchants perspective" alt="View of our Metaobject in Shopify Translate &amp;amp; Adapt app, from the merchants perspective" width="800" height="217"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In summary, you can use Shopify Platform to easily allow merchants to translate and manage your apps content, wherever you might be displaying it.&lt;/p&gt;

&lt;p&gt;This is a less known but very powerful way to extend the Shopify Platform and your apps, without having to build out your own translation engine!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.pimsical.app/" rel="noopener noreferrer"&gt;Pimsical is my business, where you can find more Shopify content, my Shopify apps or help to optimise your operations and streamline your business backend.&lt;/a&gt;&lt;/p&gt;

</description>
      <category>shopify</category>
      <category>apps</category>
    </item>
    <item>
      <title>Stop Fetch-ing in error</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Thu, 23 Feb 2023 09:02:14 +0000</pubDate>
      <link>https://forem.com/jordanfinners/stop-fetch-ing-in-error-1jdo</link>
      <guid>https://forem.com/jordanfinners/stop-fetch-ing-in-error-1jdo</guid>
      <description>&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Intro
&lt;/li&gt;
&lt;li&gt; The Problem
&lt;/li&gt;
&lt;li&gt; The Solution
&lt;/li&gt;
&lt;li&gt; Summary
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Before we jump in.&lt;br&gt;
If you are thinking I use &lt;a href="https://www.npmjs.com/package/axios" rel="noopener noreferrer"&gt;&lt;code&gt;axios&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://www.npmjs.com/package/request" rel="noopener noreferrer"&gt;&lt;code&gt;requestjs&lt;/code&gt;&lt;/a&gt; etc so this doesn't apply to me.&lt;br&gt;
Put the &lt;code&gt;npm&lt;/code&gt; package down and step away from the install!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/fetch#browser_compatibility" rel="noopener noreferrer"&gt;Fetch is now supported&lt;/a&gt; in all major browsers and even in &lt;a href="https://nodejs.org/en/blog/announcements/v18-release-announce/#fetch-experimental" rel="noopener noreferrer"&gt;NodeJS&lt;/a&gt; now!&lt;/p&gt;

&lt;p&gt;Which is awesome! We now have a single way of making requests across the browser and backend!&lt;br&gt;
So much time and npm downloads saved!&lt;/p&gt;

&lt;p&gt;Also if you need to support really old browsers, there is an official &lt;a href="https://github.com/github/fetch" rel="noopener noreferrer"&gt;polyfill&lt;/a&gt; you can load on your website to provide you with the same fetch experience.&lt;/p&gt;

&lt;p&gt;Now let’s jump in!&lt;/p&gt;
&lt;h2&gt;
  
  
  The Problem &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Fetch is awesome a really great way of making requests from your code.&lt;/p&gt;

&lt;p&gt;However I've seen in many tutorials and code the following code, which &lt;strong&gt;will&lt;/strong&gt; cause issues:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://example.com/movies.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Even in the &lt;a href="https://developer.mozilla.org/en-US/docs/web/api/fetch_api/using_fetch" rel="noopener noreferrer"&gt;Mozilla docs&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Why is this a problem?&lt;/p&gt;

&lt;p&gt;If the endpoint you are fetching, returns a not okay status code then fetch will not throw an error.&lt;br&gt;
You have to check yourself if the status code if okay or not &lt;em&gt;before&lt;/em&gt; trying to get the response body with &lt;code&gt;response.json()&lt;/code&gt;.&lt;br&gt;
Otherwise you could end up with some generic error and no context around what caused it.&lt;/p&gt;

&lt;p&gt;But, you might be saying to yourself well my endpoint always returns JSON, even in an error scenario.&lt;br&gt;
This may be the case, but if anything out of your control errors for example an API Gateway then you are very likely to get a non JSON response and again cause yourself a headache debugging.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Solution &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The solution to this is really straightforward, simply check if the response if ok, before attempting to get the response body.&lt;/p&gt;

&lt;p&gt;You can see in this example, I'm checking &lt;code&gt;response.ok&lt;/code&gt; &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/ok" rel="noopener noreferrer"&gt;which indicates if your response status was in the 2XX range&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But you can also check for specific statuses as well. Here I'm checking for a 204 - No Content status code as I won't have any JSON to return in this instance.&lt;/p&gt;

&lt;p&gt;Otherwise I reject the promise, returning the full response in the error.&lt;/p&gt;

&lt;p&gt;I've also wrapped it in a try catch for safety in order to catch any CORS, timeout errors etc.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;http://example.com/movies.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;204&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Summary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In summary, fetch is awesome!&lt;/p&gt;

&lt;p&gt;However there is a bit more to it in order to handle errors and bad responses from any endpoint you might be hitting, so double check your fetch code to make sure you are handling errors gracefully. It will definitely help when it comes to debugging any problems.&lt;/p&gt;

&lt;p&gt;Happy Building!&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>api</category>
      <category>webdev</category>
      <category>discuss</category>
    </item>
    <item>
      <title>5 things to consider when Designing Event Driven Systems</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Fri, 16 Jul 2021 18:20:09 +0000</pubDate>
      <link>https://forem.com/jordanfinners/5-things-to-consider-when-designing-event-driven-systems-4dab</link>
      <guid>https://forem.com/jordanfinners/5-things-to-consider-when-designing-event-driven-systems-4dab</guid>
      <description>&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Intro
&lt;/li&gt;
&lt;li&gt; Idempotent Nature
&lt;/li&gt;
&lt;li&gt; Data structure and storage
&lt;/li&gt;
&lt;li&gt; APIs
&lt;/li&gt;
&lt;li&gt; Versioning
&lt;/li&gt;
&lt;li&gt; Reconciliation
&lt;/li&gt;
&lt;li&gt; Summary
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Event driven systems are becoming ever more popular and integral part of our modern lives.&lt;/p&gt;

&lt;p&gt;Event driven systems consume and take action based on events, these systems also emit and define the events.&lt;/p&gt;

&lt;p&gt;This allows for you to loosely couple your services in the system allowing each service to be developed faster, independently and reduce complexity as each service is only responsible for a single thing, normally a domain or entity.&lt;br&gt;
For example Twitter could have domain for users, lists, bookmarks etc.&lt;/p&gt;

&lt;p&gt;Each of these services is responsible for a single domain even though lists are made up of users for example, they remain independent and follow &lt;a href="https://en.wikipedia.org/wiki/Single-responsibility_principle"&gt;the principle of single responsibility&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In this example you could have teams independently developing the list and user services, which means those services could be developed faster and focus on features for each rather than having them rely on each other.&lt;/p&gt;

&lt;p&gt;The bookmark service could for example take action on a &lt;code&gt;tweet-bookmarked&lt;/code&gt; event and store the reference to the tweet that a user bookmarked so that user can view a list of them later. It could also define and emit it's own events too!&lt;/p&gt;

&lt;p&gt;These events are published to a queue or bus, which can be subscribed or listened too by multiple subscribers and services. Often referred to as &lt;a href="https://aws.amazon.com/pub-sub-messaging/"&gt;pub/sub pattern&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Event driven systems are great as the events logically make sense, in the systems we interact with everyday, when you start to model them.&lt;/p&gt;

&lt;p&gt;However they come with plenty of things to think about. How to model your events, and what events to emit and how to separate your services. These are fundamental to the design of your event driven system.&lt;/p&gt;

&lt;p&gt;On top of this there are a few key things that you need to consider when designing event driven systems.&lt;/p&gt;
&lt;h2&gt;
  
  
  Idempotent Nature &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Idempotent means that multiple events being received have the same result every time.&lt;/p&gt;

&lt;p&gt;Event driven systems need to be designed and programmed in an idempotent nature as there is no guarantee events will only be received once. Imagine for instance that your system receives &lt;a href="https://zapier.com/blog/what-are-webhooks/"&gt;webhooks&lt;/a&gt; from a third party that notifies you of events, this is a very common pattern for event driven systems.&lt;/p&gt;

&lt;p&gt;With webhooks there is no guarantee that you will only get one webhook received, it can happen for a number of reasons I won't go into now.&lt;br&gt;
Likewise you will often want events to be replayed incase of a system failure.&lt;/p&gt;

&lt;p&gt;If this happens your system needs to be able to handle it and ensure the same result occurs no matter how many times the event is received. It would be no good if you ended up creating two orders instead of a single one due to receiving an order event twice.&lt;/p&gt;

&lt;p&gt;This means that your storage solutions and business logic needs factor this in when being built. For example using UPSERT rather than INSERT into a database so that events are updated if they already exist rather than inserted twice. Ensuring the business logic has the same output every time an event is put through it, regardless of the datetime or number of times an event has been seen.&lt;/p&gt;

&lt;p&gt;There's so much more to think about and detail when considering idempotency in systems, but hopefully this gives you a starting point to consider it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Data structure and storage &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We touched on it in the previous section but data is at the heart of any event driven system, so it crucial to consider it when designing your systems.&lt;/p&gt;

&lt;p&gt;One of the first things to consider is the structure of your events, as we talked about previously your events shouldn't be stateful. For example they shouldn't have properties that depend on other events occurring and store state.&lt;/p&gt;

&lt;p&gt;One of the first thing to consider is how are you planning on structuring your events, a common pattern is to have an event wrapper that contains metadata about an event around the data of the event itself. Below is &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events.html"&gt;an example from AWS&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6a7e8feb-b491-4cf7-a9f1-bf3703467718"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EC2 Instance State-change Notification"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aws.ec2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"111122223333"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2017-12-22T18:43:48Z"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"region"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"us-west-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resources"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:ec2:us-west-1:123456789012:instance/i-1234567890abcdef0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"detail"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"instance-id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;" i-1234567890abcdef0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"state"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"terminated"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where detail contains the body of the event, the other top level properties cover off the metadata around the event. You can also see it contains a version property which we'll talk about later.&lt;/p&gt;

&lt;p&gt;The next thing to consider is how will you be storing your events and data?&lt;/p&gt;

&lt;p&gt;Are you going to use an &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/patterns/event-sourcing"&gt;event sourcing pattern&lt;/a&gt; and store all your events and build up views of the data either as the events are stored or at read time. Or will your database be your view of the world and be the state of your system. Remembering of course this must handle receiving events possibly multiple times and out of order.&lt;/p&gt;

&lt;p&gt;As there will be possible significant number of events to be stored you will need to consider the storage solution carefully as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  APIs &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;You event driven system will likely include APIs to both access and affect the data held within it.&lt;/p&gt;

&lt;p&gt;The first thing to remember is you are working with a system with eventual consistency, so any time sensitive events need to be carefully considered. No matter how fast you make your system there will be occurrences where an event isn't processes 'in time'.&lt;/p&gt;

&lt;p&gt;If you have a user interface (UI) displaying these events or data resulting from these events you could use an optimistic UI, for example if an tweet is bookmarked the UI could add the tweet to the bookmarked list on the device (client) side before waiting to get the list from the backend as it may not be processed in time.&lt;/p&gt;

&lt;p&gt;You could also consider how you are handling events, for example you could use an API which synchronously updates the bookmarked database and then emits an event to other systems with a &lt;code&gt;tweet-bookmarked&lt;/code&gt; event for example. This ensures that your data store is up to date and ready for UIs to display the information when they call to get it but you still have other event driven options.&lt;/p&gt;

&lt;p&gt;When designing your API for an event driven system you also need to consider what methods you use and how you design your API, for example POST methods don't behave in an idempotent nature as they create something new each time. Whereas PUT methods do as its specific to an ID. &lt;a href="https://restfulapi.net/idempotent-rest-apis/"&gt;This is a really straightforward article covering off idempotent HTTP methods&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Depending how you are ensuring idempotency in your system e.g. constructing an ID consistently you might be able to still use a POST method and dedupe on the constructed ID, this doesn't truly follow HTTP specification but may be more pragmatic for your use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versioning &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Alongside considering your Data structure and APIs, we need to consider how to version them. Now I'm not really going to talk about versioning your API's here as there are plenty of articles about this but I am going talk to versioning your events.&lt;/p&gt;

&lt;p&gt;You will need to change and version your events inevitably, however when you do there is more to think about in an event driven system.&lt;/p&gt;

&lt;p&gt;The first thing to consider is how will your systems determine what version of event they are receiving. You can make all your events backwards compatible however this can be really tricky in the future.&lt;/p&gt;

&lt;p&gt;You can also &lt;a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-events.html"&gt;include a version field on your event. E.g. how the Eventbridge team at AWS does&lt;/a&gt;. You can then use this version in your downstream systems to correctly handle that version of your event.&lt;/p&gt;

&lt;p&gt;When you need to update an event, you should consider the changes you need to make to your event, once happy with the new version of your event schema. You need to ensure you work backwards, up stream, towards the producer of the events.&lt;/p&gt;

&lt;p&gt;Making changes to all these downstream services so they can handle both versions of your events will make the change over much smoother as each can then be deployed independently and remove any possible downtime as events could still be in transit with the old version when releasing the services.&lt;/p&gt;

&lt;p&gt;Once that has been completed you can swap over to your new event version in the producer of the event.&lt;/p&gt;

&lt;p&gt;You then need to decide if and how you are going to deprecate the previous event version.&lt;/p&gt;

&lt;p&gt;This needs real thought if you are intend to store and replay events through a system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconciliation &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;We spoke earlier about receiving webhooks from a third party, a common pattern in event driven systems. We also spoke about if they are delivered more than once, but what happens if they are delivered at all?&lt;/p&gt;

&lt;p&gt;We need to consider fault tolerance in your event driven system and reconciliation plays a large part here.&lt;/p&gt;

&lt;p&gt;If we are dealing with third parties outside of our control we need to reconcile the state they are in as we may have not either received or delivered events. Ideally you should ensure your system is fault tolerant enough to always deliver to third parties and retry or handle any failures but this might be hard initially, so a good fallback is reconciliation.&lt;/p&gt;

&lt;p&gt;Normally reconciliation jobs are run on a time schedule e.g. a common usage is every night at midnight to get all the items from a third party and check your system to ensure we have received them all, if not push the correct events through the system. I would try to reconcile each domain or entity of your system independently, although depending on the third party, you may have to be flexible with this.&lt;/p&gt;

&lt;p&gt;You could similarly do the same with a system you are delivering to and check everything is as expected.&lt;/p&gt;

&lt;p&gt;Although with all event driven systems you have levels of &lt;a href="https://en.wikipedia.org/wiki/Eventual_consistency"&gt;eventual consistency&lt;/a&gt;, where you cannot guarantee at what point in time the system is up to date, reconciliation gives you a tighter timeframe on when you can be confident that your system is correct.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Event driven systems can be really powerful, scalable and solve many business problems. However all the benefits don't come without responsibility and plenty of considerations when designing them.&lt;/p&gt;

&lt;p&gt;I'd love to hear any other tips or things you'd had to consider when designing or building event driven systems!&lt;/p&gt;

&lt;p&gt;Happy event-ing!&lt;/p&gt;

</description>
      <category>events</category>
      <category>architecture</category>
      <category>eventdriven</category>
      <category>softwaredesign</category>
    </item>
    <item>
      <title>Clouding Communications with acronyms</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Mon, 05 Jul 2021 20:31:09 +0000</pubDate>
      <link>https://forem.com/jordanfinners/clouding-communications-with-acronyms-265i</link>
      <guid>https://forem.com/jordanfinners/clouding-communications-with-acronyms-265i</guid>
      <description>&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Intro
&lt;/li&gt;
&lt;li&gt; Acronyms Seriously Suck
&lt;/li&gt;
&lt;li&gt; Cross Teams
&lt;/li&gt;
&lt;li&gt; New Starters
&lt;/li&gt;
&lt;li&gt; Context
&lt;/li&gt;
&lt;li&gt; Summary
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Let me start by saying communication is hard. Clear, concise communication even more so.&lt;/p&gt;

&lt;p&gt;Acronyms harm all communication. Acronyms are everywhere, all throughout our lives. But why.&lt;/p&gt;

&lt;p&gt;Has anyone ever seen an acronym and thought &lt;em&gt;yes, that has clarified things&lt;/em&gt;. Very rarely, if ever.&lt;/p&gt;

&lt;p&gt;I hope I can convince you of one thing by the end of this blog, to stop using acronyms.&lt;/p&gt;

&lt;p&gt;I'm going to start with an &lt;a href="https://gist.github.com/anonymous/ca9721fbf27e77667abb/forks"&gt;email from Elon Musk, sent to Space X employees in 2010&lt;/a&gt;. Regardless of your feelings about him, I hope you can see the points in his argument, that acronyms seriously suck!&lt;/p&gt;

&lt;h2&gt;
  
  
  Acronyms Seriously Suck - Elon Musk &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;There is a creeping tendency to use made up acronyms at SpaceX. Excessive use of made up acronyms is a significant impediment to communication and keeping communication good as we grow is incredibly important. Individually, a few acronyms here and there may not seem so bad, but if a thousand people are making these up, over time the result will be a huge glossary that we have to issue to new employees. No one can actually remember all these acronyms and people don't want to seem dumb in a meeting, so they just sit there in ignorance. This is particularly tough on new employees.&lt;/p&gt;

&lt;p&gt;That needs to stop immediately or I will take drastic action - I have given enough warning over the years. Unless an acronym is approved by me, it should not enter the SpaceX glossary. If there is an existing acronym that cannot reasonably be justified, it should be eliminated, as I have requested in the past.&lt;/p&gt;

&lt;p&gt;For example, there should be not "HTS" [horizontal test stand] or "VTS" [vertical test stand] designations for test stands. Those are particularly dumb, as they contain unnecessary words. A "stand" at our test site is obviously a test stand. VTS-3 is four syllables compared with "Tripod", which is two, so the bloody acronym version actually takes longer to say than the name!&lt;/p&gt;

&lt;p&gt;The key test for an acronym is to ask whether it helps or hurts communication. An acronym that most engineers outside of SpaceX already know, such as GUI, is fine to use. It is also ok to make up a few acronyms/contractions every now and again, assuming I have approved them, e.g. MVac and M9 instead of Merlin 1C-Vacuum or Merlin 1C-Sea Level, but those need to be kept to a minimum.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;He has really hit the nail on the head for me. I'd argue to go further not even have an approved list but it's the right direction!&lt;/p&gt;

&lt;h2&gt;
  
  
  Cross Teams &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Next on my hit list of why you shouldn't use acronyms is it massively hinders cross team communications. This can be between frontend and backend teams. It can be between engineering teams and marketing teams. Throughout the whole company acronyms cloud communication.&lt;/p&gt;

&lt;p&gt;Let me give an example, some people use PR in the marketing sector to refer to &lt;a href="https://en.wikipedia.org/wiki/PageRank"&gt;Page Rank&lt;/a&gt;, however in the engineering teams PR means something completely different, it means &lt;a href="https://en.wikipedia.org/wiki/Distributed_version_control#Pull_requests"&gt;Pull Request&lt;/a&gt;. You can see how already those two teams could misunderstand a conversation just from the use of this one acronyms, never-mind when you start to include other teams and more and more acronyms.&lt;/p&gt;

&lt;p&gt;I once was in a meeting with members from all different teams, and someone from the engineering department was explaining their role and what they do. Scattered throughout where fairly standard (from a engineering viewpoint) cloud acronyms e.g. EC2 (Amazon Elastic Compute Cloud), CDN (Content Delivery Network) and so on. At the time I was sat next to someone from the Human Resources department who whispered to me asking me to explain what each one meant as they didn't understand, others in the room had glazed over.&lt;/p&gt;

&lt;p&gt;These fairly common acronyms in the engineering world were harming communications, confusing some people, switching others off from the content being delivered. It's so easy for us to slip into using these acronyms if we deal with them day to day. But we need to remember the audience we are communicating with. If we reduce our use of acronyms we will be less likely to fall into the trap of clouding cross team communications.&lt;/p&gt;

&lt;h2&gt;
  
  
  New Starters &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now never-mind clouding cross team communications, acronyms can harm communications inside your own team!&lt;/p&gt;

&lt;p&gt;I can recall at least two situations where I was new to a team and in the morning stand up discussions, and I was completely lost, not because of the technical information but the use of many unfamiliar acronyms. I had no idea what they were talking about, until I had to pause the stand up and ask for them to explain what the acronym meant, which actually took longer than using the actual words contained in the acronym themselves.&lt;/p&gt;

&lt;p&gt;This happens all the time, I'm certain, you've probably experienced this yourself.&lt;/p&gt;

&lt;p&gt;Often it can be even worse if someone doesn't ask for clarity on the meaning of acronyms and leaves the conversation no clearer on what is going on and feeling out of their depth. This happened to me plenty of times early on in my career, where I'd frantically search for the meanings of acronyms after meetings to make sense of what had just happened in the meeting.&lt;/p&gt;

&lt;p&gt;It can be really off putting for new starters to your team. It can also mean new starters are spending more time trying to decipher acronyms than getting stuck in with the team.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Context is everything when communicating. It's very easy when communicating to forget the people you are communicating with likely won't have the same context and head space as you.&lt;/p&gt;

&lt;p&gt;This is even easier to forget about the other persons context when writing using &lt;a href="https://en.wikipedia.org/wiki/Asynchronous_communication#Application_layer"&gt;asynchronous communication&lt;/a&gt; e.g. email, written documentation, blogs, direct messages and so on. It's also less likely that the reader has your context and head space when consuming content from asynchronous communication sources. Making both ends of communication harder.&lt;/p&gt;

&lt;p&gt;Especially important to consider context, when some of the most common and pervasive forms of modern communications are asynchronous in nature.&lt;/p&gt;

&lt;p&gt;It is so easy to slip into the use of acronyms when writing in particular, and for me. likely to be the biggest source of confusion. (It's much easier to ask someone what they mean by an acronym when speaking directly for example).&lt;br&gt;
I've done it on several occasions when writing this blog and had to go back and correct myself. But if we remind ourselves of our readers and the readers context, we can make a conscious effort to remove acronyms and help clear up communications.&lt;/p&gt;

&lt;p&gt;I've often read blogs, and attended presentations where I've had to search out the meaning of acronyms while trying to understand the meaning of the content I'm consuming. Making the processes of understanding and digesting the content doubly hard.&lt;/p&gt;

&lt;p&gt;This is before you even consider the accessibility of the content. Try having a screen reader, read out text containing acronyms. It is so hard to maintain your train of thought and digest the content, for me at least.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Acronyms negatively impact communications throughout our lives, and yet they persist. Next time you use or hear an acronym being used, please consider how it's now clouding the communication and how you can help bring clarity to your communications.&lt;/p&gt;

&lt;p&gt;In summary, I hope I've managed to convince you to stop using acronyms. If we all make a conscious effort to stop or at least reduce the usage of acronyms, we can make communications clearer, more concise and banish the confusion.&lt;/p&gt;

</description>
      <category>communication</category>
      <category>productivity</category>
      <category>career</category>
      <category>acronyms</category>
    </item>
    <item>
      <title>Considering the advantages of Cloud Native Architecture</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Tue, 15 Jun 2021 08:47:12 +0000</pubDate>
      <link>https://forem.com/jordanfinners/considering-the-advantages-of-cloud-native-architecture-1cl1</link>
      <guid>https://forem.com/jordanfinners/considering-the-advantages-of-cloud-native-architecture-1cl1</guid>
      <description>&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Intro
&lt;/li&gt;
&lt;li&gt; Focus
&lt;/li&gt;
&lt;li&gt; Management
&lt;/li&gt;
&lt;li&gt; Cost
&lt;/li&gt;
&lt;li&gt; Responsibilities
&lt;/li&gt;
&lt;li&gt; Vendor Lock In Myth?
&lt;/li&gt;
&lt;li&gt; Summary
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;I was going to write this about the Risks of Cloud Agnostic Architecture. However I think it best to stick with the positive and highlight the advantages of a Cloud Native Architecture instead.&lt;/p&gt;

&lt;p&gt;The first thing you might be asking is...What is Cloud Native Architecture?&lt;/p&gt;

&lt;p&gt;Cloud Native Architecture is designing your system, service or application, whatever it might be, to make use of the unique capabilities of the cloud and of your chosen Cloud Provider.&lt;/p&gt;

&lt;p&gt;I consider this 'using the right tool for the job', you could use a hammer to get a screw into a plank of wood, but the right tool, the more specific tool, a screwdriver, would do a better job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Focus &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Let's take a database, a common requirement for almost any architecture.&lt;br&gt;
There are an array of options depending on your chosen Cloud Provider, to name just a handful. AWS (Amazon Web Services) has &lt;a href="https://aws.amazon.com/dynamodb/"&gt;Dynamodb&lt;/a&gt;, &lt;a href="https://aws.amazon.com/rds/aurora/"&gt;Aurora&lt;/a&gt;, &lt;a href="https://aws.amazon.com/rds/"&gt;RDS (Relational Database Service)&lt;/a&gt;. Azure has &lt;a href="https://azure.microsoft.com/en-gb/services/cosmos-db/"&gt;Cosmos DB&lt;/a&gt; (which if you haven't heard of definitely check it out, it's a favorite of mine), &lt;a href="https://azure.microsoft.com/en-gb/products/azure-sql/database/"&gt;SQL&lt;/a&gt;. Google Cloud has &lt;a href="https://cloud.google.com/spanner"&gt;Spanner&lt;/a&gt; and &lt;a href="https://cloud.google.com/bigtable"&gt;Big Table&lt;/a&gt; and the list goes on and on and on.&lt;/p&gt;

&lt;p&gt;Each has its own specific use case, or scenario it functions slightly better at than the others but they are all still a database.&lt;br&gt;
You could also spin up your own database, on cloud servers or your own servers and manage that yourself. Which is how almost everyone worked with databases before the cloud and cloud providers became popular.&lt;/p&gt;

&lt;p&gt;But a database, is still a database, in terms of infrastructure. I'm not referring to the data structures and modelling inside of a database here, just about the infrastructure.&lt;/p&gt;

&lt;p&gt;Yours and your teams time, their focus is limited, you only have a finite amount of it. Only so much you can spend your focus and time on.&lt;br&gt;
Would your business prefer that time to be spent on delivering value, delivering differentiators from your competition or spending more time on a building and managing database infrastructure.&lt;/p&gt;

&lt;p&gt;Which is a problem with plenty of existing solutions, only a handful of which I mentioned before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Management &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Continuing on from Focus and our database example. Not only are there existing solutions to a database, but they are also managed for you.&lt;br&gt;
Managed by teams who have the experience of running databases (or any other managed service) for many other businesses, at every kind of scale. That is one of the major benefits of the cloud, the providers will often have seen the scenario before with a different customer or because they have been running the database or service for a period of time before your use of it.&lt;/p&gt;

&lt;p&gt;Not only do they have the benefit of experience, but it is also their focus, their teams focus, to make that database as good as it can be for the variety of businesses using it.&lt;br&gt;
That is their focus, what is yours?&lt;/p&gt;

&lt;p&gt;I recently got a little carried away with a load test, on a cloud native serverless system.&lt;br&gt;
Which I had only previously tested with a &lt;strong&gt;few hundred&lt;/strong&gt; events. I ended up putting &lt;strong&gt;12,000&lt;/strong&gt; events through.&lt;/p&gt;

&lt;p&gt;However I didn't need to worry as the system was using managed services, which tend to be charged on usage, they scaled up, absorbed and processed that load without breaking a sweat.&lt;br&gt;
Because they were managed and will have definitely seen larger spikes of events than that, I didn't need to worry about the system coping or managing the scale myself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Now this can be a double edged sword, Cloud Native Architecture tends to be priced based on usage which does have pros and cons.&lt;/p&gt;

&lt;p&gt;There are plenty of stories where Cloud Budgets and limits haven't been put in place and people have ended up being billed large amounts due to something running wild.&lt;/p&gt;

&lt;p&gt;However for every one of those stories, there are plenty of unheard ones about the benefits of usage based pricing.&lt;/p&gt;

&lt;p&gt;For example, if you had air conditioning that you rarely used, but you kept it on and running at maximum all the time anyway, on the occasion you needed it, that would be inefficient and more expensive that it needs to be.&lt;br&gt;
This is how I like to think of usage based pricing.&lt;/p&gt;

&lt;p&gt;Why pay for a server or cluster, every hour of every day, when business as usual only needs a fraction of the computing power available? Would it not be better to only pay for what you use, when you use it.&lt;/p&gt;

&lt;p&gt;I will caveat this section with making sure your architecture is efficient with its usage of cloud native services and designed well, will help massively with cost.&lt;/p&gt;

&lt;p&gt;Also if you are worrying about your AWS bill I would recommend chatting to &lt;a href="https://www.duckbillgroup.com/"&gt;the duckbill group&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Responsibilities &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;As a business or as a member of a business, you have plenty of responsibilities and things to think about. The competition, your staff, the environment, the wider world and so on.&lt;/p&gt;

&lt;p&gt;So why add one more thing for you to be responsible for? Responsible for if your system is scaling, if its managing and coping, if it needs your attention.&lt;/p&gt;

&lt;p&gt;Now I'm not saying don't monitor your Cloud Native systems, &lt;em&gt;definitely do&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;What I am saying is that you probably have much less to be responsible for when using managed services, like we said before they are managed for you, with all the benefits that come with that.&lt;/p&gt;

&lt;p&gt;The thing you do need to be responsible for yourself is designing your system up front and spending time on your Cloud Native Architecture to ensure you are using the best tool for the job and the unique advantages that your Cloud Provider gives you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vender Lock In Myth &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;You will often hear a disadvantage of Cloud Native Architecture, is that it means you are reliant on that Cloud Provider, you are locked in to that vendor.&lt;/p&gt;

&lt;p&gt;Now it is true that because you are using Cloud Native systems you are tied to that vendor and it will be harder to migrate to another Cloud Provider, or in a disaster move over to another Cloud Provider.&lt;/p&gt;

&lt;p&gt;However, I have yet to hear of anyone move Cloud Providers unless a large cost benefit has been agreed in advance. Furthermore, no matter how agnostic your architecture is, all the surrounding permissions models, and permissions themselves will all be different per Cloud Provider and need changing, as will any Infrastructure as Code for example &lt;a href="https://www.terraform.io/"&gt;Terraform&lt;/a&gt;. This all has to be Cloud Provider specific or Cloud Native by design.&lt;/p&gt;

&lt;p&gt;If there is a disaster with your Cloud Provider (and that is a really big &lt;strong&gt;IF&lt;/strong&gt;), how quickly could your data be migrated to a new Cloud Provider, how quickly any state? And what are the statistical chances of your Cloud Provider going down or having significant issues?&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In summary, I believe utilising Cloud Native Architectures, which are often Serverless but thats another blog entirely, provide real benefits to your business and team.&lt;br&gt;
It allows you to focus, to spend your limited time, energy and money on what you are best at, at differentiating your business and delivery value quickly for your customers.&lt;/p&gt;

&lt;p&gt;Use the unique tools that each Cloud Provider gives you to your advantage, minimise the time you are spending on problems that don't make your business special, use the right tool for the job and leverage the clouds benefits.&lt;/p&gt;

&lt;p&gt;Happy Building!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>cloud</category>
    </item>
    <item>
      <title>CSP - Content Security Policy</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Fri, 11 Jun 2021 21:10:25 +0000</pubDate>
      <link>https://forem.com/jordanfinners/csp-content-security-policy-d3c</link>
      <guid>https://forem.com/jordanfinners/csp-content-security-policy-d3c</guid>
      <description>&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Intro
&lt;/li&gt;
&lt;li&gt; Directives
&lt;/li&gt;
&lt;li&gt; Values
&lt;/li&gt;
&lt;li&gt; Summary
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Lastly but by no means least, carrying on from my previous blog about &lt;a href="https://jordanfinners.dev/blogs/website-security-week"&gt;website security week&lt;/a&gt;, we're going to talk about CSP or Content Security Policy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;CSP is Content Security Policy&lt;/a&gt; this is one of the most powerful tools in your arsenal to secure your website.&lt;/p&gt;

&lt;p&gt;These are two ways to to set your content security policy, either as a header &lt;code&gt;Content-Security-Policy&lt;/code&gt; or via a meta tag in your HTML for example:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https://google.com; child-src 'none';"&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Directives &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;The content policy is made up of directives (the thing to restrict) and the value(s) on how it can be restricted. We won't cover all all the possible directives in this blog but you can find &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy#directives"&gt;a list of all the directives here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;They syntax is as follows:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: directive value; directive value value; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;There are some key directives you should set.&lt;/p&gt;

&lt;h3&gt;
  
  
  default-src
&lt;/h3&gt;

&lt;p&gt;As the name suggests this is the fallback if there aren't more specific directives used. I'd recommend setting it to 'none'&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  connect-src
&lt;/h3&gt;

&lt;p&gt;This affects what you can 'connect' to via fetch and make HTTP requests to.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; connect-src https://some.api.com; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  img-src
&lt;/h3&gt;

&lt;p&gt;This affects where you can load images from.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; img-src https://some.img.host https://another.img.place; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  form-action
&lt;/h3&gt;

&lt;p&gt;This affects where you can send form submissions to via the HTML form attributes.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; form-action https://some.api.host; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;These are just a handful of the directives you should set on your content security policy. The more specific your content security policy directives the stronger your policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Values &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;You can specify many different types of values for each directive and its important to understand the affect of each one.&lt;/p&gt;

&lt;h3&gt;
  
  
  'none'
&lt;/h3&gt;

&lt;p&gt;This won't allow loading of any resources.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  'self'
&lt;/h3&gt;

&lt;p&gt;Only allow resources from the current domain.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'self'; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Hosts
&lt;/h3&gt;

&lt;p&gt;Allow loading from any number of hosts, it can also have an optional protocol e.g. http:// or https://, an optional port e.g. some.website:8080, and/or an optional path e.g. &lt;a href="https://some.website/path/to/file"&gt;https://some.website/path/to/file&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: img-src https://some.img.host some.other.images.com img.org:8080 img.co.uk/path/to/img.jpg; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Schema
&lt;/h3&gt;

&lt;p&gt;You can set just a schema e.g. https:, http:, data: but I generally wouldn't recommend this except perhaps for inline images which are data:xxxx.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: img-src data:; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Nonce
&lt;/h3&gt;

&lt;p&gt;This works in conjunction with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce"&gt;script HTML tag nonce attribute&lt;/a&gt;, the server must generate a unique value.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: script-src nonce-DhcnhD3khTMePgXwdayK9BsMqXjhguVV; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  SHA
&lt;/h3&gt;

&lt;p&gt;This is a SHA hash of a resource for example, if you apply a content security policy the browser will generate these for you to use if you cannot use any of the other values.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: script-src sha256-jzgBGA4UWFFm; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can use any of these values in combination with one another to lockdown your content security policy as much as possible.&lt;/p&gt;

&lt;p&gt;Here is an example:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Content-Security-Policy: default-src 'none'; script-src 'self' &lt;a href="https://static.cloudflareinsights.com"&gt;https://static.cloudflareinsights.com&lt;/a&gt;; img-src 'self'; style-src 'self'; connect-src 'self' &lt;a href="https://cloudflareinsights.com"&gt;https://cloudflareinsights.com&lt;/a&gt; &lt;a href="https://api.challenge.new"&gt;https://api.challenge.new&lt;/a&gt;; font-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; manifest-src 'self';&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Summary &lt;a&gt;&lt;/a&gt;&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;In summary, setting a content security policy is one of the most powerful tools in your arsenal to secure your website. It can take some time to set up a strict content security policy but that time is payed back tenfold in the benefits it provides.&lt;/p&gt;

&lt;p&gt;Set that content security policy now!&lt;/p&gt;

&lt;p&gt;Happy Building!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>security</category>
    </item>
    <item>
      <title>Clarifying CORS - Cross-origin Resource Sharing</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Tue, 01 Jun 2021 17:56:55 +0000</pubDate>
      <link>https://forem.com/jordanfinners/clarifying-cors-cross-origin-resource-sharing-4dk9</link>
      <guid>https://forem.com/jordanfinners/clarifying-cors-cross-origin-resource-sharing-4dk9</guid>
      <description>&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Intro
&lt;/li&gt;
&lt;li&gt; Access-Control Headers
&lt;/li&gt;
&lt;li&gt; Rate Limiting
&lt;/li&gt;
&lt;li&gt; Summary
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Continuing on from my previous blog about &lt;a href="https://jordanfinners.dev/blogs/website-security-week"&gt;website security week&lt;/a&gt;, we're going to talk about a CORS on the web.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS"&gt;CORS is Cross-origin Resource Sharing&lt;/a&gt; this is often used when your website is hosted separately from your API. e.g. your website is at website.com and calls your API at api.com.&lt;br&gt;
This is a common architectural pattern as it allows each API and website to move independently and faster, however it can introduce some security issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access-Control Headers &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;To allow CORS requests your API will need to response with certain headers, which allow certain behaviors from your website/frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Access-Control-Allow-Origin
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin"&gt;This header&lt;/a&gt; can be set with either the &lt;code&gt;origin&lt;/code&gt; which will be calling the API, it can only be a single origin.&lt;br&gt;
Otherwise it can be a &lt;code&gt;*&lt;/code&gt; however this doesn't allow credentials to be passed, which we will talk about later.&lt;/p&gt;

&lt;p&gt;If at all possible prefer setting a specific origin to a &lt;code&gt;*&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Example Usage:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access-Control-Allow-Origin: &lt;a href="https://mozilla.org"&gt;https://mozilla.org&lt;/a&gt;&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Access-Control-Allow-Methods&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods"&gt;This header&lt;/a&gt; can be set with a list of HTTP Methods that are allowed to be used to contact your API.&lt;/p&gt;

&lt;p&gt;Generally speaking OPTIONS will want to be part of this list as any frontend will make a OPTIONS request, often referred to as a preflight request, before it makes the actual request. OPTIONS requests won't be made for GET requests.&lt;/p&gt;

&lt;p&gt;It can also be &lt;code&gt;*&lt;/code&gt; however, you should be specific if you can.&lt;/p&gt;

&lt;p&gt;Example Usage:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access-Control-Allow-Methods: POST, GET, OPTIONS&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Access-Control-Max-Age&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age"&gt;This header&lt;/a&gt; can be set to a time period that the frontend will cache the preflight OPTIONS request. It is a value in seconds for example 86400 seconds is 24 hours.&lt;/p&gt;

&lt;p&gt;Let's say you've set Max-Age to the above, this means that the first request you make from the frontend to the API will make an OPTIONS request and then the actual request. It will subsequently won't have to make another OPTIONS request to that API for 24 hours.&lt;/p&gt;

&lt;p&gt;Example Usage:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access-Control-Max-Age: 86400&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Access-Control-Allow-Headers&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers"&gt;This header&lt;/a&gt; can be set with a list of Headers that are allowed to be passed onto your API.&lt;/p&gt;

&lt;p&gt;Example Usage:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access-Control-Allow-Headers: X-PINGOTHER, Content-Type&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Access-Control-Allow-Credentials&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials"&gt;This header&lt;/a&gt; specifies if to include credentials in the request. Credentials count as cookies, authorization headers or TLS client certificates.&lt;/p&gt;

&lt;p&gt;Example Usage:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Access-Control-Allow-Credentials: true&lt;br&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Rate Limiting &lt;a&gt;&lt;/a&gt;&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;You should note that &lt;code&gt;Access-Control-Allow-Origin&lt;/code&gt; header only prevents browsers from making requests to the API. It does not prevent calls to your API from other machines, command line, Postman etc.&lt;br&gt;
You should ensure that you have put other security measures in place to prevent misuse of your API, including Authentication and Rate Limiting.&lt;/p&gt;

&lt;p&gt;Rate Limiting involves restricting too many calls being made to your API. It can be done in a number of ways depending on how your API is developed. I would look for libraries to help manage this for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;In summary, separating your API and website can produce real development benefits however it can introduce security problems and having to deal with CORS.&lt;br&gt;
Hopefully this helps clarify CORS and how you can secure it.&lt;/p&gt;

&lt;p&gt;Set those headers now!&lt;/p&gt;

&lt;p&gt;Happy Building!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>security</category>
    </item>
    <item>
      <title>Why your website needs validation</title>
      <dc:creator>Jordan Finneran</dc:creator>
      <pubDate>Sun, 30 May 2021 21:19:39 +0000</pubDate>
      <link>https://forem.com/jordanfinners/why-your-website-needs-validation-44jm</link>
      <guid>https://forem.com/jordanfinners/why-your-website-needs-validation-44jm</guid>
      <description>&lt;h2&gt;
  
  
  Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; Intro
&lt;/li&gt;
&lt;li&gt; Forms
&lt;/li&gt;
&lt;li&gt; Custom Validation
&lt;/li&gt;
&lt;li&gt; Summary
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Intro &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Continuing on from my previous blog about &lt;a href="https://jordanfinners.dev/blogs/website-security-week"&gt;website security week&lt;/a&gt;, we're going to talk about a validation.&lt;/p&gt;

&lt;p&gt;If accept user input, you are going to need to validate the input. Non validated user inputs can lead to security vulnerabilities for example SQL injection attacks, where user input escapes your database and starts modifying it. It can also lead to exceptions from your code if a user inputs text rather than a number for example.&lt;/p&gt;

&lt;p&gt;Please Please Please ensure you do the same validation server side as you do on the frontend (client) side.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forms &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;You user inputs should be contained in HTML forms which comes with lots of powerful validation tools built in.&lt;br&gt;
This also means you can start to add validation without adding any extra javascript, increasing performance.&lt;/p&gt;

&lt;p&gt;First thing to check on your inputs is, are you using the correct type:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  button&lt;/li&gt;
&lt;li&gt;  checkbox&lt;/li&gt;
&lt;li&gt;  color&lt;/li&gt;
&lt;li&gt;  date&lt;/li&gt;
&lt;li&gt;  datetime-local&lt;/li&gt;
&lt;li&gt;  email&lt;/li&gt;
&lt;li&gt;  file&lt;/li&gt;
&lt;li&gt;  hidden&lt;/li&gt;
&lt;li&gt;  image&lt;/li&gt;
&lt;li&gt;  month&lt;/li&gt;
&lt;li&gt;  number&lt;/li&gt;
&lt;li&gt;  password&lt;/li&gt;
&lt;li&gt;  radio&lt;/li&gt;
&lt;li&gt;  range&lt;/li&gt;
&lt;li&gt;  reset&lt;/li&gt;
&lt;li&gt;  search&lt;/li&gt;
&lt;li&gt;  submit&lt;/li&gt;
&lt;li&gt;  tel&lt;/li&gt;
&lt;li&gt;  text&lt;/li&gt;
&lt;li&gt;  time&lt;/li&gt;
&lt;li&gt;  url&lt;/li&gt;
&lt;li&gt;  week&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will provide lots of out of the box validation goodness from the outset. &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#input_types"&gt;More information on the types&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Next up for file inputs ensure you've set the &lt;code&gt;accept&lt;/code&gt; attribute which allows you to check the type of the file for example &lt;code&gt;accept="image/png, image/jpeg"&lt;/code&gt;.&lt;br&gt;
You should also set the &lt;code&gt;multiple&lt;/code&gt; attribute to whether you are allowing many files or a single.&lt;/p&gt;

&lt;p&gt;Next up for number inputs set the &lt;code&gt;step&lt;/code&gt; attribute to ensure only increments of the amount you want are allowed to be entered.&lt;br&gt;
Also set the &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; values as required to limit the numbers that can be inputted.&lt;br&gt;
For non numeric values there are &lt;code&gt;minlength&lt;/code&gt; and &lt;code&gt;maxlength&lt;/code&gt; which limit the number of characters that can be inputted.&lt;/p&gt;

&lt;p&gt;Finally we have &lt;code&gt;pattern&lt;/code&gt; attribute, this can be used to match a specific Regular Expression to validate the input. If you are using one of the existing types above, for example email, you &lt;em&gt;don't&lt;/em&gt; then need to have your own email regular expression.&lt;/p&gt;

&lt;p&gt;Example usage:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form name="venue"&amp;gt;
    &amp;lt;label&amp;gt;What is the max number of decimal things?
        &amp;lt;input name="capacity" type="number" placeholder="e.g. 32" min="0" max="100" step="0.1" /&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;label&amp;gt;Any images of your venue you wish to upload?
        &amp;lt;input name="images" type="file" accept="image/png, image/jpeg" multiple/&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;label&amp;gt;How do we contact you?
        &amp;lt;input name="contact" type="email" placeholder="e.g. bob@bob.com" /&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;label&amp;gt;Enter UUID to test pattern usage?
        &amp;lt;input name="pattern" type="text" pattern="[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}" /&amp;gt;
    &amp;lt;/label&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Custom Validation &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;If you want to extend the validation of a form, I would recommend adding an event listener on the form for &lt;code&gt;submit&lt;/code&gt; event, and then prevent the default action using &lt;code&gt;event.preventDefault()&lt;/code&gt;.&lt;br&gt;
You can then run any validation on the form using javascript and set &lt;code&gt;setCustomValidity&lt;/code&gt; on the inputs which then uses the in built goodness of forms and inputs to display the error message.&lt;/p&gt;

&lt;p&gt;Example Usage:&lt;/p&gt;

&lt;p&gt;HTML&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;form name="venue" onsubmit="submit"&amp;gt;
    &amp;lt;p class="passwordRules"&amp;gt;Passwords must have at least one uppercase and lowercase letter, one number, and at least 8 or more characters.&amp;lt;/p&amp;gt;

    &amp;lt;label&amp;gt;Password
        &amp;lt;input 
        name="password" 
        type="password" 
        required 
        placeholder="XXXXXXXX"
        pattern="(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}"
        title="Must contain at least one uppercase and lowercase letter, one number, and at least 8 or more characters"/&amp;gt;
    &amp;lt;/label&amp;gt;

    &amp;lt;label class="secondPass"&amp;gt;Confirm Password
        &amp;lt;input
        name="confirmPassword" 
        type="password" 
        required 
        placeholder="XXXXXXXX"
        pattern="(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z]).{8,}"
        title="Must contain at least one uppercase and lowercase letter, one number, and at least 8 or more characters"/&amp;gt;
    &amp;lt;/label&amp;gt;
&amp;lt;/form&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Javascript&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  /**

&lt;ul&gt;
&lt;li&gt;Handle form submission&lt;/li&gt;
&lt;li&gt;@param {Event} event the form submission event, preventing the normal form behavior
*/
async submit(event) {
event.preventDefault();
// custom validation of the passwords
this.validatePassword();
// grab the form and trigger validation
const form = this.querySelector('form');
const valid = form.reportValidity();
if (valid) {
    // do something with the form
} else {
  // form isn't valid
}
}&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;/**&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks to see if passwords match
*/
validatePassword() {
const pass = this.querySelector('input[name="password"]');
const confirmPass = this.querySelector('input[name="confirmPassword"]');
if (pass.value !== confirmPass.value) {
  confirmPass.setCustomValidity("Passwords don't match");
} else {
  confirmPass.setCustomValidity('');
}
}
&lt;/li&gt;
&lt;/ul&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;


Summary &lt;a&gt;&lt;/a&gt;
&lt;/h2&gt;


&lt;p&gt;In summary, HTML gives us really powerful validation tools to check the user input and validate with minimal Javascript, which we can also extend with a little bit of Javascript too.&lt;br&gt;
We must also ensure that any validation we have completed on the frontend is also done on the server side to prevent users circumventing your frontend and directly interacting with your API.&lt;/p&gt;

&lt;p&gt;Validating your user input prevents a whole heap of issues and vulnerabilities for your applications and business.&lt;/p&gt;

&lt;p&gt;Happy Validating!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>security</category>
    </item>
  </channel>
</rss>
