--- title: Dynamic content with metaobjects in Hydrogen description: Build a flexible CMS using Shopify metaobjects for dynamic content sections source_url: html: 'https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects' md: >- https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md --- ExpandOn this page * [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#requirements) * [Ingredients](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#ingredients) * [Step 1: Document the metaobjects CMS](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-1-document-the-metaobjects-cms) * [Step 2: Add product fragment for sections](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-2-add-product-fragment-for-sections) * [Step 3: Create edit route component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-3-create-edit-route-component) * [Step 4: Expose store subdomain](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-4-expose-store-subdomain) * [Step 5: Build store profile route](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-5-build-store-profile-route) * [Step 6: Add metaobjects to homepage](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-6-add-metaobjects-to-homepage) * [Step 7: Display all stores](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-7-display-all-stores) * [Step 8: Install rich text dependencies](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-8-install-rich-text-dependencies) * [Step 9: Create route content component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-9-create-route-content-component) * [Step 10: Build featured collections section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-10-build-featured-collections-section) * [Step 11: Build featured products section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-11-build-featured-products-section) * [Step 12: Build hero banner section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-12-build-hero-banner-section) * [Step 13: Build store profile section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-13-build-store-profile-section) * [Step 14: Build stores grid section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-14-build-stores-grid-section) * [Step 15: Create section renderer](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-15-create-section-renderer) * [Step 16: Add section parsing utility](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-16-add-section-parsing-utility) * [Step 17: Add setup guide](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-17-add-setup-guide) * [Step 18: Link field screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-18-link-field-screenshot) * [Step 19: Featured collections definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-19-featured-collections-definition-screenshot) * [Step 20: Featured products definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-20-featured-products-definition-screenshot) * [Step 21: Hero section definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-21-hero-section-definition-screenshot) * [Step 22: Rich text section definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-22-rich-text-section-definition-screenshot) * [Step 23: Store profile definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-23-store-profile-definition-screenshot) * [Step 24: Stores grid definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-24-stores-grid-definition-screenshot) * [Step 25: Store definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-25-store-definition-screenshot) * [Step 26: Definitions list screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-26-definitions-list-screenshot) * [Step 27: Route definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-27-route-definition-screenshot) # Dynamic content with metaobjects in Hydrogen This recipe creates a content management system using Shopify metaobjects. It lets you create and manage dynamic content sections through your Shopify admin, providing a flexible way to build pages with reusable components. Key features: * Dynamic route-based content rendering * Modular section components (Hero, Featured Products, Featured Collections, Stores) * Content editing capabilities with direct links to Shopify admin * Rich text support with Slate editor * Comprehensive documentation with visual guides This recipe includes example section components that can be customized or extended to match your specific content needs. See the included `guides/metaobjects/README.md` file for detailed setup instructions. Note You need to create the metaobject definitions in your Shopify admin before using this recipe. Each section component has a one-to-one relationship with a metaobject definition. *** ## Requirements * Basic understanding of Shopify metaobjects * Shopify store with metaobjects enabled (Shopify Plus or development store) * Metaobject definitions created in your Shopify admin * Environment variable: PUBLIC\_STORE\_DOMAIN (your store's admin domain) *** ## Ingredients *New files added to the template by this recipe.* | File | Description | | - | - | | [app/components/EditRoute.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/components/EditRoute.tsx) | | | [app/routes/stores.$name.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/routes/stores.$name.tsx) | | | [app/routes/stores.\_index.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/routes/stores._index.tsx) | | | [app/sections/RouteContent.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/RouteContent.tsx) | | | [app/sections/SectionFeaturedCollections.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionFeaturedCollections.tsx) | | | [app/sections/SectionFeaturedProducts.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionFeaturedProducts.tsx) | | | [app/sections/SectionHero.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionHero.tsx) | | | [app/sections/SectionStoreProfile.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionStoreProfile.tsx) | | | [app/sections/SectionStores.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionStores.tsx) | | | [app/sections/Sections.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/Sections.tsx) | | | [app/utils/parseSection.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/utils/parseSection.ts) | | | [guides/metaobjects/README.md](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/README.md) | | | [guides/metaobjects/images/definition\_link.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_link.png) | | | [guides/metaobjects/images/definition\_section\_featured\_collections.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_featured_collections.png) | | | [guides/metaobjects/images/definition\_section\_featured\_products.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_featured_products.png) | | | [guides/metaobjects/images/definition\_section\_hero.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_hero.png) | | | [guides/metaobjects/images/definition\_section\_rich\_text.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_rich_text.png) | | | [guides/metaobjects/images/definition\_section\_store\_profile.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_store_profile.png) | | | [guides/metaobjects/images/definition\_section\_stores\_grid.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_stores_grid.png) | | | [guides/metaobjects/images/definition\_store.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_store.png) | | | [guides/metaobjects/images/definitions\_list.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definitions_list.png) | | | [guides/metaobjects/images/definiton\_route.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definiton_route.png) | | *** ## Step 1: Document the metaobjects CMS Update the README file with metaobjects CMS documentation and an architecture overview. #### File: [README.md](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/README.md) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/patches/README.md.db10ed.patch)) ## File ````diff @@ -1,6 +1,8 @@ -# Hydrogen template: Skeleton +# Hydrogen template: Metaobjects as CMS -Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dovetail with [Remix](https://remix.run/), Shopify’s full stack web framework. This template contains a **minimal setup** of components, queries and tooling to get started with Hydrogen. +This Hydrogen template demonstrates how to use Shopify Metaobjects as a content management system (CMS). Hydrogen is Shopify's stack for headless commerce, designed to work with [Remix](https://remix.run/), Shopify's full stack web framework. + +This template shows how to create a flexible, section-based content architecture using Shopify's native Metaobjects, allowing merchants to manage content directly from the Shopify admin without external CMS dependencies. [Check out Hydrogen docs](https://shopify.dev/custom-storefronts/hydrogen) [Get familiar with Remix](https://remix.run/docs/en/v1) @@ -16,18 +18,60 @@ Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dov - Prettier - GraphQL generator - TypeScript and JavaScript flavors -- Minimal setup of components and routes +- **Metaobjects-based CMS architecture** +- **Dynamic section rendering system** +- **Content management through Shopify admin** + +## Metaobjects Architecture + +This template implements a hierarchical content structure: + +``` +Route (Metaobject) + └── Sections (References) + ├── SectionHero + ├── SectionFeaturedProducts + ├── SectionFeaturedCollections + ├── SectionStoreProfile + └── SectionStoreGrid +``` + +### Key Features + +- **Route-based content**: Each route can have its own set of sections +- **Reusable sections**: Create once, use across multiple routes +- **Type-safe**: Full TypeScript support with generated types +- **Merchant-friendly**: Content managed directly in Shopify admin +- **Extensible**: Easy to add new section types ## Getting started **Requirements:** - Node.js version 18.0.0 or higher +- Shopify store with Metaobjects enabled +- Metaobject definitions created in Shopify admin ```bash npm create @shopify/hydrogen@latest ``` +## Setting up Metaobjects + +1. **Create Metaobject definitions** in your Shopify admin: + - Navigate to Settings → Custom data → Metaobjects + - Create definitions for Route, SectionHero, SectionFeaturedProducts, etc. + - See `guides/metaobjects/README.md` for detailed field configurations + +2. **Create content entries**: + - Add Route entries for pages you want to manage + - Create Section entries and link them to Routes + - Configure section content and references + +3. **Query and render**: + - Routes automatically query their associated sections + - Sections component handles dynamic rendering based on type + ## Building for production ```bash @@ -40,6 +84,21 @@ npm run build npm run dev ``` +## Creating New Sections + +1. Define the Metaobject in Shopify admin +2. Create a React component in `app/sections/` +3. Add the GraphQL fragment for querying +4. Register in the Sections component switch statement + +Example: +```tsx +export function SectionExample(props: SectionExampleFragment) { + const section = parseSection<...>(props); + return
...
; +} +``` + ## Setup for using Customer Account API (`/account` section) -Follow step 1 and 2 of +Follow step 1 and 2 of \ No newline at end of file ```` *** ## Step 2: Add product fragment for sections Add RECOMMENDED\_PRODUCT\_FRAGMENT for displaying product collections in metaobject sections. #### File: [fragments.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/lib/fragments.ts) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/patches/fragments.ts.026109.patch)) ```diff @@ -232,3 +232,25 @@ export const FOOTER_QUERY = `#graphql } ${MENU_FRAGMENT} ` as const; + +// @description Fragment for recommended products needed by ProductItem component +export const RECOMMENDED_PRODUCT_FRAGMENT = `#graphql + fragment RecommendedProduct on Product { + id + title + handle + priceRange { + minVariantPrice { + amount + currencyCode + } + } + featuredImage { + id + url + altText + width + height + } + } +` as const; ``` *** ## Step 3: Create edit route component Add the edit route component for managing metaobject-based content in development. #### File: [EditRoute.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/components/EditRoute.tsx) ## File ```tsx import {useState, useEffect} from 'react'; import {Link, useMatches} from 'react-router'; /** * Displays an `Edit Route` button in the top right corner of the page * This button opens a new tab that let's you easily edit the metaobject entry in the Shopify Admin * This is only display when in development or when in preview branch deployment */ export function EditRoute({routeId}: {routeId: string}) { const [url, setUrl] = useState(null); const [root] = useMatches(); // @ts-expect-error data might not have publicStoreSubdomain const publicStoreSubdomain = root?.data?.publicStoreSubdomain; useEffect(() => { setUrl(new URL(window.location.href)); }, []); if (!url || !publicStoreSubdomain) return null; const isDev = url.hostname.includes('localhost') || url.hostname.includes('127.0.0.1'); const isPreview = url.hostname.includes('preview'); const legacyId = routeId.split('/').pop(); const adminEditUrl = `https://admin.shopify.com/store/${publicStoreSubdomain}/content/entries/route/${legacyId}`; const shouldShowEditLink = isDev || isPreview; if (!shouldShowEditLink) return null; return ( Edit Route ); } ``` *** ## Step 4: Expose store subdomain Expose the public store subdomain for metaobject queries and content management. #### File: [root.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/root.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/patches/root.tsx.5e9998.patch)) ```diff @@ -90,6 +90,8 @@ export async function loader(args: Route.LoaderArgs) { country: args.context.storefront.i18n.country, language: args.context.storefront.i18n.language, }, + // @description Add public store subdomain for metaobjects + publictoreSubdomain: args.context.env.PUBLIC_STORE_DOMAIN, }; } ``` *** ## Step 5: Build store profile route Add a dynamic store profile route for displaying store-specific metaobject content. #### File: [stores.$name.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/routes/stores.$name.tsx) ## File ```tsx import {useLoaderData} from 'react-router'; import type {Route} from './+types/stores.$name'; // 1. Add metaobject content imports import {ROUTE_CONTENT_QUERY, RouteContent} from '~/sections/RouteContent'; export const meta: Route.MetaFunction = () => { return [{title: 'Hydrogen | Home'}]; }; export async function loader(args: Route.LoaderArgs) { // Start fetching non-critical data without blocking time to first byte const deferredData = loadDeferredData(args); // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); return {...deferredData, ...criticalData}; } /** * Load data necessary for rendering content above the fold. This is the critical data * needed to render the page. If it's unavailable, the whole page should 400 or 500 error. */ async function loadCriticalData({context, params}: Route.LoaderArgs) { const {storefront} = context; const {name} = params; // 2. Query for the route's content metaobject const [{route}] = await Promise.all([ storefront.query(ROUTE_CONTENT_QUERY, { variables: {handle: `route-${name}`}, }), // Add other queries here, so that they are loaded in parallel ]); return {route}; } /** * Load data for rendering content below the fold. This data is deferred and will be * fetched after the initial page load. If it's unavailable, the page should still 200. * Make sure to not throw any errors here, as it will cause the page to 500. */ function loadDeferredData({context}: Route.LoaderArgs) { // No deferred data for this route return {}; } export default function Store() { const {route} = useLoaderData(); return (
{/* 3. Render the route's content sections */}
); } ``` *** ## Step 6: Add metaobjects to homepage Integrate the `RouteContent` component to render metaobject sections on the homepage. #### File: [\_index.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/routes/_index.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/patches/_index.tsx.243e26.patch)) ## File ```diff @@ -1,19 +1,11 @@ -import { - Await, - useLoaderData, - Link, -} from 'react-router'; +import {useLoaderData} from 'react-router'; import type {Route} from './+types/_index'; -import {Suspense} from 'react'; -import {Image} from '@shopify/hydrogen'; -import type { - FeaturedCollectionFragment, - RecommendedProductsQuery, -} from 'storefrontapi.generated'; -import {ProductItem} from '~/components/ProductItem'; + +// @description Add metaobject content imports +import {ROUTE_CONTENT_QUERY, RouteContent} from '~/sections/RouteContent'; export const meta: Route.MetaFunction = () => { - return [{title: 'Hydrogen | Home'}]; + return [{title: 'Hydrogen Metaobject | Home'}]; }; export async function loader(args: Route.LoaderArgs) { @@ -31,14 +23,18 @@ export async function loader(args: Route.LoaderArgs) { * needed to render the page. If it's unavailable, the whole page should 400 or 500 error. */ async function loadCriticalData({context}: Route.LoaderArgs) { - const [{collections}] = await Promise.all([ - context.storefront.query(FEATURED_COLLECTION_QUERY), + const {storefront} = context; + + // @description Query the home route metaobject + const [{route}] = await Promise.all([ + storefront.query(ROUTE_CONTENT_QUERY, { ``` *** ## Step 7: Display all stores Add a store listing page that shows all stores from metaobjects with a grid layout. #### File: [stores.\_index.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/routes/stores._index.tsx) ## File ```tsx import {useLoaderData} from 'react-router'; import type {Route} from './+types/stores._index'; // 1. Add metaobject content imports import {ROUTE_CONTENT_QUERY, RouteContent} from '~/sections/RouteContent'; export const meta: Route.MetaFunction = () => { return [{title: 'Hydrogen | Home'}]; }; export async function loader(args: Route.LoaderArgs) { // Start fetching non-critical data without blocking time to first byte const deferredData = loadDeferredData(args); // Await the critical data required to render initial state of the page const criticalData = await loadCriticalData(args); return {...deferredData, ...criticalData}; } /** * Load data necessary for rendering content above the fold. This is the critical data * needed to render the page. If it's unavailable, the whole page should 400 or 500 error. */ async function loadCriticalData({context}: Route.LoaderArgs) { const {storefront} = context; // 2. Query for the route's content metaobject const [{route}] = await Promise.all([ storefront.query(ROUTE_CONTENT_QUERY, { variables: {handle: 'route-stores'}, cache: storefront.CacheNone(), }), // Add other queries here, so that they are loaded in parallel ]); return {route}; } /** * Load data for rendering content below the fold. This data is deferred and will be * fetched after the initial page load. If it's unavailable, the page should still 200. * Make sure to not throw any errors here, as it will cause the page to 500. */ function loadDeferredData({context}: Route.LoaderArgs) { // No deferred data for this route return {}; } export default function Stores() { const {route} = useLoaderData(); return (
{/* 3. Render the route's content sections */}
); } ``` *** ## Step 8: Install rich text dependencies Add Slate dependencies for rich text editing in metaobject sections. #### File: [package.json](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/package.json) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/patches/package.json.f30b0a.patch)) ```diff @@ -21,7 +21,9 @@ "react": "18.3.1", "react-dom": "18.3.1", "react-router": "7.9.2", - "react-router-dom": "7.9.2" + "react-router-dom": "7.9.2", + "slate": "^0.101.4", + "slate-react": "^0.101.3" }, "devDependencies": { "@eslint/compat": "^1.2.5", ``` *** ## Step 9: Create route content component Add the main component for fetching and rendering metaobject-based route content. #### File: [RouteContent.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/RouteContent.tsx) ## File ```tsx import {SECTIONS_FRAGMENT, Sections} from '~/sections/Sections'; import {EditRoute} from '~/components/EditRoute'; import type {RouteContentQuery} from 'storefrontapi.generated'; export function RouteContent({route}: {route: RouteContentQuery['route']}) { if (!route?.sections) { return

No route content sections

; } return (
{route?.id && } {route?.sections && }
); } export const ROUTE_CONTENT_QUERY = `#graphql query RouteContent($handle: String!) { route: metaobject(handle: {type: "route", handle: $handle}) { type id title: field(key: "title") { key value } sections: field(key: "sections") { ...Sections } } } ${SECTIONS_FRAGMENT} `; ``` *** ## Step 10: Build featured collections section Add a section component for displaying featured product collections from metaobjects. #### File: [SectionFeaturedCollections.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionFeaturedCollections.tsx) ## File ```tsx import type { SectionFeaturedCollectionsFragment, FeaturedCollectionImageFragment, } from 'storefrontapi.generated'; import {parseSection} from '~/utils/parseSection'; import type {ParsedMetafields} from '@shopify/hydrogen'; import {Image} from '@shopify/hydrogen'; export function SectionFeaturedCollections( props: SectionFeaturedCollectionsFragment, ) { const section = parseSection< SectionFeaturedCollectionsFragment, { heading?: ParsedMetafields['single_line_text_field']; } >(props); const {id, heading, collections} = section; return (
{heading &&

{heading.parsedValue}

} {collections?.nodes && ( )}
); } const FEATURED_COLLECTION_FRAGMENT = `#graphql fragment FeaturedCollectionImage on Image { altText width height url } fragment FeaturedCollection on Collection { id title handle image { ...FeaturedCollectionImage } } `; export const SECTION_FEATURED_COLLECTIONS_FRAGMENT = `#graphql fragment SectionFeaturedCollectionsField on MetaobjectField { type key value } fragment SectionFeaturedCollections on Metaobject { type id heading: field(key: "heading") { ...SectionFeaturedCollectionsField } collections: field(key: "collections") { references(first: 10) { nodes { ... on Collection { ...FeaturedCollection } } } } withCollectionTitles: field(key: "with_collection_titles") { ...SectionFeaturedCollectionsField } } ${FEATURED_COLLECTION_FRAGMENT} `; ``` *** ## Step 11: Build featured products section Add a section component for showcasing featured products with a customizable layout. #### File: [SectionFeaturedProducts.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionFeaturedProducts.tsx) ## File ```tsx import {Money, Image} from '@shopify/hydrogen'; import {Link} from 'react-router'; import type {SectionFeaturedProductsFragment} from 'storefrontapi.generated'; export function SectionFeaturedProducts( props: SectionFeaturedProductsFragment, ) { const {heading, body, products, withProductPrices} = props; return (
{heading &&

{heading.value}

} {body &&

{body.value}

} {products?.references?.nodes && (
{products.references.nodes.map((product) => { const {variants, priceRange, title} = product; const variant = variants?.nodes?.[0]; return ( {variant.image && ( )}
{title}
{withProductPrices && ( From   )} ); })}
)}
); } const FEATURED_PRODUCT_FRAGMENT = `#graphql fragment FeaturedProduct on Product { id title handle productType variants(first: 1) { nodes { title image { altText width height url } } } priceRange { minVariantPrice { amount currencyCode } } } `; export const SECTION_FEATURED_PRODUCTS_FRAGMENT = `#graphql fragment SectionFeaturedProducts on Metaobject { type heading: field(key: "heading") { key value } body: field(key: "body") { key value } products: field(key: "products") { key references(first: 10) { nodes { ... on Product { ...FeaturedProduct } } } } withProductPrices: field(key: "with_product_prices") { key value } } ${FEATURED_PRODUCT_FRAGMENT} `; ``` *** ## Step 12: Build hero banner section Add a hero banner section with an image, heading, and call-to-action from metaobjects. #### File: [SectionHero.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionHero.tsx) ## File ```tsx import type {ParsedMetafields} from '@shopify/hydrogen'; import {parseSection} from '~/utils/parseSection'; import {Link} from 'react-router'; import type {SectionHeroFragment} from 'storefrontapi.generated'; export function SectionHero(props: SectionHeroFragment) { const section = parseSection< SectionHeroFragment, { heading?: ParsedMetafields['single_line_text_field']; subheading?: ParsedMetafields['single_line_text_field']; } >(props); const {image, heading, subheading, link} = section; const backgroundImage = image?.image?.url ? `url("${image.image.url}")` : undefined; return (
{heading &&

{heading.parsedValue}

} {subheading &&

{subheading.value}

} {link?.href?.value && ( {link?.text?.value} )}
); } const MEDIA_IMAGE_FRAGMENT = `#graphql fragment MediaImage on MediaImage { image { altText url width height } } `; const LINK_FRAGMENT = `#graphql fragment Link on MetaobjectField { ... on MetaobjectField { reference { ...on Metaobject { href: field(key: "href") { value } target: field(key: "target") { value } text: field(key: "text") { value } } } } } `; export const SECTION_HERO_FRAGMENT = `#graphql fragment SectionHero on Metaobject { type heading: field(key: "heading") { key value } subheading: field(key: "subheading") { key value } link: field(key: "link") { ...Link } image: field(key: "image") { key reference { ... on MediaImage { ...MediaImage } } } } ${LINK_FRAGMENT} ${MEDIA_IMAGE_FRAGMENT} `; ``` *** ## Step 13: Build store profile section Add a store profile section that displays store details, hours, and contact information. #### File: [SectionStoreProfile.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionStoreProfile.tsx) ## File ```tsx import type {ParsedMetafields} from '@shopify/hydrogen'; import {parseSection} from '~/utils/parseSection'; import {Link} from 'react-router'; import type {SectionStoreProfileFragment} from 'storefrontapi.generated'; import type {Key, ReactElement, JSXElementConstructor, ReactNode} from 'react'; export function SectionStoreProfile(props: SectionStoreProfileFragment) { const section = parseSection< SectionStoreProfileFragment, // override metafields types that have been parsed { store: { hours?: ParsedMetafields['list.single_line_text_field']; }; } >(props); const {image, heading, description, hours, address} = section.store; return (
Back to Stores

{image?.image?.url && ( {image?.image?.altText )}
{heading &&

{heading.value}

} {description &&

{description.value}

}
Address
{address &&
{address.value}
}
{hours?.parsedValue && (

Opening Hours
{hours.parsedValue.map((day: string) => (

{day}

))}
)}
); } export const STORE_PROFILE_FRAGMENT = `#graphql fragment StoreProfileField on MetaobjectField { type key value } fragment StoreProfile on Metaobject { type id handle title: field(key: "title") { ...StoreProfileField } heading: field(key: "heading") { ...StoreProfileField } description: field(key: "description") { ...StoreProfileField } address: field(key: "address") { ...StoreProfileField } hours: field(key: "hours") { ...StoreProfileField } image: field(key: "image") { type key reference { ... on MediaImage { image { altText url width height } } } } } `; export const SECTION_STORE_PROFILE_FRAGMENT = `#graphql fragment SectionStoreProfile on Metaobject { type id handle store: field(key: "store") { reference { ...on Metaobject { ...StoreProfile } } } } ${STORE_PROFILE_FRAGMENT} `; ``` *** ## Step 14: Build stores grid section Add a grid layout section for displaying multiple store locations from metaobjects. #### File: [SectionStores.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/SectionStores.tsx) ## File ```tsx import type {ParsedMetafields} from '@shopify/hydrogen'; import {parseSection} from '~/utils/parseSection'; import {Link} from 'react-router'; import type {SectionStoresFragment} from 'storefrontapi.generated'; export function SectionStores(props: SectionStoresFragment) { const section = parseSection< SectionStoresFragment, // override metafields types that have been parsed { heading?: ParsedMetafields['single_line_text_field']; } >(props); const {heading, stores} = section; return (
{heading?.value &&

{heading.value}

}
{stores && stores.nodes.map((store) => { if (!store) { return null; } const {image, heading, address} = store; return ( {image?.image?.url && ( {image.image.altText )} {heading && (

{heading.value}

)} {address &&
{address?.value}
} ); })}
); } const STORE_ITEM_FRAGMENT = `#graphql fragment StoreItemField on MetaobjectField { type key value } fragment StoreItemImage on MediaImage { image { altText url(transform: {maxWidth: 600, maxHeight: 600}) width height } } fragment StoreItem on Metaobject { type id handle heading: field(key: "heading") { ...StoreItemField } address: field(key: "address") { ...StoreItemField } image: field(key: "image") { key reference { ... on MediaImage { ...StoreItemImage } } } } `; export const SECTION_STORES_FRAGMENT = `#graphql fragment SectionStores on Metaobject { type heading: field(key: "heading") { ...StoreItemField } stores: field(key: "stores") { references(first: 10) { nodes { ...StoreItem } } } } ${STORE_ITEM_FRAGMENT} `; ``` *** ## Step 15: Create section renderer Add a dynamic section renderer that maps metaobject types to React components. #### File: [Sections.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/sections/Sections.tsx) ## File ```tsx import {SECTION_HERO_FRAGMENT, SectionHero} from '~/sections/SectionHero'; import { SECTION_FEATURED_PRODUCTS_FRAGMENT, SectionFeaturedProducts, } from '~/sections/SectionFeaturedProducts'; import { SECTION_FEATURED_COLLECTIONS_FRAGMENT, SectionFeaturedCollections, } from '~/sections/SectionFeaturedCollections'; import {SECTION_STORES_FRAGMENT, SectionStores} from '~/sections/SectionStores'; import { SECTION_STORE_PROFILE_FRAGMENT, SectionStoreProfile, } from '~/sections/SectionStoreProfile'; import type {SectionsFragment} from 'storefrontapi.generated'; export function Sections({sections}: {sections: SectionsFragment}) { return (
{sections?.references?.nodes.map((section) => { switch (section.type) { case 'section_hero': return ; case 'section_featured_products': return ; case 'section_featured_collections': return ; case 'section_stores_grid': return ; case 'section_store_profile': return ; // case 'section_another': // return ; default: // eslint-disable-next-line no-console console.log(`Unsupported section type: ${section.type}`); return null; } })}
); } export const SECTIONS_FRAGMENT = `#graphql fragment Sections on MetaobjectField { ... on MetaobjectField { references(first: 10) { nodes { ... on Metaobject { id type ...SectionHero ...SectionFeaturedProducts ...SectionFeaturedCollections ...SectionStores ...SectionStoreProfile } } } } } # All section fragments ${SECTION_HERO_FRAGMENT} ${SECTION_FEATURED_PRODUCTS_FRAGMENT} ${SECTION_FEATURED_COLLECTIONS_FRAGMENT} ${SECTION_STORES_FRAGMENT} ${SECTION_STORE_PROFILE_FRAGMENT} `; ``` *** ## Step 16: Add section parsing utility Add a utility function for parsing and transforming metaobject field data. #### File: [parseSection.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/app/utils/parseSection.ts) ## File ```ts import type {ParsedMetafields} from '@shopify/hydrogen'; import {parseMetafield} from '@shopify/hydrogen'; /** * Recursively parse metafields (objects containing a type, value and key) * into a more usable format. Removes nested reference and references keys. */ export function parseSection(_section: InputType) { const section = liftEach(_section, [ 'reference', 'references', // 'nodes', ] as const); const parsed = {} as Record; // parse each key in the section for (const key in section) { const node = section[key]; if (typeof node === 'object') { // @ts-expect-error node might not have type and value properties const isMetafield = node?.type && node?.value; const isArray = Array.isArray(node); if (isArray) { // Break the recursion for TypeScript 5.9+ by treating as unknown[] parsed[key] = (node as unknown[]).map((item) => parseSection(item)); } else if (isMetafield) { parsed[key] = parseMetafieldValue(node); } else if (node && Object.keys(node as object).length > 0) { parsed[key] = parseSection(node as unknown); } else { delete parsed[key]; } } else { parsed[key] = node; } } return parsed as unknown as typeof section & ReturnType; } function parseMetafieldValue(node: Record) { let parsed; switch (node?.type) { case 'single_line_text_field': return parseMetafield(node); case 'multi_line_text_field': return parseMetafield(node); case 'list.single_line_text_field': return parseMetafield( node, ); case 'list.collection_reference': return parseMetafield( node, ); // NOTE: expand with other field types as needed for your project default: parsed = node; } return parsed; } type LiftOtherKeys = KeyToLift extends keyof Section ? Lift : object; type Lift = Section extends object ? Section extends Array ? Lift[] : { [P in Exclude]: P extends 'value' ? NonNullable> | undefined : Lift; } & LiftOtherKeys : Section; type LiftEach = KeysToLift extends readonly [ infer FirstKeyToLift, ...infer RemainingKeysToLift, ] ? LiftEach, RemainingKeysToLift> : Section; /** * Lifts a key from an object, and returns a new object with the key removed. */ function lift( value: Section, key: KeyToRemove, ): Lift { const isArray = Array.isArray(value); function liftObject(value: any) { const entries = Object.entries(value) .filter(([prop]) => prop !== key) .map(([prop, val]) => { const liftedVal = lift(val, key); return [prop, liftedVal]; }); const target = Object.fromEntries(entries); const source = key in value ? lift((value as any)[key], key) : {}; const lifted = Array.isArray(source) ? source : Object.assign(target, source); return lifted; } return ( value && typeof value === 'object' ? isArray ? value.map((item) => liftObject(item)) : liftObject(value) : value ) as Lift; } /** * Lifts each key in an array from an object, and returns a new object with the keys removed. */ function liftEach>( obj: Section, keys: KeysToRemove, ): LiftEach { return keys.reduce((result, keyToLift) => { return lift(result, keyToLift); }, obj) as LiftEach; } ``` *** ## Step 17: Add setup guide A comprehensive guide for setting up metaobject definitions in the Shopify admin. #### File: [README.md](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/README.md) ## File ````md # Metaobjects Overview This document describes the high-level content architecture and metaobject definitions to create a basic content management system (CMS) based on metaobjects. ## 1. Content Architecture ```bash Metaobject Definitions ┌─────────────────────────────────────────────────┐ │ │ │ Route │ │ │ │ ┌─────────────────────────────────────────┐ │ │ │ │ │ │ │ Sections │ │ │ │ │ │ │ │ ┌─────────────────────────────────────┐ │ │ │ │ │ SectionHero │ │ │ │ │ ├─────────────────────────────────────┤ │ │ │ │ │ SectionFeaturedProducts │ │ │ │ │ └─────────────────────────────────────┘ │ │ │ │ ... │ │ │ │ │ │ │ └─────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────┘ ``` --- ## 2. Metaobject Definitions The following is the list of metaojects that are used in this example: ![Metaobject definitions list](./images/definitions_list.png 'Metaobject Definitions List') ```` *** ## Step 18: Link field screenshot A screenshot showing the Link metaobject field configuration. #### File: [definition\_link.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_link.png) ![templates/skeleton/guides/metaobjects/images/definition\_link.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_link.png) *** ## Step 19: Featured collections definition screenshot A screenshot of a "Featured Collections" section metaobject definition. #### File: [definition\_section\_featured\_collections.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_featured_collections.png) ![templates/skeleton/guides/metaobjects/images/definition\_section\_featured\_collections.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_featured_collections.png) *** ## Step 20: Featured products definition screenshot A screenshot of a "Featured Products" section metaobject definition. #### File: [definition\_section\_featured\_products.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_featured_products.png) ![templates/skeleton/guides/metaobjects/images/definition\_section\_featured\_products.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_featured_products.png) *** ## Step 21: Hero section definition screenshot A screenshot of a Hero section metaobject definition with image and text fields. #### File: [definition\_section\_hero.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_hero.png) ![templates/skeleton/guides/metaobjects/images/definition\_section\_hero.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_hero.png) *** ## Step 22: Rich text section definition screenshot A screenshot of a "Richtext" section metaobject definition. #### File: [definition\_section\_rich\_text.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_rich_text.png) ![templates/skeleton/guides/metaobjects/images/definition\_section\_rich\_text.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_rich_text.png) *** ## Step 23: Store profile definition screenshot A screenshot of a "Store Profile" section metaobject definition. #### File: [definition\_section\_store\_profile.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_store_profile.png) ![templates/skeleton/guides/metaobjects/images/definition\_section\_store\_profile.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_store_profile.png) *** ## Step 24: Stores grid definition screenshot A screenshot of a "Stores Grid" section metaobject definition. #### File: [definition\_section\_stores\_grid.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_stores_grid.png) ![templates/skeleton/guides/metaobjects/images/definition\_section\_stores\_grid.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_section_stores_grid.png) *** ## Step 25: Store definition screenshot A screenshot of a "Store" metaobject definition with location and contact fields. #### File: [definition\_store.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_store.png) ![templates/skeleton/guides/metaobjects/images/definition\_store.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definition_store.png) *** ## Step 26: Definitions list screenshot A screenshot showing a list of all metaobject definitions in the Shopify admin. #### File: [definitions\_list.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definitions_list.png) ![templates/skeleton/guides/metaobjects/images/definitions\_list.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definitions_list.png) *** ## Step 27: Route definition screenshot A screenshot of a "Route" metaobject definition with a "Sections" reference field. #### File: [definiton\_route.png](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definiton_route.png) ![templates/skeleton/guides/metaobjects/images/definiton\_route.png](https://raw.githubusercontent.com/Shopify/hydrogen/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/metaobjects/ingredients/templates/skeleton/guides/metaobjects/images/definiton_route.png) *** * [Requirements](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#requirements) * [Ingredients](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#ingredients) * [Step 1: Document the metaobjects CMS](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-1-document-the-metaobjects-cms) * [Step 2: Add product fragment for sections](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-2-add-product-fragment-for-sections) * [Step 3: Create edit route component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-3-create-edit-route-component) * [Step 4: Expose store subdomain](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-4-expose-store-subdomain) * [Step 5: Build store profile route](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-5-build-store-profile-route) * [Step 6: Add metaobjects to homepage](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-6-add-metaobjects-to-homepage) * [Step 7: Display all stores](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-7-display-all-stores) * [Step 8: Install rich text dependencies](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-8-install-rich-text-dependencies) * [Step 9: Create route content component](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-9-create-route-content-component) * [Step 10: Build featured collections section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-10-build-featured-collections-section) * [Step 11: Build featured products section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-11-build-featured-products-section) * [Step 12: Build hero banner section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-12-build-hero-banner-section) * [Step 13: Build store profile section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-13-build-store-profile-section) * [Step 14: Build stores grid section](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-14-build-stores-grid-section) * [Step 15: Create section renderer](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-15-create-section-renderer) * [Step 16: Add section parsing utility](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-16-add-section-parsing-utility) * [Step 17: Add setup guide](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-17-add-setup-guide) * [Step 18: Link field screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-18-link-field-screenshot) * [Step 19: Featured collections definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-19-featured-collections-definition-screenshot) * [Step 20: Featured products definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-20-featured-products-definition-screenshot) * [Step 21: Hero section definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-21-hero-section-definition-screenshot) * [Step 22: Rich text section definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-22-rich-text-section-definition-screenshot) * [Step 23: Store profile definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-23-store-profile-definition-screenshot) * [Step 24: Stores grid definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-24-stores-grid-definition-screenshot) * [Step 25: Store definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-25-store-definition-screenshot) * [Step 26: Definitions list screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-26-definitions-list-screenshot) * [Step 27: Route definition screenshot](https://shopify.dev/docs/storefronts/headless/hydrogen/cookbook/metaobjects.md#step-27-route-definition-screenshot)