Guides · Integrations

Shopify

Connect a space's Shopify store to pick its collections and products in content, add a product grid block, and render live prices and stock on the site.

10 min read

Connect a space's Shopify store, and editors pick its collections and products in content: a Collection field in a product grid, a Product field in a feature block. Content stores which item was picked; the site reads its prices, stock and images live from Shopify.

Connecting a store

The connection goes through the Obelism app in Shopify, and is made by an admin or developer of the space.

  1. On www, open the space’s page and under Shopify enter the store’s address (acme, or acme.myshopify.com) and choose Connect store. Or install the Obelism app from the store’s Shopify admin.
  2. Shopify asks to allow the app's access to the store. Allow it.
  3. Back on Obelism, sign in if needed, and choose the space the store belongs to and the environment its storefront uses (usually production).
  • One store per space, in every environment. Develop, staging and production pick from the same store, so a product picked in develop is the same product after promotion.
  • A store belongs to one space, and a space has one store. Connecting another store to a space means disconnecting the first.
  • Content only reads products and collections. The app can also add files, their alt texts and their translations to products, which Obelism media does when you ask it to. It never sees orders, customers or payments.
  • Moving or disconnecting: on the space’s page on www, under Shopify → Move or disconnect, or open the Obelism app from the store’s Shopify admin. Content already pointing at the store’s items keeps them.
  • Uninstalling the app in Shopify disconnects the store, and Obelism forgets its tokens; 48 hours later Shopify asks us to delete the store's data, and we do.

The product grid block

Once the space has a store, Settings → Shopify offers Add a product grid block: a block type, productGrid, with a heading, a collection and a number of columns (2 to 6, 4 by default). It is added to the environment you're in, like any change to the content model; promote the model to bring it to the next environment. After that it's a type like any other: rename it, add fields, or delete it.

Where the model is managed in code, Settings show the block's definition to add to the model file instead.

Shopify fields

Any content type can have a field of type Shopify, holding a collection or a product (chosen when the field is created, and fixed after it is saved), one or, with multiple, a list with an optional minimum and maximum. See fields. A multiple field's picked items drag into the order you want, by their grip or with Space and the arrow keys, and delivery keeps that order.

Editors choose items with a picker that searches the store by title and shows each item's image: in the entry editor, the canvas and the form in the site's prompt bar. A space without a store says so and links to www, and hides the Shopify features until a store is connected.

Editing by prompt keeps or removes the items an entry has, but never picks new ones: a model can't know a store's products.

Tick Shopify products on a link field (products: true in the model) and it offers A product once the space has a store: a button or card that goes to a product, picked with the same picker. Nothing is imported, and no page is needed for it:

{
	"cta": {
		"product": { "id": "gid://shopify/Product/42", "handle": "mug" },
		"hash": "reviews"
	}
}

The delivery API answers it as type: "product", with the product and its product page (below) in entry, or null when it has none. Link to the product page's path, or to your own product route by handle (/products/mug). Prompts keep or remove a linked product, but never pick a new one.

Product pages

For the products you want to say more about than the store does (a story, a video, a comparison), make a page per product. A product page is an ordinary page whose type has a Product field marked unique (one product, not translated): each published page of that type is the page of its product, and no two can have the same one.

Once the space has a store, Settings → Shopify offers Add a product page type: a page type, productPage, with the product, a blocks field and the SEO fields (no title: the product's comes from the store), and a products folder for its pages, as in the store's own /products/<handle>. Change it like any other type afterwards, or give one of your own page types a unique product field instead.

A new product page starts from a product: under Content → New entry, choose the product page type and pick the product; it names the page, gives it the product's handle as slug and puts it in the products folder.

Where your site shows a product is set per environment under Settings → Preview and site → Preview URL → Product pages at: a path after the preview URL with {handle}, /products/{handle} by default (Shopify's own). The canvas opens a product page there (and shows any product of the store there, in its Products column), and the prompt bar on your site opens product pages there from its page menu.

Pages are only made for the products that need one; the rest keep their store page. A link to a product with a product page is delivered with that page. On the site's product route, look the page up by the product's id rather than by path, since a handle can change in Shopify after the page was made:

GET /api/delivery/v1/entries?contentType=productPage&fields.product=gid://shopify/Product/42

No page found means the product has no marketing content: render the store's product alone.

Mark the product on that route with the SDK's editableProduct(product), next to editable(page). When someone edits on the site, a product without a page then offers Add a product page in the prompt bar, which makes the draft for that product and shows it:

<main {...editableProduct(product)} {...editable(productPage)}>
	…
</main>

Collection pages

Collections get pages the same way, for what goes around a collection's products on your site: an introduction, a banner, a buying guide. A collection page's type has a Collection field marked unique (one collection, not translated); a type has at most one unique Shopify field, so a page is about a product or a collection, never both.

  • Settings → Shopify offers Add a collection page type: collectionPage, with the collection, a blocks field and the SEO fields (no title: the collection's comes from the store), and a collections folder for its pages.

  • A new collection page starts from a collection under Content → New entry, named after it, with its handle as slug, in the collections folder.

  • Settings → Preview and site → Preview URL → Collection pages at sets where your site shows a collection, /collections/{handle} by default. The canvas opens collection pages there, and shows any collection of the store there in its Collections column.

  • On your collection route, look the page up by the collection's id, and render the store's collection alone without one:

    GET /api/delivery/v1/entries?contentType=collectionPage&fields.collection=gid://shopify/Collection/7
  • Mark the collection with the SDK's editableCollection(collection) (@obelism/content 0.20 or later), next to editable(page), and the prompt bar on your site offers Add a collection page on a collection without one.

In content

A Shopify field stores the item's id, the same in every environment, and its handle when it was picked:

{
	"collection": {
		"id": "gid://shopify/Collection/451234",
		"handle": "new-arrivals"
	}
}

With multiple, a list of those. The delivery API returns it as stored. Content never copies prices, stock or images, so they are never out of date.

If the handle is changed in Shopify later, the stored one stays as it was until the item is picked again. Fetch by id, and use the handle only for links.

On the site

Settings → API tokens → Copy as .env gives the store's domain and a Storefront API token, made when the store was connected:

SHOPIFY_STORE_DOMAIN=acme.myshopify.com
SHOPIFY_STOREFRONT_TOKEN=…

The Storefront token only reads what the store sells, so it is safe in the browser. With the SDK (@obelism/content 0.17.0 and later):

import { fetchShopifyCollection } from '@obelism/content'

// A productGrid block: { heading, collection, columns }.
const collection = await fetchShopifyCollection(block.collection, {
	shop: process.env.SHOPIFY_STORE_DOMAIN!,
	storefrontToken: process.env.SHOPIFY_STOREFRONT_TOKEN!,
	first: 12,
})

const products = collection?.products ?? []
// Each: id, handle, title, availableForSale, featuredImage,
// priceRange.minVariantPrice ({ amount, currencyCode })

fetchShopifyProducts(items, options) does the same for a product field, in the field's order. Both leave out what the store no longer has, and take country and language for a market's prices. See the JavaScript SDK.

A Hydrogen or Liquid storefront already queries Shopify: use the id in its own queries instead (collection(id: …) in the Storefront API).

npx @obelism/content types types the fields as ShopifyItem ({ id, handle }), and npx @obelism/content init writes an example item for each.

On a Hydrogen storefront

A Hydrogen storefront on Shopify Oxygen queries the Storefront API itself; the { id, handle } Content stores are all it needs. Use the two values from Copy as .env, or Hydrogen's own PUBLIC_STORE_DOMAIN and PRIVATE_STOREFRONT_API_TOKEN:

import { gql } from '@shopify/hydrogen'

const PRODUCT = gql(`#graphql
	query Product($id: ID!) {
		product(id: $id) {
			title
			priceRange { minVariantPrice { amount currencyCode } }
		}
	}
`)

const { data } = await storefront.graphql(PRODUCT, {
	variables: { id: item.id }, // gid://shopify/Product/…
})
const product = data?.product

storefront is Hydrogen's client (createStorefrontClient), and graphql answers { data, errors, headers }.

fetchShopifyCollection and fetchShopifyProducts work there too when you'd rather not write the query; they take the same two values.

  • Prices and stock are the store's, read at render time. Cache the queries with Hydrogen's Cache.short() or Cache.long(), and let the webhooks tell you to refresh what you keep yourself; on Oxygen the TTL is what bounds staleness.
  • On Oxygen there is no tag cache and no purging of the live deployment's cache, so the TTL is what bounds how stale a price can be (caching on Oxygen).
  • Images come from Content, not from the Storefront API: use the asset's url as it is (assets).
  • Shoppable images: the delivery asset's products carry each product's point (position) when the use asks for them, so a Hydrogen storefront draws the hotspots from that (products in a file). The theme's Shoppable image app block instead reads the product's hotspots metafield, which the Storefront API doesn't expose.

Keeping your site in step

Prices, stock and images come from the Storefront API when your site renders, so a site that caches its pages needs to hear when the store changes. Add a webhook under Settings → Webhooks with product.updated, product.deleted, collection.updated and collection.deleted, and revalidate the pages and cache tags each event names. See Webhooks.

Media in Shopify

Files in Media tagged with a product can go onto that product in Shopify, without downloading and uploading them again. In the Shopify admin, a product's page has an Obelism media block (add it with the page's ⋯ menu) that lists the files tagged with the product, and More actions → Add from Obelism media opens the whole library. Add to product puts the file on the product with its alt text, and in the store's other languages when the file has alt text in them. A file is uploaded to the store once: adding it to another product uses the same Shopify file. When the file changes in Media (a new version or new alt text), the block shows it and Update replaces Shopify's copy in place, keeping its URL and products. Nothing goes to Shopify unless someone there asks for it.

The products tagged on a file with a point become hotspots in the theme: add the Shoppable image app block to the product template in the theme editor, and it shows the product's images that have hotspots, each linking to its product. Images (JPEG, PNG, GIF and WebP) and videos (MP4, MOV and WebM, up to 1 GB and 10 minutes) can be added; a video uploads in the background and shows as processing until Shopify has it ready, and only its alt text updates afterwards.

On this page

Marks in the margin

  • Note
  • Watch out
  • Updated

Scribes marked doubtful lines in the margin. That practice is called obelism.

Stuck on a step?