# Pack Digital Documentation - Complete Reference
---
# A/B Testing API: Endpoints and Implementation
## React Hooks `@pack/hydrogen`
We offer hooks that you can integrate into your storefront to determine which test and variant your visitors are assigned to. This functionality is useful for sending event data to your analytics platform to track your metrics or leveraging this information for code-based testing.
### `useAbTest()`
The `useAbTest` returns information about the test that the user is currently bucketed into.
```ts{{ title: "useAbTest(): Test | null"}}
interface Test {
id: string;
handle: string;
testVariant: {
id: string;
handle: string;
};
}
```
```tsx{{ title: "Example"}}
import {useAbTest} from '@pack/hydrogen';
...
export function Hero() {
const abTest = useAbTest();
return (
I am on this test {abTest.handle}
{
abTest.testVariant.handle === 'variant-b' ? (
I am variant b
) : (
I am variant control
)
}
);
}
```
### `useAbTestId()`
The `useAbTestId` returns the id of the test that the user is currently bucketed into.
```ts{{ title: "useAbTestId(): string | undefined"}}
import {useAbTestId} from '@pack/hydrogen';
...
export function Hero() {
const testId = useAbTestId();
return (
My test ID is: {testId}
);
}
```
### `useAbTestHandle()`
The `useAbTestHandle` returns the handle of the test that the user is currently bucketed into.
```ts{{ title: "useAbTestHandle(): string | undefined"}}
import {useAbTestHandle} from '@pack/hydrogen';
...
export function Hero() {
const testHandle = useAbTestHandle();
return (
My test handle is: {testHandle}
);
}
```
### `useAbTestVariantId()`
The `useAbTestVariantId` returns the id of the variant of the test that the user is currently bucketed into.
```ts{{ title: "useAbTestVariantId(): string | undefined"}}
import {useAbTestVariantId} from '@pack/hydrogen';
...
export function Hero() {
const testVariantId = useAbTestVariantId();
return (
My curent variant ID is: {testVariantId}
);
}
```
### `useAbTestVariantHandle()`
The `useAbTestVariantHandle` returns the handle of the variant of the test that the user is currently bucketed into.
```ts{{ title: "useAbTestVariantHandle(): string | undefined"}}
import {useAbTestVariantHandle} from '@pack/hydrogen';
...
export function Hero() {
const testVariantHandle = useAbTestVariantHandle();
return (
My curent variant handle is: {testVariantHandle}
);
}
```
## Manually bucketing yourself into a test
If you would like to bucket yourself into a test to validate that everything is working, you can add these query parameters to the URL.
* **test\_id** (string): A human readable label for your section.
* **test\_handle** (string): A unique identifier for your section.
* **test\_variant\_id** (string): An optional string that lets you categorize this section. For example, "Heros"
or "Text Blocks".
* **test\_variant\_handle** (Array\): An array of supported fields that make up your section.
Note, the only requirement is that you have both a `test_*` and `variant_*` parameter included so we know what variant and test to display.
It is possible to use different combinations of the parameters, such as:
* `?test_handle=ZZZ&test_variant_handle=YYY`
* `?test_id=ZZZ&test_variant_id=YYY`
* `?test_handle=ZZZ&test_variant_id=YYY`
* `?test_id=ZZZ&test_variant_handle=YYY`
---
# Content Management API: Endpoints and Implementation
Content management is a core part of Pack — the very reason Pack exists is so you can easily and flexibly manage complex content on your storefront. On this page, we'll dive into the different content management endpoints you can use to manage your content programmatically. Plus, we'll look at all our GraphQL endpoints for managing your content.
***
## Authentication
All GraphQL Admin API queries need a valid Pack access token.
Add your token as an `Authorization` header to all API queries.
If you use the `Secret Token`, you'll have permission to read and write through the API. If you use the `Public Token`, you'll only have read access.
The [@pack/client package](/pack-client) offers a client for working with the Pack GraphQL API.
```tsx {{ title: 'typescript'}}
import { PackClient } from '@pack/client';
const packClient = new PackClient({
apiUrl: 'https://app.packdigital.com/graphql',
token: '{YOUR_SECRET_TOKEN}',
contentEnvironment: 'content_environment_handle',
});
const query = `
query {
siteSettings {
settings
seo {
title
description
keywords
}
}
}
`;
async function fetchSiteSettings() {
const response = await packClient.fetch(query);
console.log(response.data.siteSettings);
}
fetchSiteSettings();
```
***
## Page
A page is structured data that will hold information like title, description, SEO data, and most importantly its sections – the building blocks of your site's content. When navigating to routes in your storefront under the `/pages/` template, it will use this page data to get content.
[Learn more about the Page content model](/developer-resources/cms-models#page)
### Queries
```graphql
page(id: ID!, version: Version): Page
```
Returns a page by ID in a draft or published state.
Reference: [/content-management-api/queries/page](/content-management-api/queries/page)
```graphql
pageByHandle(handle: String!, version: Version): Page
```
Returns a page by handle in a draft or published state.
Reference: [/content-management-api/queries/pageByHandle](/content-management-api/queries/pageByHandle)
```graphql
pages(after: String, before: String, first: Int, last: Int, version: Version): PageConnection!
```
Returns an array of all your pages in a draft or published state paginated by a cursor.
Reference: [/content-management-api/queries/pages](/content-management-api/queries/pages)
```graphql
pageHistory(id: ID!, after: String, before: String, first: Int, last: Int): PageRevisionConnection!
```
Returns an array of all revisions for the page by ID paginated by a cursor.
Reference: [/content-management-api/queries/pageHistory](/content-management-api/queries/pageHistory)
```graphql
pageRevision(id: ID!, revisionId: ID!): PageRevision
```
Returns a page's revision.
Reference: [/content-management-api/queries/pageRevision](/content-management-api/queries/pageRevision)
### Mutations
```graphql
pageCreate(input: PageCreateInput!): Page
```
Creates a page.
Reference: [/content-management-api/mutations/pageCreate](/content-management-api/mutations/pageCreate)
```graphql
pageUpdate(id: ID!, input: PageUpdateInput!): Page
```
Updates a page.
Reference: [/content-management-api/mutations/pageUpdate](/content-management-api/mutations/pageUpdate)
```graphql
pageDelete(id: ID!): DeletePayload
```
Deletes a page.
Reference: [/content-management-api/mutations/pageDelete](/content-management-api/mutations/pageDelete)
```graphql
pagePublish(id: ID!, publishComment: String): Page
```
Publishes a page.
Reference: [/content-management-api/mutations/pagePublish](/content-management-api/mutations/pagePublish)
```graphql
pageUnpublish(id: ID!): Page
```
Unpublishes a page.
Reference: [/content-management-api/mutations/pageUnpublish](/content-management-api/mutations/pageUnpublish)
```graphql
pageRestore(id: ID!, revisionId: ID!): Page
```
Restores a page to a specific revision.
Reference: [/content-management-api/mutations/pageRestore](/content-management-api/mutations/pageRestore)
```graphql
pageDeleteBulk(ids: [ID!]!): Job
```
Bulk delete pages.
Reference: [/content-management-api/mutations/pageDeleteBulk](/content-management-api/mutations/pageDeleteBulk)
```graphql
pageUpdateBulk(ids: [ID!], input: [PageUpdateInput!]!): Job
```
Allows you to bulk update pages.
Reference: [/content-management-api/mutations/pageUpdateBulk](/content-management-api/mutations/pageUpdateBulk)
```graphql
pagePublishBulk(ids: [ID!]!): Job
```
Bulk publish pages.
Reference: [/content-management-api/mutations/pagePublishBulk](/content-management-api/mutations/pagePublishBulk)
```graphql
pageUnpublishBulk(ids: [ID!]!): Job
```
Bulk unpublish pages.
Reference: [/content-management-api/mutations/pageUnpublishBulk](/content-management-api/mutations/pageUnpublishBulk)
```graphql
pageAddSectionsBulk(ids: [ID!]!, input: [PageAddSectionsInput!]!): Job
```
Assign a section to multiple pages.
Reference: [/content-management-api/mutations/pageAddSectionsBulk](/content-management-api/mutations/pageAddSectionsBulk)
***
## Product Page
> **Warning**: Product pages in Pack are **not** the actual product pages in Shopify. Pack
> product pages are separate data that will hold content and can be used in
> conjunction with your Shopify product.
A product page is structured data that will hold information like title, description, SEO data, and most importantly its sections – the building blocks of your site's content. When navigating to routes in your storefront under the `/products/` template, it will use this page data to get content.
[Learn more about the Product Page content model](/developer-resources/cms-models#product-page)
### Queries
```graphql
productPage(id: ID!, version: Version): ProductPage!
```
Returns a product page by ID in a draft or published state.
Reference: [/content-management-api/queries/productPage](/content-management-api/queries/productPage)
```graphql
productPageByHandle(handle: String!, version: Version): ProductPage!
```
Returns a product page by handle in a draft or published state.
Reference: [/content-management-api/queries/productPageByHandle](/content-management-api/queries/productPageByHandle)
```graphql
productPages(after: String, before: String, first: Int, last: Int, version: Version): ProductPageConnection!
```
Returns an array of all your product pages in a draft or published state paginated by a cursor.
Reference: [/content-management-api/queries/productPages](/content-management-api/queries/productPages)
```graphql
productPageHistory(id: ID!, after: String, before: String, first: Int, last: Int): ProductPageRevisionConnection!
```
Returns an array of all revisions for the product page by ID paginated by a cursor.
Reference: [/content-management-api/queries/productPageHistory](/content-management-api/queries/productPageHistory)
```graphql
productPageRevision(id: ID!, revisionId: ID!): ProductPageRevision
```
Returns a product page's revision.
Reference: [/content-management-api/queries/productPageRevision](/content-management-api/queries/productPageRevision)
### Mutations
```graphql
productPageUpdate(id: ID!, input: ProductPageUpdateInput!): ProductPage
```
Updates a product page.
Reference: [/content-management-api/mutations/productPageUpdate](/content-management-api/mutations/productPageUpdate)
```graphql
productPagePublish(id: ID!, publishComment: String): ProductPage
```
Publishes a product page.
Reference: [/content-management-api/mutations/productPagePublish](/content-management-api/mutations/productPagePublish)
```graphql
productPageUnpublish(id: ID!): ProductPage
```
Unpublishes a product page.
Reference: [/content-management-api/mutations/productPageUnpublish](/content-management-api/mutations/productPageUnpublish)
```graphql
productPageRestore(id: ID!, revisionId: ID!): ProductPage
```
Restores a product page to a specific revision.
Reference: [/content-management-api/mutations/productPageRestore](/content-management-api/mutations/productPageRestore)
```graphql
productPageUpdateBulk(ids: [ID!], input: [ProductPageUpdateInput!]!): Job
```
Allows you to bulk update product pages.
Reference: [/content-management-api/mutations/productPageUpdateBulk](/content-management-api/mutations/productPageUpdateBulk)
```graphql
productPagePublishBulk(ids: [ID!]!): Job
```
Bulk publish product pages.
Reference: [/content-management-api/mutations/productPagePublishBulk](/content-management-api/mutations/productPagePublishBulk)
```graphql
productPageUnpublishBulk(ids: [ID!]!): Job
```
Bulk unpublish product pages.
Reference: [/content-management-api/mutations/productPageUnpublishBulk](/content-management-api/mutations/productPageUnpublishBulk)
```graphql
productPageAddSectionsBulk(ids: [ID!]!, input: [ProductPageAddSectionsInput!]!): Job
```
Assign a section to multiple product pages.
Reference: [/content-management-api/mutations/productPageAddSectionsBulk](/content-management-api/mutations/productPageAddSectionsBulk)
***
## Collection Page
> **Warning**: Collection pages in Pack are **not** the actual collection pages in Shopify.
> Pack collection pages are separate data that will hold content and can be used
> in conjunction with your Shopify collection.
A collection page is structured data that will hold information like title, description, SEO data, and most importantly its sections – the building blocks of your site's content. When navigating to routes in your storefront under the `/collections/` template, it will use this page data to get content.
[Learn more about the Collection Page content model](/developer-resources/cms-models#collection)
### Queries
```graphql
collectionPage(id: ID!, version: Version): CollectionPage!
```
Returns a collection page by ID in a draft or published state.
Reference: [/content-management-api/queries/collectionPage](/content-management-api/queries/collectionPage)
```graphql
collectionPageByHandle(handle: String!, version: Version): CollectionPage!
```
Returns a collection page by handle in a draft or published state.
Reference: [/content-management-api/queries/collectionPageByHandle](/content-management-api/queries/collectionPageByHandle)
```graphql
collectionPages(after: String, before: String, first: Int, last: Int, version: Version): CollectionPageConnection!
```
Returns an array of all your collection pages in a draft or published state paginated by a cursor.
Reference: [/content-management-api/queries/collectionPages](/content-management-api/queries/collectionPages)
```graphql
collectionPageHistory(id: ID!, after: String, before: String, first: Int, last: Int): CollectionPageRevisionConnection!
```
Returns an array of all revisions for the collection page by ID paginated by a cursor.
Reference: [/content-management-api/queries/collectionPageHistory](/content-management-api/queries/collectionPageHistory)
```graphql
collectionPageRevision(id: ID!, revisionId: ID!): CollectionPageRevision
```
Returns a collection page's revision.
Reference: [/content-management-api/queries/collectionPageRevision](/content-management-api/queries/collectionPageRevision)
### Mutations
```graphql
collectionPageUpdate(id: ID!, input: CollectionPageUpdateInput!): CollectionPage
```
Updates a collection page.
Reference: [/content-management-api/mutations/collectionPageUpdate](/content-management-api/mutations/collectionPageUpdate)
```graphql
collectionPagePublish(id: ID!, publishComment: String): CollectionPage
```
Publishes a collection page.
Reference: [/content-management-api/mutations/collectionPagePublish](/content-management-api/mutations/collectionPagePublish)
```graphql
collectionPageUnpublish(id: ID!): CollectionPage
```
Unpublishes a collection page.
Reference: [/content-management-api/mutations/collectionPageUnpublish](/content-management-api/mutations/collectionPageUnpublish)
```graphql
collectionPageRestore(id: ID!, revisionId: ID!): CollectionPage
```
Restores a collection page to a specific revision.
Reference: [/content-management-api/mutations/collectionPageRestore](/content-management-api/mutations/collectionPageRestore)
```graphql
collectionPageUpdateBulk(ids: [ID!], input: [CollectionPageUpdateInput!]!): Job
```
Allows you to bulk update collection pages.
Reference: [/content-management-api/mutations/collectionPageUpdateBulk](/content-management-api/mutations/collectionPageUpdateBulk)
```graphql
collectionPagePublishBulk(ids: [ID!]!): Job
```
Bulk publish collection pages.
Reference: [/content-management-api/mutations/collectionPagePublishBulk](/content-management-api/mutations/collectionPagePublishBulk)
```graphql
collectionPageUnpublishBulk(ids: [ID!]!): Job
```
Bulk unpublish collection pages.
Reference: [/content-management-api/mutations/collectionPageUnpublishBulk](/content-management-api/mutations/collectionPageUnpublishBulk)
```graphql
collectionPageAddSectionsBulk(ids: [ID!]!, input: [CollectionPageAddSectionsInput!]!): Job
```
Assign a section to multiple collection pages.
Reference: [/content-management-api/mutations/collectionPageAddSectionsBulk](/content-management-api/mutations/collectionPageAddSectionsBulk)
***
## Blog
A blog is structured data that will hold information like title, description, SEO data, and most importantly its sections – the building blocks of your site's content. When navigating to routes in your storefront under the `/blogs/` template, it will use this page data to get content.
[Learn more about the Blog content model](/developer-resources/cms-models#blog)
### Queries
```graphql
blog(id: ID!, version: Version): Blog
```
Returns a blog by ID in a draft or published state.
Reference: [/content-management-api/queries/blog](/content-management-api/queries/blog)
```graphql
blogByHandle(handle: String!, version: Version): Blog
```
Returns a blog by handle in a draft or published state.
Reference: [/content-management-api/queries/blogByHandle](/content-management-api/queries/blogByHandle)
```graphql
blogs(after: String, before: String, first: Int, last: Int, version: Version): BlogConnection!
```
Returns an array of all your blogs in a draft or published state paginated by a cursor.
Reference: [/content-management-api/queries/blogs](/content-management-api/queries/blogs)
```graphql
blogHistory(id: ID!, after: String, before: String, first: Int, last: Int): BlogRevisionConnection!
```
Returns an array of all revisions for the blog by ID paginated by a cursor.
Reference: [/content-management-api/queries/blogHistory](/content-management-api/queries/blogHistory)
```graphql
blogRevision(id: ID!, revisionId: ID!): BlogRevision
```
Returns a blog's revision.
Reference: [/content-management-api/queries/blogRevision](/content-management-api/queries/blogRevision)
### Mutations
```graphql
blogCreate(input: BlogCreateInput!): Blog
```
Creates a blog.
Reference: [/content-management-api/mutations/blogCreate](/content-management-api/mutations/blogCreate)
```graphql
blogUpdate(id: ID!, input: BlogUpdateInput!): Blog
```
Updates a blog.
Reference: [/content-management-api/mutations/blogUpdate](/content-management-api/mutations/blogUpdate)
```graphql
blogDelete(id: ID!): DeletePayload
```
Deletes a blog.
Reference: [/content-management-api/mutations/blogDelete](/content-management-api/mutations/blogDelete)
```graphql
blogPublish(id: ID!, publishComment: String): Blog
```
Publishes a blog.
Reference: [/content-management-api/mutations/blogPublish](/content-management-api/mutations/blogPublish)
```graphql
blogUnpublish(id: ID!): Blog
```
Unpublishes a blog.
Reference: [/content-management-api/mutations/blogUnpublish](/content-management-api/mutations/blogUnpublish)
```graphql
blogRestore(id: ID!, revisionId: ID!): Blog
```
Restores a blog to a specific revision.
Reference: [/content-management-api/mutations/blogRestore](/content-management-api/mutations/blogRestore)
```graphql
blogDeleteBulk(ids: [ID!]!): Job
```
Bulk delete blogs.
Reference: [/content-management-api/mutations/blogDeleteBulk](/content-management-api/mutations/blogDeleteBulk)
```graphql
blogUpdateBulk(ids: [ID!], input: [BlogUpdateInput!]!): Job
```
Allows you to bulk update blogs.
Reference: [/content-management-api/mutations/blogUpdateBulk](/content-management-api/mutations/blogUpdateBulk)
```graphql
blogPublishBulk(ids: [ID!]!): Job
```
Bulk publish blogs.
Reference: [/content-management-api/mutations/blogPublishBulk](/content-management-api/mutations/blogPublishBulk)
```graphql
blogUnpublishBulk(ids: [ID!]!): Job
```
Bulk unpublish blogs.
Reference: [/content-management-api/mutations/blogUnpublishBulk](/content-management-api/mutations/blogUnpublishBulk)
```graphql
blogAddSectionsBulk(ids: [ID!]!, input: [BlogAddSectionsInput!]!): Job
```
Assign a section to multiple blogs.
Reference: [/content-management-api/mutations/blogAddSectionsBulk](/content-management-api/mutations/blogAddSectionsBulk)
***
## Article
An article is structured data that will hold information like title, description, SEO data, and most importantly its sections – the building blocks of your site's content. When navigating to routes in your storefront under the `/articles/` template, it will use this page data to get content.
[Learn more about the Article content model](/developer-resources/cms-models#article)
### Queries
```graphql
article(id: ID!, version: Version): Article
```
Returns an article by ID in a draft or published state.
Reference: [/content-management-api/queries/article](/content-management-api/queries/article)
```graphql
articleByHandle(handle: String!, version: Version): Article
```
Returns an article by hande in a draft or published state.
Reference: [/content-management-api/queries/articleByHandle](/content-management-api/queries/articleByHandle)
```graphql
articles(after: String, before: String, first: Int, last: Int, version: Version): ArticleConnection!
```
Returns an array of all your articles in a draft or published state paginated by a cursor.
Reference: [/content-management-api/queries/articles](/content-management-api/queries/articles)
```graphql
articleHistory(id: ID!, after: String, before: String, first: Int, last: Int): ArticleRevisionConnection!
```
Returns an array of all revisions for the article by ID paginated by a cursor.
Reference: [/content-management-api/queries/articleHistory](/content-management-api/queries/articleHistory)
```graphql
articleRevision(id: ID!, revisionId: ID!): ArticleRevision
```
Returns an article's revision.
Reference: [/content-management-api/queries/articleRevision](/content-management-api/queries/articleRevision)
### Mutations
```graphql
articleCreate(input: ArticleCreateInput!): Article
```
Creates an article.
Reference: [/content-management-api/mutations/articleCreate](/content-management-api/mutations/articleCreate)
```graphql
articleUpdate(id: ID!, input: ArticleUpdateInput!): Article
```
Updates an article.
Reference: [/content-management-api/mutations/articleUpdate](/content-management-api/mutations/articleUpdate)
```graphql
articleDelete(id: ID!): DeletePayload
```
Deletes an article.
Reference: [/content-management-api/mutations/articleDelete](/content-management-api/mutations/articleDelete)
```graphql
articlePublish(id: ID!, publishComment: String): Article
```
Publishes an article.
Reference: [/content-management-api/mutations/articlePublish](/content-management-api/mutations/articlePublish)
```graphql
articleUnpublish(id: ID!): Article
```
Unpublishes an article.
Reference: [/content-management-api/mutations/articleUnpublish](/content-management-api/mutations/articleUnpublish)
```graphql
articleRestore(id: ID!, revisionId: ID!): Article
```
Restores an article to a specific revision.
Reference: [/content-management-api/mutations/articleRestore](/content-management-api/mutations/articleRestore)
```graphql
articleDeleteBulk(ids: [ID!]!): Job
```
Bulk delete articles.
Reference: [/content-management-api/mutations/articleDeleteBulk](/content-management-api/mutations/articleDeleteBulk)
```graphql
articleUpdateBulk(ids: [ID!], input: [ArticleUpdateInput!]!): Job
```
Bulk update articles.
Reference: [/content-management-api/mutations/articleUpdateBulk](/content-management-api/mutations/articleUpdateBulk)
```graphql
articlePublishBulk(ids: [ID!]!): Job
```
Bulk publish articles.
Reference: [/content-management-api/mutations/articlePublishBulk](/content-management-api/mutations/articlePublishBulk)
```graphql
articleUnpublishBulk(ids: [ID!]!): Job
```
Bulk unpublish articles.
Reference: [/content-management-api/mutations/articleUnpublishBulk](/content-management-api/mutations/articleUnpublishBulk)
```graphql
articleAddSectionsBulk(ids: [ID!]!, input: [ArticleAddSectionsInput!]!): Job
```
Assign a section to multiple articles.
Reference: [/content-management-api/mutations/articleAddSectionsBulk](/content-management-api/mutations/articleAddSectionsBulk)
***
## Section
A section is the structured data that houses the content for your page. The section's data model is derived from its corresponding component schema in your code base.
[Learn more about the Section content model](/developer-resources/cms-models#section)
### Queries
```graphql
section(id: ID!, version: Version): Section
```
Returns a section by ID in a draft or published state.
Reference: [/content-management-api/queries/section](/content-management-api/queries/section)
```graphql
sections(after: String, before: String, first: Int, last: Int, version: Version): SectionConnection!
```
Returns an array of all your sections in a draft or published state paginated by a cursor.
Reference: [/content-management-api/queries/sections](/content-management-api/queries/sections)
```graphql
sectionHistory(id: ID!, after: String, before: String, first: Int, last: Int): SectionRevisionConnection!
```
Returns an array of all revisions for the section by ID paginated by a cursor.
Reference: [/content-management-api/queries/sectionHistory](/content-management-api/queries/sectionHistory)
```graphql
sectionRevision(id: ID!, revisionId: ID!): SectionRevision
```
Returns a section's revision.
Reference: [/content-management-api/queries/sectionRevision](/content-management-api/queries/sectionRevision)
```graphql
sectionReferences(id: ID!, revisionId: ID!): [Reference!]!
```
Returns a list of references where the section is used.
Reference: [/content-management-api/queries/sectionReferences](/content-management-api/queries/sectionReferences)
### Mutations
```graphql
sectionUpsert(input: SectionUpsertInput!): Section
```
Creates or updates a section.
Reference: [/content-management-api/mutations/sectionUpsert](/content-management-api/mutations/sectionUpsert)
```graphql
sectionDelete(id: ID!): DeletePayload
```
Deletes a section.
Reference: [/content-management-api/mutations/sectionDelete](/content-management-api/mutations/sectionDelete)
```graphql
sectionPublish(id: ID!): Section
```
Publishes a section.
Reference: [/content-management-api/mutations/sectionPublish](/content-management-api/mutations/sectionPublish)
```graphql
sectionUnpublish(id: ID!): Section
```
Unpublishes a section.
Reference: [/content-management-api/mutations/sectionUnpublish](/content-management-api/mutations/sectionUnpublish)
```graphql
sectionRestore(id: ID!, revisionId: ID!): Section
```
Restores a section to a specific revision.
Reference: [/content-management-api/mutations/sectionRestore](/content-management-api/mutations/sectionRestore)
```graphql
sectionDeleteBulk(ids: [ID!]!): Job
```
Bulk delete sections.
Reference: [/content-management-api/mutations/sectionDeleteBulk](/content-management-api/mutations/sectionDeleteBulk)
```graphql
sectionPublishBulk(ids: [ID!]!): Job
```
Bulk publish sections.
Reference: [/content-management-api/mutations/sectionPublishBulk](/content-management-api/mutations/sectionPublishBulk)
```graphql
sectionUnpublishBulk(ids: [ID!]!): Job
```
Bulk unpublish sections.
Reference: [/content-management-api/mutations/sectionUnpublishBulk](/content-management-api/mutations/sectionUnpublishBulk)
## Site Settings
This is the storefront site settings.
[Learn more about the Site Settings content model](/developer-resources/cms-models#site-settings)
### Queries
```graphql
siteSettings(version: Version): SiteSettings!
```
Returns the site settings in a draft or published state.
Reference: [/content-management-api/queries/siteSettings](/content-management-api/queries/siteSettings)
```graphql
siteSettingsHistory(id: ID!, after: String, before: String, first: Int, last: Int): SiteSettingsRevisionConnection!
```
Returns an array of all revisions for the site settings by ID paginated by a cursor.
Reference: [/content-management-api/queries/siteSettingsHistory](/content-management-api/queries/siteSettingsHistory)
```graphql
siteSettingsRevision(id: ID!, revisionId: ID!): SiteSettingsRevision
```
Returns a site settings' revision.
Reference: [/content-management-api/queries/siteSettingsRevision](/content-management-api/queries/siteSettingsRevision)
```graphql
faviconUploadUrl(id: String!): String!
```
Returns the favicon upload URL.
Reference: [/content-management-api/queries/faviconUploadUrl](/content-management-api/queries/faviconUploadUrl)
### Mutations
```graphql
siteSettingsUpdate(input: SiteSettingsUpdateInput!): SiteSettings!
```
Updates the site settings.
Reference: [/content-management-api/mutations/siteSettingsUpdate](/content-management-api/mutations/siteSettingsUpdate)
```graphql
siteSettingsPublish(id: ID!, publishComment: String): SiteSettings!
```
Publishes the site settings.
Reference: [/content-management-api/mutations/siteSettingsPublish](/content-management-api/mutations/siteSettingsPublish)
```graphql
siteSettingsRestore(revisionId: String!): SiteSettings!
```
Restores the site settings to a specific revision.
Reference: [/content-management-api/mutations/siteSettingsRestore](/content-management-api/mutations/siteSettingsRestore)
***
## Content Releases
Content Releases stage coordinated CMS changes and publish them together. Release-scoped reads and writes use the `X-Pack-Release-Id` header; when the header is present, Pack reads release drafts where they exist and falls through to current live content where they do not.
Use the [Content Releases guide](/create-manage-content/content-releases) for the editor workflow and conflict review process.
### Queries
```graphql
contentRelease(id: ID, handle: String): ContentRelease
```
Returns a release by ID or handle, including its drafts and conflicts when requested.
Reference: [/create-manage-content/content-releases](/create-manage-content/content-releases)
```graphql
contentReleases(first: Int, after: String, status: ContentReleaseStatus): ContentReleaseConnection!
```
Returns paginated releases, optionally filtered by status.
Reference: [/create-manage-content/content-releases](/create-manage-content/content-releases)
### Mutations
```graphql
contentReleaseCreate(input: ContentReleaseCreateInput!): ContentRelease!
```
Creates an open release.
Reference: [/create-manage-content/content-releases](/create-manage-content/content-releases)
```graphql
contentReleaseUpdate(id: ID!, input: ContentReleaseUpdateInput!): ContentRelease!
```
Updates a release name or description.
Reference: [/create-manage-content/content-releases](/create-manage-content/content-releases)
```graphql
contentReleaseArchive(id: ID!): ContentRelease!
```
Archives an open release.
Reference: [/create-manage-content/content-releases](/create-manage-content/content-releases)
```graphql
contentReleasePublish(id: ID!, publishComment: String, overwriteConflicts: Boolean): ContentRelease!
```
Publishes release drafts together. Set overwriteConflicts only after reviewing conflicts.
Reference: [/create-manage-content/content-releases](/create-manage-content/content-releases)
```graphql
contentReleaseMovePageChanges(input: ContentReleaseMovePageChangesInput!): ContentRelease!
```
Moves unscheduled page changes into a release.
Reference: [/create-manage-content/content-releases](/create-manage-content/content-releases)
***
## Schedules
Schedules are used to publish or unpublish content at a specific time in the future.
[Learn more about the Schedule content model](/developer-resources/cms-models#schedule)
### Queries
```graphql
schedule(id: ID!): Schedule
```
Returns a schedule by ID.
Reference: [/content-management-api/queries/schedule](/content-management-api/queries/schedule)
```graphql
schedules(after: String, before: String, first: Int, last: Int): ScheduleConnection!
```
Returns a list of schedules paginated by a cursor.
Reference: [/content-management-api/queries/schedules](/content-management-api/queries/schedules)
```graphql
schedulesByContentId(contentId: ID!, after: String, before: String, first: Int, last: Int): ScheduleConnection!
```
Returns a list of schedules that contain a content ID paginated by a cursor.
Reference: [/content-management-api/queries/schedulesByContentId](/content-management-api/queries/schedulesByContentId)
### Mutations
```graphql
scheduleCreate(input: CreateScheduleInput!): Schedule!
```
Creates a schedule.
Reference: [/content-management-api/mutations/scheduleCreate](/content-management-api/mutations/scheduleCreate)
```graphql
scheduleUpdate(input: UpdateScheduleInput!): Schedule!
```
Updates a schedule.
Reference: [/content-management-api/mutations/scheduleUpdate](/content-management-api/mutations/scheduleUpdate)
```graphql
scheduleAddContent(id: ID!, input: AddContentToScheduleInput!): Schedule!
```
Adds content to a schedule by ID.
Reference: [/content-management-api/mutations/scheduleAddContent](/content-management-api/mutations/scheduleAddContent)
```graphql
scheduleDelete(id: ID!): DeletePayload
```
Deletes a schedule.
Reference: [/content-management-api/mutations/scheduleDelete](/content-management-api/mutations/scheduleDelete)
```graphql
schedulePublish(id: ID!): Schedule!
```
Publishes a schedule.
Reference: [/content-management-api/mutations/schedulePublish](/content-management-api/mutations/schedulePublish)
***
## Templates
A template is a container for template sections and is used to repeat these sections throughout pages that use the template.
[Learn more about the Template content model](/developer-resources/cms-models#template)
### Queries
```graphql
template(id: ID!, version: Version): Template
```
Returns a template by ID in a draft or published state.
Reference: [/content-management-api/queries/template](/content-management-api/queries/template)
```graphql
templates(after: String, before: String, first: Int, last: Int, version: Version): TemplateConnection!
```
Returns an array of all your templates in a draft or published state paginated by a cursor.
Reference: [/content-management-api/queries/templates](/content-management-api/queries/templates)
```graphql
templateHistory(id: ID!, after: String, before: String, first: Int, last: Int): TemplateVersionConnection!
```
Returns an array of all revisions for the template by ID paginated by a cursor.
Reference: [/content-management-api/queries/templateHistory](/content-management-api/queries/templateHistory)
```graphql
templateReferences(id: ID!): [Reference]
```
Returns a list of references where the template is used.
Reference: [/content-management-api/queries/templateReferences](/content-management-api/queries/templateReferences)
### Mutations
```graphql
templateCreate(input: TemplateCreatetInput!): Template!
```
Creates a template.
Reference: [/content-management-api/mutations/templateCreate](/content-management-api/mutations/templateCreate)
```graphql
templateUpdate(id: ID!, input: TemplateUpdateInput!): Template
```
Updates a template.
Reference: [/content-management-api/mutations/templateUpdate](/content-management-api/mutations/templateUpdate)
```graphql
templateDelete(id: ID!): DeletePayload!
```
Deletes a template.
Reference: [/content-management-api/mutations/templateDelete](/content-management-api/mutations/templateDelete)
```graphql
templatePublish(id: ID!): Template
```
Publishes a template.
Reference: [/content-management-api/mutations/templatePublish](/content-management-api/mutations/templatePublish)
```graphql
templatePublishBulk(ids: [ID!]!): Job
```
Bulk publishes templates.
Reference: [/content-management-api/mutations/templatePublishBulk](/content-management-api/mutations/templatePublishBulk)
```graphql
templateUnpublish(id: ID!): Template
```
Unpublishes a template.
Reference: [/content-management-api/mutations/templateUnpublish](/content-management-api/mutations/templateUnpublish)
```graphql
templateUnpublishBulk(ids: [ID!]!): Job
```
Bulk unpublishes templates.
Reference: [/content-management-api/mutations/templateUnpublishBulk](/content-management-api/mutations/templateUnpublishBulk)
```graphql
templateAddSectionsBulk(ids: [ID!]!, input: [TemplateAddSectionsInput!]!): Job
```
Assigns sections to multiple templates.
Reference: [/content-management-api/mutations/templateAddSectionsBulk](/content-management-api/mutations/templateAddSectionsBulk)
---
# articleAddSectionsBulk - Mutation
[Back to Content Management API](/content-management-api)
Assign sections to multiple articles.
### Arguments
* **ids** (\[ID!]): Array of article IDs that you want to add sections to.
* **input** (\[ArticleAddSectionsInput!]!): An object that takes in an array of sectionIds and a strategy for how to handle the sections attached to the articles.sectionIds: array of section IDsstrategy: clone | link
### Returns
* **Job.\*** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleAddSectionsBulk($ids: [ID!]!, $input: [ArticleAddSectionsInput!]!) {
articleAddSectionsBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
"ids": ["article-id-1", "article-id-2"],
"input": [
{
"sectionIds": ["section-id-1", "section-id-2"],
"strategy": "clone"
}
]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleAddSectionsBulk": {
"id": "1"
}
}
}
```
---
# articleCreate - Mutation
[Back to Content Management API](/content-management-api)
Creates an article.
### Arguments
* **input** (ArticleCreateInput!): An object with the following fields:title: String!handle: String!description: Stringseo: SEOInputsectionIds: \[ID!]firstPublishedAt: DatefirstPublishedAtTimezone: String
### Returns
* **Article.\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleCreate($input: ArticleCreateInput!) {
articleCreate(input: $input) {
id
title
handle
description
author
category
tags
excerpt
bodyHtml
status
sections {
edges {
node {
id
}
}
}
seo {
title
description
image
}
publishedAt
firstPublishedAt
firstPublishedAtTimezone
blog {
id
}
user {
id
}
storeId
createdAt
updatedAt
}
}
`;
const variables = {
"input": {
"title": "My Article",
"handle": "my-article",
"description": "This is my article.",
"seo": {
"title": "My Article",
"description": "This is my article.",
"image": "https://example.com/image.jpg"
},
"sectionIds": ["sectionId1", "sectionId2"],
"firstPublishedAt": "2022-01-01T00:00:00Z",
"firstPublishedAtTimezone": "America/New_York"
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleCreate": {
"id": "articleId123",
"title": "My Article",
"handle": "my-article",
"description": "This is my article.",
"author": "Author Name",
"category": "Category",
"tags": ["tag1", "tag2"],
"excerpt": "This is an excerpt.",
"bodyHtml": "
This is the body.
",
"status": "draft",
"sections": {
"edges": [
{
"node": {
"id": "sectionId1"
}
},
{
"node": {
"id": "sectionId2"
}
}
]
},
"seo": {
"title": "My Article",
"description": "This is my article.",
"image": "https://example.com/image.jpg"
},
"publishedAt": "2022-01-01T00:00:00Z",
"firstPublishedAt": "2022-01-01T00:00:00Z",
"firstPublishedAtTimezone": "America/New_York",
"blog": {
"id": "blogId123"
},
"user": {
"id": "userId123"
},
"storeId": "storeId123",
"createdAt": "2024-08-15T12:00:00Z",
"updatedAt": "2024-08-15T12:00:00Z"
}
}
}
```
---
# articleDelete - Mutation
[Back to Content Management API](/content-management-api)
Deletes an article.
### Arguments
* **id** (ID!): The ID of the article you want to delete.
### Returns
* **DeletePayload.\*** (DeletePayload): Any requested field from the DeletePayload object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleDelete($id: ID!) {
articleDelete(id: $id) {
... on DeletePayload {
success
}
}
}
`;
const variables = {
"id": "60f4b3b3b3b3b3b3b3b3b3b"
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleDelete": {
"success": true
}
}
}
```
---
# articleDeleteBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk delete articles.
### Arguments
* **ids** (\[ID!]): Array of article IDs that you want to delete.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleDeleteBulk($ids: [ID!]!) {
articleDeleteBulk(ids: $ids) {
id
}
}
`;
const variables = {
"ids": ["60f4b3b3b3b3b3b3b3b3b3b", "60f4b3b3b3b3b3b3b3b3b3c"]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleDeleteBulk": {
"id": "1"
}
}
}
```
---
# articlePublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes an article.
### Arguments
* **id** (ID!): The ID of the article you want to publish.
* **publishComment** (String): An optional string to comment on the article publish.
### Returns
* **Article.\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticlePublish($id: ID!, $publishComment: String) {
articlePublish(id: $id, publishComment: $publishComment) {
id
title
handle
description
author
category
tags
sections {
id
title
handle
description
}
}
}
`;
const variables = {
"id": "1",
"publishComment": "Publishing article."
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articlePublish": {
"id": "1",
"title": "My Article",
"handle": "my-article",
"description": "This is my article.",
"author": "John Doe",
"category": "Uncategorized",
"tags": ["tag1", "tag2"],
"sections": [
{
"id": "section-id-1",
"title": "Section 1",
"handle": "section-1",
"description": "This is section 1."
},
{
"id": "section-id-2",
"title": "Section 2",
"handle": "section-2",
"description": "This is section 2."
}
]
}
}
}
```
---
# articlePublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk publish articles.
### Arguments
* **ids** (\[ID!]): Array of article IDs that you want to publish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticlePublishBulk($ids: [ID!]!) {
articlePublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['1', '2']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articlePublishBulk": {
"id": "1"
}
}
}
```
---
# articleRestore - Mutation
[Back to Content Management API](/content-management-api)
Restores an article to a specific revision.
### Arguments
* **id** (ID!): The ID of the article you want to restore.
* **revisionId** (ID!): The ID of the revision you want to use to restore.
### Returns
* **Article.\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleRestore($id: ID!, $revisionId: ID!) {
articleRestore(id: $id, revisionId: $revisionId) {
id
title
handle
description
author
category
tags
sections {
id
title
handle
description
}
}
}
`;
const variables = {
"id": "1",
"revisionId": "1"
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleRestore": {
"id": "article-id-1",
"title": "Article Title",
"handle": "article-title",
"description": "Article description",
"author": "Author Name",
"category": "Category Name",
"tags": ["tag1", "tag2"],
"sections": [
{
"id": "section-id-1",
"title": "Section Title",
"handle": "section-title",
"description": "Section description"
}
]
}
}
}
```
---
# articleUnpublish - Mutation
[Back to Content Management API](/content-management-api)
Unpublishes article.
### Arguments
* **id** (ID!): The ID of the article you want to unpublish.
### Returns
* **Article.\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleUnpublish($id: ID!) {
articleUnpublish(id: $id) {
id
title
handle
description
author
category
tags
sections {
id
title
handle
description
}
}
}
`;
const variables = {
id: '1'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleUnpublish": {
"id": "article-id-1",
"title": "Article Title",
"handle": "article-title",
"description": "Article description",
"author": "Author Name",
"category": "Category Name",
"tags": ["tag1", "tag2"],
"sections": [
{
"id": "section-id-1",
"title": "Section Title",
"handle": "section-title",
"description": "Section description"
}
]
}
}
}
```
---
# articleUnpublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk unpublish articles.
### Arguments
* **ids** (\[ID!]): Array of article IDs that you want to unpublish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: 'GraphQL'}}
import ApiClient from '@example/protocol-api'
const client = new ApiClient(token)
await client.contacts.list()
```
```js {{ title: '@pack/client'}}
import ApiClient from '@example/protocol-api'
const client = new ApiClient(token)
await client.contacts.list()
```
```json {{ title: 'Response' }}
{
"has_more": false,
"data": [
{
"id": "WAz8eIbvDR60rouK",
"username": "FrankMcCallister",
"phone_number": "1-800-759-3000",
"avatar_url": "https://assets.protocol.chat/avatars/frank.jpg",
"display_name": null,
"conversation_id": "xgQQXg3hrtjh7AvZ",
"last_active_at": 705103200,
"created_at": 692233200
},
{
"id": "hSIhXBhNe8X1d8Et"
// ...
}
]
}
```
---
# articleUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates an article.
### Arguments
* **id** (ID!): The ID of the article you want to update.
* **input** (ArticleUpdateInput!): An object.title: Stringhandle: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringseo: SEOInputtemplate: StringsectionIds: \[ID!]
### Returns
* **Article.\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleUpdate($id: ID!, $input: ArticleUpdateInput!) {
articleUpdate(id: $id, input: $input) {
id
title
handle
description
author
category
tags
sections {
id
title
handle
description
}
}
}
`;
const variables = {
"id": "1",
"input": {
"title": "Updated Article Title",
"handle": "updated-article-title",
"description": "Updated article description.",
"seo": {
"title": "Updated SEO Title",
"description": "Updated SEO Description",
"keywords": ["Updated", "SEO", "Keywords"]
},
"template": "updated-template",
"sectionIds": ["section-id-1", "section-id-2"]
}
};
const response = await packClient.fetch(query, { variables:
variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleUpdate": {
"id": "1",
"title": "Updated Article Title",
"handle": "updated-article-title",
"description": "Updated article description.",
"author": "John Doe",
"category": "Uncategorized",
"tags": ["tag1", "tag2"],
"sections": [
{
"id": "section-id-1",
"title": "Section 1",
"handle": "section-1",
"description": "This is section 1."
},
{
"id": "section-id-2",
"title": "Section 2",
"handle": "section-2",
"description": "This is section 2."
}
]
}
}
}
```
---
# articleUpdateBulk - Mutation
[Back to Content Management API](/content-management-api)
Allows you to bulk update articles.
### Arguments
* **ids** (\[ID!]): Array of article IDs that you want to update.
* **input** (\[ArticleUpdateInput!]!): Array of objects for each corresponding article ID to update.title: Stringhandle: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringseo: SEOInputtemplate: StringsectionIds: \[ID!]
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ArticleUpdateBulk($ids: [ID!]!, $input: [ArticleUpdateInput!]!) {
articleUpdateBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
"ids": ["article-id-1", "article-id-2"],
"input": [
{
"title": "New Title",
"handle": "new-title",
"firstPublishedAt": "2021-01-01T00:00:00Z",
"firstPublishedAtTimezone": "America/New_York",
"description": "New Description",
"seo": {
"title": "New SEO Title",
"description": "New SEO Description",
"image": "https://example.com/image.jpg",
"keywords": ["keyword1", "keyword2"]
},
"template": "new-template",
"sectionIds": ["section-id-1", "section-id-2"]
},
{
"title": "New Title 2",
"handle": "new-title-2",
"firstPublishedAt": "2021-01-01T00:00:00Z",
"firstPublishedAtTimezone": "America/New_York",
"description": "New Description 2",
"seo": {
"title": "New SEO Title 2",
"description": "New SEO Description 2",
"image": "https://example.com/image2.jpg",
"keywords": ["keyword3", "keyword4"]
},
"template": "new-template-2",
"sectionIds": ["section-id-3", "section-id-4"]
}
]
};
const response = await packClient.fetch(query, { variables:
variables
});
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleUpdateBulk": {
"id": "1"
}
}
}
```
---
# blogAddSectionsBulk - Mutation
[Back to Content Management API](/content-management-api)
Assign a sections to multiple blogs.
### Arguments
* **ids** (\[ID!]): Array of blog IDs that you want to add sections to.
* **input** (\[BlogAddSectionsInput!]!): An object that takes in an array of sectionIds and a srategy for how to handle the sections attached to the blogs.sectionIds: array of section IDsstrategy: clone | link
### Returns
* **Job.\*** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogAddSectionsBulk($ids: [ID!]!, $input: [BlogAddSectionsInput!]!) {
blogAddSectionsBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
"ids": ["blog-id-1", "blog-id-2"],
"input": [
{
"sectionIds": ["section-id-1", "section-id-2"],
"strategy": "clone"
}
]
}
const response = await packClient.fetch(query, { variables:
variables
});
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogAddSectionsBulk": {
"id": "1"
}
}
}
```
---
# blogCreate - Mutation
[Back to Content Management API](/content-management-api)
Creates a blog.
### Arguments
* **input** (BlogCreateInput!): An object.title: String!handle: String!description: Stringseo: SEOInputsectionIds: \[ID!]
### Returns
* **Blog.\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
fragment BlogResourceFields on BlogResource {
title
handle
description
}
mutation BlogCreate($input: BlogCreateInput!) {
blogCreate(input: $input) {
...BlogResourceFields
}
}
`;
const variables = {
"input": {
"title": "My Blog",
"handle": "my-blog",
"description": "This is my blog",
"seo": {
"title": "My Blog",
"description": "This is my blog",
"image": "https://example.com/image.jpg",
"keywords": ["blog", "example"]
},
"sectionIds": ["section-id"]
}
};
const response = await packClient.fetch(query, { variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogCreate": {
"title": "My Blog",
"handle": "my-blog",
"description": "This is my blog"
}
}
}
```
---
# blogDelete - Mutation
[Back to Content Management API](/content-management-api)
Deletes a blog.
### Arguments
* **id** (ID!): The ID of the blog you want to delete.
### Returns
* **DeletePayload.\*** (DeletePayload): Any requested field from the DeletePayload object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogDelete($id: ID!) {
blogDelete(id: $id) {
... on DeletePayload {
success
}
}
}
`;
const variables = {
"id": "60f4b3b3b3b3b3b3b3b3b3b"
};
const response = await packClient.fetch(query, { variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogDelete": {
"success": true
}
}
}
```
---
# blogDeleteBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk delete blogs.
### Arguments
* **ids** (\[ID!]): Array of blog IDs that you want to delete.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogDeleteBulk($ids: [ID!]!) {
blogDeleteBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['blog-1', 'blog-2']
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogDeleteBulk": {
"id": "1"
}
}
}
```
---
# blogPublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes a blog.
### Arguments
* **id** (ID!): The ID of the blog you want to publish.
* **publishComment** (String): An optional string to comment on the blog publish.
### Returns
* **Blog.\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogPublish($id: ID!, $publishComment: String) {
blogPublish(id: $id, publishComment: $publishComment) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-BLOG-ID',
publishComment: 'YOUR-PUBLISH-COMMENT'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogPublish": {
"id": "blog-id-1",
"title": "Blog Title",
"handle": "blog-title",
"description": "Blog Description"
}
}
}
```
---
# blogPublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk publish blogs.
### Arguments
* **ids** (\[ID!]): Array of blog IDs that you want to publish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogPublishBulk($ids: [ID!]!) {
blogPublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['1', '2']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogPublishBulk": {
"id": "1"
}
}
}
```
---
# blogRestore - Mutation
[Back to Content Management API](/content-management-api)
Restores a blog to a specific revision.
### Arguments
* **id** (ID!): The ID of the blog you want to restore.
* **revisionId** (ID!): The ID of the revision you want to use to restore.
### Returns
* **Blog.\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogRestore($id: ID!, $revisionId: ID!) {
blogRestore(id: $id, revisionId: $revisionId) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-BLOG-ID',
revisionId: 'YOUR-REVISION-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogRestore": {
"id": "60f4b3b3b3b3b3b3b3b3b3b",
"title": "Blog Title",
"handle": "blog-title",
"description": "Blog Description"
}
}
}
```
---
# blogUnpublish - Mutation
[Back to Content Management API](/content-management-api)
Unpublishes blog.
### Arguments
* **id** (ID!): The ID of the blog you want to unpublish.
### Returns
* **Blog.\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogUnpublish($id: ID!) {
blogUnpublish(id: $id) {
id
title
handle
description
author
category
tags
sections {
id
title
handle
description
}
}
}
`;
const variables = {
id: 'blog-id'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogUnpublish": {
"id": "blog-id",
"title": "Blog Title",
"handle": "blog-title",
"description": "Blog Description",
"author": "Author Name",
"category": "Category Name",
"tags": ["tag1", "tag2"],
"sections": [
{
"id": "section-id",
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
}
]
}
}
}
```
---
# blogUnpublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk unpublish blogs.
### Arguments
* **ids** (\[ID!]): Array of blog IDs that you want to unpublish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogUnpublishBulk($ids: [ID!]!) {
blogUnpublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['blog-1', 'blog-2']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogUnpublishBulk": {
"id": "1"
}
}
}
```
---
# blogUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates a blog.
### Arguments
* **id** (ID!): The ID of the blog you want to update.
* **input** (BlogUpdateInput!): An object.title: Stringhandle: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringseo: SEOInputtemplate: StringsectionIds: \[ID!]
### Returns
* **Blog.\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogUpdate($id: ID!, $input: BlogUpdateInput!) {
blogUpdate(id: $id, input: $input) {
id
title
handle
description
}
}
`;
const variables = {
"id": "YOUR-BLOG-ID",
"input": {
"title": "New Blog Title",
"handle": "new-blog-title",
"description": "New blog description",
"seo": {
"title": "New Blog Title",
"description": "New blog description",
"image": "https://example.com/image.jpg",
"keywords": ["new", "blog", "keywords"]
},
"template": "blog-template",
"sectionIds": ["YOUR-SECTION-ID"]
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogUpdate": {
"id": "blog-id-1",
"title": "New Blog Title",
"handle": "new-blog-title",
"description": "New blog description"
}
}
}
```
---
# blogUpdateBulk - Mutation
[Back to Content Management API](/content-management-api)
Allows you to bulk update blogs.
### Arguments
* **ids** (\[ID!]): Array of blog IDs that you want to update.
* **input** (\[BlogUpdateInput!]!): Array of objects for each corresponding blog ID to update.title: Stringhandle: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringseo: SEOInputtemplate: StringsectionIds: \[ID!]
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation BlogUpdateBulk($ids: [ID!]!, $input: [BlogUpdateInput!]!) {
blogUpdateBulk(ids: $ids, input: $input) {
id
title
handle
description
author
category
tags
sections {
id
title
handle
description
}
}
}
`;
const variables = {
"ids": ["blog-id-1", "blog-id-2"],
"input": [
{
"title": "New Blog Title",
"handle": "new-blog-title",
"description": "New blog description",
"firstPublishedAt": "2022-01-01T00:00:00Z",
"firstPublishedAtTimezone": "UTC",
"seo": {
"title": "New Blog Title",
"description": "New blog description",
"image": "https://example.com/image.jpg",
"keywords": ["keyword1", "keyword2"]
},
"template": "blog-template",
"sectionIds": ["section-id-1", "section-id-2"]
}
]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogUpdateBulk": {
"id": "blog-id-1",
"title": "New Blog Title",
"handle": "new-blog-title",
"description": "New blog description",
"author": "Author Name",
"category": "Category Name",
"tags": ["tag1", "tag2"],
"sections": [
{
"id": "section-id-1",
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
},
{
"id": "section-id-2",
"title": "Section Title 2",
"handle": "section-title-2",
"description": "Section Description 2"
}
]
}
}
}
```
---
# collectionPageAddSectionsBulk - Mutation
[Back to Content Management API](/content-management-api)
Add multiple sections to multiple pages.
### Arguments
* **ids** (\[ID!]): Array of page IDs that you want to add sections to.
* **input** (\[CollectionPageAddSectionsInput!]!): An object that takes in an array of sectionIds and a srategy for how to handle the sections attached to the pages.sectionIds: array of section IDsstrategy: clone | link
### Returns
* **Job.\*** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageAddSectionsBulk": {
"id": "1"
}
}
}
```
---
# collectionPagePublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes a collection page.
### Arguments
* **id** (ID!): The ID of the page you want to publish.
* **publishComment** (String): An optional string to comment on the page publish.
### Returns
* **CollectionPage.\*** (CollectionPage): Any requested field from the Collectionpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation CollectionPagePublish($id: ID!, $publishComment: String) {
collectionPagePublish(id: $id, publishComment: $publishComment) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-PAGE-ID',
publishComment: 'YOUR-PUBLISH-COMMENT'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPagePublish": {
"id": "60f4b3b3b3b3b3b3b3b3b3b",
"title": "My Page",
"handle": "my-page",
"description": "My page description"
}
}
}
```
---
# collectionPagePublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk publish collection pages.
### Arguments
* **ids** (\[ID!]): Array of collection page IDs that you want to publish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation CollectionPagePublishBulk($ids: [ID!]!) {
collectionPagePublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['', '']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPagePublishBulk": {
"id": "1"
}
}
}
```
---
# collectionPageRestore - Mutation
[Back to Content Management API](/content-management-api)
Restores a collection page to a specific revision.
### Arguments
* **id** (ID!): The ID of the collection page you want to restore.
* **revisionId** (ID!): The ID of the revision you want to use to restore.
### Returns
* **CollectionPage.\*** (CollectionPage): Any requested field from the Collectionpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation CollectionPageRestore($id: ID!, $revisionId: ID!) {
collectionPageRestore(id: $id, revisionId: $revisionId) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-COLLECTION-PAGE-ID',
revisionId: 'YOUR-REVISION-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageRestore": {
"id": "1",
"title": "Collection Page Title",
"handle": "collection-page-title",
"description": "Collection Page Description"
}
}
}
```
---
# collectionPageUnpublish - Mutation
[Back to Content Management API](/content-management-api)
Unpublishes collection page.
### Arguments
* **id** (ID!): The ID of the collection page you want to unpublish.
### Returns
* **CollectionPage.\*** (CollectionPage): Any requested field from the Collectionpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation CollectionPageUnpublish($id: ID!) {
collectionPageUnpublish(id: $id) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-COLLECTION-PAGE-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageUnpublish": {
"id": "60f4b3b3b3b3b3b3b3b3b3b",
"title": "My Collection Page",
"handle": "my-collection-page",
"description": "This is my collection page."
}
}
}
```
---
# collectionPageUnpublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk unpublish collection pages.
### Arguments
* **ids** (\[ID!]): Array of collection page IDs that you want to unpublish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation CollectionPageUnpublishBulk($ids: [ID!]!) {
collectionPageUnpublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageUnpublishBulk": {
"id": "1"
}
}
}
```
---
# collectionPageUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates a collection page.
### Arguments
* **id** (ID!): The ID of the collection page you want to update.
* **input** (CollectionPageUpdateInput!): An object.title: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringtemplate: StringsectionIds: \[ID!]
### Returns
* **CollectionPage.\*** (CollectionPage): Any requested field from the Collectionpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation CollectionPageUpdate($id: ID!, $input: CollectionPageUpdateInput!) {
collectionPageUpdate(id: $id, input: $input) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-COLLECTION-PAGE-ID',
input: {
title: 'YOUR-TITLE',
firstPublishedAt: 'YOUR-FIRST-PUBLISHED-AT',
firstPublishedAtTimezone
description: 'YOUR-DESCRIPTION',
template: 'YOUR-TEMPLATE',
sectionIds: ['YOUR-SECTION-ID']
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageUpdate": {
"id": "60f4b3b3b3b3b3b3b3b3b3b",
"title": "My Collection Page",
"handle": "my-collection-page",
"description": "This is my collection page."
}
}
}
```
---
# collectionPageUpdateBulk - Mutation
[Back to Content Management API](/content-management-api)
Allows you to bulk update collection pages.
### Arguments
* **ids** (\[ID!]): Array of collection page IDs that you want to update.
* **input** (\[CollectionPageUpdateInput!]!): Array of objects for each corresponding page ID to update.title: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringtemplate: StringsectionIds: \[ID!]
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation CollectionPageUpdateBulk($ids: [ID!]!, $input: [CollectionPageUpdateInput!]!) {
collectionPageUpdateBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
ids: ['', ''],
input: [
{
title: 'New Title',
firstPublishedAt: '2022-01-01T00:00:00Z',
firstPublishedAtTimezone: 'America/New_York',
description: 'New Description',
template: 'default',
sectionIds: ['', '']
}
]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageUpdateBulk": {
"id": "1"
}
}
}
```
---
# pageAddSectionsBulk - Mutation
[Back to Content Management API](/content-management-api)
Adds sections to multiple pages.
### Arguments
* **ids** (\[ID!]): Array of page IDs that you want to add sections to.
* **input** (\[PageAddSectionsInput!]!): An object that accepts an array of sectionIds and a srategy for how to handle the sections attached to the pages.sectionIds: array of section IDsstrategy: clone | link
### Returns
* **Job.\*** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PageAddSectionsBulk($ids: [ID!]!, $input: [PageAddSectionsInput!]!) {
pageAddSectionsBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
ids: ['page-id-1', 'page-id-2'],
input: [
{
sectionIds: ['section-id-1', 'section-id-2'],
strategy: 'clone'
}
]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageAddSectionsBulk": {
"id": "1"
}
}
}
```
---
# pageCreate - Mutation
[Back to Content Management API](/content-management-api)
Creates a new page.
### Arguments
* **input** (PageCreateInput!): An object.title: String!handle: String!description: Stringseo: SEOInputsectionIds: \[ID!]
### Returns
* **Page.\*** (Page): Any requested field from the page object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
fragment PageResourceFields on PageResource {
title
handle
description
}
mutation PageCreate($input: PageCreateInput!) {
pageCreate(input: $input) {
...PageResourceFields
}
}
`;
const variables = {
"input": {
"title": "My Page",
"handle": "my-page",
"description": "This is my page.",
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageCreate": {
"title": "My Page",
"handle": "my-page",
"description": "This is my page."
}
}
}
```
---
# pageDelete - Mutation
[Back to Content Management API](/content-management-api)
Deletes a page.
### Arguments
* **id** (ID!): The ID of the page you want to delete.
### Returns
* **DeletePayload.\*** (DeletePayload): Any requested field from the DeletePayload object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PageDelete($id: ID!) {
pageDelete(id: $id) {
id
}
}
`;
const response = await packClient.fetch(query, { variables: { id: 'YOUR-PAGE-ID' } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"pageDelete": {
"id": "0190e20e-e462-749f-9f64-d3894de9b139"
}
}
```
---
# pageDeleteBulk - Mutation
[Back to Content Management API](/content-management-api)
Deletes multiple pages.
### Arguments
* **ids** (\[ID!]): Array of page IDs that you want to delete.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PageDeleteBulk($ids: [ID!]!) {
pageDeleteBulk(ids: $ids) {
id
}
}
`
const variables = {
ids: ["page-id-1", "page-id-2"]
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageDeleteBulk": {
"id": "1",
}
}
}
```
---
# pagePublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes a page.
### Arguments
* **id** (ID!): The ID of the page you want to publish.
* **publishComment** (String): An optional comment to include with the publish.
### Returns
* **Page.\*** (Page): Any requested field from the page object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PagePublish($id: ID!) {
pagePublish(id: $id) {
id
}
}
`
const response = await packClient.fetch(query, { variables: { id: 'YOUR-PAGE-ID' } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pagePublish": {
"id": "page-id"
}
}
}
```
---
# pagePublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Publishes multiple pages.
### Arguments
* **ids** (\[ID!]): Array of page IDs that you want to publish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PagePublishBulk($ids: [ID!]!) {
pagePublishBulk(ids: $ids) {
id
}
}
`
const variables = {
ids: [
"page-id-1",
"page-id-2"
]
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pagePublishBulk": {
"id": "1",
}
}
}
```
---
# pageRestore - Mutation
[Back to Content Management API](/content-management-api)
Restores a page to a specific revision.
### Arguments
* **id** (ID!): The ID of the page you want to restore.
* **revisionId** (ID!): The ID of the revision you want to use to restore.
### Returns
* **Page.\*** (Page): Any requested field from the page object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PageRestore($id: ID!, $revisionId: ID!) {
pageRestore(id: $id, revisionId: $revisionId) {
id
revisionId
}
}
`
const response = await packClient.fetch(query, { variables: { id: "your-page-id", revisionId: "your-revision-id" } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"PageRestore": {
"id": "your-page-id",
"revisionId": "your-revision-id"
}
}
}
```
---
# pageUnpublish - Mutation
[Back to Content Management API](/content-management-api)
Unpublishes page.
### Arguments
* **id** (ID!): The ID of the page you want to unpublish.
### Returns
* **Page.\*** (Page): Any requested field from the page object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PageUnpublish($id: ID!) {
pageUnpublish(id: $id) {
id
}
}
`
const response = await packClient.fetch(query, { variables: { id: 'your-page-id' } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"PageUnpublish": {
"id": "your-page-id"
}
}
}
```
---
# pageUnpublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk unpublish pages.
### Arguments
* **ids** (\[ID!]): Array of page IDs that you want to unpublish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PageUnpublishBulk($ids: [ID!]!) {
pageUnpublishBulk(ids: $ids) {
id
}
}
`
const variables = {
ids: [
"page-id-1",
"page-id-2"
]
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageUnpublishBulk": {
"id": "1",
}
}
}
```
---
# pageUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates a page.
### Arguments
* **id** (ID!): The ID of the page you want to update.
* **input** (PageUpdateInput!): An object.title: Stringhandle: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringseo: SEOInputtemplate: StringsectionIds: \[ID!]
### Returns
* **Page.\*** (Page): Any requested field from the page object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
fragment PageResourceFields on PageResource {
id
description
}
mutation PageUpdate($id: ID!, $input: PageUpdateInput!) {
pageUpdate(id: $id, input: $input) {
...PageResourceFields
}
}
`
const variables = {
id: "page-id",
input: {
description: "This is my updated page.",
}
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"pageUpdate": {
"id": "page-id",
"description": "This is my updated page.",
}
}
```
---
# pageUpdateBulk - Mutation
[Back to Content Management API](/content-management-api)
Allows you to bulk update pages.
### Arguments
* **ids** (\[ID!]): Array of page IDs that you want to update.
* **input** (\[PageUpdateInput!]!): Array of objects for each corresponding page ID to update.title: Stringhandle: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringseo: SEOInputtemplate: StringsectionIds: \[ID!]
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation PageUpdateBulk($ids: [ID!]!, $input: [PageUpdateInput!]!) {
pageUpdateBulk(ids: $ids, input: $input) {
id
}
}
`
const variables = {
ids: [
"page-id-1",
"page-id-2"
],
input: [
{
template: "Page"
},
{
template: "Page"
}
]
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageUpdateBulk": {
"id": "job-id"
}
}
}
```
---
# productPageAddSectionsBulk - Mutation
[Back to Content Management API](/content-management-api)
Assign a sections to multiple proudct pages.
### Arguments
* **ids** (\[ID!]): Array of page IDs that you want to add sections to.
* **input** (\[ProductPageAddSectionsInput!]!): An object that takes in an array of sectionIds and a srategy for how to handle the sections attached to the pages.sectionIds: array of section IDsstrategy: clone | link
### Returns
* **Job.\*** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPageAddSectionsBulk($ids: [ID!]!, $input: [ProductPageAddSectionsInput!]!) {
productPageAddSectionsBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
"ids": ["product-page-id-1", "product-page-id-2"],
"input": [
{
"sectionIds": ["section-id-1", "section-id-2"],
"strategy": "clone"
}
]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageAddSectionsBulk": {
"id": "1"
}
}
}
```
---
# productPagePublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes a product page.
### Arguments
* **id** (ID!): The ID of the page you want to publish.
* **publishComment** (String): An optional string to comment on the page publish.
### Returns
* **ProductPage.\*** (ProductPage): Any requested field from the Productpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPagePublish($id: ID!, $publishComment: String) {
productPagePublish(id: $id, publishComment: $publishComment) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-PAGE-ID',
publishComment: 'YOUR-PUBLISH-COMMENT'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPagePublish": {
"id": "YOUR-PAGE-ID",
"title": "YOUR-PAGE-TITLE",
"handle": "YOUR-PAGE-HANDLE",
"description": "YOUR-PAGE-DESCRIPTION"
}
}
}
```
---
# productPagePublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk publish product pages.
### Arguments
* **ids** (\[ID!]): Array of product page IDs that you want to publish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPagePublishBulk($ids: [ID!]!) {
productPagePublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['1', '2']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPagePublishBulk": {
"id": "1"
}
}
}
```
---
# productPageRestore - Mutation
[Back to Content Management API](/content-management-api)
Restores a product page to a specific revision.
### Arguments
* **id** (ID!): The ID of the product page you want to restore.
* **revisionId** (ID!): The ID of the revision you want to use to restore.
### Returns
* **ProductPage.\*** (ProductPage): Any requested field from the Productpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPageRestore($id: ID!, $revisionId: ID!) {
productPageRestore(id: $id, revisionId: $revisionId) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-PAGE-ID',
revisionId: 'YOUR-REVISION-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageRestore": {
"id": "YOUR-PAGE-ID",
"title": "YOUR-PAGE-TITLE",
"handle": "YOUR-PAGE-HANDLE",
"description": "YOUR-PAGE-DESCRIPTION"
}
}
}
```
---
# productPageUnpublish - Mutation
[Back to Content Management API](/content-management-api)
Unpublishes product page.
### Arguments
* **id** (ID!): The ID of the product page you want to unpublish.
### Returns
* **ProductPage.\*** (ProductPage): Any requested field from the Productpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPageUnpublish($id: ID!) {
productPageUnpublish(id: $id) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-PAGE-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageUnpublish": {
"id": "1",
"title": "Product Page Title",
"handle": "product-page-title",
"description": "Product Page Description"
}
}
}
```
---
# productPageUnpublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk unpublish product pages.
### Arguments
* **ids** (\[ID!]): Array of product page IDs that you want to unpublish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPageUnpublishBulk($ids: [ID!]!) {
productPageUnpublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['product-page-id-1', 'product-page-id-2']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageUnpublishBulk": {
"id": "1"
}
}
}
```
---
# productPageUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates a product page.
### Arguments
* **id** (ID!): The ID of the product page you want to update.
* **input** (ProductPageUpdateInput!): An object.title: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringtemplate: StringsectionIds: \[ID!]
### Returns
* **ProductPage.\*** (ProductPage): Any requested field from the Productpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPageUpdate($id: ID!, $input: ProductPageUpdateInput!) {
productPageUpdate(id: $id, input: $input) {
id
title
handle
description
}
}
`;
const variables = {
id: 'YOUR-PAGE-ID',
input: {
title: 'YOUR-TITLE',
firstPublishedAt: 'YOUR-FIRST-PUBLISHED-AT',
firstPublishedAtTimezone: 'YOUR-FIRST-PUBLISHED-AT-TIMEZONE',
description: 'YOUR-DESCRIPTION',
template: 'YOUR-TEMPLATE',
sectionIds: ['YOUR-SECTION-ID']
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageUpdate": {
"id": "YOUR-PAGE-ID",
"title": "YOUR-TITLE",
"handle": "YOUR-HANDLE",
"description": "YOUR-DESCRIPTION"
}
}
}
```
---
# productPageUpdateBulk - Mutation
[Back to Content Management API](/content-management-api)
Allows you to bulk update product pages.
### Arguments
* **ids** (\[ID!]): Array of product page IDs that you want to update.
* **input** (\[ProductPageUpdateInput!]!): Array of objects for each corresponding page ID to update.title: StringfirstPublishedAt: DateTimefirstPublishedAtTimezone: Stringdescription: Stringtemplate: StringsectionIds: \[ID!]
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ProductPageUpdateBulk($ids: [ID!]!, $input: [ProductPageUpdateInput!]!) {
productPageUpdateBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
"ids": ["product-page-id-1", "product-page-id-2"],
"input": [
{
"title": "New Title",
"firstPublishedAt": "2022-01-01T00:00:00Z",
"firstPublishedAtTimezone": "America/New_York",
"description": "New Description",
"template": "New Template",
"sectionIds": ["section-id-1", "section-id-2"]
},
{
"title": "New Title 2",
"firstPublishedAt": "2022-01-01T00:00:00Z",
"firstPublishedAtTimezone": "America/New_York",
"description": "New Description 2",
"template": "New Template 2",
"sectionIds": ["section-id-3", "section-id-4"]
}
]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageUpdateBulk": {
"id": "1"
}
}
}
```
---
# scheduleAddContent - Mutation
[Back to Content Management API](/content-management-api)
Adds content to a schedule by ID.
### Arguments
* **id** (ID!): The ID of the schedule.
* **input** (AddContentToScheduleInput!): An object.content: \[ContentToSchedule!]!The ContentToSchedule object format is:contentId: ID!contentType: ContentType!
### Returns
* **Schedule.\*** (Schedule): Any requested field from the Schedule object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation scheduleAddContent($id: ID!, $input: AddContentToScheduleInput!) {
scheduleAddContent(id: $id, input: $input) {
id
title
description
executeAt
timezone
content {
id
type
data
}
}
}
`;
const variables = {
id: 'YOUR-SCHEDULE-ID',
input: {
content: [
{
contentId: 'YOUR-CONTENT-ID',
contentType: 'YOUR-CONTENT0-TYPE'
}
]
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"scheduleAddContent": {
"id": "YOUR-SCHEDULE-ID",
"title": "Schedule Title",
"description": "Schedule Description",
"executeAt": "2022-01-01T00:00:00Z",
"timezone": "America/New_York",
"content": [
{
"id": "YOUR-CONTENT-ID",
"type": "YOUR-CONTENT-TYPE",
"data": {
"title": "Content Title",
"handle": "content-handle",
"description": "Content Description",
"seo": {
"title": "Content SEO Title",
"description": "Content SEO Description",
"keywords": ["content", "keywords"]
},
"sectionIds": ["YOUR-SECTION-ID"]
}
}
]
}
}
}
```
---
# scheduleCreate - Mutation
[Back to Content Management API](/content-management-api)
Creates a schedule.
### Arguments
* **input** (CreateScheduleInput!): An object.title: String!description: StringexecuteAt: DateTimetimezone: Stringcontent: \[ContentToSchedule!]
### Returns
* **Schedule.\*** (Schedule): Any requested field from the Schedule object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query =
`mutation scheduleCreate($input: CreateScheduleInput!) {
scheduleCreate(input: $input) {
id
title
description
executeAt
timezone
content {
id
type
data {
title
handle
description
seo {
title
description
keywords
}
sectionIds
}
}
}
}`;
const variables = {
input: {
title: 'Schedule Title',
description: 'Schedule Description',
executeAt: '2022-01-01T00:00:00Z',
timezone: 'America/New_York',
content: [
{
type: 'YOUR-CONTENT-TYPE',
data: {
title: 'Content Title',
handle: 'content-handle',
description: 'Content Description',
seo: {
title: 'Content SEO Title',
description: 'Content SEO Description',
keywords: ['content', 'keywords']
},
sectionIds: ['YOUR-SECTION-ID']
}
}
]
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"scheduleCreate": {
"id": "1",
"title": "Schedule Title",
"description": "Schedule Description",
"executeAt": "2022-01-01T00:00:00Z",
"timezone": "America/New_York",
"content": [
{
"id": "1",
"type": "YOUR-CONTENT-TYPE",
"data": {
"title": "Content Title",
"handle": "content-handle",
"description": "Content Description",
"seo": {
"title": "Content SEO Title",
"description": "Content SEO Description",
"keywords": ["content", "keywords"]
},
"sectionIds": ["YOUR-SECTION-ID"]
}
}
]
}
}
}
```
---
# scheduleDelete - Mutation
[Back to Content Management API](/content-management-api)
Deletes a schedule.
### Arguments
* **id** (ID!): The ID of the schedule you want to delete.
### Returns
* **DeletePayload.\*** (DeletePayload): Any requested field from the DeletePayload object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation scheduleDelete($id: ID!) {
scheduleDelete(id: $id) {
success
}
}
`;
const variables = {
id: 'YOUR-SCHEDULE-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"scheduleDelete": {
"success": true
}
}
}
```
---
# schedulePublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes a schedule.
### Arguments
* **id** (ID!): The ID of the schedule you want to publish.
### Returns
* **Schedule.\*** (Schedule): Any requested field from the Schedule object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation schedulePublish($id: ID!) {
schedulePublish(id: $id) {
id
name
status
}
}
`;
const variables = {
id: 'YOUR-SCHEDULE-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"schedulePublish": {
"id": "YOUR-SCHEDULE-ID",
"name": "Schedule Name",
"status": "PUBLISHED"
}
}
}
```
---
# scheduleUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates a schedule.
### Arguments
* **input** (CreateScheduleInput!): An object.title: Stringdescription: StringexecuteAt: DateTimetimezone: Stringcontent: \[ContentToSchedule!]
### Returns
* **Schedule.\*** (Schedule): Any requested field from the Schedule object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation ScheduleUpdate($input: UpdateScheduleInput!) {
scheduleUpdate(input: $input) {
id
title
description
executeAt
timezone
content {
id
type
data
}
}
}
`;
const variables = {
"input": {
"id": "YOUR-SCHEDULE-ID",
"title": "New Schedule Title",
"description": "New Schedule Description",
"executeAt": "2022-01-01T00:00:00Z",
"timezone": "America/New_York",
"content": [
{
"id": "YOUR-CONTENT-ID",
"type": "page",
"data": {
"title": "New Page Title",
"handle": "new-page",
"description": "New Page Description",
"seo": {
"title": "New Page SEO Title",
"description": "New Page SEO Description",
"keywords": ["new", "page", "keywords"]
},
"sectionIds": ["YOUR-SECTION-ID"]
}
}
]
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"scheduleUpdate": {
"id": "YOUR-SCHEDULE-ID",
"title": "New Schedule Title",
"description": "New Schedule Description",
"executeAt": "2022-01-01T00:00:00Z",
"timezone": "America/New_York",
"content": [
{
"id": "YOUR-CONTENT-ID",
"type": "page",
"data": {
"title": "New Page Title",
"handle": "new-page",
"description": "New Page Description",
"seo": {
"title": "New Page SEO Title",
"description": "New Page SEO Description",
"keywords": ["new", "page", "keywords"]
},
"sectionIds": ["YOUR-SECTION-ID"]
}
}
]
}
}
}
```
---
# sectionDelete - Mutation
[Back to Content Management API](/content-management-api)
Deletes a section.
### Arguments
* **id** (ID!): The ID of the section you want to delete.
### Returns
* **DeletePayload.\*** (DeletePayload): Any requested field from the DeletePayload object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation sectionDelete($id: ID!) {
sectionDelete(id: $id) {
id
}
}
`;
const variables = {
id: 'YOUR-SECTION-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionDelete": {
"id": "SECTION_ID"
}
}
}
```
---
# sectionDeleteBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk delete sections.
### Arguments
* **ids** (\[ID!]): Array of section IDs that you want to delete.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation sectionDeleteBulk($ids: [ID!]!) {
sectionDeleteBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['SECTION_ID']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionDeleteBulk": {
"id": "1"
}
}
}
```
---
# sectionPublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes a section.
### Arguments
* **id** (ID!): The ID of the section you want to publish.
* **publishComment** (String): An optional string to comment on the section publish.
### Returns
* **section.\*** (section): Any requested field from the section object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation sectionPublish($id: ID!) {
sectionPublish(id: $id) {
id
name
status
}
}
`;
const variables = {
id: 'YOUR-SECTION-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionPublish": {
"id": "section-1",
"name": "Section 1",
"status": "published"
}
}
}
```
---
# sectionPublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk publish sections.
### Arguments
* **ids** (\[ID!]): Array of section IDs that you want to publish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation sectionPublishBulk($ids: [ID!]!) {
sectionPublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['SECTION_ID']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionPublishBulk": {
"id": "1"
}
}
}
```
---
# sectionRestore - Mutation
[Back to Content Management API](/content-management-api)
Restores a section to a specific revision.
### Arguments
* **id** (ID!): The ID of the section you want to restore.
* **revisionId** (ID!): The ID of the revision you want to use to restore.
### Returns
* **section.\*** (section): Any requested field from the section object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation sectionRestore($id: ID!, $revisionId: ID!) {
sectionRestore(id: $id, revisionId: $revisionId) {
id
name
status
}
}
`;
const variables = {
id: 'YOUR-SECTION-ID',
revisionId: 'YOUR-REVISION-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionRestore": {
"id": "SECTION_ID",
"name": "SECTION_NAME",
"status": "SECTION_STATUS"
}
}
}
```
---
# sectionUnpublish - Mutation
[Back to Content Management API](/content-management-api)
Unpublishes section.
### Arguments
* **id** (ID!): The ID of the section you want to unpublish.
### Returns
* **section.\*** (section): Any requested field from the section object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation sectionUnpublish($id: ID!) {
sectionUnpublish(id: $id) {
id
name
status
}
}
`;
const variables = {
id: 'YOUR-SECTION-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionUnpublish": {
"id": "YOUR-SECTION-ID",
"name": "YOUR-SECTION-NAME",
"status": "UNPUBLISHED"
}
}
}
```
---
# sectionUnpublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk unpublish sections.
### Arguments
* **ids** (\[ID!]): Array of section IDs that you want to unpublish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation sectionUnpublishBulk($ids: [ID!]!) {
sectionUnpublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['SECTION_ID']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionUnpublishBulk": {
"id": "1"
}
}
}
```
---
# sectionUpsert - Mutation
[Back to Content Management API](/content-management-api)
Creates or modifies an existing section.
### Arguments
* **input** (SectionUpsertInput): An object....sectionfields: ...
### Returns
* **Section.\*** (Section): Any requested field from the Section object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const sectionUpsertQuery = `
mutation SectionUpsert($input: SectionUpsertInput!) {
sectionUpsert(input: $input) {
id
title
status
data
dataSource
hasReferences
publishedAt
createdAt
updatedAt
parentContentType
runningTests {
id
title
testVariants {
id
title
}
}
}
}
`;
const variables = {
"input": {
"title": "My Article Image",
"data": {
"sectionName": "My Article Image",
"sectionType": "local",
"sectionVisibility": "visible",
"resourceType": "article",
"_template": "image",
"image": {
"alt": "My Article Image",
"imageDesktop": {
"src": "https://example.com/image.jpg"
},
},
"section": {
"maxWidth": "max-w-[90rem]",
"enablePadding": true
}
}
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionUpsert": {
"id": "60f3b1b3b3b3b3b3b3b3b3b3",
"title": "My Article Image",
"status": "draft",
"data": {
"sectionName": "My Article Image",
"sectionType": "local",
"sectionVisibility": "visible",
"resourceType": "article",
"_template": "image",
"image": {
"alt": "My Article Image",
"imageDesktop": {
"src": "https://example.com/image.jpg"
}
},
"section": {
"maxWidth": "max-w-[90rem]",
"enablePadding": true
}
},
}
}
}
```
---
# siteSettingsPublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes the site settings.
### Arguments
* **id** (ID!): The ID of the site settings object you want to publish.
* **publishComment** (String): An optional string to comment on the site settings publish.
### Returns
* **SiteSettings.\*** (SiteSettings): Any requested field from the SiteSettings object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation siteSettingsPublish($id: ID!) {
siteSettingsPublish(id: $id) {
id
title
description
logo {
url
}
favicon {
url
}
theme {
primaryColor
secondaryColor
backgroundColor
textColor
}
}
}
`;
const variables = {
id: 'YOUR-SITESETTINGS-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"siteSettingsPublish": {
"id": "YOUR-SITESETTINGS-ID",
"title": "Site Title",
"description": "Site Description",
"logo": {
"url": "https://example.com/logo.png"
},
"favicon": {
"url": "https://example.com/favicon.png"
},
"theme": {
"primaryColor": "#000000",
"secondaryColor": "#FFFFFF",
"backgroundColor": "#FFFFFF",
"textColor": "#000000"
}
}
}
}
```
---
# siteSettingsRestore - Mutation
[Back to Content Management API](/content-management-api)
Restores the site settings to a specific revision.
### Arguments
* **revisionId** (ID!): The ID of the revision you want to use to restore.
### Returns
* **SiteSettings.\*** (SiteSettings): Any requested field from the SiteSettings object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation siteSettingsRestore($revisionId: ID!) {
siteSettingsRestore(revisionId: $revisionId) {
id
title
description
logo {
url
}
favicon {
url
}
theme {
primaryColor
secondaryColor
backgroundColor
textColor
}
socialLinks {
name
url
}
meta {
title
description
keywords
}
createdAt
updatedAt
}
}
`;
const variables = {
revisionId: 'REVISION_ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"siteSettingsRestore": {
"id": "SITE_SETTINGS_ID",
"title": "Site Title",
"description": "Site Description",
"logo": {
"url": "https://example.com/logo.png"
},
"favicon": {
"url": "https://example.com/favicon.png"
},
"theme": {
"primaryColor": "#000000",
"secondaryColor": "#000000",
"backgroundColor": "#000000",
"textColor": "#000000"
},
"socialLinks": [
{
"name": "Facebook",
"url": "https://facebook.com"
}
],
"meta": {
"title": "Meta Title",
"description": "Meta Description",
"keywords": "Meta Keywords"
},
"createdAt": "2022-01-01T00:00:00.000Z",
"updatedAt": "2022-01-01T00:00:00.000Z"
}
}
}
```
---
# siteSettingsUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates the site settings.
### Arguments
* **input** (SiteSettingsUpdateInput!): An object.settings: JSON!seo: JSONfavicon: String
### Returns
* **SiteSettings.\*** (SiteSettings): Any requested field from the SiteSettings object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation SiteSettingsUpdate($input: SiteSettingsUpdateInput!) {
siteSettingsUpdate(input: $input) {
settings
seo
favicon
}
}
`;
const variables = {
input: {
settings: {
title: 'My Site',
description: 'My site description',
logo: 'https://example.com/logo.png',
primaryColor: '#000000',
secondaryColor: '#FFFFFF',
accentColor: '#FF0000',
footer: 'My site footer',
social: {
facebook: 'https://facebook.com',
twitter: 'https://twitter.com',
linkedin: 'https://linkedin.com',
instagram: 'https://instagram.com',
youtube: 'https://youtube.com',
},
},
seo: {
title: 'My Site',
description: 'My site description',
keywords: 'site, description',
image: 'https://example.com/image.png',
},
favicon: 'https://example.com/favicon.ico',
},
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"siteSettingsUpdate": {
"settings": {
"title": "My Site",
"description": "My site description",
"logo": "https://example.com/logo.png",
"primaryColor": "#000000",
"secondaryColor": "#FFFFFF",
"accentColor": "#FF0000",
"footer": "My site footer",
"social": {
"facebook": "https://facebook.com",
"twitter": "https://twitter.com",
"linkedin": "https://linkedin.com",
"instagram": "https://instagram.com",
"youtube": "https://youtube.com"
}
},
"seo": {
"title": "My Site",
"description": "My site description",
"keywords": "site, description",
"image": "https://example.com/image.png"
},
"favicon": "https://example.com/favicon.ico"
}
}
}
```
---
# templateAddSectionsBulk - Mutation
[Back to Content Management API](/content-management-api)
Assigns a sections to multiple templates.
### Arguments
* **ids** (\[ID!]): Array of template IDs that you want to add sections to.
* **input** (\[TemplateAddSectionsInput!]!): An object that takes in an array of sectionIds and a srategy for how to handle the sections attached to the templates.sectionIds: array of section IDsstrategy: clone | link
### Returns
* **Job.\*** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation TemplateAddSectionsBulk($ids: [ID!]!, $input: [TemplateAddSectionsInput!]!) {
templateAddSectionsBulk(ids: $ids, input: $input) {
id
}
}
`;
const variables = {
ids: ['TEMPLATE_ID'],
input: [
{
sectionIds: ['SECTION_ID'],
strategy: 'clone'
}
]
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateAddSectionsBulk": {
"id": "JOB_ID"
}
}
}
```
---
# templateCreate - Mutation
[Back to Content Management API](/content-management-api)
Creates a template.
### Arguments
* **input** (TemplateCreateInput!): An object.title: String!type: String!sectionIds: \[ID!]
### Returns
* **Template.\*** (Template): Any requested field from the Template object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation TemplateCreate($input: TemplateCreateInput!) {
templateCreate(input: $input) {
id
title
type
sectionIds
}
}
`;
const variables = {
input: {
title: 'YOUR-TEMPLATE-TITLE',
type: 'YOUR-TEMPLATE-TYPE',
sectionIds: ['YOUR-SECTION-ID']
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateCreate": {
"id": "TEMPLATE_ID",
"title": "TEMPLATE_TITLE",
"type": "TEMPLATE_TYPE",
"sectionIds": ["SECTION_ID"]
}
}
}
```
---
# templateDelete - Mutation
[Back to Content Management API](/content-management-api)
Deletes a template.
### Arguments
* **id** (ID!): The ID of the template you want to delete.
### Returns
* **DeletePayload.\*** (DeletePayload): Any requested field from the DeletePayload object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation templateDelete($id: ID!) {
templateDelete(id: $id) {
id
}
}
`;
const variables = {
id: 'YOUR-TEMPLATE-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateDelete": {
id: "YOUR-TEMPLATE-ID"
}
}
}
```
---
# templatePublish - Mutation
[Back to Content Management API](/content-management-api)
Publishes a template.
### Arguments
* **id** (ID!): The ID of the template you want to publish.
### Returns
* **Template.\*** (Template): Any requested field from the Template object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation templatePublish($id: ID!) {
templatePublish(id: $id) {
id
title
type
isDefault
status
publishedAt
createdAt
}
}
`;
const variables = {
id: 'YOUR-TEMPLATE-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templatePublish": {
"id": "YOUR-TEMPLATE-ID",
"title": "YOUR-TEMPLATE-TITLE",
"type": "YOUR-TEMPLATE-TYPE",
"isDefault": false,
"status": "published",
"publishedAt": "2022-01-01T00:00:00Z",
"createdAt": "2022-01-01T00:00:00Z"
}
}
}
```
---
# templatePublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk-publishes templates.
### Arguments
* **ids** (\[ID!]): Array of template IDs that you want to publish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation templatePublishBulk($ids: [ID!]!) {
templatePublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['TEMPLATE_ID']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templatePublishBulk": {
"id": "1"
}
}
}
```
---
# templateUnpublish - Mutation
[Back to Content Management API](/content-management-api)
Unpublishes a template.
### Arguments
* **id** (ID!): The ID of the template you want to unpublish.
### Returns
* **Template.\*** (Template): Any requested field from the Template object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation templateUnpublish($id: ID!) {
templateUnpublish(id: $id) {
id
name
status
}
}
`;
const variables = {
id: 'YOUR-TEMPLATE-ID'
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateUnpublish": {
"id": "TEMPLATE_ID",
"name": "TEMPLATE_NAME",
"status": "UNPUBLISHED"
}
}
}
```
---
# templateUnpublishBulk - Mutation
[Back to Content Management API](/content-management-api)
Bulk-unpublishes templates.
### Arguments
* **ids** (\[ID!]): Array of template IDs that you want to unpublish.
### Returns
* **Job** (Job): Any requested field from the Job object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
mutation templateUnpublishBulk($ids: [ID!]!) {
templateUnpublishBulk(ids: $ids) {
id
}
}
`;
const variables = {
ids: ['TEMPLATE_ID']
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateUnpublishBulk": {
"id": "1"
}
}
}
```
---
# templateUpdate - Mutation
[Back to Content Management API](/content-management-api)
Updates a template.
### Arguments
* **input** (TemplateCreateInput!): An object.title: Stringdisjunctive: Booleanrules: \[TemplateRuleInput]sectionIds: \[ID!]
### Returns
* **Template.\*** (Template): Any requested field from the Template object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
fragment TemplateResourceFields on TemplateResource {
title
disjunctive
rules {
field
operator
value
}
}
mutation TemplateUpdate($input: TemplateUpdateInput!) {
templateUpdate(input: $input) {
...TemplateResourceFields
}
}
`;
const variables = {
"input": {
"id": "YOUR-TEMPLATE-ID",
"title": "YOUR-TEMPLATE-TITLE",
"disjunctive": false,
"rules": [
{
"field": "YOUR-FIELD",
"operator": "YOUR-OPERATOR",
"value": "YOUR-VALUE"
}
],
"sectionIds": ["YOUR-SECTION-ID"]
}
};
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateUpdate": {
"title": "YOUR-TEMPLATE-TITLE",
"disjunctive": false,
"rules": [
{
"field": "YOUR-FIELD",
"operator": "YOUR-OPERATOR",
"value": "YOUR-VALUE"
}
]
}
}
}
```
---
# article - Query
[Back to Content Management API](/content-management-api)
Returns an article by ID in a draft or published state.
### Arguments
* **id** (ID!): The ID of the article.
* **version** (Version): The state of the article you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **Article.\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetArticle($id: ID!, $version: Version) {
article(id: $id, version: $version) {
title
handle
description
}
}
`
const variables = {
id: "article-id",
version: "CURRENT"
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"article": {
"title": "Article Title",
"handle": "article-handle",
"description": "Article description."
}
}
}
```
---
# articleByHandle - Query
[Back to Content Management API](/content-management-api)
Returns an article by hande in a draft or published state.
### Arguments
* **handle** (String!): The handle of the article.
* **version** (Version): The state of the article you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **Article\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ArticleByHandle($handle: String!, $version: Version) {
articleByHandle(handle: $handle, version: $version) {
title
handle
description
}
}
`
const variables = {
handle: "article-handle",
version: "CURRENT"
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleByHandle": {
"title": "Article Title",
"handle": "article-handle",
"description": "Article description."
}
}
}
```
---
# articleHistory - Query
[Back to Content Management API](/content-management-api)
Returns array of all revisions for the article by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the article.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **ArticleRevisionConnection.edges** (\[ArticleRevisionEdge!]!): A list of edges.
* **ArticleRevisionConnection.nodes** (\[ArticleRevision!]!): A list of the nodes contained in ArticleRevisionEdge.
* **ArticleRevisionConnection.articleInfo** (ArticleInfo!): Information to aid in pagination.
* **ArticleRevisionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ArticleHistory($id: ID!) {
articleHistory(id: $id) {
nodes {
title
handle
description
}
}
}
`
const variables = {
id: "0190cc74-7ecf-7e07-a9be-faa85c92f7c4",
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleHistory": {
"nodes": [
{
"title": "Article Title",
"handle": "article-handle",
"description": "Article description."
}
]
}
}
}
```
---
# articleRevision - Query
[Back to Content Management API](/content-management-api)
Returns an article's revision.
### Arguments
* **id** (ID!): The ID of the article.
* **revisionId** (ID!): The ID of the revision.
### Returns
* **Article.\*** (Article): Any requested field from the Article object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ArticleRevision($id: ID!, $revisionId: ID!) {
articleRevision(id: $id, revisionId: $revisionId) {
title
handle
description
}
}
`
const variables = {
id: 'article-id',
revisionId: 'revision-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articleRevision": {
"title": "Article Title",
"handle": "article-handle",
"description": "Article description."
}
}
}
```
---
# articles - Query
[Back to Content Management API](/content-management-api)
Returns array of all your articles in a draft or published state paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
* **version** (Version): The state of the article you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
### Returns
* **ArticleConnection.edges** (\[ArticleEdge!]!): A list of edges.
* **ArticleConnection.nodes** (\[Article!]!): A list of the nodes contained in ArticleEdge.
* **ArticleConnection.articleInfo** (ArticleInfo!): Information to aid in pagination.
* **ArticleConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query Articles($version: Version) {
articles (version: $version) {
totalCount
edges {
node {
... on PageResource {
title
handle
description
}
}
}
}
}
`
const response = await packClient.fetch(query);
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"articles": {
"totalCount": 3,
"edges": [
{
"node": {
"title": "Article Title",
"handle": "article-handle",
"description": "Article description."
}
},
{
"node": {
"title": "Article Title",
"handle": "article-handle",
"description": "Article description."
}
},
{
"node": {
"title": "Article Title",
"handle": "article-handle",
"description": "Article description."
}
}
]
}
}
}
```
---
# blog - Query
[Back to Content Management API](/content-management-api)
Returns a blog by ID in a draft or published state.
### Arguments
* **id** (ID!): The ID of the blog.
* **version** (Version): The state of the blog you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **Blog.\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query Blog($id: ID!, $version: Version) {
blog(id: $id, version: $version) {
title
handle
description
}
}
`
const variables = {
id: 'blog-id',
version: "CURRENT"
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blog": {
"title": "Blog Title",
"handle": "blog-handle",
"description": "Blog Description"
}
}
}
```
---
# blogByHandle - Query
[Back to Content Management API](/content-management-api)
Returns a blog by hande in a draft or published state.
### Arguments
* **handle** (String!): The handle of the blog.
* **version** (Version): The state of the blog you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **Blog\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query BlogByHandle($handle: String!, $version: Version) {
blogByHandle(handle: $handle, version: $version) {
title
handle
description
}
}
`
const variables = {
handle: "blog-handle",
version: "CURRENT"
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogByHandle": {
"title": "Blog Title",
"handle": "blog-handle",
"description": "Blog Description"
}
}
}
```
---
# blogHistory - Query
[Back to Content Management API](/content-management-api)
Returns array of all revisions for the blog by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the blog.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **BlogRevisionConnection.edges** (\[BlogRevisionEdge!]!): A list of edges.
* **BlogRevisionConnection.nodes** (\[BlogRevision!]!): A list of the nodes contained in BlogRevisionEdge.
* **BlogRevisionConnection.blogInfo** (BlogInfo!): Information to aid in pagination.
* **BlogRevisionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query BlogHistory($id: ID!) {
blogHistory(id: $id) {
nodes {
title
handle
description
}
}
}
`
const variables = {
id: 'blog-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogHistory": {
"nodes": [
{
"title": "Blog Title",
"handle": "blog-handle",
"description": "Blog Description"
}
]
}
}
}
```
---
# blogRevision - Query
[Back to Content Management API](/content-management-api)
Returns a blog's revision.
### Arguments
* **id** (ID!): The ID of the blog.
* **revisionId** (ID!): The ID of the revision.
### Returns
* **Blog.\*** (Blog): Any requested field from the Blog object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query BlogRevision($id: ID!, $revisionId: ID!) {
blogRevision(id: $id, revisionId: $revisionId) {
title
handle
description
}
}
`
const variables = {
id: 'blog-id',
revisionId: 'revision-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogRevision": {
"title": "Blog Title",
"handle": "blog-handle",
"description": "Blog Description"
}
}
}
```
---
# blogs - Query
[Back to Content Management API](/content-management-api)
Returns array of all your blogs in a draft or published state paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
* **version** (Version): The state of the blog you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
### Returns
* **BlogConnection.edges** (\[BlogEdge!]!): A list of edges.
* **BlogConnection.nodes** (\[Blog!]!): A list of the nodes contained in BlogEdge.
* **BlogConnection.blogInfo** (BlogInfo!): Information to aid in pagination.
* **BlogConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query Blogs($version: Version) {
blogs (version: $version) {
totalCount
edges {
node {
... on PageResource {
title
handle
description
}
}
}
}
}
`
const variables = {
version: 'PUBLISHED',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"blogs": {
"totalCount": 2,
"edges": [
{
"node": {
"title": "Blog Title",
"handle": "blog-handle",
"description": "Blog description."
}
},
{
"node": {
"title": "Blog Title",
"handle": "blog-handle",
"description": "Blog description."
}
}
]
}
}
}
```
---
# collectionPage - Query
[Back to Content Management API](/content-management-api)
Returns a collection page by ID in a draft or published state.
### Arguments
* **id** (ID!): The ID of the collection page.
* **version** (Version): The state of the collection page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **CollectionPage.\*** (CollectionPage): Any requested field from the Collectionpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetCollectionPage($id: ID!, $version: Version) {
collectionPage(id: $id, version: $version) {
title
handle
description
}
}
`
const variables = {
id: 'collection-page-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPage": {
"title": "Collection Page Title",
"handle": "collection-page-title",
"description": "Collection Page Description"
}
}
}
```
---
# collectionPageByHandle - Query
[Back to Content Management API](/content-management-api)
Returns a collection page by handle in a draft or published state.
### Arguments
* **handle** (String!): The handle of the collection page.
* **version** (Version): The state of the collection page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **CollectionPage.\*** (CollectionPage): Any requested field from the Collectionpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetCollectionPageByHandle($handle: String!, $version: Version) {
collectionPageByHandle(handle: $handle, version: $version) {
title
handle
description
}
}
`
const variables = {
id: 'collection-page-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageByHandle": {
"title": "Collection Page Title",
"handle": "collection-page-title",
"description": "Collection Page Description"
}
}
}
```
---
# collectionPageHistory - Query
[Back to Content Management API](/content-management-api)
Returns array of all revisions for the collection page by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the collection page.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **CollectionPageRevisionConnection.edges** (\[CollectionPageEdge!]!): A list of edges.
* **CollectionPageRevisionConnection.nodes** (\[CollectionPageRevision!]!): A list of the nodes contained in CollectionPageRevisionEdge.
* **CollectionPageRevisionConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **CollectionPageRevisionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query CollectionPageHistory($id: ID!, $cursor: String) {
collectionPageHistory(id: $id, first: 10, after: $cursor) {
nodes {
title
handle
description
}
}
}
`
const variables = {
id: 'product-page-id',
revisionId: 'revision-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageHistory": {
"nodes": [
{
"title": "Collection Title",
"handle": "collection-handle",
"description": "Collection Description"
},
{
"title": "Collection Title",
"handle": "collection-handle",
"description": "Collection Description"
}
]
}
}
}
```
---
# collectionPageRevision - Query
[Back to Content Management API](/content-management-api)
Returns a collection page's revision.
### Arguments
* **id** (ID!): The ID of the collection page.
* **revisionId** (ID!): The ID of the revision.
### Returns
* **CollectionPage.\*** (CollectionPage): Any requested field from the CollectionPage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query CollectionPageRevision($id: ID!, $revisionId: ID!) {
collectionPageRevision(id: $id, revisionId: $revisionId) {
title
handle
description
}
}
`
const variables = {
id: 'collection-page-id',
revisionId: 'revision-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPageRevision": {
"title": "Collection Page Title",
"handle": "collection-page-title",
"description": "Collection Page Description"
}
}
}
```
---
# collectionPages - Query
[Back to Content Management API](/content-management-api)
Returns array of all your collection pages in a draft or published state paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
* **version** (Version): The state of the page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
### Returns
* **CollectionPageConnection.edges** (\[CollectionPageEdge!]!): A list of edges.
* **CollectionPageConnection.nodes** (\[CollectionPage!]!): A list of the nodes contained in collectionPageEdge.
* **CollectionPageConnection.pageInfo** (CollectionPageInfo!): Information to aid in pagination.
* **collectionPageConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetCollections($version: Version) {
collectionPages(version: $version) {
totalCount
edges {
node {
... on PageResource {
title
handle
description
}
}
}
}
}
`
const response = await packClient.fetch(query);
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"collectionPages": {
"totalCount": 2,
"edges": [
{
"node": {
"title": "Collection Title",
"handle": "collection-handle",
"description": "Collection Description"
}
},
{
"node": {
"title": "Collection Title",
"handle": "collection-handle",
"description": "Collection Description"
}
}
]
}
}
}
```
---
# faviconUploadUrl - Query
[Back to Content Management API](/content-management-api)
Returns the favicon upload URL.
### Arguments
* **id** (String!): The ID of the site settings object.
### Returns
* **String** (String!): The favicon upload URL.
```js {{ title: 'GraphQL'}}
import ApiClient from '@example/protocol-api'
const client = new ApiClient(token)
await client.contacts.list()
```
```js {{ title: '@pack/client'}}
import ApiClient from '@example/protocol-api'
const client = new ApiClient(token)
await client.contacts.list()
```
```json {{ title: 'Response' }}
{
"has_more": false,
"data": [
{
"id": "WAz8eIbvDR60rouK",
"username": "FrankMcCallister",
"phone_number": "1-800-759-3000",
"avatar_url": "https://assets.protocol.chat/avatars/frank.jpg",
"display_name": null,
"conversation_id": "xgQQXg3hrtjh7AvZ",
"last_active_at": 705103200,
"created_at": 692233200
},
{
"id": "hSIhXBhNe8X1d8Et"
// ...
}
]
}
```
---
# page - Query
[Back to Content Management API](/content-management-api)
Returns a page by ID in a draft or published state.
### Arguments
* **id** (ID!): The ID of the page.
* **version** (Version): The state of the page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **Page.\*** (Page): Any requested field from the page object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query Page($id: ID!) {
page(id: $id) {
id
title
}
}
`;
const response = await packClient.fetch(query, { variables: { id: 'YOUR-PAGE-ID' } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"page": {
"id": "0190e0f1-56ce-7057-b325-31ab648e58cb",
"title": "Testing Page"
}
}
}
```
---
# pageByHandle - Query
[Back to Content Management API](/content-management-api)
Returns a page by hande in a draft or published state.
### Arguments
* **handle** (String!): The handle of the page.
* **version** (Version): The state of the page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **Page\*** (Page): Any requested field from the page object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query pageByHandle($handle: String!) {
pageByHandle(handle: $handle) {
id
title
}
}
`;
const response = await packClient.fetch(query, { variables: { handle: 'your-page-handle' } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageByHandle": {
"id": "your-page-id",
"title": "your-page-title"
}
}
}
```
---
# pageHistory - Query
[Back to Content Management API](/content-management-api)
Returns array of all revisions for the page by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the page.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **PageHistory.\*** (PageHistory): Any requested field from the PageHistory object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query PageHistory($id: ID!) {
pageHistory(id: $id) {
nodes {
title
handle
}
}
}
`;
const response = await packClient.fetch(query, { variables: { id: '0190e0f1-56ce-7057-b325-31ab648e58cb' } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageHistory": {
"nodes": [
{
"title": "Testing Page",
"handle": "testing-page"
}
]
}
}
}
```
---
# pageRevision - Query
[Back to Content Management API](/content-management-api)
Returns a page's revision.
### Arguments
* **id** (ID!): The ID of the page.
* **revisionId** (ID!): The ID of the revision.
### Returns
* **PageRevision.\*** (PageRevision): Any requested field from the PageRevision object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query PageRevision($id: ID!, $revisionId: ID!) {
pageRevision(id: $id, revisionId: $revisionId) {
title
handle
description
}
}
`;
const response = await packClient.fetch(query, { variables: { id: '0190e0f1-56ce-7057-b325-31ab648e58cb', revisionId: '0190e0f1-56d3-70d2-979c-a03de3334005' } });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pageRevision": {
"title": "Testing Page",
"handle": "testing-page",
"description": "Testing."
}
}
}
```
---
# pages - Query
[Back to Content Management API](/content-management-api)
Returns array of all your pages in a draft or published state paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list. Maximum value is 25.
* **last** (Int): Returns up to the last n elements from the list.
* **version** (Version): The state of the page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
### Returns
* **PageConnection.edges** (\[PageEdge!]!): A list of edges.
* **PageConnection.nodes** (\[Page!]!): A list of the nodes contained in PageEdge.
* **PageConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **PageConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query pages {
pages {
edges {
cursor
node {
id
title
handle
}
}
totalCount
}
}
`;
const response = await packClient.fetch(query);
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"pages": {
"edges": [
{
"cursor": "cursor-id",
"node": {
"id": "page-id",
"title": "page-title",
"handle": "page-handle"
}
},
{
"cursor": "cursor-id",
"node": {
"id": "page-id",
"title": "page-title",
"handle": "page-handle"
}
},
],
"totalCount": 2
}
}
}
```
---
# productPage - Query
[Back to Content Management API](/content-management-api)
Returns a product page by ID in a draft or published state.
### Arguments
* **id** (ID!): The ID of the product page.
* **version** (Version): The state of the product page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **ProductPage.\*** (ProductPage): Any requested field from the Productpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ProductPage($id: ID!, $country: CountryCode, $language: LanguageCode) @inContext(country: $country, language: $language) {
productPage(id: $id) {
title
handle
seo {
title
description
}
}
}
`;
const variables = {
id: 'product-page-id',
country: 'US',
language: 'EN'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPage": {
"title": "Product Title",
"handle": "product-handle",
"seo": {
"title": "Product Title",
"description": "Product Description",
}
}
}
}
```
---
# productPageByHandle - Query
[Back to Content Management API](/content-management-api)
Returns a product page by hande in a draft or published state.
### Arguments
* **handle** (String!): The handle of the product page.
* **version** (Version): The state of the product page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **ProductPage.\*** (ProductPage): Any requested field from the Productpage object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ProductPageByHandle($handle: String!) {
productPageByHandle(handle: $handle) {
title
handle
seo {
title
description
image
}
}
}
`;
const variables = {
handle: 'new-product',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageByHandle": {
"title": "Product Title",
"handle": "product-handle",
"seo": {
"title": "Product Title",
"description": "Product Description",
}
}
}
}
```
---
# productPageHistory - Query
[Back to Content Management API](/content-management-api)
Returns array of all revisions for the product page by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the product page.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **ProductPageRevisionConnection.edges** (\[ProductPageEdge!]!): A list of edges.
* **ProductPageRevisionConnection.nodes** (\[ProductPageRevision!]!): A list of the nodes contained in ProductPageRevisionEdge.
* **ProductPageRevisionConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **ProductPageRevisionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ProductPageHistory($id: ID!) {
productPageHistory(id: $id) {
nodes {
title
handle
description
}
}
}
`;
const variables = {
id: 'product-page-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageHistory": {
"nodes": [
{
"title": "Product Title",
"handle": "product-handle",
"description": "Product Description"
}
]
}
}
}
```
---
# productPageRevision - Query
[Back to Content Management API](/content-management-api)
Returns a product page's revision.
### Arguments
* **id** (ID!): The ID of the product page.
* **revisionId** (ID!): The ID of the revision.
### Returns
* **ProductPageRevision.\*** (ProductPageRevision): Any requested field from the ProductPageRevision object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ProductPageRevision($id: ID!, $revisionId: ID!) {
productPageRevision(id: $id, revisionId: $revisionId) {
title
handle
description
}
}
`
const variables = {
id: 'product-page-id',
revisionId: 'revision-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPageRevision": {
"title": "Product Title",
"handle": "product-handle",
"description": "Product Description"
}
}
}
```
---
# productPages - Query
[Back to Content Management API](/content-management-api)
Returns array of all your product pages in a draft or published state paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
* **version** (Version): The state of the page you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
### Returns
* **ProductPageConnection.edges** (\[ProductPageEdge!]!): A list of edges.
* **ProductPageConnection.nodes** (\[ProductPage!]!): A list of the nodes contained in ProductPageEdge.
* **ProductPageConnection.pageInfo** (ProductPageInfo!): Information to aid in pagination.
* **ProductPageConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query ProductPages($first: Int) {
productPages(
first: $first
) {
totalCount
edges {
node {
... on PageResource {
title
handle
description
}
}
}
}
}
`;
const variables = {
first: 3,
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"productPages": {
"totalCount": 26,
"edges": [
{
"node": {
"title": "product-title",
"handle": "product-handle",
"description": "product-description"
}
},
{
"node": {
"title": "product-title",
"handle": "product-handle",
"description": "product-description"
}
},
{
"node": {
"title": "product-title",
"handle": "product-handle",
"description": "product-description"
}
}
]
}
}
}
```
---
# schedule - Query
[Back to Content Management API](/content-management-api)
Returns a schedule by ID.
### Arguments
* **id** (ID!): The ID of the schedule.
### Returns
* **Schedule.\*** (Schedule): Any requested field from the Schedule object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query Schedule($id: ID!) {
schedule(id: $id) {
title
description
}
}
`
const variables = {
id: 'schedule-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"schedule": {
"title": "Schedule Title",
"description": "Schedule Description"
}
}
}
```
---
# schedules - Query
[Back to Content Management API](/content-management-api)
Returns a list of schedules paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **SectionConnection.edges** (\[ScheduleEdge!]!): A list of edges.
* **SectionConnection.nodes** (\[Schedule!]!): A list of the nodes contained in ScheduleEdge.
* **SectionConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **SectionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query {
schedules(first: 5) {
edges {
cursor
node {
id
title
description
}
}
}
}
`
const variables = {
first: 5
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"schedules": {
"edges": [
{
"cursor": "cursor",
"node": {
"id": "schedule-id",
"title": "Schedule Title",
"description": "Schedule Description"
}
}
]
}
}
}
```
---
# schedulesByContentId - Query
[Back to Content Management API](/content-management-api)
Returns a list of schedules that contain a content ID paginated by a cursor.
### Arguments
* **contentId** (ID!): Content ID to look for in schedules.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **SectionConnection.edges** (\[ScheduleEdge!]!): A list of edges.
* **SectionConnection.nodes** (\[Schedule!]!): A list of the nodes contained in ScheduleEdge.
* **SectionConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **SectionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetSchedulesByContentId($contentId: ID!, $after: String, $before: String, $first: Int, $last: Int) {
schedulesByContentId(contentId: $contentId, after: $after, before: $before, first: $first, last: $last) {
edges {
cursor
node {
title
description
}
}
nodes {
title
description
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
totalCount
}
}
`
const variables = {
contentId: 'content-id',
after: 'cursor',
first: 10
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"schedulesByContentId": {
"edges": [
{
"cursor": "cursor",
"node": {
"title": "Schedule Title",
"description": "Schedule Description"
}
}
],
"nodes": [
{
"title": "Schedule Title",
"description": "Schedule Description"
}
],
"pageInfo": {
"hasNextPage": true,
"hasPreviousPage": false,
"startCursor": "cursor",
"endCursor": "cursor"
},
"totalCount": 1
}
}
}
```
---
# section - Query
[Back to Content Management API](/content-management-api)
Returns a section by ID in a draft or published state.
### Arguments
* **id** (ID!): The ID of the section.
* **version** (Version): The state of the section you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **Section.\*** (Section): Any requested field from the Section object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetSection($id: ID!, $version: Version) {
section(id: $id, version: $version) {
title
handle
description
}
}
`
const variables = {
id: 'section-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"section": {
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
}
}
}
```
---
# sectionHistory - Query
[Back to Content Management API](/content-management-api)
Returns array of all revisions for the section by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the section.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **SectionRevisionConnection.edges** (\[SectionRevisionEdge!]!): A list of edges.
* **SectionRevisionConnection.nodes** (\[SectionRevision!]!): A list of the nodes contained in SectionRevisionEdge.
* **SectionRevisionConnection.sectionInfo** (SectionInfo!): Information to aid in pagination.
* **SectionRevisionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query SectionHistory($id: ID!, $after: String, $before: String, $first: Int, $last: Int) {
sectionHistory(id: $id, after: $after, before: $before, first: $first, last: $last) {
edges {
cursor
node {
title
handle
description
}
}
totalCount
}
}
`
const variables = {
id: 'section-id',
after: 'cursor',
before: 'cursor',
first: 10,
last: 10
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionHistory": {
"edges": [
{
"cursor": "cursor",
"node": {
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
}
}
],
"totalCount": 1
}
}
}
```
---
# sectionHistory - Query
[Back to Content Management API](/content-management-api)
Returns array of all revisions for the section by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the section of which you want to find its references.
### Returns
* **Reference.\*** (\[Reference!]!): A list of references that this section is used in.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query SectionReferences($id: ID!) {
sectionReferences(id: $id) {
id
title
handle
description
}
}
`
const variables = {
id: 'section-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionReferences": {
"id": "section-id",
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
}
}
}
```
---
# sectionRevision - Query
[Back to Content Management API](/content-management-api)
Returns a section's revision.
### Arguments
* **id** (ID!): The ID of the section.
* **revisionId** (ID!): The ID of the revision.
### Returns
* **Section.\*** (Section): Any requested field from the Section object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query SectionRevision($id: ID!, $revisionId: ID!) {
sectionRevision(id: $id, revisionId: $revisionId) {
title
handle
description
}
}
`
const variables = {
id: 'product-page-id',
revisionId: 'revision-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sectionRevision": {
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
}
}
}
```
---
# sections - Query
[Back to Content Management API](/content-management-api)
Returns array of all your sections in a draft or published state paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
* **version** (Version): The state of the section you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
### Returns
* **SectionConnection.edges** (\[SectionEdge!]!): A list of edges.
* **SectionConnection.nodes** (\[Section!]!): A list of the nodes contained in SectionEdge.
* **SectionConnection.sectionInfo** (SectionInfo!): Information to aid in pagination.
* **SectionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query {
sections {
edges {
cursor
node {
title
handle
description
}
}
totalCount
}
}
`
const response = await packClient.fetch(query);
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"sections": {
"edges": [
{
"cursor": "cursor",
"node": {
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
}
},
{
"cursor": "cursor",
"node": {
"title": "Section Title",
"handle": "section-title",
"description": "Section Description"
}
}
],
"totalCount": 2
}
}
}
```
---
# siteSettings - Query
[Back to Content Management API](/content-management-api)
Returns the site settings in a draft or published state.
### Arguments
* **version** (Version): The state of the section you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **SiteSettings.\*** (SiteSettings): Any requested field from the SiteSettings object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetSiteSettings($version: Version) {
siteSettings(version: $version) {
id
seo
}
}
`
const response = await packClient.fetch(query);
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"siteSettings": {
"id": "site-id",
"seo": {
"title": "SEO Title",
"description": "SEO Description",
"keywords": "SEO Keywords"
}
}
}
}
```
---
# siteSettingsHistory - Query
[Back to Content Management API](/content-management-api)
Returns an array of all revisions for the site settings by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the site settings object.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **SiteSettingsRevisionConnection.edges** (\[SiteSettingsRevisionEdge!]!): A list of edges.
* **SiteSettingsRevisionConnection.nodes** (\[SiteSettingsRevision!]!): A list of the nodes contained in SiteSettingsRevisionEdge.
* **SiteSettingsRevisionConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **SiteSettingsRevisionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query siteSettingsHistory($id: ID!, $after: String, $before: String, $first: Int, $last: Int) {
siteSettingsHistory(id: $id, after: $after, before: $before, first: $first, last: $last) {
edges {
cursor
node {
id
seo
}
}
totalCount
}
}
`
const variables = {
id: 'site-settings-id',
first: 10
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"siteSettingsHistory": {
"edges": [
{
"cursor": "cursor",
"node": {
"id": "site-settings-id",
"seo": {
"title": "SEO Title",
"description": "SEO Description",
"keywords": "SEO Keywords"
}
}
}
],
"totalCount": 1
}
}
}
```
---
# siteSettingsRevision - Query
[Back to Content Management API](/content-management-api)
Returns a site settings' revision.
### Arguments
* **id** (ID!): The ID of the section.
* **revisionId** (ID!): The ID of the revision.
### Returns
* **SiteSettings.\*** (SiteSettings): Any requested field from the SiteSettingsRevision object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query SiteSettingsRevision($id: ID!, $revisionId: ID!) {
siteSettingsRevision(id: $id, revisionId: $revisionId) {
id
seo
}
}
`
const variables = {
id: 'site-settings-id',
revisionId: 'revision-id'
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"siteSettingsRevision": {
"id": "site-settings-id",
"seo": {
"title": "SEO Title",
"description": "SEO Description",
"keywords": "SEO Keywords",
"image": {
"url": "https://example.com/image.jpg",
"alt": "Image Alt Text"
}
}
}
}
}
```
---
# template - Query
[Back to Content Management API](/content-management-api)
Returns a template by ID in a draft or published state.
### Arguments
* **id** (ID!): The ID of the template.
* **version** (Version): The state of the template you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
### Returns
* **Template.\*** (Template): Any requested field from the Template object.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query GetTemplate($id: ID!, $version: Version) {
template(id: $id, version: $version) {
id
title
status
}
}
`
const variables = {
id: 'template-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"template": {
"title": "Template Title",
"status": "DRAFT"
}
}
}
```
---
# templateHistory - Query
[Back to Content Management API](/content-management-api)
Returns an array of all revisions for the template by ID paginated by a cursor.
### Arguments
* **id** (ID!): The ID of the template.
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
### Returns
* **TemplateVersionConnection.edges** (\[TemplateEdge]!): A list of edges.
* **TemplateVersionConnection.nodes** (\[Template!]!): A list of the nodes contained in TemplateEdge.
* **TemplateVersionConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **TemplateVersionConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query templateHistory($id: ID!, $after: String, $before: String, $first: Int, $last: Int) {
templateVersion(id: $id) {
edges(after: $after, before: $before, first: $first, last: $last) {
nodes {
id
title
status
}
totalCount
}
}
}
`
const variables = {
id: 'template-id',
after: 'cursor',
first: 10
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateVersion": {
"edges": [
{
"cursor": "cursor",
"node": {
"id": "template-id",
"title": "Template Title",
"status": "DRAFT"
}
}
],
"totalCount": 1
}
}
}
```
---
# templateReferences - Query
[Back to Content Management API](/content-management-api)
Returns a list of references where the template is used.
### Arguments
* **id** (ID!): The ID of the template of which you want to find its references.
### Returns
* **Reference.\*** (\[Reference!]!): A list of references that this template is used in.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query TemplateReferences($id: ID!) {
templateReferences(id: $id) {
id
title
handle
contentType
}
}
`
const variables = {
id: 'template-id',
}
const response = await packClient.fetch(query, { variables: variables });
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templateReferences": [
{
"id": "page-id",
"title": "Page Title",
"handle": "page-handle",
"contentType": "page"
},
{
"id": "page-id",
"title": "Page Title",
"handle": "page-handle",
"contentType": "page"
},
]
}
}
```
---
# templates - Query
[Back to Content Management API](/content-management-api)
Returns an array of all your templates in a draft or published state paginated by a cursor.
### Arguments
* **after** (String): Returns the elements that come after the specified cursor.
* **before** (String): Returns the elements that come before the specified cursor.
* **first** (Int): Returns up to the first n elements from the list.
* **last** (Int): Returns up to the last n elements from the list.
* **version** (Version): The state of the section you want to retrieve.To get the latest content use CURRENT.To get the latest published content use PUBLISHED
* **country** (CountryCode): The country context for the query. Used with the @inContext directive.
* **language** (LanguageCode): The language context for the query. Used with the @inContext directive.
### Returns
* **TemplateConnection.edges** (\[TemplateEdge!]!): A list of edges.
* **TemplateConnection.nodes** (\[Template!]!): A list of the nodes contained in TemplateEdge.
* **TemplateConnection.pageInfo** (PageInfo!): Information to aid in pagination.
* **TemplateConnection.totalCount** (Int): The total count of items.
```js {{ title: '@pack/client'}}
import { PackClient } from '@pack/client'
const packClient = new PackClient({
token: 'YOUR-PACK-TOKEN'
});
const query = `
query {
templates(first: 5) {
edges {
cursor
node {
id
title
status
}
}
}
}
`
const response = await packClient.fetch(query);
console.log(response.data);
```
```json {{ title: 'Response' }}
{
"data": {
"templates": {
"edges": [
{
"cursor": "cursor",
"node": {
"id": "template-id",
"title": "Template Title",
"status": "DRAFT"
}
},
{
"cursor": "cursor",
"node": {
"id": "template-id",
"title": "Template Title",
"status": "PUBLISHED"
}
}
]
}
}
}
```
---
# @pack/client: Client Library Documentation
The `@pack/client` package provides a client for interacting with the Pack GraphQL API.
## Quick Start
Install the client with a package manager:
```bash {{ title: 'npm'}}
npm install @pack/client
```
```bash {{ title: 'yarn'}}
yarn add @pack/client
```
Import and create a new client instance, and use its methods to interact with your project's Content Lake. Below are some simple examples using Remix. Read further for more comprehensive documentation.
```ts {{title: "Example"}}
// Loader function for this specific route
import {json} from '@shopify/remix-oxygen';
export async function loader({ context, params }: LoaderFunctionArgs) {
return json({
token: context.env.PACK_SECRET_TOKEN,
});
}
// In your route file, use the loader
import { useLoaderData } from '@remix-run/react';
export const SomeComponent = () => {
const { token } = useLoaderData<{ token: string }>();
// Initialize the client with the fetched token
const packClient = new PackClient({
apiUrl: 'https://app.packdigital.com/graphql', // defaults to our CDN API https://apicdn.packdigital.com/graphql
token: token,
contentEnvironment: 'content_environment_handle', // defaults to the primary content environment
});
// Make a query to fetch the site settings
useEffect(() => {
if (packClient) {
const query = `
query SiteSettings($version: Version) {
siteSettings(version: $version) {
id
status
settings
seo {
title
description
keywords
}
favicon
publishedAt
createdAt
updatedAt
}
}
`;
const fetchData = async () => {
const response = await packClient.fetch(query, {variables: { version: 'CURRENT' }});
console.log(response.data.siteSettings);
};
fetchData();
}
}, []);
return (
Site Settings
);
};
```
```ts
interface PackClientOptions {
/** Pack API token */
token: string
/** The content environment handle */
contentEnvironment?: string
/** The URL of the Pack GraphQL API */
apiUrl?: string
}
```
* [GraphQL CMS API](/content-management-api): Learn more about our GraphQL content APIs
---
# @pack/hydrogen: Hydrogen Integration SDK
The `@pack/hydrogen` package provides clients and utilities to get Pack running on your Hydrogen storefront.
## Quick Start
Install the client with a package manager:
```bash {{ title: 'npm'}}
npm install @pack/hydrogen
```
```bash {{ title: 'yarn'}}
yarn add @pack/hydrogen
```
## Adding Pack TS types
In the root of your project, add this import to your `remix.env.d.ts` file.
```ts {{title: "remix.env.d.ts"}}
import type { Pack } from '@pack/hydrogen'
```
## Setting Pack context in `server.ts`
Import these to your `server.ts` file.
```ts {{title: "server.ts"}}
import { createPackClient, PreviewSession } from '@pack/hydrogen'
```
Initialize a Pack preview session where you open a cache and initialize a Hydrogen session. This preview session is how the Pack client will know to pull published or draft content.
```ts {{title: "server.ts"}}
const [cache, session, previewSession] = await Promise.all([
caches.open('hydrogen'),
HydrogenSession.init(request, [env.SESSION_SECRET]),
PreviewSession.init(request, [env.SESSION_SECRET]),
])
```
Create a Pack client and add it to the context. This client will be passed into your Remix loader context so you can query your Pack content from loaders.
```ts {{title: "server.ts"}}
const pack = createPackClient({
cache,
waitUntil,
token: env.PACK_SECRET_TOKEN,
preview: { session: previewSession },
contentEnvironment: env.PACK_CONTENT_ENVIRONMENT,
})
/**
* Create a Remix request handler and pass
* Hydrogen's Storefront client to the loader context.
*/
const handleRequest = createRequestHandler({
build: remixBuild,
mode: process.env.NODE_ENV,
getLoadContext: () => ({
session,
storefront,
cart,
pack,
env,
waitUntil,
}),
})
```
## Creating endpoint for preview mode
We now need to create a `/api/edit` route on your storefront that can be called so the Customizer and preview links can display your draft content.
To do this, create an `api.edit.ts` file in your Hydrogen project's `/app/routes` folder and add the following code to it.
```ts {{title: "/app/routes/api.edit.ts"}}
import { previewModeAction, previewModeLoader } from '@pack/hydrogen'
import { type ActionFunction, type LoaderFunction } from '@shopify/remix-oxygen'
export const action: ActionFunction = previewModeAction
export const loader: LoaderFunction = previewModeLoader
```
---
# @pack/react: React Component Library
The `@pack/react` package provides SDKs and components to enable your React app to connect with Pack.
## Quick Start
Install with a package manager:
```bash {{ title: 'npm'}}
npm install @pack/react
```
```bash {{ title: 'yarn'}}
yarn add @pack/react
```
Here is a list of exports from this package:
```js
import {
registerSection,
registerStorefrontSettingsSchema,
useSiteSettings,
PreviewProvider,
RenderSections,
} from '@pack/react'
```
***
## ``
The `` component is a React provider for your app and will give context for important info such as `siteSettings`, `previewInfo`, and more.
```ts{{ title: " props"}}
interface PreviewContentProps {
children: ReactNode;
siteSettings: any;
isPreviewModeEnabled?: boolean;
}
```
```tsx{{ title: "Example"}}
//root.tsx
import { PreviewProvider } from '@pack/react';
...
export default function App() {
const { siteSettings, isPreviewModeEnabled } = useLoaderData();
return (
);
}
```
***
## `registerSection()`
`registerSection` is a function that will register your React components to become Sections. If you create a component and want to make it an editable section on your storefront, it is important to register it with this function.
### Usage
Here at Pack, we recommend creating a `sections/` folder in the project where all your sections will live. This `sections/` folder will export a `registerSections` utility function that makes it easy to register all your sections in the root of your app.
Here is an example from our Blueprint Theme of how to use `registerSections()` in your storefront's `root.tsx`.
**Arguments**
* **section** (React.ComponentType\ & { Schema?: Schema }): The React component that you want to become an interactable section in your storefront and the Customizer.
* **name** (string): A unique name for this section.
```tsx{{ title: "~/sections/index.tsx"}}
import { registerSection } from '@pack/react';
import { Hero } from './Hero';
import { FiftyFiftyHero } from './FiftyFiftyHero';
import { TextBlock } from './TextBlock';
import { Image } from './Image';
import { HTML } from './HTML';
export { Hero, FiftyFiftyHero, TextBlock };
export function registerSections() {
registerSection(Hero, { name: 'hero' });
registerSection(FiftyFiftyHero, { name: 'fifty-fifty-hero' });
registerSection(TextBlock, { name: 'text-block' });
registerSection(Image, { name: 'image-block' });
registerSection(HTML, { name: 'html-block' });
}
```
```tsx{{ title: "root.tsx"}}
import { useLoaderData } from '@remix-run/react';
import { defer, LoaderArgs } from '@shopify/remix-oxygen';
import { RenderSections } from '@pack/react';
import { registerSections } from '~/sections';
registerSections()
export async function loader({ context }: LoaderArgs) {
const { data } = await context.pack.query(HOME_PAGE_QUERY);
return defer({
page: data.page,
});
}
export default function Index() {
const { page } = useLoaderData();
return (
);
}
```
***
## ``
The `` component will take in your page content data and return an array of React components that you have registered as sections.
```tsx{{ title: "Example"}}
//_index.tsx
import { useLoaderData } from '@remix-run/react';
import { defer, LoaderArgs } from '@shopify/remix-oxygen';
import { RenderSections } from '@pack/react';
export async function loader({ context }: LoaderArgs) {
const { data } = await context.pack.query(HOME_PAGE_QUERY);
return defer({
page: data.page,
});
}
export default function Index() {
const { page } = useLoaderData();
return (
);
}
```
***
## `registerStorefrontSettingsSchema()`
Similar to [`registerSection`](/pack-react#register-section), the `registerStorefrontSettingsSchema` is a function that will register your site settings schema to be editable withing the [Customizer](/create-manage-content/customizer). This will allow you to create a global setting for your store to be used for headers, footers, etc. You can access these settings by using the [`useSiteSettings`](/pack-react#use-site-settings) hook.
### Usage
Here at Pack, we recommend creating a `settings/` folder in the project where all your site settings will live. This `settings/` folder will export a `registerSiteSettings` utility function that makes it easy to register all your settings in the root of your app.
**Arguments**
* **settings** (Array\): An array of schemas that define your site settings. Note, the schema structure is the same as what you would use in a section.
```tsx{{ title: "~/settings/index.ts"}}
import {registerStorefrontSettingsSchema} from '@pack/react';
import footer from './footer';
export function registerSiteSettings() {
registerStorefrontSettingsSchema([footer]);
}
```
```ts{{ title: "~/settings/footer.ts"}}
export default {
label: 'Footer',
name: 'footer',
component: 'group',
description: 'Menu, social, legal, email marketing',
fields: [
{
label: 'Menu',
name: 'menu',
component: 'group',
description: 'Footer menu links',
fields: [
{
label: 'Menu Title',
name: 'title',
component: 'text',
},
{
label: 'Menu Item Links',
name: 'links',
component: 'group-list',
itemProps: {
label: '{{item.link.text}}',
},
fields: [
{
label: 'Link',
name: 'link',
component: 'link',
},
],
},
],
},
{
label: 'Legal',
name: 'legal',
component: 'group',
description: 'Legal links',
fields: [
{
label: 'Legal Links',
name: 'links',
component: 'group-list',
itemProps: {
label: '{{item.link.text}}',
},
fields: [
{
label: 'Link',
name: 'link',
component: 'link',
},
],
defaultValue: [
{
link: {
text: 'Privacy Policy',
url: '/pages/privacy-policy',
},
},
{
link: {
text: 'Terms & Conditions',
url: '/pages/terms-conditions',
},
},
],
},
],
},
],
};
```
```tsx{{ title: "root.tsx"}}
import { useLoaderData } from '@remix-run/react';
import { defer, LoaderArgs } from '@shopify/remix-oxygen';
import { RenderSections } from '@pack/react';
import { registerSections } from '~/sections';
import { registerSiteSettings } from '~/settings';
registerSections();
registerSiteSettings();
export async function loader({ context }: LoaderArgs) {
const { data } = await context.pack.query(HOME_PAGE_QUERY);
return defer({
page: data.page,
});
}
export default function Index() {
const { page } = useLoaderData();
return (
);
}
```
***
## `useSiteSettings()`
The `useSiteSettings` hook allows you to access the structured data that you defined with your site settings schema. This hook must be used with the [``](/pack-react#preview-provider) component.
### Usage
Here at Pack, we recommend creating a `sections/` folder in the project where all your sections will live. This `sections/` folder will export a `registerSections` utility function that makes it easy to register all your sections in the root of your app.
```tsx{{ title: "Layout.tsx"}}
import {ReactNode} from 'react';
import {Header} from './Header';
import {Footer} from './Footer';
import {useSiteSettings} from '@pack/react';
export function Layout({
children,
}: {
siteSettings?: Record;
children: ReactNode;
}) {
const siteSettings = useSiteSettings();
return (
<>
{children}
>
);
}
```
***
---
# @pack/types: TypeScript Type Definitions
The `@pack/types` provides TypeScript definitions for Pack projects, enabling strong type-checking of sections and data to enhance code quality and developer productivity.
## Quick Start
Install with a package manager:
```bash {{ title: 'npm'}}
npm install @pack/types
```
```bash {{ title: 'yarn'}}
yarn add @pack/types
```
Here is a list of exports from this package:
```js
import type {
Section,
SectionSchema,
SectionMap,
SectionObjectSchema,
SiteSetting,
TextField,
TextAreaField,
ImageField,
MarkdownField,
LinkField,
NumberField,
ColorField,
DateField,
ProductSearchField,
CollectionsField,
ProductBundlesField,
HtmlField,
TagsField,
GroupField,
ListField,
GroupListField,
BlocksField,
ToggleField,
RadioGroupField,
} from '@pack/types';
```
## Typing Your Sections For Validation
You can utilize @pack/types for both section and storefront setting schemas validation. Validation errors will be logged in terminal upon running your storefront.
Start by typing your section and storefront settings schemas by importing the `Section` and `SiteSetting` from `@pack/types` which are now expected when using `@pack/react` `registerSection()` and `registerStorefrontSettingsSchema()`.
When using `registerSection`, type the passed in section schema, for example:
```ts{{ title: "/sections/index.ts"}}
import {registerSection} from '@pack/react';
import type {Section} from '@pack/types';
export function registerSections() {
registerSection(TabbedThreeTiles as Section, {name: 'tabbed-three-tiles'});
registerSection(ThreeTiles as Section, {name: 'three-tiles'});
registerSection(TwoTiles as Section, {name: 'two-tiles'});
}
```
When using `registerStorefrontSettings`, type the passed in site setting schema, for example:
```ts{{ title: "/storefront-settings/index.ts"}}
import {registerStorefrontSettingsSchema} from '@pack/react';
import type {SiteSetting} from '@pack/types';
export function registerStorefrontSettings() {
registerStorefrontSettingsSchema([
account as SiteSetting,
analytics as SiteSetting,
cart as SiteSetting,
collection as SiteSetting,
footer as SiteSetting,
]);
}
```
## Core Schema Types
**`Section`**: The base interface for all section components. Sections are the building blocks of pages in Pack.
```ts
interface Section {
component: React.ComponentType
schema: SectionSchema
}
```
**`SectionSchema`**: Defines the configuration structure for a section, including its settings and blocks.
```ts
interface SectionSchema {
name: string
settings: FieldSchema[]
blocks?: BlockSchema[]
max_blocks?: number
presets?: SectionPreset[]
}
```
**`SectionMap`**: A mapping type that connects section names to their corresponding section components.
```ts
type SectionMap = {
[key: string]: Section
}
```
**`SectionObjectSchema`**: Defines the schema for section objects, which can include settings and blocks.
```ts
interface SectionObjectSchema {
type: 'section'
settings: FieldSchema[]
}
```
**`SiteSetting`**: Defines global site-wide settings
```ts
interface SiteSetting {
type: string
id: string
label: string
category: string
default?: any
}
```
## Field Types
These are the available field types for section and settings schemas:
### Text & Content Fields
**`TextField`**: Single-line text input field
```ts
interface TextField {
type: 'text'
id: string
label: string
default?: string
placeholder?: string
}
```
**`TextAreaField`**: Multi-line text input field
```ts
interface TextAreaField {
type: 'textarea'
id: string
label: string
default?: string
rows?: number
}
```
**`MarkdownField`**: Rich text editor with markdown support
```ts
interface MarkdownField {
type: 'markdown'
id: string
label: string
default?: string
}
```
**`HtmlField`**: HTML content editor
```ts
interface HtmlField {
type: 'html'
id: string
label: string
default?: string
}
```
**`TagsField`**: Input field for managing tags/labels
```ts
interface TagsField {
type: 'tags'
id: string
label: string
default?: string[]
}
```
### Media Fields
**`ImageField`**: Image upload and selection field
```ts
interface ImageField {
type: 'image'
id: string
label: string
default?: string
max_size?: number
allowed_extensions?: string[]
}
```
**`LinkField`**: URL/link input field
```ts
interface LinkField {
type: 'link'
id: string
label: string
default?: {
url: string
text?: string
target?: '_blank' | '_self'
}
}
```
### Numeric & Date Fields
**`NumberField`**: Numeric input field
```ts
interface NumberField {
type: 'number'
id: string
label: string
default?: number
min?: number
max?: number
step?: number
}
```
**`DateField`**: Date picker field
```ts
interface DateField {
type: 'date'
id: string
label: string
default?: string
format?: string
}
```
**`ColorField`**: Color selector field
```ts
interface ColorField {
type: 'color'
id: string
label: string
default?: string
opacity?: boolean
}
```
### Product & Collection Fields
**`ProductSearchField`**: Search and select products
```ts
interface ProductSearchField {
type: 'product_search'
id: string
label: string
multi_select?: boolean
}
```
**`CollectionsField`**: Select from available collections
```ts
interface CollectionsField {
type: 'collections'
id: string
label: string
multi_select?: boolean
}
```
**`ProductBundlesField`**: Select and configure product bundles
```ts
interface ProductBundlesField {
type: 'product_bundles'
id: string
label: string
multi_select?: boolean
}
```
### Group & List Fields
**`GroupField`**: Container for grouping related fields
```ts
interface GroupField {
type: 'group'
id: string
label: string
fields: FieldSchema[]
}
```
**`ListField`**: Repeatable field for creating lists
```ts
interface ListField {
type: 'list'
id: string
label: string
field: FieldSchema
max_items?: number
}
```
**`GroupListField`**: Repeatable group of fields
```ts
interface GroupListField {
type: 'group_list'
id: string
label: string
fields: FieldSchema[]
max_items?: number
}
```
**`BlocksField`**: Dynamic blocks of content with different schemas
```ts
interface BlocksField {
type: 'blocks'
id: string
label: string
blocks: BlockSchema[]
max_blocks?: number
}
```
### Selection Fields
**`ToggleField`**: Boolean on/off toggle
```ts
interface ToggleField {
type: 'toggle'
id: string
label: string
default?: boolean
}
```
**`RadioGroupField`**: Select one option from multiple choices
```ts
interface RadioGroupField {
type: 'radio_group'
id: string
label: string
options?: Array<{label: string, value: string}> | string[]
default?: string
direction?: 'horizontal' | 'vertical';
variant?: 'radio' | 'button';
}
```
---
# Content Environments: Managing Multiple Content Versions
> **Warning**: Content environments are only available on Pack storefronts.
Content environments in your space act like separate containers, allowing you to create and manage different versions of your content independently. They're like code branches, perfect for testing and development, making it easier for your team to handle content structure changes after publishing.
By default, every storefront has one default content environment known as the **primary environment**. Your primary environment is the one that is used for content on your storefront, unless otherwise specified.

When creating a new content environment, they begin as duplicates of the primary environment, and changes made to entities within any environment, including the primary, don't impact data in other environments. Each environment operates independently, ensuring your modifications are contained and won't affect the rest.
> **Warning**: Content environments are only available on Pack storefronts.
Content environments are crucial for managing and previewing different versions of your website's content in a controlled and isolated manner.
They are especially beneficial in development and testing phases, allowing you to experiment and make changes without impacting your live site, whether you’re creating a staging environment or preparing a holiday campaign.
## Creating a new content environment
1. Go to Pack’s admin > **Home**.
2. Click on the **+** button next to the content environments section on the right-hand side of the dashboard.
3. Select the source environment that you want to duplicate.
4. Assign a name and handle to your new environment.
This process enables you to work on different versions of your content, test new features, or make changes without affecting your primary content environment.

## Promoting content environments
1. Navigate to the desired content environment.
2. Click on the **Promote to Primary environment** button.
Promoting an environment to be your primary environment means it becomes the default environment for your storefront, and its content will be displayed to your end-users.

## Managing content environments
1. Navigate to Pack’s admin > **Home**.
2. Click on the content environment you wish to manage.
3. Here, you can rename, change the handle, or delete the environment as needed.
## Force storefront to use content environments
You can force the storefront to use a specific content environment by using the Content Environment toggle in the [Customizer](/create-manage-content/customizer).

* [Learn how to use content environments in your development workflow](/developer-resources/content-environments): A guide on using content environments in your development and editing workflow.
---
# Content Management in Pack: Creating and Organizing Page Content
At Pack, managing your content is streamlined and efficient. Our platform enables you to effortlessly add, modify, and remove pages, blogs, articles, and more.
Our flexible sections allow for the easy integration of content into your pages. You can establish universal sections for all product pages or tailor them for specific ones. To enhance your development experience, we provide a live content customizer preview, allowing you to develop and edit sections as a merchant would.
You can manage your content using the [Customizer](/create-manage-content/customizer#managing-content), or by navigating to Pack Admin and selecting the content you wish to manage.

* [Customizer Content Management](/create-manage-content/customizer#managing-content): Discover how to manage content using the customizer.
* [Managing Sections](/create-manage-content/sections): A guide on section management.
* [Previewing Content](/create-manage-content/previewing): Techniques to preview your content.
---
# Content Releases
Content Releases let your team prepare a group of content changes and publish them all at once. They are useful for product launches, campaign updates, seasonal merchandising, localization updates, homepage refreshes, or any project where several content changes need to go live together.
Content Releases replace the previous Page Drafts and Storefront Settings Drafts workflows. Instead of making separate drafts for one page or one settings area, you can create one release and add all related edits to it.
For example, a holiday launch release might include:
* A new homepage hero
* Updated product page content
* A reusable promotional section
* Updated navigation or announcement bar settings
Everything in the release can be reviewed together and published together.
## What changes when you use a release
When you select a release in the Customizer, your edits are saved to that release instead of going live immediately.
Only the content you edit becomes part of the release. Everything else continues to use the current live content.
For example, if your release only changes the homepage and two product pages, then publishing the release only updates those items. Pages that were not edited in the release are left alone.
## Create a release
The fastest way to create a release is from the Customizer. Open the perspective picker in the toolbar to switch between live content, releases, and tests.

1. Open the **Customizer**.
2. Open the perspective picker in the toolbar.
3. Select **Create new release** at the bottom of the picker.
4. Enter a release name.
5. Optionally add a description so your team knows what the release is for.
6. Save the release.

Use clear release names like “Holiday Homepage Launch,” “Spring Campaign,” or “BFCM Product Updates” so your team can quickly understand what each release contains.
## Edit content in a release
After you create or select a release, continue editing in the Customizer as usual.
1. Open the **Customizer**.
2. Open the perspective picker in the toolbar.
3. Select the release you want to work in.
4. Make your content changes.
5. Click **Save to Release**.
While a release is selected, saved changes are kept in that release. They do not update live content until the release is published.
You can include changes to:
* Pages
* Articles and blogs
* Product and collection pages
* Sections
* Templates
* Storefront or shop settings
> **Note**: If a piece of content is not edited in the release, publishing the release
> will not change it.
## Move unscheduled page edits into a release
If you already made page edits that are not scheduled and not part of a release, you can move those edits into a new release from the Customizer save menu.
1. Open the page with the unscheduled edits in the **Customizer**.
2. Open the save button dropdown.
3. Choose the option to move the page changes into a release.
4. Enter a release name and optional description.
5. Save.
Pack creates the release, moves the page edits into it, and switches you into that release so you can keep reviewing or editing.
## Review a release before publishing
Before you publish, review the release to confirm what will go live.
In Pack Admin, open **Releases** to see:
* The release name, description, and status
* The drafts included in the release
* The type of content each draft changes
* Who last updated each draft
* Any conflicts that need review before publishing
You can also review release details from the Customizer when a release is selected.
## Understand conflicts and overwrites
A conflict happens when content in a release and the live version of that same content have both changed.
This can happen when anyone publishes changes directly to live content while a release is being prepared. It does not matter whether the live change was made by another editor or by the same person who created the release. Live changes are not automatically added to existing releases.
Conflicts can also happen when two releases include changes to the same page, section, template, or settings area.
When Pack finds a conflict, the publish dialog shows what changed so you can decide what to do next.
When conflicts exist, you can:
* **Cancel publishing** to avoid changing live content.
* **Publish anyway** if the release should replace the current live content.
> **Warning**: Publishing anyway can overwrite newer live changes. Review conflicts carefully
> before continuing.
The important thing to know is that Pack publishes the saved release version of the content item. If the release includes the homepage, publishing that release updates the homepage to match what is saved in the release. It does not only publish the single field you remember editing.
### Common publishing scenarios
**Only your release changed the content.** You update the homepage hero in a release, and no one changes the live homepage before launch. When you publish, the release version of the homepage hero goes live. Other pages and settings are unchanged.
**Someone changed unrelated live content.** You update the homepage hero in a release, and another editor updates a product page directly on live. Publishing your release does not change that product page, because it was not part of the release.
**Someone changed the same live content.** You update the homepage hero headline in a release, and then someone updates the live homepage hero image before your release launches. That person could be another editor, or it could be you making a separate live change. Pack warns you that the live homepage changed. If you publish anyway, the homepage goes live as it is saved in your release, which can overwrite the newer live image change.
**Two releases changed the same content.** Release A and Release B both include homepage hero changes. If Release A goes live first, Release B may not include Release A's changes. Publishing Release B later can replace the hero with the version saved in Release B.
When multiple editors or releases touch the same content, review the conflicts before choosing **Publish anyway**.
## Publish a release
Publish a release when the content has been reviewed and is ready to go live.
1. Open **Releases** in Pack Admin, or select the release in the **Customizer**.
2. Review the included drafts.
3. Click **Publish**.
4. Review the publish confirmation.
5. If conflicts are shown, either cancel or choose **Publish anyway**.
6. Confirm the publish.
After publishing, Pack updates the live content included in the release and triggers the normal storefront update process.
## Archive a release
Archive a release when you no longer plan to publish it.
1. Open **Releases** in Pack Admin.
2. Open the actions menu for the release.
3. Select **Archive**.
4. Confirm the archive action.
Archived releases can no longer be edited or published from the Customizer.
## Best practices
* Create one release for each launch, campaign, or coordinated update.
* Use clear names and descriptions so your team knows what each release is for.
* Keep unrelated work in separate releases.
* Before publishing, review every draft included in the release.
* Be extra careful when multiple releases touch the same page, section, template, or settings area.
* If Pack shows conflicts, review them before choosing **Publish anyway**.
* Archive releases that are no longer needed.
---
# Pack Customizer: Visual Editing Platform for Marketers and Developers

The Customizer is Pack's visual editor for your storefront or shop that makes teamwork a breeze for both devs and content creators.
> **Note**: The Customizer becomes available once the storefront or shop is deployed for
> the first time. You can locate it in the Pack Admin's left sidebar.
## Managing Content

In Customizer, you can manage content on your pages in the form of sections. You can create flexible and reusable content across pages by linking sections and leveraging section templates.
Learn how to manage your content [here](/create-manage-content/customizer#managing-content).
## Storefront and Shop Preview URLs

Switch between preview URLs to view and make changes specific to any previews, whether you’re looking at new sections or tweaks to a layout. Add or remove custom URLs to fit your team's workflow.
Learn more about preview URLs by checking out this [guide](/developer-resources/preview-urls).
## Storefront and Shop Settings

Storefront and Shop Settings is the hub for adjusting your store’s main features like headers, footers, and cart settings. It’s fully customizable, letting you add or remove settings as needed.
You can quickly access Storefront or Shop Settings from the right utility bar in the Customizer.
To learn more about how to use the settings, check out our guide for [Managing Storefront and Shop Settings](/create-manage-content/global-settings#accessing-storefront-settings).
## Content Environments
> **Warning**: Content Environments are only available on storefronts.

The Customizer allows you to switch between [content environments](/create-manage-content/content-environments) to make it easier for your team to manage and maintain isolated instances of your storefront. This will allow you to visually test and edit content with a simple switch of a toggle.
## Content Scheduling

You can schedule pages and site settings in the Customizer to be published at a later date. This feature ensures your store updates exactly when you need it to, without having to manually publish pages.
Learn more about scheduling content [here](/create-manage-content/scheduling).
## Content Releases
Content Releases let you stage a coordinated set of CMS changes and publish them together. Use the perspective picker in the Customizer toolbar to switch between **Live**, **Releases**, and **A/B Tests**. When you select a release, reads and saves are scoped to that release, and saved edits become release-scoped drafts instead of immediately changing live content.
Learn more about Content Releases [here](/create-manage-content/content-releases).
## Media Manager

The Media Manager in Pack streamlines the process of managing your store's media. Easily upload, edit, and delete images directly from the Customizer.
Edits—including changes to alt text—synchronize instantly with Shopify, ensuring consistent media management across platforms.
You can access Media Manager from the right utility bar in the Customizer.
Explore the capabilities of the Media Manager in more detail [here](/create-manage-content/media-manager).
## Layouts
Layouts let you save a page's section configuration as a reusable template and apply it to other pages. From the page menu (three dots) in the Customizer sidebar, you can **Save as layout** to capture the current page's sections, or **Manage layouts** to browse, edit, and apply your saved layouts.
Learn more about creating and managing layouts [here](/create-manage-content/layouts).
## Shareable Preview Link

The Customizer enables you to share content drafts through a shareable preview link. This allows you to share unpublished work for your team's feedback or approval.
You can share a preview link with anyone by clicking the share button in the Customizer's toolbar.
* [Managing Sections](/create-manage-content/sections): Learn how to use the Customizer to edit your content.
* [Managing Preview URLs](/developer-resources/preview-urls): Understand how to spin up a local development environment.
* [Storefront and Shop Settings](/create-manage-content/global-settings#accessing-storefront-settings): Learn about how to manage your storefront and shop settings in the Customizer.
* [Content Environments](/create-manage-content/content-environments): Learn how to manage content environments in the Customizer.
* [Layouts](/create-manage-content/layouts): Learn how to save and apply reusable page layouts in the Customizer.
* [Scheduling Content](/create-manage-content/scheduling): Learn how to schedule content in the Customizer.
* [Content Releases](/create-manage-content/content-releases): Learn how to stage coordinated changes and publish them together.
* [Managing Media](/create-manage-content/media-manager): Learn how to manage media in the Customizer.
---
# Managing Global Content in Pack's Customizer: A Guide for Marketers
This guide explains how to access, edit, and manage global content elements like headers, footers, and site-wide settings in your Pack-powered Hydrogen storefront using the Customizer - no coding required.
*This article is part of our* [*Content Management*](https://docs.packdigital.com/resources/content-management) *series focusing on how marketers can work efficiently with Pack's Customizer.*
## What is Global Content?
Global content refers to elements that appear consistently across your entire storefront, including:
* Headers and navigation
* Footers
* Announcement bars
* Site-wide color schemes and typography
* Cart settings
* Account settings
* Promotional banners
Pack's Customizer makes it easy to manage these elements in one central location through Storefront Settings, ensuring a consistent brand experience across your entire site.
## Accessing the Customizer
The [Customizer](https://docs.packdigital.com/create-manage-content/customizer) becomes available once your storefront is deployed for the first time:
1. Log in to your Pack Admin dashboard
2. Find and click "Customizer" in the left sidebar navigation
3. You'll see your storefront displayed with editing tools in a sidebar
## Accessing Storefront Settings
To manage global content elements:
1. In the Customizer, look for the gear icon (⚙️) in the right utility bar
2. Click on "Storefront Settings" or "Shop Settings"
3. A panel will open with all available global settings for your site

## Accessing Storefront Settings via Pack Admin
You can also manage your storefront settings directly from Pack Admin.\
Go to **Pack Admin → Settings → Storefront Settings** to view and edit your storefront’s configuration in JSON format.
The JSON editor provides three tabs:
* **View** – see the current settings in JSON format
* **Value Edit Mode** – edit only the values in a safer, structured mode
* **Raw Edit Mode** – edit the raw JSON directly
> **Warning**: Be cautious when editing in JSON mode—invalid syntax may cause errors or data loss.
When you’re finished, click **Save**, or use the three-dot menu next to it to **Publish Storefront Settings** live to your site.

## Common Global Elements You Can Edit
Depending on how your developer has configured your site, you'll typically be able to edit these global elements:
### Header Settings
* Logo (upload or replace)
* Navigation menu items and links
* Dropdown menus and sub-navigation
* Header style, color, and layout options
### Footer Settings
* Footer logo
* Footer menus and link sections
* Copyright text
* Newsletter signup settings
* Social media links
### Announcement Bar
* Enable/disable the bar
* Bar text content
* Link destination
* Background and text colors
### Theme Settings
* Primary and secondary brand colors
* Typography and font selections
* Button styles
* Background colors or images
### Cart Settings
* Cart drawer layout
* Cart messages and prompts
* Checkout button text
* Cart recommendations
### Customer Account Settings
The "Account settings" in your global Storefront Settings refer to the customer account experience across your site. These settings control how customers interact with their accounts throughout your store, including:
* Login and registration form appearance
* Account page layouts and navigation
* Account-related messaging (welcome emails, notifications)
* Password reset process
* Customer dashboard configuration
* Wishlist functionality (if enabled)
* Order history display options
## Making Changes to Global Settings
1. Navigate to the appropriate section in Storefront Settings
2. Make your desired changes
3. See a live preview of your changes in the Customizer
4. Click "Save" at the bottom of the panel to save your changes
5. Click "Publish Settings" to push the changes live on your site
**Important:** Remember to click both "Save" and "Publish Settings" to make your changes live. Updates to settings are not automatically published with the rest of your storefront content when a new deploy is triggered.
## Best Practices for Managing Global Content
* **Maintain brand consistency:** Use the same color scheme, typography, and design elements across all global components
* **Keep navigation intuitive:** Organize your header navigation logically and consistently
* **Mobile-friendly:** Preview your global elements on mobile devices to ensure they look good on all screen sizes
* **Test links:** After making changes, verify all navigation links and buttons work correctly
* **Review all pages:** Check how global content appears across different page types
## Difference Between Global Settings and Sections
It's important to understand the difference between global settings and regular sections:
* **Global Settings:** Applied site-wide, managed through Storefront Settings, and affect every page
* **Sections:** Individual content blocks that appear on specific pages - learn more in the [Managing Sections](/create-manage-content/sections) guide
* **Linked Sections:** Sections that can be reused across multiple pages (changes to a linked section affect all pages where it appears)
* **Section Templates:** Applied to specific page types (like all product pages or all collection pages) - see the [Managing Templates](/create-manage-content/templates) guide for details
## Working with Content Environments
You can also manage your global content across different environments:
1. In the Customizer, use the environment switcher in the top toolbar
2. Switch between environments (like "Development," "Staging," or "Production")
3. Make and test changes in a development environment before publishing to production
Learn more about this feature in the [Content Environments](/create-manage-content/content-environments) documentation.
## Scheduling Global Content Updates
For time-sensitive global content changes:
1. Make your changes in Storefront Settings
2. Instead of publishing immediately, click the calendar icon
3. Select your desired publish date and time
4. Save your scheduled changes
This is perfect for planning seasonal header updates, promotional banners, or holiday-specific global content. For more detailed instructions, see the [Scheduling Content](https://docs.packdigital.com/create-manage-content/scheduling) guide.
## Getting Help
If you notice global settings you need are missing or not working as expected, you may need to contact your development team. These settings are configured by developers in the site's code and then made available to you through the Customizer interface.
By leveraging Pack's Customizer to manage your global content, you can maintain a consistent, professional, and brand-aligned experience across your entire storefront without needing any coding knowledge.
* [Customizer](/create-manage-content/customizer): Get an overview of Pack’s Customizer and its capabilities.
* [Sections](/create-manage-content/sections): Discover how to create and manage sections in Pack.
* [Templates](/create-manage-content/templates): Understand how to work with page templates in Pack.
* [Content Environments](/create-manage-content/content-environments): Learn about managing different content versions with environments.
* [Scheduling Content](/create-manage-content/scheduling): Find out how to schedule content for future publication.
* [Publishing Content](/create-manage-content/publishing): Learn the process of publishing your content from draft to live.
---
# Layouts: Save and Reuse Page Section Configurations
Layouts let you save a page's section configuration as a reusable template that can be applied to other pages. Instead of manually recreating the same section arrangement across multiple pages, you can save a layout once and apply it wherever you need it.
> **Warning**: Layouts are a beta feature available after opting into the new Customizer
> experience from your account settings.
## Saving a Layout
You can save any page's sections as a reusable layout from the Customizer.
1. Open the **Customizer** and navigate to the page you want to save as a layout.
2. Click the **page menu** (three dots) in the Customizer sidebar.
3. Select **Save as layout**.
4. Enter a **name** for your layout.
5. Optionally upload a **preview image** to help identify the layout later.
6. Select or deselect which **sections** to include in the layout.
7. Reorder sections via **drag-and-drop** if needed.
8. Click **Save**.
> **Note**: Template-type sections are excluded from layouts. Only local and linked
> sections can be saved as part of a layout.
## Managing Layouts
You can view and manage all your saved layouts from the Customizer.
1. Open the **Customizer** and click the **page menu** (three dots) in the sidebar.
2. Select **Manage layouts**.
3. Browse your saved layouts, displayed as cards with preview images.
Each layout card provides the following actions (via the card menu):
* **View details** — See the sections included in the layout.
* **Apply layout** — Apply the layout to the current page (see [Applying a Layout](#applying-a-layout)).
* **Edit layout details** — Rename the layout or change its preview image.
* **Reset with current content** — Update the layout to match the current page's section configuration.
* **Delete layout** — Permanently remove the layout.
## Applying a Layout
You can apply a saved layout to any page to quickly set up its sections.
1. Open the **Customizer** and navigate to the target page.
2. Click the **page menu** (three dots) and select **Manage layouts**.
3. Find the layout you want to apply, open its **card menu**, and select **Apply layout**.
4. Choose an application mode:
* **Replace** — Removes all existing local and linked sections on the page and replaces them with the layout's sections. Template-type sections are preserved.
* **Add** — Keeps all existing sections and adds the layout's sections. Choose to add them at the **end** (default) or **beginning** of the page.
5. Review the options and confirm.
> **Note**: When a layout is applied, **local sections** are copied as independent
> sections on the target page — edits to these sections will not affect the
> original layout or other pages. **Linked sections** remain shared — any edits
> to a linked section will be reflected everywhere it appears.
## Layouts vs Templates
Both layouts and templates help you structure page content, but they serve different purposes.
| | Layouts | Section Templates |
| ------------------- | --------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| **Created in** | Customizer, from an existing page's sections | Admin, under Sections > Templates |
| **Scope** | Applied per page on demand | Assigned to a page type and shared across all pages using that template |
| **Content updates** | Sections are copied at the time of application — changes do not sync back to the layout | Edits to a template section propagate to all pages using the template (unless overridden per page) |
| **Best for** | Quickly replicating a page's section arrangement across other pages | Maintaining a consistent set of sections across all pages of a given type |
* [Managing Sections](/create-manage-content/sections): Learn about local sections, linked sections, and section templates.
* [Templates](/create-manage-content/templates): Learn how to manage page templates in Pack.
* [Customizer](/create-manage-content/customizer): Explore the full capabilities of the Pack Customizer.
---
# Localization
Reaching customers in their own language and currency can have a huge impact on your conversion rates and customer loyalty. With Pack's localization tools for your Hydrogen storefront, you'll be able to tailor your content, pricing, and promotions to each market—boosting engagement and growing your sales.
## Pack Admin
### Locales
Navigate to `Settings > Localization` in the Admin to create, disable, or remove locales, and to set your store’s primary locale.
#### Primary Locale
* By default, the primary locale is `en-US`. Any content created before localization was enabled inherits this locale.
#### Fallback Locales
When a visitor requests content in a specific locale and no translation exists, Pack automatically falls back through a chain:
1. **Requested locale** — The exact locale requested
2. **Fallback locale** — A configured fallback (e.g., `fr-CA` can fall back to `fr-FR`)
3. **Primary locale** — Your store's primary locale (usually `en-US`)
This means you don't need to translate every piece of content for every locale—untranslated content gracefully falls back to the next available language.
##### Setting a Fallback Locale
When creating a locale, you can optionally set a fallback locale. For example, setting French Canadian (`fr-CA`) to fall back to French (`fr-FR`) means any untranslated `fr-CA` content will display the `fr-FR` version before falling back to your primary locale.
#### Creating Locales
* Click **Add Locale** in the top-right.
* In the modal, choose your **Language** and **Region**, then click **Create**.
#### Deleting Locales
To remove a locale, click the delete icon next to it in the locales list.
When you delete a locale:
* All content in that locale is removed
* Products, collections, and site settings are automatically copied to your primary locale (if they don't already exist there) to prevent data loss
* The deletion runs in the background and may take a few moments for stores with lots of content
**Note:** You cannot delete your primary locale or a locale that is used as a fallback by another locale.
## Pack Customizer
### Switching Locales
Use the locale dropdown in the Customizer toolbar (which defaults to your primary locale) to switch between languages.

Selecting a locale from that list will load your storefront content for that region.

### Translating Content
When you select a non-primary locale, each section shows a badge indicating its translation status:
* **Grey badge** = using primary-locale fallback
* **Orange badge** = translation exists

Hover over the badge to see detailed locale information and helpful tooltips.

Already translated? You’ll see an orange badge and locale details on hover.

To add or update a translation, switch to your target locale, edit the content, and save. The badge will automatically update from grey to orange to confirm your new translation.
## Supported Content Types
The following content types support localization:
* Pages
* Articles
* Blogs
* Products
* Collections
* Sections
* Templates
* Site Settings
Each of these can be translated independently per locale.
* [Adding Localization to Your Storefront](/developer-resources/localization): Learn how to add localization to your storefront.
---
# Media Asset Management: Uploading and Organizing Images
Pack’s Media Manager simplifies media handling, offering seamless integration with Shopify.

## Upload Media
1. Access the Media Manager via Customizer.
2. Click on "Upload Media."
3. Once uploaded, images appear in the list and are accessible in Shopify under Content > Files.
## Edit Media
> **Warning**: Be cautious: Deleting media is irreversible and will remove it from Shopify as
> well.
You can edit Media alt text or delete it by clicking on the three dots next to the image you want to edit in the Media Manager.
---
# Organization and Storefront Management: Team and Access Control
## Managing Organizations
Pack’s admin offers comprehensive tools for managing team members, from inviting new members to modifying roles and access levels, and removing members when necessary.

### Invite a New Member
1. Access Pack's admin.
2. Using the left sidebar, go to **Organization Settings > Organization Members**.
3. Click on **Invite** at the top of the page.
4. Enter the user’s email address and [select their role](/create-manage-content/organizations-storefronts#managing-storefronts).
5. Send the invitation. The invitee will receive an email to join your organization.
### Modify Member Access
1. Access Pack's admin.
2. Using the left sidebar, go to **Organization Settings > Organization Members**.
3. Click the three dots next to the user to modify their access.
4. Select or deselect storefronts/shops the user should have access to.
### Modify a Member's Role
1. Access Pack's admin.
2. Using the left sidebar, go to **Organization Settings > Organization Members**.
3. Find the member whose role you wish to modify.
4. Click the three dots next to their name and choose **Change Role.**
5. [Select the new role](/create-manage-content/organizations-storefronts#managing-storefronts) from the available options.
### Remove a Member
> **Warning**: Removing a member revokes their access to your entire organization.
1. Access Pack's admin.
2. Using the left sidebar, go to **Organization Settings > Organization Members**.
3. Click on the three dots next to the member you want to remove.
4. Choose **Suspend Account**.
5. Confirm the member's identity by entering their email.
6. Click **Suspend Account** to complete the removal.
## Managing Storefronts

### Renaming a Storefront
> **Warning**: Only organization admins and storefront admins have the authority to manage storefront members.
1. In Pack's Admin, click on **Settings**.
2. Input a new name for your storefront.
3. Click on **Save changes**.
### Managing Storefront Members

### Adding Invited Organization Members to Storefronts
> **Warning**: Only Organization admins and Storefront admins have the authority to manage
> Storefront members.
> **Note**: When you're adding users to a storefront, remember to **invite them to your
> organization** first. Make sure to assign them the same role you’d like them to have in your storefront.
1. In Pack's admin, click on **Settings**.
2. Select **Team**.
3. Choose the role you wish to assign to the new member and select **Add**.
### Roles and Permissions
#### Org Admin
* Can manage all aspects of the organization.
* Multiple org admins allowed.
* Permissions include:
* **Billing**:
* View billing details.
* Purchase additional seats (package accounts are not considered seats).
* **Organization Management**:
* Create a new organization.
* Transfer organization ownership.
* View organization members.
* **User Management**:
* Invite new user (any role).
* Remove any user from the organization.
#### Storefront Admin
* Can manage multiple storefronts.
* Permissions include:
* **Storefront Management**:
* List storefronts.
* Create a new storefront.
* Remove a storefront.
* Edit storefront settings.
* **Storefront Team Management**:
* Assign a storefront admin.
* Invite new developers or editors.
* Assign an existing user to a storefront.
* Remove a user from a storefront.
#### Developer
* Can manage storefront deployments and access storefront code.
* Permissions include:
* **Storefront Development**:
* Edit GitHub repository.
* Edit environment variables (with the exception of enterprise plans).
* Utilize the storefront customizer.
* Manage storefront media.
* Deploy or rollback changes.
* View revision history.
#### Editor
* Can manage storefront deployments but cannot access storefront code.
* Permissions include:
* All Developer permissions except for:
* Editing GitHub repository.
* Editing environment variables.
* Note: Only the enterprise plan offers granular user assignment.
### Removing Members from Storefronts
1. In Pack's admin, click on **Settings**.
2. Select **Team**.
3. Click on the three dots next to the member you wish to remove.
4. Press **Remove from Storefront**.
### Deleting a Storefront
> **Warning**: Only organization admins and storefront admins have the authority to delete
> Storefronts.
> **Danger**: **This action cannot be undone.**
> Once you delete a storefront, you will not
> be able to edit or access its content, create future deployments and builds,
> make API calls related to this storefront, and access the code unless you have
> cloned it to your GitHub organization.

1. In **Pack's admin**, navigate to the storefront you wish to delete.
2. Click on **Settings**.
3. Scroll down to the bottom of the page and click on **Delete Storefront**.
---
# Page Drafts have moved to Content Releases
Page Drafts have been replaced by [Content Releases](/create-manage-content/content-releases).
Content Releases let you stage page edits alongside related section, template, product, collection, blog, article, and settings edits, then review and publish everything together.
Use Content Releases when you want to:
* Prepare a campaign or merchandising launch without exposing partial changes.
* Review all related content drafts in one place.
* Detect live changes that happened after a release draft was created.
* Publish coordinated edits together.
Go to the [Content Releases guide](/create-manage-content/content-releases) for the current workflow.
---
# Content Preview System: Testing and Sharing Your Work
Previewing pages in Pack's customizer is a vital step to ensure your page looks and functions as intended before you go live. Review and test your pages across different devices and screen sizes.

## Entering Preview Mode
1. Select **Customizer** in Pack’s admin.
2. Click **Preview** — located on the right side of the top nav.
3. Your view will switch from edit mode to preview mode, hiding the Customizer's interface so that you get a clean view of the page.
## Previewing pages on Different Devices
1. While you’re in preview mode, click on the monitor icon dropdown next to the **Preview** button.
2. Select from desktop, mobile, or tablet views to see how your page adapts to different screen sizes.
3. To switch back into edit mode, click the **Edit mode** button on the right of the top nav.
## Viewing Live Page Version
Preview mode does not reflect the live version of your page. To view the current live version, follow these steps:
> **Warning**: The shared link is generated for your primary content environment. Prepend
> `&environment=YOUR_CE_HANDLE` to the URL to view other content environments.
1. In the Customizer, find the page's full URL above your page preview that appears in the **preview URL** dropdown menu.
2. Copy and paste this URL into an Incognito browser window.
3. This view reflects the page without cached data, showing only changes that are deployed and published.
---
# Working with Shopify Products in Pack
Product pages in Pack are directly linked with Shopify. They cannot be created manually in Pack but are automatically generated when new products are added and synced from Shopify.

**To create a product page:**
* Add a new product in Shopify.
* Ensure the product is added to the **Pack**, **Online Store**, and **Hydrogen** sales channels:
* **Online Store** and **Hydrogen** are necessary for the product page to be accessible on production outside of Customizer.
* **Pack** is needed for the private app to fetch the products for Pack.
* Pack will then sync the product and create a corresponding product page in Pack.

You can customize your product pages by changing their templates, or adding new sections inside Customizer.
## Collections
Collection pages are generated automatically when collections are synced from Shopify.

You can customize your collection pages by changing their templates, or adding new sections inside Customizer.

## Bundles
Bundles are an easy way to group a set of products together, so that you can to expose the bundle’s data as a whole and use it in your storefront or shop.
Bundles are commonly used to create a set of products that you can add to a cart all at once, or merchandise within an upsell experience.

### Creating a Bundle
1. In Pack Admin, go to Products > Bundles.
2. Select Create Bundle.
3. Name your bundle, give it a description, and then search and select products you would like in your bundle
## Groups
Product groupings make it easier to merchandise your product data by creating associations between products.
Products can only belong to one group at a time, so each product group is unique. If you need to associate a product with multiple groups, check out [product bundles](#bundles)
Data of products in a group will be available on the products in your storefront or shop.
### Creating a Group
1. In Pack’s Admin, go to **Products > Groups**.
2. Select **Create Group**.
3. Name your group, give it a description, and then search and select products you would like to associate with your group.

## SEO Settings
To make sure search engines can identify and display your product pages, you’ll want to check your SEO settings in Shopify.
SEO settings for your product pages are synced from Shopify's product SEO metadata and are read-only in Pack.
**Here's a breakdown of what each setting does:**
* **Page Title**: This is the title of the product page as it appears in search engine results. It should be concise and descriptive, accurately reflecting the content of the page. A well-crafted page title can improve the page's visibility in search results.
* **Meta Description**: This is a brief summary of the product page that appears below the page title in search results. It should provide a clear overview of the product and entice shoppers to click on the link.
* **URL Handle**: This is the URL slug that appears in the browser's address bar. It should be straightforward, readable, and ideally contain key terms related to the product.
* **Metafields**: These are custom fields that can be used to store additional information about a product. They can be used for SEO purposes, like adding specific attributes or keywords that might not be covered in the standard description.
**To make changes to your SEO settings that are synced from Shopify, you need to:**
* Go to the Shopify Admin.
* Navigate to **Products**, select the product, and scroll down to the SEO section.
* Here, you can edit the **Page title**, **Meta description**, **URL handle**, and **Metafields**.

In Pack’s admin on the product page, you can modify:
* **Tags**: These are keywords associated with your product that can help shoppers and search engines understand what your product is about.
* **Don't index page**: If this is enabled, it instructs search engines not to index this particular product page, meaning it won’t appear in search results.
* **Don't follow page**: When enabled, this tells search engines not to follow any links on this product page. This can be used to prevent search engines from indexing linked content that might not be relevant or authoritative.

Adjusting these settings can significantly impact how your product pages perform in search engine rankings and how attractive they are to potential customers browsing online.
## Product Syncing
Pack automatically syncs products from Shopify. This means that any changes you make to your products in Shopify will be reflected in Pack, and your storefront or shop will automatically deploy to reflect any updates.
---
# Properties Panel
> **Warning**: For storefronts using A/B testing: The Properties Panel is not yet available in this version, but we are working on integrating it and it will be available soon.
Properties Panel is a powerful tool that allows you to customize the look and feel of your content. It's particularly useful when you have an existing section that you like but want to tweak it slightly without asking a developer. The panel provides an interface for adjusting visual properties of elements on your page, making it easy to create polished designs without needing any coding knowledge.
## Coming Soon: AI Styling
We're building an AI assistant that will handle styling for you automatically. Soon, you'll be able to simply describe the look you want ("make this section feel more premium" or "create a newsletter signup that stands out") and the AI will adjust all these properties behind the scenes. This will make creating beautiful, on-brand content even faster and easier!
## Getting Started
To start customizing your content, go to the Customizer and click the crosshair icon in the bottom right corner. This lets you click on any part of your page and instantly edit how it looks using the panel on the right side.

## What You Can Customize
### LAYOUT
**Display**
* **Block**: Perfect for headers, paragraphs, or any content that should take up its own line
* **Inline**: Great for buttons or text that should sit side-by-side with other content
* **Flex**: Ideal when you want to arrange multiple items in a neat row or column
* **Grid**: Best for creating organized layouts with multiple sections
*Common use case: You have three feature cards that are stacking vertically, but you want them side-by-side. Change the container to "Flex" to arrange them in a row.*
**Position** Use the dropdown to choose how the element is positioned, then adjust:
* **Top/Bottom**: Move elements up or down on the page
* **Left/Right**: Move elements left or right on the page
*Common use case: You want to add a "Sale!" badge that sits in the top-right corner of a product image, or move a signup form closer to your headline.*
### DIMENSIONS
**Width**: Make your content boxes, images, or sections wider or narrower. You can use exact pixel sizes (like 300px) or percentages.
**Height**: Control how tall your elements are, using pixels or other units.
*Common use case: Your call-to-action button feels too small and gets lost on the page - increase the width and height to make it more prominent and clickable.*
### SPACING
**Margin**: Adds breathing room *around* your content - like creating a buffer zone between elements. You can set different values for all sides.
**Padding**: Adds space *inside* your content box between the edges and your actual content - perfect for giving text more breathing room or making buttons feel less cramped (note: this will make the content area smaller within the box). You can adjust padding for all sides.
*Common use case: Your sections feel cramped and hard to read. Add margin to create space between sections, and add padding to give text more breathing room inside each box.*
### BACKGROUND
**Background Color**: Click to add a color behind your content to make sections stand out or match your brand colors.
*Common use case: You want to highlight an important announcement or make your testimonials section stand out from the rest of your page by giving it a colored background.*
### TYPOGRAPHY
**Color**: Click to add color and change your text color to match your brand or improve readability.
**Font Size**: Make text bigger for headlines or smaller for fine print using pixel values.
**Font Weight**: Use the dropdown to make text bold or adjust how thick/thin it appears.
**Line Height**: Adjust spacing between lines of text for better readability.
**Letter Spacing**: Spread out or tighten up letters for stylistic effect.
**Text Align**: Use the dropdown to center headlines, left-align body text, or right-align content.
*Common use case: Your headline doesn't feel important enough - make it bigger, bolder, and center-aligned. Or your paragraph text feels too dense - increase the line height for easier reading.*
### BORDER
**Border Width**: Add thin or thick borders around content boxes using pixel values.
**Border Radius**: Create rounded corners for a modern, friendly look using pixel values.
**Border Style**: Use the dropdown to choose solid lines, dashed lines, or dotted borders.
**Border Color**: Click to add color and match borders to your brand colors.
*Common use case: You want to make your signup form look more modern by adding rounded corners, or create a visual separation by adding a subtle border around your pricing table.*
### EFFECTS
**Box Shadow**: Add subtle shadows to make elements appear to "pop off" the page - great for call-to-action buttons or important sections.
*Common use case: Your main CTA button blends into the page too much - add a subtle shadow to make it appear elevated and more clickable.*
## Pro Tips for Success
**Start with the Right Element**: Always check that you've selected the specific piece of content you want to change. The properties you see will only affect what's currently selected.
**Nested Content Styling**: Some content is organized in layers (like an accordion with multiple sections inside). If you style the outer container but don't see changes, you might need to select and style the inner content pieces individually.
Example of changing the color of text in an outer container (notice the inner content is not affected)

Example of changing the color of text in an inner container (notice the inner content is affected)

**Preview as You Go**: Make small changes and see how they look before making big adjustments. This helps you find the perfect balance for your design.
---
# Publishing Content: From Draft to Live Site
Publishing pages is an essential step in making your content visible to customers on your live storefront or shop.
When you publish a page, you are essentially pushing all the changes and updates you've made to the page in the Customizer live for your customers to see.
For coordinated launches that include multiple content items, use [Content Releases](/create-manage-content/content-releases) to stage related edits and publish them together.
Upon clicking Publish, a new deploy begins, which updates your storefront or shop with the latest change. The time taken for this content to be visible will vary based on your caching settings.
## Publishing Pages

**To make a page available on your live storefront or shop:**
1. Access the **Customizer**.
2. Select the page you wish to publish.
3. Click the **Publish** button located in the upper right corner of the Customizer, next to the Preview button.
Alternatively, you can publish a page from Pack’s admin > **Pages** > three dots menu for the selected page > **Publish**.

The storefront or shop will rebuild to incorporate the latest changes on that page.
## Publishing Content Releases
Content Releases publish a group of release-scoped drafts together. This is useful when a launch includes changes across multiple pages, sections, templates, products, collections, blogs, articles, or settings.
Before publishing, Pack checks whether any live content changed after the release drafts were created. If conflicts exist, the publish dialog shows the affected drafts and field-level differences so you can cancel or intentionally publish anyway.
Learn the full workflow in the [Content Releases guide](/create-manage-content/content-releases).
## Unpublishing Pages
To remove a page from your live storefront or shop, you can follow the same steps as above, but instead of clicking Publish, click Unpublish.
The storefront or shop will rebuild, and the page will no longer be live (regardless of whether auto-publish is enabled).
## Product status in Shopify vs. Pack
Pack synchronizes with Shopify to ensure that all products, whether in draft or published status, are reflected within Pack. However, it is important to note that deleted or archived products will not appear in the Pack Admin -> Products section.
**For a product to be accessible within the Pack Customizer, it must meet the following requirements:**
* The product must be available (i.e., not in draft status) within Shopify.
* It must be added to the **Pack**, **Online Store**, and **Hydrogen** sales channels:
* **Online Store** and **Hydrogen** are necessary for the product page to be accessible on production outside of Customizer.
* **Pack** is needed for the private app to fetch the products for Pack.
### Understanding the Draft Status in Pack vs. Shopify:
* **Draft in Shopify:** If a product or collection is in draft status in Shopify, it means the product is not yet ready for purchase and will not be accessible within the Pack Customizer.
* **Draft in Pack:** A product or collection that is not in draft status in Shopify but is marked as draft in Pack will have all its sections visible within Pack Customizer and Preview Mode, however it will show the default template without any sections on the live storefront.
You must publish the product or collection in both Shopify and Pack to make it visible with all the customizations on the live storefront.
---
# URL Management: Setting Up and Managing Redirects
You can manage all your URL redirects in Shopify. Here's how:
1. Navigate to **Shopify Admin** -> **Apps** -> **Online Store** -> **Navigation**.
2. Click on **'View URL Redirects'**.
3. Click on **'Create URL Redirect'** or **'Import URL Redirects'**.

---
# Content Scheduling: Automating Future Content Publication
With Pack, you can schedule your content to go live at a specified time. This feature is particularly useful for coordinating new product launches, special promotions, or timed discounts.
By scheduling a page or storefront/shop settings for a specific date and time, Pack will automatically deploy and publish these changes at the designated time.
## Schedule a Page
You can add a page to a schedule via the Customizer or Pack’s admin. Please allow up to 15 minutes for the scheduled changes to go live. Wait times will be impacted by your Hydrogen caching policy.
### Using the Customizer
1. Navigate to the **Customizer**.
2. Make changes to your page.
3. Select "Schedule Page" in the top left corner.
4. Choose to create a new schedule or add to an existing one.
5. Confirm your schedule by checking that your page indicates a 'Scheduled' page status.

### Using Pack's Admin
1. In Pack’s admin, go to **Pages**.
2. Click the three dots next to a page and select **Add to Schedule**.
3. Choose to create a new schedule or add to an existing one.
4. All pages under a schedule will be deployed and published at the set time.

## Schedule Site Settings
Storefront or shop settings can be scheduled the same way as pages, and can share the same schedule.

1. In Pack’s admin, navigate to **Customizer** and click on **Site Settings**.
2. Make changes and select **Site Settings > Schedule Site Settings**.
3. Fill out the schedule details, and press "Create Schedule."
## Remove Page from a Schedule

### Using the Customizer
1. Navigate to the **Customizer** and select the **Scheduled** button for the page.
2. Choose the schedule to remove the page from and click the pencil icon.
3. Remove the page from the schedule.
### Using Pack Admin
1. Go to Pack’s admin, then click **Schedules**.
2. Click on the schedule and remove the page.
## Edit an Existing Schedule
### Using the Customizer
1. Navigate to the **Customizer** and select **Scheduled**.
2. Edit the schedule details.
### Using Pack Admin
1. Go to Pack’s admin, then click **Schedules**.
2. Click on the schedule to edit and save changes.
## Run a Schedule Manually
> **Note**: This is only available in Pack’s admin.

1. Go to Pack’s admin > **Schedules**.
2. Select **Run Schedule Now** for immediate deployment.
3. Depending on your storefront's or shop's caching policy, your content may take time to appear even after content has been published.
## Delete a Schedule
> **Note**: A schedule that hasn’t run yet will not be executed if it's deleted. Old
> schedules that have already run can be deleted without any issues.

### Using the Customizer
1. Navigate to the **Customizer** and select **Scheduled**.
2. Choose the schedule and delete it.
### Using Pack’s Admin
1. Go to Pack’s admin, click **Schedules**, select the schedule and click **Delete**.
## Clear a Schedule
You can clear an existing schedule to clear the time and date without deleting the schedule itself.
### Using Pack’s Admin
1. Go to Pack’s admin, click **Schedules**, select the schedule, and click **Clear Schedule**.
---
# Working with Sections: Creation, Management and Templates
You can view all of your sections via Pack’s Admin > **Sections**, and manage them in Pack’s [Customizer](/create-manage-content/customizer).

## Local Sections
Local sections are unique, and only appear on one page. By default, any new section you create the customizer will be a local section.
1. Select **Customizer** in Pack's admin.
2. Find the **Sections** menu on the left panel and click the **+**
3. Click **Add New Section**
4. Choose the section(s) you want to add; multiple selections are possible.
5. Advanced users have the option to add an HTML section to create a section from scratch using HTML.
6. Click **Add Selected**
7. Click on the new section to start editing it.

You can edit, duplicate, or delete, your section by clicking the three dots next to the Page dropdown menu—located at the top of the left hand panel.
## Copying a Section
You can create an independent copy of an existing, populated section from another page. This process creates a distinct version of the section. When you make edits to the copy of your section, your changes will not affect the original version.
1. Select **Customizer** in Pack's admin.
2. Find the **Sections** menu on the left panel and click the **+**
3. Select **Copy Existing Section**
4. Find the section you want to copy by name and select it to create a unique copy.
5. Click on the new section to start editing it.

## Linking a Section
When you link a section, you’re adding a pre-existing, populated section from another page to your current page. This creates a link between the sections, displaying identical content on both pages.
Any edits made to a linked section will be reflected on all pages where it's displayed, due to its 'linked' nature.
1. Select **Customizer** in Pack’s admin.
2. Find the **Sections** menu on the left panel and click the **+**
3. Select **Link Existing Section**
4. Search for the existing section by name and click to link it.

> **Warning**: When you delete a linked section, you’ll need to re-add it, customize it, and
> re-link it to all of your pages.
## Nested Sections
Nested sections let editors add a section as a **block inside another section**, rather than as a top-level section on a page. This is useful when you have a reusable content unit — a promo banner, a feature card, a CTA — that needs to appear inside other sections (e.g., inside a grid, accordion, or carousel) and stay in sync wherever it's used.
> **Note**: Nested sections require a developer to enable the feature on a section's
> blocks field. See [Section schema → Blocks
> Field](/developer-resources/section-schema-api#blocks-field) for the schema
> configuration.
### Adding a nested section
Once your developer has enabled section types on a blocks field, the field's picker will offer two new options alongside the normal inline blocks:
* **Section types** — creates a brand-new section of the chosen type, linked into this block.
* **Insert from Library** — embeds an existing section from your store as a block.
### Local vs linked nested sections
Just like top-level sections, nested sections can be **local** (used in one place) or **linked** (used in multiple places).
* A nested section with a **gray icon** is local — only this block references it.
* A nested section with a **purple icon** and colored label is linked — it appears elsewhere too.
When you expand a linked nested section, a banner reminds you that your changes will apply everywhere it's used.
### Editing a nested section
Click into a nested section block to drill into its full editor — the same one you'd see for a top-level section. Use the action menu (the `⋯`) to rename the block.
Renaming a nested section updates its title everywhere — the rename is on the underlying section, not the reference.
### Publishing
When you publish a parent section (or the page it lives on), Pack automatically publishes any nested sections inside it that have draft changes. You don't need to manually track which children need to ship — the modal will tell you how many nested sections are included.
> **Note**: **Unpublishing a parent does NOT unpublish nested sections.** Because a nested
> section may be used in other places, Pack leaves it published so it stays live
> wherever else it's referenced. To take a nested section fully offline,
> unpublish it directly from the Sections admin.
### References card
Open a section's detail page in the Sections admin to see the References card. It shows which pages, templates, products, collections, blogs, articles, **and other sections** reference this one. Click any row to navigate to the parent.
### Depth limit
Nested sections can be nested up to **three levels deep**. Past that, the picker will hide section types — only inline blocks can be added.
## Section Templates
Section Templates simplify content management for your e-commerce site by letting you apply and update key sections—like banners, product highlights, or promotions—across multiple pages at once. Make a change in one place, and it instantly updates everywhere, ensuring a consistent shopping experience while saving you time and effort.

### Applying a Section Template to a page
You can simply change or apply a section template to a page by following these steps:
1. Go to the **Customizer**.
2. Click on the **Section Template** button above the sections.
3. Select the desired section template from the list.
4. Click **Save** button.

### Making section changes apply for all pages
When you want to make changes to a section that is part of a Section Template and have it apply to all pages that use the template, you can do so editing the section in the Section Template list.
To do this, click the **Section Template** button above the sections, find the section you want to edit, customize the content, and save.

### Making changes apply to a specific page
You can customize the content of a section within a Section Template for a specific page without impacting other pages.
To do this, in the page’s section list, find the section that belongs to the Section Template, customize the content to fit your needs, and save.
You will get an notification that the section has been customized for this page.

The section will now be marked as customized, indicated by the dashed border and broken chain icon, and changes made to the section is unique to this page.

### Reverting specific page changes
If you have customized a section for a specific page and want to revert the changes to match the Section Template, find the section in the page's section list, click the action menu, and select **Reset changes**.

### Section Template Section Visibility
When you apply a Section Template to a page, all sections within the template will be added to the page. You can choose to hide the section per page or for all pages that use the template.
To hide a section for a specific page, find the section in the page's section list, click the action menu, and select **Hide section on page**.

To hide a section on all pages, click the **Section Template** button above the sections, find the section you want to hide, click the action menu, and select **Hide section**.

## Adding a New Section
You can add a new section via [Customizer](/create-manage-content/customizer).

To add a section to multiple pages, follow these steps:
1. Navigate to Pack's admin, then select **Pages**.
2. Check the box next to all the pages you want to add the section to.
3. Click on the three dots at the top of the page list and select **Add sections**.

## Publishing and Unpublishing Sections
Publishing and unpublishing sections allows you to control the visibility of specific content elements across your storefront or shop.
This feature is particularly valuable for managing dynamic content, such as promotional banners, seasonal messages, or time-sensitive announcements.
### When you publish a section:
* The section becomes visible on all pages where it is used.
* This is ideal for displaying content like promotional banners, which you might want to appear across multiple pages at the same time.
### To publish a section:
1. Navigate to Pack’s admin, then select **Sections**.
2. Click on the three dots next to the Section name you wish to publish.
3. Select Publish.

## Hiding a Section
Hiding a section is useful when you want to temporarily remove a section from the live site without deleting it.
1. Navigate to Pack's Customizer to the page where the section is located.
2. Click on the three dots next to the Section name you wish to hide.
3. Select Hide Section.
## Deleting a Section
> **Warning**: This action is irreversible. The section will be removed from all the pages
> it's currently being used in.
1. Navigate to Pack’s admin > **Sections**.
2. Find the section you wish to delete and click on the three dots next to the section name.
3. Choose **Delete** and confirm.

## Viewing all pages linked to a specific Section
You can verify which pages are currently using a specific section by following these steps:
1. Navigate to Pack’s admin > **Sections**.
2. Click on the particular Section.
3. Review the 'References' section which lists all pages the section is currently being used in.

## Section Folders
Section Folders help you organize your sections into logical groups, making it easier to find and manage sections as your library grows.
### Creating a Folder
1. Navigate to Pack's Admin > **Sections**.
2. In the Folders panel, click the **+** icon in the header.
3. Enter a name for your folder and click **Save**.
### Adding Sections to a Folder
You can add sections to folders from the section's action menu:
1. Navigate to Pack's Admin > **Sections**.
2. Find the section you want to organize and click the three dots next to its name.
3. Select **Add to Folder** and choose the destination folder.
You can also add sections to folders from the Customizer when working with existing sections.
### Filtering by Folder
Once you have sections organized into folders, you can filter to view only sections in a specific folder:
**In Admin:**
1. Navigate to Pack's Admin > **Sections**.
2. Click on a folder name in the Folders panel to filter the section list.
3. Click **All Sections** to view all sections again.
**In Customizer:**
1. Open the Customizer and click **+** to add a section.
2. Select the **Existing** tab to browse existing sections.
3. Expand the **Folders** section in the sidebar and click a folder name to filter.
### Renaming a Folder
1. Navigate to Pack's Admin > **Sections**.
2. Hover over the folder you want to rename and click the three dots.
3. Select **Rename folder** and enter the new name.
4. Click **Save**.
### Removing Sections from a Folder
1. Navigate to Pack's Admin > **Sections**.
2. Find the section you want to remove from its folder and click the three dots.
3. Select **Remove from Folder**.
The section will remain in your library but will no longer appear in that folder.
### Deleting a Folder
1. Navigate to Pack's Admin > **Sections**.
2. Hover over the folder you want to delete and click the three dots.
3. Select **Delete folder** and confirm.
> **Warning**: Deleting a folder does not delete the sections inside it. The sections will remain in your library and can be found in **All Sections**.
## Coding a new section
Sections are React components that have the capability to be rendered and interacted with via Customizer.
Sections allow you, as a developer, to create components that are editable by a storefront editor and have a live preview of your component changing as you edit the storefront or shop. All your sections live in the /sections folder of your codebase.
You can create a new section by adding a new file to the /sections folder. The file should be a .tsx file that exports a React component.
export const MyFirstSection = ({ cms }: any) => {
// In this example cms would be { howdy: 'Howdy Label' }
// defined by its Schema
return (
Hello, my name is {cms.howdy}
);
}
// Section Customizer Schema
MyFirstSection.Schema = {
label: 'My First Section',
key: 'myFirstSection',
fields: [
{
name: 'howdy',
component: 'text',
label: 'Howdy Label',
}
]
}
The cms prop on the component will match up with your components Schema fields. You can see there is a text component on the Schema field named howdy, which becomes an input text field in the Customizer where its value can now be rendered through your React component. See the full list of available components in the [Section Schema API](/section-schema-api).
### Registering your section
The final step to start using your component is to register it in the `index.tsx` of the `/sections` folder. This will allow your component to be available in the Customizer.
1. Import your section into `/sections/index.tsx`.
2. Add it to the `registerSections` function.
3. Refresh the Customizer.
import { MyFirstSection } from './MyFirstSection';
export function registerSections() {
registerSection(MyFirstSection, { name: my-first-section' });
}
* [Section Schema API](/developer-resources/section-schema-api): Learn how to use the Section Schema API to create dynamic sections.
---
# SEO Optimization for Pack Storefronts and Shops
Search Engine Optimization (SEO) is crucial for improving your storefront's visibility on search engines, driving organic traffic, and ultimately increasing sales. Effective SEO ensures that your content ranks higher in search results, making it easier for potential customers to find your products and services.
## Managing Storefront SEO Settings
To manage the SEO settings for your entire storefront:
1. Navigate to Pack Admin, click on **Settings** - **Storefront Settings**. Scroll down and make the desired changes to your SEO here.

2. Once done, scroll back up, and click **Save**.
3. To publish the changes live to your site, click on the three dots next to the Save button and click on **Publish Storefront Settings** button to apply the changes.

## Managing Page, Article, and Blog SEO Settings
For optimizing SEO on specific pages, articles, or blog posts:
1. Navigate to Pack Admin and select **Pages** or **Articles**.
2. Navigate to the particular page, article, or blog you want to optimize.
3. On the right side of the page, you will find the SEO settings section where you can modify meta titles, descriptions, keywords, and other relevant SEO parameters.

## Managing Product Page and Collection SEO Settings
To optimize SEO for your product pages and collections:
1. Navigate to Pack Admin.
2. Navigate to **Products** or **Collections** depending on what you wish to edit.
3. Click on the desired product or collection to view the current SEO settings. This is read only.

4. Click the three dots next to the product or collection and select **View in Shopify admin** to edit the SEO settings in Shopify.

## Customizing the behavior of SEO settings in Blueprint
You can customize the behavior of SEO settings, such as page titles and descriptions, by modifying the `app/lib/seo.server.ts` file.
For more information, check out our [SEO and Meta Tags documentation](/developer-resources/blueprint-setup#seo-and-meta-tags).
---
# Storefront Settings Drafts have moved to Content Releases
Storefront Settings Drafts have been replaced by [Content Releases](/create-manage-content/content-releases).
Content Releases let you stage Storefront or Shop Settings changes alongside related page, section, template, product, collection, blog, and article edits, then review and publish everything together.
Use Content Releases when you want to:
* Prepare global header, footer, navigation, or cart updates with related page changes.
* Review all related content drafts in one place.
* Detect live changes that happened after a release draft was created.
* Publish coordinated edits together.
Go to the [Content Releases guide](/create-manage-content/content-releases) for the current workflow.
---
# Templates in Pack: Understanding and Managing Page Structures
> **Warning**: Templates are only available on Pack storefronts.
## Changing page's section template
You can change a page section template inside the Customizer:
1. Navigate to the **Customizer**.
2. Click on **Section Template settings** in the left panel.
3. Select a template from the dropdown menu.
4. Click **Save**.

## Creating a section template
You can create a new template in Pack’s admin:
1. Navigate to Pack’s admin.
2. Click on **Sections > Templates** in the left sidebar.
3. Click on **Create template** in the top right corner.
4. Enter a name for your template, select a template type and optionally add sections.
5. Click **Save**.

## Editing a section template
You can edit a section template in Pack’s admin:
1. Navigate to Pack’s admin.
2. Click on **Sections > Templates** in the left sidebar.
3. Click on the template you want to edit.
## Duplicating a section template
You can easily duplicate a template with all its sections in Pack’s admin:
1. Navigate to Pack’s admin.
2. Click on **Sections > Templates** in the left sidebar.
3. Click on the three dots next to the template you want to duplicate.
4. Choose whether you want to copy the existing sections as new sections or link to the existing sections.
## Deleting a section template
> **Warning**: Currently, you cannot delete a template. This feature is coming soon.
## Publishing a section template
You can publish a template in Pack’s admin:
1. Navigate to Pack’s admin.
2. Click on **Sections > Templates** in the left sidebar.
3. Click on the three dots next to the template you want to publish.
4. Click **Publish**.
You can follow the same steps to unpublish a template.

* [Developing with Templates](/developer-resources/templates): Learn how to customize React-based route templates to define page layouts and render dynamic sections in your Pack storefront.
* [Sections](/create-manage-content/sections): Learn more about sections
* [Localhost Setup](/developer-resources/blueprint-setup): Learn how to set up your localhost
---
# Hydrogen + Pack Integration: Technical Implementation Guide
Integrating Pack into your existing Hydrogen project is simple and takes just a few minutes.
Pack simplifies building and managing your Hydrogen storefront with easy-to-use developer APIs, CRO tools, and a visual page editor. This makes it easier and faster to customize and enhance your storefront needs.
## Prerequisites
1. A Shopify Hydrogen v2 project
2. A [Pack](https://app.packdigital.com/) account
3. Node.js version 16.14.0 or higher
4. `npm` (or your package manager of choice, such as `yarn` or `pnpm`)
### Install the Pack dependencies
1. Install the latest version of [`@pack/hydrogen`](/pack-hydrogen)
```bash {{ title: 'npm'}}
npm install @pack/hydrogen@latest
```
2. Install the latest version of [`@pack/react`](/pack-react)
```bash {{ title: 'npm'}}
npm install @pack/react@latest
```
### Update remix.env.d.ts (optional if using Typescript)
If you are using typescript in your Hydrogen project, you will need to add some additional Pack types to your environment definition types.
```typescript {{ title: 'remix.env.d.ts'}}
interface Env {
SESSION_SECRET: string
PUBLIC_STOREFRONT_API_TOKEN: string
PRIVATE_STOREFRONT_API_TOKEN: string
PUBLIC_STORE_DOMAIN: string
PUBLIC_STOREFRONT_API_VERSION: string
PUBLIC_STOREFRONT_ID: string
PACK_PUBLIC_TOKEN: string
PACK_SECRET_TOKEN: string
PACK_STOREFRONT_ID: string
PACK_CONTENT_ENVIRONMENT?: string
PUBLIC_PACK_CONTENT_ENVIRONMENT?: string
PACK_API_URL?: string
PRIVATE_SHOPIFY_CHECKOUT_DOMAIN?: string
PRIVATE_SHOPIFY_STORE_MULTIPASS_SECRET?: string
}
```
```typescript {{ title: 'remix.env.d.ts'}}
import type {Pack} from '@pack/hydrogen';
...
declare module '@shopify/remix-oxygen' {
export interface AppLoadContext {
session: HydrogenSession;
storefront: Storefront;
env: Env;
pack: Pack;
}
}
```
### Modify your `server.ts`
1. Import `createPackClient` and `PreviewSession` from `@pack/hydrogen`.
```typescript {{ title: 'server.ts'}}
import { createPackClient, PackSession, handleRequest } from '@pack/hydrogen'
```
2. Initialize Pack's preview session where you initialize the `AppSession`. This will create a session storage that Pack will use to manage if you are in preview mode or not.
```typescript {{ title: 'server.ts'}}
const [cache, session, packSession] = await Promise.all([
caches.open('hydrogen'),
AppSession.init(request, [env.SESSION_SECRET]),
PackSession.init(request, [env.SESSION_SECRET]),
])
```
3. Creating the `pack` client
```typescript {{ title: 'server.ts'}}
const pack = createPackClient({
cache,
waitUntil,
storeId: env.PACK_STOREFRONT_ID,
token: env.PACK_SECRET_TOKEN,
session: packSession,
contentEnvironment: env.PACK_CONTENT_ENVIRONMENT,
})
```
4. Adding the Pack client to app load context. By adding `pack` to your loader context, you can use this Pack client to make requests to fetch data from [Pack's CMS API](/content-management-api)
```typescript {{ title: 'server.ts'}}
const response = await handleRequest(
pack,
request,
createRequestHandler({
build: remixBuild,
mode: process.env.NODE_ENV,
getLoadContext: () => ({
cache,
waitUntil,
session,
storefront,
cart,
env,
pack,
}),
}),
)
```
### Adding preview route
By adding this `api.edit.ts` route, this will allow the customizer and preview links to authenticate and set your storefront in preview mode to pull in draft content.
1. Create a `api.edit.ts` file in `./app/routes`
2. Add in the action and loader handlers
```typescript {{ title: '/app/routes/api.edit.ts'}}
import { previewModeAction, previewModeLoader } from '@pack/hydrogen'
import type { ActionFunction, LoaderFunction } from '@shopify/remix-oxygen'
export const action: ActionFunction = previewModeAction
export const loader: LoaderFunction = previewModeLoader
```
### Add Pack's `PreviewProvider` to your `root.tsx`
1. Request Pack data in root loader
```typescript {{ title: '/app/root.ts'}}
export async function loader({context, request}: LoaderFunctionArgs) {
const {storefront, session, pack} = context;
const isPreviewModeEnabled = pack.isPreviewModeEnabled();
const siteSettings = await context.pack.query(SITE_SETTINGS_QUERY);
return defer({
customizerMeta: pack.preview?.session.get('customizerMeta'),
isPreviewModeEnabled,
siteSettings,
...
})
})
const SITE_SETTINGS_QUERY = `#graphql
query SiteSettings($version: Version) {
siteSettings(version: $version) {
id
status
settings
publishedAt
createdAt
updatedAt
favicon
seo {
title
description
keywords
}
}
}
`
```
2. Wrap your app in the [`PreviewProvider`](/pack-react#preview-provider). This will provide context to get setting and other data from Pack.
```typescript {{ title: '/app/root.ts'}}
import {PreviewProvider} from '@pack/react';
import { useLoaderData } from '@remix-run/react';
...
export default function App() {
const {customizerMeta, isPreviewModeEnabled, siteSettings} = useLoaderData();
return (
...
);
}
```
### You're done!
After this you are set to start querying data in your route `loaders` and [rendering sections](/pack-react#render-sections). You should be able to use the customizer and edit and add new sections.
You can take a look at our [Blueprint for storefronts](https://github.com/packdigital/pack-hydrogen-theme-blueprint) or [Blueprin for shops](https://github.com/packdigital/pack-shop-theme-blueprint) and check out how we structure and implement our Hydrogen project to manage and render sections.
Feel free to also take a look at some of our additional resources and guides below to learn more about storefront or shop development and customization.
* [Setting up a Pack storefront](/getting-started/storefront-setup): Learn how to set up a Pack storefront.
* [Learn more about @pack/hydrogen](/pack-hydrogen): Learn more about the @pack/hydrogen package.
* [Understanding Sections](/create-manage-content/sections#coding-a-new-section): Programmatically create your own sections
* [How to register a custom section](/pack-react#register-section): Learn how to register your section.
* [All about @pack/react](/pack-react): Learn more about the components and hooks from @pack/react
---
# Blueprint Admin Requirements
You can find the document at [this link](https://pattern-shape-99f.notion.site/Blueprint-Admin-User-Requirements-16dd4b2c5c7280ce8c47f2062e92a0a1).
This document outlines the administrative requirements and specifications for various content sections within the Pack Blueprint theme. It provides detailed guidelines for different types of content displays, including text elements, hero sections, product presentations, and media layouts.
---
# Container Settings in Pack: Implementation and Usage
This guide explains how to implement and use container settings in your Pack-powered Hydrogen storefront. Container settings provide consistent spacing, width, and alignment controls that can be applied to any section in your storefront.
## What Are Container Settings?
Container settings are a reusable group of fields that control the layout container for sections throughout your storefront. They typically include:
* Section padding (top, bottom)
* Container width
* Container alignment
* Background color or image
* Content width constraints
By implementing container settings as a reusable function, you ensure consistent layout options across all your sections.
## Implementing Container Settings
Here's how to implement container settings as a reusable module:
```ts
// app/settings/container.ts
import { COLOR_PICKER_DEFAULTS } from '~/settings/common';
export function containerSettings() {
return {
label: 'Container Settings',
name: 'container',
component: 'group',
description: 'Section padding, width, background',
fields: [
// Padding settings
{
label: 'Section Padding',
name: 'padding',
component: 'group',
fields: [
{
label: 'Top Padding (desktop)',
name: 'topDesktop',
component: 'select',
options: [
{ label: 'None', value: 'md:pt-0' },
{ label: 'Small', value: 'md:pt-4' },
{ label: 'Medium', value: 'md:pt-8' },
{ label: 'Large', value: 'md:pt-16' },
{ label: 'Extra Large', value: 'md:pt-24' },
],
defaultValue: 'md:pt-16',
},
{
label: 'Bottom Padding (desktop)',
name: 'bottomDesktop',
component: 'select',
options: [
{ label: 'None', value: 'md:pb-0' },
{ label: 'Small', value: 'md:pb-4' },
{ label: 'Medium', value: 'md:pb-8' },
{ label: 'Large', value: 'md:pb-16' },
{ label: 'Extra Large', value: 'md:pb-24' },
],
defaultValue: 'md:pb-16',
},
{
label: 'Top Padding (mobile)',
name: 'topMobile',
component: 'select',
options: [
{ label: 'None', value: 'pt-0' },
{ label: 'Small', value: 'pt-4' },
{ label: 'Medium', value: 'pt-6' },
{ label: 'Large', value: 'pt-10' },
{ label: 'Extra Large', value: 'pt-16' },
],
defaultValue: 'pt-10',
},
{
label: 'Bottom Padding (mobile)',
name: 'bottomMobile',
component: 'select',
options: [
{ label: 'None', value: 'pb-0' },
{ label: 'Small', value: 'pb-4' },
{ label: 'Medium', value: 'pb-6' },
{ label: 'Large', value: 'pb-10' },
{ label: 'Extra Large', value: 'pb-16' },
],
defaultValue: 'pb-10',
},
],
},
// Background settings
{
label: 'Background Settings',
name: 'background',
component: 'group',
fields: [
{
label: 'Background Type',
name: 'type',
component: 'radio-group',
direction: 'horizontal',
variant: 'radio',
options: [
{ label: 'None', value: 'none' },
{ label: 'Color', value: 'color' },
{ label: 'Image', value: 'image' },
],
defaultValue: 'none',
},
{
label: 'Background Color',
name: 'color',
component: 'color',
colors: COLOR_PICKER_DEFAULTS,
condition: { field: 'type', value: 'color' },
},
{
label: 'Background Image',
name: 'image',
component: 'image',
condition: { field: 'type', value: 'image' },
},
{
label: 'Background Image Position',
name: 'position',
component: 'select',
options: [
{ label: 'Top', value: 'bg-top' },
{ label: 'Center', value: 'bg-center' },
{ label: 'Bottom', value: 'bg-bottom' },
],
defaultValue: 'bg-center',
condition: { field: 'type', value: 'image' },
},
{
label: 'Background Image Size',
name: 'size',
component: 'select',
options: [
{ label: 'Cover', value: 'bg-cover' },
{ label: 'Contain', value: 'bg-contain' },
{ label: 'Auto', value: 'bg-auto' },
],
defaultValue: 'bg-cover',
condition: { field: 'type', value: 'image' },
},
],
},
// Container width settings
{
label: 'Container Width',
name: 'width',
component: 'select',
options: [
{ label: 'Full Width', value: 'w-full' },
{ label: 'Extra Large (1280px)', value: 'max-w-screen-xl mx-auto' },
{ label: 'Large (1024px)', value: 'max-w-screen-lg mx-auto' },
{ label: 'Medium (768px)', value: 'max-w-screen-md mx-auto' },
{ label: 'Small (640px)', value: 'max-w-screen-sm mx-auto' },
],
defaultValue: 'max-w-screen-xl mx-auto',
},
// Content alignment
{
label: 'Content Alignment',
name: 'alignment',
component: 'select',
options: [
{ label: 'Left', value: 'text-left' },
{ label: 'Center', value: 'text-center' },
{ label: 'Right', value: 'text-right' },
],
defaultValue: 'text-left',
},
],
defaultValue: {
padding: {
topDesktop: 'md:pt-16',
bottomDesktop: 'md:pb-16',
topMobile: 'pt-10',
bottomMobile: 'pb-10',
},
background: { type: 'none' },
width: 'max-w-screen-xl mx-auto',
alignment: 'text-left',
},
};
}
```
## Using Container Settings in Section Schemas
```ts
// app/sections/HeroSection/HeroSection.schema.ts
import { containerSettings } from '~/settings/container';
export function Schema() {
return {
category: 'Hero',
label: 'Hero Section',
key: 'hero-section',
previewSrc: 'https://example.com/preview.jpg',
fields: [
{
label: 'Heading',
name: 'heading',
component: 'text',
defaultValue: 'Welcome to our store',
},
{
label: 'Subheading',
name: 'subheading',
component: 'text',
defaultValue: 'Discover our amazing products',
},
containerSettings(),
],
};
}
```
## Applying Container Settings in Components
```tsx
// app/sections/HeroSection/HeroSection.tsx
import React from 'react';
import type { SectionProps } from '~/lib/types';
export function HeroSection({ data }: SectionProps) {
const { heading, subheading, container } = data;
const { padding, background, width, alignment } = container;
const containerClasses = [
padding.topDesktop,
padding.bottomDesktop,
padding.topMobile,
padding.bottomMobile,
width,
alignment,
].join(' ');
let backgroundStyle = {};
if (background.type === 'color') {
backgroundStyle = { backgroundColor: background.color! };
} else if (background.type === 'image' && background.image) {
backgroundStyle = {
backgroundImage: `url(${background.image.url})`,
backgroundPosition: background.position,
backgroundSize: background.size,
};
}
return (
{heading}
{subheading}
);
}
```
## Reusable SectionContainer Component
```tsx
// app/components/SectionContainer.tsx
import React from 'react';
interface SectionContainerProps {
container: {
padding: {
topDesktop: string;
bottomDesktop: string;
topMobile: string;
bottomMobile: string;
};
background: {
type: 'none' | 'color' | 'image';
color?: string;
image?: { url: string };
position?: string;
size?: string;
};
width: string;
alignment: string;
};
className?: string;
children: React.ReactNode;
}
export function SectionContainer({
container,
className = '',
children,
}: SectionContainerProps) {
const { padding, background, width, alignment } = container;
const containerClasses = [
padding.topDesktop,
padding.bottomDesktop,
padding.topMobile,
padding.bottomMobile,
width,
alignment,
className,
].join(' ');
let backgroundStyle = {};
if (background.type === 'color') {
backgroundStyle = { backgroundColor: background.color! };
} else if (background.type === 'image' && background.image) {
backgroundStyle = {
backgroundImage: `url(${background.image.url})`,
backgroundPosition: background.position,
backgroundSize: background.size,
};
}
return (
{children}
);
}
```
## Extending Container Settings
```ts
export function extendedContainerSettings(additionalOptions = {}) {
const baseSettings = containerSettings();
const extendedFields = [
...baseSettings.fields!,
{
label: 'Content Width',
name: 'contentWidth',
component: 'select',
options: [
{ label: 'Full Width', value: 'w-full' },
{ label: 'Three-quarters', value: 'w-3/4 mx-auto' },
{ label: 'Two-thirds', value: 'w-2/3 mx-auto' },
{ label: 'Half', value: 'w-1/2 mx-auto' },
],
defaultValue: 'w-full',
},
...(additionalOptions.fields || []),
];
return {
...baseSettings,
fields: extendedFields,
defaultValue: {
...baseSettings.defaultValue!,
contentWidth: 'w-full',
...(additionalOptions.defaultValue || {}),
},
};
}
```
## Theme Presets
```ts
// app/settings/theme-presets.ts
export const LIGHT_PRESET = {
background: { type: 'color', color: '#ffffff' },
textColor: '#000000',
accentColor: '#3b82f6',
};
export const DARK_PRESET = {
background: { type: 'color', color: '#111827' },
textColor: '#ffffff',
accentColor: '#60a5fa',
};
export const BRAND_PRESET = {
background: { type: 'color', color: '#f8fafc' },
textColor: '#334155',
accentColor: '#0891b2',
};
export function applyThemePreset(preset) {
return {
label: 'Theme Preset',
name: 'themePreset',
component: 'select',
options: [
{ label: 'Default', value: 'default' },
{ label: 'Light', value: 'light' },
{ label: 'Dark', value: 'dark' },
{ label: 'Brand', value: 'brand' },
],
defaultValue: 'default',
onChange: (field, form) => {
if (field.value === 'light') {
form.mutators.setValues({
container: {
...form.values.container,
background: LIGHT_PRESET.background,
},
textColor: LIGHT_PRESET.textColor,
accentColor: LIGHT_PRESET.accentColor,
});
} else if (field.value === 'dark') {
form.mutators.setValues({
container: {
...form.values.container,
background: DARK_PRESET.background,
},
textColor: DARK_PRESET.textColor,
accentColor: DARK_PRESET.accentColor,
});
} else if (field.value === 'brand') {
form.mutators.setValues({
container: {
...form.values.container,
background: BRAND_PRESET.background,
},
textColor: BRAND_PRESET.textColor,
accentColor: BRAND_PRESET.accentColor,
});
}
},
};
}
```
## Common Settings Module
```ts
// app/settings/common.ts
export const COLOR_PICKER_DEFAULTS = [
{ label: 'White', color: '#ffffff' },
{ label: 'Black', color: '#000000' },
{ label: 'Brand Primary', color: '#0891b2' },
{ label: 'Brand Secondary', color: '#60a5fa' },
{ label: 'Gray 100', color: '#f3f4f6' },
{ label: 'Gray 200', color: '#e5e7eb' },
{ label: 'Gray 500', color: '#6b7280' },
{ label: 'Gray 900', color: '#111827' },
{ label: 'Red', color: '#ef4444' },
{ label: 'Yellow', color: '#eab308' },
{ label: 'Green', color: '#22c55e' },
{ label: 'Blue', color: '#3b82f6' },
{ label: 'Purple', color: '#a855f7' },
];
export const COLOR_SCHEMA_DEFAULT_VALUE = {
white: '#ffffff',
black: '#000000',
brandPrimary: '#0891b2',
brandSecondary: '#60a5fa',
};
export const BUTTONS = [
{ label: 'Primary', value: 'btn-primary' },
{ label: 'Secondary', value: 'btn-secondary' },
{ label: 'Tertiary', value: 'btn-tertiary' },
{ label: 'Outline', value: 'btn-outline' },
{ label: 'Accent', value: 'btn-accent' },
{ label: 'Link', value: 'btn-link' },
];
export const FLEX_POSITIONS = {
desktop: [
{ label: 'Top Left', value: 'md:justify-start md:items-start' },
{ label: 'Top Center', value: 'md:justify-center md:items-start' },
{ label: 'Top Right', value: 'md:justify-end md:items-start' },
{ label: 'Middle Left', value: 'md:justify-start md:items-center' },
{ label: 'Middle Center', value: 'md:justify-center md:items-center' },
{ label: 'Middle Right', value: 'md:justify-end md:items-center' },
{ label: 'Bottom Left', value: 'md:justify-start md:items-end' },
{ label: 'Bottom Center', value: 'md:justify-center md:items-end' },
{ label: 'Bottom Right', value: 'md:justify-end md:items-end' },
],
mobile: [
{ label: 'Top Left', value: 'justify-start items-start' },
{ label: 'Top Center', value: 'justify-center items-start' },
{ label: 'Top Right', value: 'justify-end items-start' },
{ label: 'Middle Left', value: 'justify-start items-center' },
{ label: 'Middle Center', value: 'justify-center items-center' },
{ label: 'Middle Right', value: 'justify-end items-center' },
{ label: 'Bottom Left', value: 'justify-start items-end' },
{ label: 'Bottom Center', value: 'justify-center items-end' },
{ label: 'Bottom Right', value: 'justify-end items-end' },
],
};
export const OBJECT_POSITIONS = {
desktop: [
{ label: 'Top', value: 'md:object-top' },
{ label: 'Center', value: 'md:object-center' },
{ label: 'Bottom', value: 'md:object-bottom' },
{ label: 'Left', value: 'md:object-left' },
{ label: 'Right', value: 'md:object-right' },
{ label: 'Top Left', value: 'md:object-left-top' },
{ label: 'Top Right', value: 'md:object-right-top' },
{ label: 'Bottom Left', value: 'md:object-left-bottom' },
{ label: 'Bottom Right', value: 'md:object-right-bottom' },
],
mobile: [
{ label: 'Top', value: 'object-top' },
{ label: 'Center', value: 'object-center' },
{ label: 'Bottom', value: 'object-bottom' },
{ label: 'Left', value: 'object-left' },
{ label: 'Right', value: 'object-right' },
{ label: 'Top Left', value: 'object-left-top' },
{ label: 'Top Right', value: 'object-right-top' },
{ label: 'Bottom Left', value: 'object-left-bottom' },
{ label: 'Bottom Right', value: 'object-right-bottom' },
],
};
```
## Best Practices for Container Settings
* Use the same container settings function in all sections
* Keep class names consistent for spacing, alignment, and other styles
* Define section-specific container extensions in separate functions
## Performance Considerations
* Optimize class generation to avoid unnecessary concatenations
* Consider memoizing container classes when they don't change
* Use utility classes rather than inline styles when possible
## Accessibility Considerations
* Ensure background and text colors have sufficient contrast
* Include appropriate accessibility attributes in container elements
* Test container layouts with screen readers and keyboard navigation
## Responsive Design
* Include separate settings for mobile and desktop layouts
* Use a mobile-first approach with responsive class modifiers
* Test containers at various viewport sizes
## Troubleshooting
### Settings Not Appearing
1. Verify the `containerSettings()` function is included in your fields array
2. Check that it returns the correct schema structure
3. Ensure you’re not exceeding field limits in your schema
### Styles Not Applying
1. Confirm you’re extracting container values correctly in your component
2. Verify class names match your CSS framework (e.g., Tailwind CSS)
3. Inspect the HTML to ensure classes are applied
### Default Values Not Working
1. Ensure `defaultValue` object matches your fields
2. Check that the component handles missing or undefined values
3. Provide fallback values in your component if needed
## Conclusion
Container settings provide a powerful way to ensure layout consistency across your Pack-powered Hydrogen storefront. By implementing them as reusable functions, you create a design system that can be easily maintained, extended, and customized by content editors without developer intervention.
---
# Blueprint Theme Architecture: Components and Customization
Pack’s Blueprint is like an advanced, component-based Hydrogen theme that’s designed to help you spin up a Shopify Hydrogen storefront quickly.
We’ve enhanced Shopify Hydrogen's out-of-the-box demo site, providing a rich, customizable, storefront that is ready to deploy.
Access [Pack’s Blueprint for storefronts](https://github.com/packdigital/pack-hydrogen-theme-blueprint) on Github.
Access [Pack’s Blueprint for shops on Github](https://github.com/packdigital/pack-shop-theme-blueprint).

# Key benefits:
* **Enhanced user interactions:** subtle animations throughout elevate the user experience
* **Integrated with Pack’s Sections and Schemas:** Offers a variety of functionalities including hero sections, tile grids, and testimonial sliders, enabling you to create a store that caters to your unique needs.
* **Data and analytics:** Data layer logic through GA4
* **Advanced Cart Functionality:** Introduces enriched cart features such as a free shipping meter and upsell options, elevating the shopping experience.
* **Supports product grouping:** Use Pack’s product grouping feature to create groupings or bundles you can use in upsell experiences.
# Technical Specs
**Remix:** By leveraging Remix for server-side rendering, Pack’s Blueprint Theme gives you high control over how assets load, as well as faster page loads and performance.
**Tailwind CSS:** adds a layer of responsiveness and visual appeal, guaranteeing a seamless experience across all devices. Plus, we’ve done some light styling and animations to give it all a bit more polish.
**Typescript:** Helps minimize runtime errors, easy to strip out if you prefer Javascript.
**Cart API:** Built using Shopify’s cart API.
# Features at a Glance:
## Responsive components
* Shoppable Social Video
* Heros—multiple slides and configurations images
* 50/50 Heros
* Tile rows
* Tile grids
* Product tiles
* Image tiles
* Text/markdown blocks
* Video blocks
* Video embed blocks
* Testimonials slider
* Product slider
* Reviews slider
* Form Builder
* Accordions
* Icon Rows
Customize any of these components, or create new ones and add them to Pack’s component library. Then add components from the library to any page in Pack’s customizer, edit the, (copy, imagery etc.), and push it live—no technical background required.
## Site settings, menus and modals
* Promo bar
* Modal
* Mobile sidebar menu
* Footer
* Email newsletter sign up
* Country selector (change currency, UI/UX for language change)
* Header
* Dropdown menus
* Slide out mobile menu
Use the global settings schema to customize the site’s look and feel, and add new settings as needed.
## Search, data & analytics
* Search sidebar
* Search page
* Leverages Shopify’s API
* SEO and schema markup fields
## Side cart
* Free shipping meter
* Cart upsell product slider
* Discount code field
## Shopify analytics
* Page views
* Add to carts
## Data layer logic through GA4 (QA’d by Elevar + Fueled)
Triggers placed throughout the site:
* Login
* Register
* Add to cart
* Remove from cart
* View cart
* PDP view
* Collection view
* Product item click
* Email Subscribe
* Phone subscribe
* Search results
## Automatic frontend product groupings
Based on [groupings set in Pack admin](/create-manage-content/products#groups).
# Getting Started
Implementing Pack's Blueprint Theme involves several key steps, including server configuration adjustments and leveraging loaders for dynamic content. For a detailed walkthrough, refer to the available guides and documentation resources.
To hydrogen-themedive deeper into integrating Pack's Hydrogen Theme, explore our detailed [integration guide](/developer-resources/blueprint-setup).
---
# Blueprint for storefronts sections
Sections are modular reusable components that enable swift incorporation of pre-designed content templates into your pages. website.
Pack's Blueprint theme comes with a variety of premade sections that you can use to build out your storefront.
Learn more about sections and how to manage them [here](/create-manage-content/sections).
## Container Settings
When you add a new section to a page in Customizer, you can configure the section's container settings. These settings are unique to each section and allow you to customize the content and layout of the section.
* **Background color**: Choose a background color for the section.
* **Padding**: Add padding to the section's content.
* **Bottom margin**: Add a bottom margin to the section.
* **Top padding (tablet/desktop)**: Add additional top padding to the section on tablet and desktop devices.
* **Bottom padding (tablet/desktop)**: Add additional bottom padding to the section on tablet and desktop devices.
* **Bottom margin (tablet/desktop)**: Add a bottom margin to the section on tablet and desktop devices.
* **Top padding (mobile)**: Add additional top padding to the section on mobile devices.
* **Bottom padding (mobile)**: Add additional bottom padding to the section on mobile devices.
* **Bottom margin (mobile)**: Add a bottom margin to the section on mobile devices.
## Section Settings
Section settings are unique to each section and allow you to customize the content and layout of the section. These settings can include:
* **Above The Fold**: Controls whether the section's heading is set as an H1 tag, typically used for the primary section on a page.
* **Full Width and Full Bleed**: Toggles that remove any width or padding constraints on the section, making it span the full width of the page.
* **Height**: Customizable height settings for desktop and mobile, allowing for static heights or aspect ratios, with options to set minimum and maximum heights.
## Product
### Shoppable Social Video
The **Shoppable Social Video** section allows you to display a video with a slider of associated products that viewers can shop from. The section is customizable with various settings for the video, products, slider, text, and background.

**Section Fields:**
* **Products:**
* **Product:** The product to display, selectable through a product search.
* **Image:** An image to override the product's featured image.
* **Badge Text:** Text displayed as a badge on the product card.
* **Short Description:** A brief description displayed when the product card is expanded.
* **Video Settings:**
* **Video URL:** The direct link to the video file.
* **Poster Image:** The image shown as the video poster while the video loads.
* **Product Settings:**
* **Enable Star Rating:** Toggle to display a star rating on the product card.
* **Enable Quantity Selector:** Toggle to display a quantity selector.
* **Choose Options Button Text:** Text for the button when a product has options.
* **Choose Options Button Style:** Style for the options button.
* **Add To Cart Button Text:** Text for the 'Add To Cart' button.
* **Add To Cart Button Style:** Style for the 'Add To Cart' button.
* **Notify Me Text:** Text for the 'Notify Me' button when the product is out of stock.
* **View Product Text:** Text for the 'View Product' button.
* **Badge Background Color:** Background color for the badge.
* **Badge Text Color:** Text color for the badge.
* **Slider Settings:**
* **Enable Scrollbar:** Toggle to enable a scrollbar if more than one product is displayed.
* **Scrollbar Color:** The color of the scrollbar.
* **Slide Background Color:** Background color for each slide.
* **Slide Background Opacity:** Opacity level for the slide background (0 to 1.0).
* **Slide Background Blur (px):** Blur effect for the slide background.
* **Slide Text Color:** Text color for the product information on the slide.
* **Text Settings:**
* **Heading:** The heading text for the section.
* **Subtext:** The subtext displayed below the product slider, supports markdown.
* **Scroll For More Text:** Text displayed on the scroll button.
* **Text and Icon Color:** Color for any text and icons overlaying the video.
* **Background Settings:**
* **Color Type:** The type of background color (Solid or Gradient).
* **First Color:** The primary color for the background.
* **Second Color (optional):** The secondary color for the gradient.
* **Third Color (optional):** The tertiary color for the gradient.
### Product
The **Product** section allows you to display a specific product on your page, complete with all the details and options available for purchase. This section fetches the product data either from the CMS or directly from the Shopify store, ensuring that the product is active and available on the Hydrogen sales channel.

**Section Fields:**
* **Product:**
* **Product:** Select the product to display using the product search. The product must be active and available on the Hydrogen sales channel.
### Products Grid
The **Products Grid** section displays a grid of products that are fetched dynamically based on their handles. This section allows you to showcase a collection of products in a customizable grid layout with options for star ratings and other product details.

**Section Fields:**
* **Heading:**
* You can set a heading for the products grid section.
* **Products:**
* **Product:** Select the products to display using the product search. The products must be active and available on the Hydrogen sales channel.
* **Grid Settings:**
* **Columns (desktop):** Select the number of columns to display on desktop devices.
* **Columns (tablet):** Select the number of columns to display on tablet devices.
* **Columns (mobile):** Select the number of columns to display on mobile devices.
* **Product Item Settings:**
* **Enable Star Rating:** Toggle to show or hide the star ratings for products. Note: For the actual star rating, API logic must be first implemented in the ProductStars component. Otherwise, the manual rating set in site settings will be displayed.
### Products Slider
The **Products Slider** section displays a slider of products fetched dynamically based on their handles. This section allows you to showcase a collection of products in a customizable slider format with options for star ratings, slider styles, and more.

**Section Fields:**
* **Heading:**
* You can set a heading for the products slider section.
* **Products:**
* **Product:** Select the products to display using the product search. The products must be active and available on the Hydrogen sales channel.
* **Footer Button:**
* **Link:** Option to add a button that can link to an external URL or a product modal. To link to a product modal, format the URL as `?product=`.
* **Product Item Settings:**
* **Enable Star Rating:** Toggle to show or hide the star ratings for products. Note: For the actual star rating, API logic must be first implemented in the ProductStars component. Otherwise, the manual rating set in site settings will be displayed.
* **Slider Settings:**
* **Slider Style:** Choose the style of the slider. Options include contained, contained with loop, full bleed, full bleed with gradient. Note: Loop and centered settings apply only if the number of products is at least twice the number of slides per view.
* **Slides Per View (desktop):** Set the number of slides visible on desktop devices.
* **Slides Per View (tablet):** Set the number of slides visible on tablet devices. You can use decimals to show partial slides.
* **Slides Per View (mobile):** Set the number of slides visible on mobile devices. You can use decimals to show partial slides.
* **Section Settings:**
* **Button Style:** Customize the style of the footer button.
* **Full Width:** Toggle to remove the max width limitation, allowing the slider to span the full width of the container.
## Text
### Accordions
The **Accordion** section creates collapsible sections.

**Section Fields:**
* **Heading:** Text input for the accordion's main heading.
* **Accordions:** A group list field where you can add multiple accordion items. Each item includes:
* **Header:** The text displayed in the accordion's header.
* **Body:** The Markdown content displayed when the accordion is expanded.
* **Default Open:** A toggle to set whether the accordion is open by default.
* **Accordion Header Background Color:** A dropdown to select the background color of the accordion header.
* **Accordion Header Text Color:** A dropdown to select the text color of the accordion header.
### Form Builder
The **Form Builder** section allows you to create customizable forms with various input fields and options, enabling the collection of user data directly on your site. It's equipped with features like Google reCAPTCHA for spam protection and supports multiple input types, including text, email, phone, and file uploads.

**Section Fields:**
* **Form Endpoint:**
* You set the endpoint where the form data will be submitted. The submit button is disabled until this is provided.
* **Heading:**
* You can set a heading for the form.
* **Fields:**
* You can add various input fields like text, email, phone, etc. Each field can be customized with options like label, placeholder, and whether it's required.
* **Submit Button Text:**
* You can customize the text displayed on the form's submit button.
* **reCAPTCHA v2 Enabled:**
* You can enable Google reCAPTCHA v2 for the form.
### Icon Row
The **Icon Row** section allows you to display a row of icons or images with accompanying labels, typically used to highlight key features or benefits. This section is customizable, letting you choose from a set of predefined icons or upload your own images.

**Section Fields:**
* **Heading:**
* You can set a heading for the Icon Row section.
* **Subtext:**
* You can add a markdown-supported subtext below the heading.
* **Icons:**
* You can configure each icon or image with options to select from predefined icons, upload a custom image, set alternative text, and label the icon or image.
### Markdown
The **Markdown** section allows you to add rich text content to your page using Markdown editor.

**Section Fields:**
* **Content:**
* You can enter your text using Markdown editor.
* **Center All Text:**
* You can toggle whether the text should be centered within the section.
### Text Block
The **Text Block** section allows you to add a heading, subtext, and buttons in a simple, centered layout. It's useful for highlighting key messages or calls to action on your site.

**Section Fields:**
* **Heading:**
* You can set a heading for the text block.
* **Subtext:**
* You can add a markdown-supported subtext below the heading.
* **Buttons:**
* You can configure up to two buttons with customizable links and styles.
## Heros
### Hero Banner
The **Hero Banner** section is designed to display a large, visually impactful banner with customizable content, images, and layout options. It's highly configurable, allowing different setups for desktop and mobile views.

**Section Fields:**
* **Image Settings:**
* You can configure different images for desktop and mobile devices.
* Options include setting alternative text, and positioning for images on both desktop and mobile.
* **Text Settings:**
* This allows you to set the banner's heading, subheading, and text color.
* You can also add up to two buttons with customizable styles and links.
* **Content Settings:**
* You can enable a dark overlay on the banner image, adjust the position and alignment of the content, and set the maximum width for both desktop and mobile views.
### Half Hero
The **Half Hero** section combines media (images or video) and text content, split across two columns. This section is versatile and allows for different layouts and content alignments depending on your needs.

**Section Fields:**
* **Media Settings:**
* You can configure image and video settings, including upload options, crop positions, aspect ratios, and order of media display on different devices.
* **Content Settings:**
* You can set a heading, subtext, superheading, and buttons. The content can be aligned differently on desktop and mobile devices, with options to adjust the maximum width and text color.
### Hero Slider
The **Hero Slider** section allows you to create a dynamic hero banner with multiple slides, each containing images, videos, text, and buttons. It's designed to highlight key content in a visually impactful way, with options for autoplay and various transition effects.

**Section Fields:**
* **Slides:**
* You can configure each slide individually with options for images, videos, text, and buttons. Each slide can have a unique setup for desktop and mobile.
* **Slider Settings:**
* You can control the slider behavior, including autoplay, delay between transitions, transition effects, and the appearance of pagination bullets.
## Bundles
### Build Your Own Bundle
The **Build Your Own Bundle** section allows you to create a customizable bundle by selecting products from different groupings. This section is designed to provide a flexible bundling experience with options to apply discounts at various tiers.

**Section Fields:**
* **Product Groupings:**
* Group products into categories. You can create multiple groupings, and each grouping will appear in the bundle builder.
* **Tiers:**
* Set up different discount tiers. Each tier can apply a discount type, such as a percentage off or a free item, based on the number of products added to the bundle.
* **Summary Heading Default:**
* Set the default heading for the bundle summary before any discount tier is met.
* **Preselected Products:**
* Optionally, preselect products to be included in the bundle by default. Only products that are part of a product grouping will be preselected.
## HTML
### HTML
The **HTML** section allows you to include custom HTML content within your page.

**Section Fields:**
* **HTML:**
* Add your custom HTML content here. This field accepts raw HTML.
* **Content Settings:**
* Adjust the alignment of the content and text within the section.
## Media
### Image
The **Image** section allows you to display images with customizable settings for desktop and mobile devices, including optional links and captions.

**Section Fields:**
* **Image Settings:**
* Configure the image for both tablet/desktop and mobile devices.
* Options include setting the aspect ratio and crop position.
* **Content Settings:**
* Add an optional link to make the image clickable.
* Include an optional caption below the image.
### Image Tiles Slider
The **Image Tiles Slider** section allows you to create a responsive image slider with customizable settings, including the number of tiles per view, aspect ratio, and button styles.

**Section Fields:**
* **Header Settings:**
* Customize the heading, subheading, and alignment of the section header.
* **Tiles:**
* Configure individual image tiles, including image settings, heading, and buttons.
* Set up to two buttons per tile, with options to make the image clickable.
* **Content Settings:**
* Control the appearance of the content overlay, button styles, and content alignment.
* **Section Settings:**
* Adjust the number of tiles visible per view on different screen sizes, the image aspect ratio, and the section's width.
### Image Tiles Grid
The **Image Tiles Grid** section allows you to create a responsive grid of image tiles with customizable settings, including the number of columns, grid spacing, and content alignment.

**Section Fields:**
* **Header Settings:**
* Customize the heading, subheading, and alignment of the section header.
* **Tiles:**
* Configure individual image tiles, including image settings, heading, and buttons.
* Set up to two buttons per tile, with options to make the image clickable.
* **Content Settings:**
* Control the appearance of the content overlay, button styles, and content alignment.
* **Section Settings:**
* Adjust the number of grid columns and spacing on different screen sizes, the image aspect ratio, and the section's width.
### Image Tiles Mosaic
The **Image Tiles Mosaic** section enables the creation of a grid with a mosaic layout, featuring customizable primary and grid tiles. This section allows you to configure various display options for different screen sizes.

**Section Fields:**
* **Header Settings:**
* Customize the heading, subheading, and alignment of the section header.
* **Primary Tile:**
* Configure the primary tile, including its placement relative to the grid, aspect ratios for different screen sizes, and content.
* **Grid Tiles:**
* Select a grid layout and configure individual grid tiles. You can customize their aspect ratios for mobile and content.
* **Content Settings:**
* Control overlay appearance, content position and alignment, tile heading size, and the option to make the image clickable.
### Social Images Grid
The **Social Images Grid** section allows you to display a grid of images, each linked to a corresponding social media post. This grid is designed to feature four images, and each image is overlaid with a platform-specific icon.

**Section Fields:**
* **Images:**
* **Image Alt:** Alt text for the image.
* **Image:** The image itself, selected from the media manager.
* **Platform:** Select the social media platform (e.g., Instagram, Facebook, etc.).
* **Social Post URL:** The URL to the social media post.
* **Section Settings:**
* **Full Width:** Option to remove the max width constraint for the section.
* **Full Bleed:** Option to remove padding around the section, making the images flush with the edges.
### Tabbed Tiles Slider
The **Tabbed Tiles Slider** section allows you to create a dynamic and interactive slider with multiple tabs, each containing a set of tiles. Users can switch between tabs to view different sets of content.

**Section Fields:**
* **Header Settings:**
* **Heading:** Main heading for the section.
* **Subheading:** Optional subheading for the section.
* **Alignment:** Control the alignment of the heading and subheading.
* **Tabs:**
* **Tab Name:** Name displayed on the tab.
* **Tiles:** Each tab can contain multiple tiles, with fields for:
* **Image Alt:** Alt text for the image.
* **Image:** Image displayed on the tile.
* **Image Crop Position:** Position of the image crop.
* **Heading:** Heading for the tile.
* **Description:** Optional description text for the tile.
* **Link:** Optional link associated with the tile.
* **Footer Button:**
* **Text:** The text for the button.
* **URL:** The URL the button links to.
* **New Tab:** Option to open the link in a new tab.
* **Type:** Type of the link.
* **Section Settings:**
* **Tiles Per View:** Configure how many tiles are displayed per view on desktop, tablet, and mobile.
* **Image Aspect Ratio:** Select the aspect ratio for the images.
* **Text Color:** Set the text color for the section.
* **Tile Text Alignment:** Control the alignment of the text within each tile.
* **Tile Heading Size:** Adjust the size of the tile headings.
* **Footer Button Style:** Choose the style of the footer button.
* **Full Width:** Option to make the section full width, removing the max-width constraint.
### Tiles Slider
The **Tiles Slider** section allows you to showcase a series of tiles in a slider format. It offers various customization options for tile content, layout, and appearance.

**Section Fields:**
* **Header Settings:**
* **Heading:** Main heading for the section.
* **Subheading:** Optional subheading for the section.
* **Alignment:** Controls the alignment of the heading and subheading.
* **Tiles:**
* **Image Alt:** Alt text for the image.
* **Image:** Image displayed on the tile.
* **Image Crop Position:** Position of the image crop.
* **Heading:** Heading text for the tile.
* **Description:** Optional description text for the tile.
* **Link:** Optional link associated with the tile.
* **Footer Button:**
* **Text:** The text for the button.
* **URL:** The URL the button links to.
* **New Tab:** Option to open the link in a new tab.
* **Type:** Type of the link.
* **Section Settings:**
* **Tiles Per View:** Configure how many tiles are displayed per view on desktop, tablet, and mobile.
* **Image Aspect Ratio:** Select the aspect ratio for the images.
* **Text Color:** Set the text color for the section.
* **Tile Text Alignment:** Control the alignment of the text within each tile.
* **Tile Heading Size:** Adjust the size of the tile headings.
* **Footer Button Style:** Choose the style of the footer button.
* **Full Width:** Option to make the section full width, removing the max-width constraint.
### Tiles Stack
The **Tiles Stack** section allows you to showcase a vertical stack of tiles with customizable content, layout, and appearance options.

**Section Fields:**
* **Header Settings:**
* **Heading:** The main heading for the section.
* **Subheading:** Optional subheading for the section.
* **Alignment:** Controls the alignment of the heading and subheading.
* **Tiles:**
* **Image Alt:** Alt text for the image.
* **Image:** Image displayed on the tile.
* **Image Crop Position:** Position of the image crop.
* **Heading:** Heading text for the tile.
* **Description:** Optional description text for the tile.
* **Link:** Optional link associated with the tile.
* **Section Settings:**
* **Image Aspect Ratio:** Select the aspect ratio for the images.
* **Text Color:** Set the text color for the section.
* **Tile Text Alignment:** Control the alignment of the text within each tile.
* **Tile Heading Size:** Adjust the size of the tile headings.
* **Full Width:** Option to make the section full width, removing the max-width constraint.
### Video
The **Video** section is designed to embed videos with customizable settings, including autoplay, looping, controls, and optional links.
**Section Fields:**
* **Media Settings:**
* **Video Title:** The title of the video, used for accessibility.
* **Video URL (tablet/desktop):** Direct link to the video for tablet/desktop views.
* **Poster Image (tablet/desktop):** Image displayed before the video loads on tablet/desktop.
* **Video Aspect Ratio (tablet/desktop):** Aspect ratio of the video for tablet/desktop.
* **Video URL (mobile):** Direct link to the video for mobile views.
* **Poster Image (mobile):** Image displayed before the video loads on mobile.
* **Video Aspect Ratio (mobile):** Aspect ratio of the video for mobile.
* **Play Settings:**
* **Autoplay:** Automatically play the video when it is in view.
* **Loop:** Enable looping of the video.
* **Pause & Play:** Toggle the ability to pause and play the video.
* **Sound:** Enable sound for the video (only applicable if autoplay is off).
* **Controls:** Show native video controls.
* **Content Settings:**
* **Link:** Optional link to make the video clickable, only if video controls and pause/play are disabled.
* **Section Settings:**
* **Max Width:** Set the maximum width of the section.
* **Enable Vertical Padding:** Toggle vertical padding around the section.
* **Enable Horizontal Padding:** Toggle horizontal padding around the section.
### Video Embed Section
The **Video Embed** section is designed to embed external videos using HTML code, with customizable aspect ratios and padding settings.
**Section Fields:**
* **Media Settings:**
* **Video Embed (HTML):** Direct HTML embed code for the video.
* **Video Aspect Ratio:** Set the aspect ratio for the video to maintain the correct proportions before it loads when in view.
* **Section Settings:**
* **Max Width:** Set the maximum width of the section.
* **Enable Vertical Padding:** Toggle vertical padding around the section.
* **Enable Horizontal Padding:** Toggle horizontal padding around the section.
## Marketing
### Marketing Signup
The **Marketing Signup** section facilitates email and phone signups with flexible user experience by toggling between signup types.

**Section Fields:**
* **Type:**
* Options: Email, Phone, or Email & Phone.
* Default: Email.
* **Heading:**
* Default: "Stay In Touch."
* **Email Signup Settings:**
* **List ID:** Required to enable the submit button.
* **Heading, Subtext, Placeholder Text, Button Text, Thank You Text.**
* **Phone Signup Settings:**
* Similar to Email settings, with additional fields for SMS consent and inline links.
## Slider
### Press Slider
The **Press Slider** section creates a responsive slider to display press quotes alongside corresponding logos.

**Section Fields:**
* **Slides:**
* **Alt Text:** Configurable per slide for accessibility.
* **Image:** Press logo image.
* **Quote:** Text content displayed in the slider.
* **Section Settings:**
* **Full Width:** Toggle to remove the max width limitation.
* **Text Color:** Customize the text color within the slider.
### Products Slider
The **Products Slider** section is a dynamic slider designed to showcase products based on their handles.

**Section Fields:**
* **Heading:**
* Configurable heading text for the slider section.
* **Products:**
* Allows selection of products via a handle search and includes a maximum of four product handles by default.
* **Product Item Settings:**
* **Star Rating, Color Variant Selector, Quick Shop:** Toggling options for each feature.
* **Slider Settings:**
* Adjustable settings for the slider style and the number of slides visible on desktop, tablet, and mobile.
* **Section Settings:**
* Customizes the button style and the full-width option for the slider.
### Testimonial Slider
The **Testimonial Slider** section displays a collection of testimonials in a slider format.

**Section Fields:**
* **Heading:**
* Configurable heading text for the testimonial section.
* **Testimonial Slides:**
* A group list where each slide includes a title, body, author, and rating.
* **Link:**
* Configurable link text and URL, which can be set to open in a new tab.
* **Section Settings:**
* **Full Width:** An option to make the slider span the full width of the container.
* **Color Customizations:** Options to customize the text color, slider pagination bullet color, and review star color.
* [Sections](/create-manage-content/sections): Learn more about sections.
---
# Set up Blueprint
## Get started
Create a free Pack account and explore our [Quick Start](/getting-started/quickstart) guide.
Continue with the following guide or check out the README files:
* [Pack Storefront Blueprint](https://github.com/packdigital/pack-hydrogen-theme-blueprint/blob/main/README.md)
* [Pack Shop Blueprint](https://github.com/packdigital/pack-shop-theme-blueprint/blob/main/README.md).
## Requirements
* Node.js version 16.14.0 or newer
After cloning your project, begin by installing your node packages:
npm install
## Using our CLI tool
To streamline the setup of your Blueprint Theme, we provide a command-line interface (CLI) tool that simplifies the process. This tool automates several steps, making it easier and faster to get your development environment ready. Here’s how to use it:
1. **Cloning and Setting Up the Blueprint Theme**: Open your terminal and run the following command:
npx @pack/create-hydrogen@latest
This command does the following:
* Clones the Blueprint Theme repository to your local machine.
* Installs all the necessary node packages using `npm install`.
* Prepares your development environment for immediate use.
2. **Running Your Project Locally**: Once the setup is complete, you can start your development server with:
npm run dev
This command serves your project on `http://localhost:3000`, allowing you to view and test your site in a local development environment.
3. **No Pack Account Required**: If you don't have a Pack account yet, no worries! The CLI tool allows you to run the Blueprint Theme with default Shopify data. This means you can start exploring the capabilities of the Hydrogen framework and how it integrates with Shopify without any initial setup or configuration.
4. **Next Steps**: After exploring the Blueprint Theme project, you might want to customize it further by connecting it to your Shopify Hydrogen storefront. You can do this by setting up environment variables as described below.
## Environment Variables
To run your application locally, you can use Shopify's mock.shop API to simulate a Shopify storefront. You can set the `PUBLIC_STORE_DOMAIN` environment variable to `mock.shop` to use the mock.shop API.
SESSION_SECRET="foobar"
PUBLIC_STORE_DOMAIN="mock.shop"
PUBLIC_STOREFRONT_API_TOKEN="foobar"
You can automatically pull in your Shopify environment variables directly from your Shopify Hydrogen storefront using the Hydrogen CLI. Run the command below and follow its prompts.
npx shopify hydrogen env pull
> **Warning**: Please double check the `.env` file to ensure all necessary variables are present. If you set `PACK_SECRET_TOKEN` as a secret environment variable in Shopify, you must manually add it to your `.env` file.
Alternatively, manually create a `.env` file and copy the values from your Shopify Hydrogen storefront. Locate the variables by navigating to the Hydrogen storefront > Storefront Settings > Environments & Variables. The necessary variables include:
SESSION_SECRET="XXX"
PUBLIC_STOREFRONT_API_TOKEN="XXX"
PUBLIC_STORE_DOMAIN="XXX"
PACK_PUBLIC_TOKEN="XXX"
PACK_SECRET_TOKEN="XXX"
PACK_STOREFRONT_ID="XXX"
## Building for Production
This command emulates the deployment process Shopify Oxygen uses when deploying your site live.
```bash
npm run build
```
## Building for Local Development
This command initiates a server locally on your machine at `http://localhost:3000`.
```bash
npm run dev
```
## Pack Customizer Content
> **Warning**: Please ensure `` component is always included on your routes, as the Customizer connection logic is handled within this component.
Access Pack Customizer data using the `pack` object in the Hydrogen `context`. Consider this example:
```tsx
export async function loader({params, context, request}: LoaderFunctionArgs) {
const {handle} = params;
const storeDomain = context.storefront.getShopifyDomain();
const searchParams = new URL(request.url).searchParams;
const selectedOptions: any = [];
// Assign selected options from the query string
searchParams.forEach((value, name) => {
selectedOptions.push({name, value});
});
const {data} = await context.pack.query(PRODUCT_PAGE_QUERY, {
variables: {handle},
});
const {product} = await context.storefront.query(PRODUCT_QUERY, {
variables: {
handle,
selectedOptions,
},
});
...
}
```
The `data` object contains all the Pack Section Setting content from CMS Authors in the Customizer, defined per Section's Setting schema. The `product` object holds Shopify-specific data from the Storefront API.
Refer to [Section Schema API](https://docs.packdigital.com/section-schema-api).
## Caching
Pack employs the same Caching Strategy as the Hydrogen framework. For an example, see `app/lib/pack/create-pack-client.ts`
**NOTE:** The `lib/pack` library will eventually be moved to its own NPM package by Pack.
```tsx
export function createPackClient(options: CreatePackClientOptions): Pack {
const {apiUrl, cache, waitUntil, preview, contentEnvironment} = options;
const previewEnabled = !!preview?.session.get('enabled');
const previewEnvironment = preview?.session.get('environment');
return {
preview,
isPreviewModeEnabled: () => previewEnabled,
async query(
query: string,
{variables, cache: strategy = CacheLong()}: QueryOptions = {},
) {
const queryHash = await hashQuery(query, variables);
const withCache = createWithCache>({
cache,
waitUntil,
});
// Preview environment overrides the content environment set during client creation
const environment =
previewEnvironment || contentEnvironment || PRODUCTION_ENVIRONMENT;
const fetchOptions = {
apiUrl,
query,
variables,
token: options.token,
previewEnabled,
contentEnvironment: environment,
};
// Cache is bypassed in preview mode
if (previewEnabled) return packFetch(fetchOptions);
return withCache(queryHash, strategy, () => packFetch(fetchOptions));
},
};
}
```
## Data Layer
The Pack Blueprint Theme sends `pageView` and `addToCart` (coming soon) events to Shopify Analytics via the Hydrogen hook. For details, visit:
To see how events are submitted, refer to the `products` route (`app/routes/products.$handle.tsx`):
```tsx
export async function loader({params, context, request}: LoaderFunctionArgs) {
...
if (!data.productPage) {
throw new Response(null, {status: 404});
}
// Optionally set a default variant for a consistently "orderable" product
const selectedVariant =
product.selectedVariant ?? product?.variants?.nodes[0];
const productAnalytics: ShopifyAnalyticsProduct = {
productGid: product.id,
variantGid: selectedVariant.id,
name: product.title,
variantName: selectedVariant.title,
brand: product.vendor,
price: selectedVariant.price.amount,
};
const analytics = {
pageType: AnalyticsPageType.product,
resourceId: product.id,
products: [productAnalytics],
totalValue: parseFloat(selectedVariant.price.amount),
};
return defer({
product,
productPage: data.productPage,
selectedVariant,
storeDomain,
analytics,
});
}
```
## SEO and Meta Tags
You can find the meta settings such as the title and description in the `app\lib\seo.server.ts` file.
By default, the site title is appended to the end of the page title using a `|` separator. For example, if the page title is "About Us" and the site title is "My Store," the resulting title tag will be "About Us | My Store." You can modify this behavior for each resource by passing an optional `affixSiteTitleToSeoTitle` parameter to the `getMeta()` function. Setting `affixSiteTitleToSeoTitle` to `false` will prevent the site title from being appended to the page title.
The `seoSiteTitle` is derived from the Pack Admin under **Settings -> Search Engine Optimization**. If this field is empty, it pulls the site title directly from Shopify.
For the homepage (`/`), the logic in `getMeta()` treats it uniquely. By default, if the page handle is `'/'`, it sets the `title` to the `siteTitle`. You can change this behavior by commenting out or adjusting the following code in the `getMeta()` function:
```javascript
if (page?.handle === '/') {
pageTitle = pageTitle === 'Homepage' ? siteTitle : pageTitle;
title = siteTitle;
}
```
By commenting out this block, the homepage will use the SEO title specified in the page's SEO settings, which you can set by navigating to **Pack Admin -> Pages -> Homepage -> SEO Settings**.
The `getMeta()` function also manages the meta description and Open Graph image (`media`) for the page. It prioritizes the SEO description and image specified for the resource (such as a product or collection) and falls back to the site-wide SEO settings or Shopify's defaults if those are not available. The description is truncated to 155 characters to ensure it fits within search engine result snippets.
Additionally, the `getMeta()` function handles the robots meta tags (`noIndex` and `noFollow`) based on the page's SEO settings. If `page.seo.noIndex` or `page.seo.noFollow` are set to `true`, the corresponding robots meta tags will be added to the page. You customize these in the Pack Admin under **Pages -> \[Page] -> SEO Settings**.
---
# CMS Content Models: Structure and Implementation
## Page
```ts
type Page{
id: String!
title: String!
handle: String!
description: String
status: String!
seo: SEO!
publishedAt: DateTime
firstPublishedAt: DateTime
firstPublishedAtTimezone: String
createdAt: DateTime
updatedAt: DateTime
action: String
sections: SectionConnection
template: {
id: String!
title: String!
type: String!
status: String!
sections: {
totalCount: Int
pageInfo: {
hasNextPage: Boolean
endCursor: String
}
edges: [
{
cursor: String
node: Section
}
]
}
}
}
```
## Page History
```ts
type PageHistory {
nodes: [PageRevision]
pageInfo: PageInfo
}
type PageRevision {
id: String!
pageId: String!
title: String!
handle: String!
publishedAt: DateTime
createdAt: DateTime
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String!
}
```
## Page Revision
```ts
type PageRevision {
id: String!
pageId: String!
title: String!
handle: String!
description: String
status: String!
sections: [Section]
user: User!
publishedAt: DateTime
storeId: String!
createdAt: DateTime
}
```
## Page Info
```ts
type PageInfo {
hasNextPage: Boolean!
endCursor: String!
}
```
## Blog
```ts
type Blog = {
id: ID!;
title: string!;
handle: string!;
description: string;
status: string!;
seo: SEO;
publishedAt: string | null;
firstPublishedAt: string | null;
firstPublishedAtTimezone: string | null;
createdAt: string;
updatedAt: string;
sections: SectionConnection;
template: Template;
};
```
## Article
```ts
type Article = {
id: ID!;
title: string!;
handle: string!;
description: string;
author: string;
category: string;
tags: string[];
excerpt: string;
bodyHtml: string;
status: string!;
sections: SectionConnection;
seo: SEO;
publishedAt: string | null;
firstPublishedAt: string | null;
firstPublishedAtTimezone: string | null;
blog: any | null;
user: any | null;
storeId: string;
createdAt: string;
updatedAt: string;
};
```
## Product Page
```ts
type ProductPage {
id: ID!
title: String!
handle: String!
description: String
status: String!
seo: SEO
publishedAt: DateTime
firstPublishedAt: DateTime
firstPublishedAtTimezone: String
createdAt: DateTime
updatedAt: DateTime
sections: SectionConnection!
template: Template
}
```
## Product Page History
```ts
type ProductPageHistory {
nodes: [ProductPageRevision]
pageInfo: PageInfo
}
```
## Product Page Revision
```ts
type ProductPageRevision {
id: ID!
productId: String!
title: String!
handle: String!
description: String
status: String!
seo: SEO
sections: [Section]
user: User
publishedAt: DateTime
storeId: String!
createdAt: DateTime
}
```
## Collection
```ts
type Collection {
id: ID!
collectionId: String!
title: String!
handle: String!
description: String
status: String!
sections: [Section]
seo: SEO!
publishedAt: DateTime
firstPublishedAt: DateTime
firstPublishedAtTimezone: String
user: User
storeId: String!
createdAt: DateTime\
}
```
## Section
```ts
type SectionConnection {
totalCount: Int!
pageInfo: PageInfo!
edges: [SectionEdge]
}
```
```ts
type Section {
id: String!
parentContentType: String
title: String!
status: String!
data: SectionData
dataSource: String
hasReferences: Boolean
publishedAt: DateTime
createdAt: DateTime
updatedAt: DateTime
}
```
```ts
type SectionData {
tinaId: String!
heading: String
section: {
fullWidth: Boolean
textColor: String
aboveTheFold: Boolean
}
_template: String!
sectionName: String
sectionType: String
resourceType: String
sectionVisibility: String
}
```
## Template
```ts
type Template {
id: ID!
templateId: String!
title: String!
type: String!
isDefault: Boolean!
status: String!
sections: SectionConnection
publishedAt: DateTime
createdAt: DateTime
}
```
## Site Settings
```ts
type SiteSettings {
id: ID!
createdAt: DateTime
favicon: String
publishedAt: DateTime
seo: SEO
settings: Settings,
status: String
updatedAt: DateTime
}
```
## Schedule
```ts
type Schedule {
id: ID!
title: String!
description: String
executeAt: DateTime
timezone: String
state: ScheduleState!
totalContentCount: Int!
content: [Content!]
createdAt: DateTime
updatedAt: DateTime
}
```
## Content Environment
```ts
type ContentEnvironment {
id: ID!
name: String!
handle: String!
storeId: String!
createdAt: DateTime!
}
```
## User
```ts
type User {
id: String!
first_name: String!
last_name: String!
email: String!
avatar_url: String
}
```
## SEO
```ts
type SEO {
title: String!
description: String
image: String
keywords: [String]
noIndex: Boolean
noFollow: Boolean
}
```
---
# Content Environment Variables
You can set the `PACK_CONTENT_ENVIRONMENT` and `PUBLIC_PACK_CONTENT_ENVIRONMENT` (both are required) environment variables to force the storefront to use a specific content environment outside of the Customizer, e.g. for a specific Github branch.
# Preview Provider
in `` pass the `contentEnvironment` with the content environment handle you want to use.
```tsx
{children}
```
* [Content Environments](/create-manage-content/content-environments): Learn how to create and manage different versions of your content
---
# Customizer Interface Development: Overlay and Field Hotspots
The customizer overlay is a quick and easy way to focus in on the sections of your storefront. The overlay will highlight the currently selected section.

## Enabling the overlay
To enable the overlay in the Customizer simply click the overlay toggle at the bottom right of the customizer.
1. Select **Customizer** in Pack's admin.
2. Find the overlay toggle at the bottom right of the customizer.
3. Should be able to hover over sections and see the overlay enabled now.

To disable the overlay, simply click the toggle again.
## Overlay actions
Once you have clicked on a section, the overlay be highlighted in blue, and the section's form will appear on the left side of the screen.
The section overlay has a couple of quick actions you can take, found on the left side of the overlay:
* **Move section up**: This will move the section up in the order of sections on the page.
* **Move section down**: This will move the section down in the order of sections on the page.
* **Duplicate section**: This will duplicate the section, creating a copy of it.
* **Toggle section visibility**: This will toggle the visibility of the section, hiding it from the storefront.
* **Remove section**: This will remove the section from the page.

## Customizer Section Field Hotspots
The customizer section field hotspots are a way to quickly access the fields of a section by clicking on the HTML elements that uses the field's value. This can help you quickly navigate to the content you want to edit without having to click through lots of menu.

## Enabling the hotspots
The hotspots are enabled when the customizer overlay is enabled. However, to get the hotspots to show up, you need to add the `data-pack-field-id` attribute to the HTML element that uses the field's value. Here is an example below:
> **Note**: The `data-pack-field-id` attribute should be set to the path of the field you
> are trying to target. It is made up of the field's key in the section's
> schema. In the example below, the `pack-field-id` is `title.heading`.
```tsx
const MySection = (cms) => {
return {
{cms.title.heading}
}
}
MySection.Schema = {
label: 'My Section',
key: 'my-section',
fields: [
{
label: 'Title',
key: 'title',
component: 'group',
fields: [
{
label: 'Heading',
key: 'heading',
component: 'text',
}
]
}
]
}
```
Once enabled, you can click on the HTML element that uses the field's value, and the section's form will appear on the left side of the screen with the field focused.
### Pack Field ID for array types
If you are using an array type field ([`group-list`](/section-schema-api#group-list-field), [`list`](/section-schema-api#list-field), or [`blocks`](/section-schema-api#blocks-field)), you can use the `data-pack-field-id` attribute to target a specific field in the array. The `data-pack-field-id` should be set to the path of the field you are trying to target, with the index of the item in the array. Here is an example below:
```tsx
const MyTileSection = (cms) => {
return (
{cms.tiles.map((tile, index) => (
{tile.title}
{tile.subheading}
))}
)
}
MyTileSection.Schema = {
label: 'My Tile Section',
key: 'my-tile-section',
fields: [
{
label: 'Tiles',
key: 'tiles',
component: 'group-list',
fields: [
{
label: 'Title',
key: 'title',
component: 'text',
},
{
label: 'Sub Heading',
key: 'subheading',
component: 'text',
},
],
},
],
}
```
* [Section Schema API](/section-schema-api): Learn how to use the Section Schema API to create dynamic sections.
---
# Implementing Global Content and Layout: A Technical Guide for Developers
This guide explains how to implement global content elements like headers, footers, and site-wide settings in a Pack-powered Hydrogen storefront from a technical perspective. You'll learn how to define global settings schemas, fetch global content in loaders, and create layout components to render consistent elements across your site.
## Introduction to Global Content
Global content refers to elements that appear consistently across multiple pages of your storefront, such as:
* Headers and navigation
* Footers
* Announcements and banners
* Site-wide settings (colors, typography, etc.)
* Social media links
Pack provides a structured approach to managing global content through Storefront Settings, which are accessible to content editors via the [Customizer](https://docs.packdigital.com/create-manage-content/customizer) but defined by developers in code.
## Global Content Implementation Process
Implementing global content in a Pack-powered Hydrogen storefront involves these steps:
1. **Set up global Storefront Settings schema in your code**
2. **Set up global content fetching in your root loader**
3. **Implement the global layout component**
4. **Connect the layout to your routes**
Let's walk through each step with code examples.
## Step 1: Set Up Global Storefront Settings Schema
In Pack, global elements like headers, footers, and promotional banners are managed through Storefront Settings in the Customizer. As a developer, you need to define the settings schema in your code.
Global settings are defined in the `/settings/index.js` file of your project. This file exports an array of settings that will be available to content editors in the Customizer.
```javascript
// settings/index.js
// Must be an array
const settings = [
{
label: 'Header',
name: 'header',
component: 'group',
fields: [
{
label: 'Logo',
name: 'logo',
component: 'image',
},
{
label: 'Menu Items',
name: 'menuItems',
component: 'group-list',
itemProps: {
label: '{{item.label}}',
},
fields: [
{
label: 'Label',
name: 'label',
component: 'text',
},
{
label: 'Link',
name: 'link',
component: 'link',
},
{
label: 'Submenu Items',
name: 'submenuItems',
component: 'group-list',
itemProps: {
label: '{{item.label}}',
},
fields: [
{
label: 'Label',
name: 'label',
component: 'text',
},
{
label: 'Link',
name: 'link',
component: 'link',
},
],
},
],
},
],
},
{
label: 'Footer',
name: 'footer',
component: 'group',
fields: [
{
label: 'Logo',
name: 'logo',
component: 'image',
},
{
label: 'Footer Links',
name: 'footerLinks',
component: 'group-list',
fields: [
{
label: 'Section Title',
name: 'title',
component: 'text',
},
{
label: 'Links',
name: 'links',
component: 'group-list',
itemProps: {
label: '{{item.label}}',
},
fields: [
{
label: 'Label',
name: 'label',
component: 'text',
},
{
label: 'Link',
name: 'link',
component: 'link',
},
],
},
],
},
{
label: 'Copyright Text',
name: 'copyright',
component: 'text',
},
],
},
{
label: 'Theme Settings',
name: 'theme',
component: 'group',
fields: [
{
label: 'Primary Color',
name: 'primaryColor',
component: 'color',
defaultValue: '#000000',
},
{
label: 'Secondary Color',
name: 'secondaryColor',
component: 'color',
defaultValue: '#ffffff',
},
{
label: 'Font Primary',
name: 'fontPrimary',
component: 'select',
options: [
{ label: 'Sans Serif', value: 'sans-serif' },
{ label: 'Serif', value: 'serif' },
],
defaultValue: 'sans-serif',
},
],
},
{
label: 'Announcement Bar',
name: 'announcement',
component: 'group',
fields: [
{
label: 'Enable',
name: 'enabled',
component: 'toggle',
defaultValue: false,
},
{
label: 'Text',
name: 'text',
component: 'text',
},
{
label: 'Link',
name: 'link',
component: 'link',
},
{
label: 'Background Color',
name: 'backgroundColor',
component: 'color',
defaultValue: '#000000',
},
{
label: 'Text Color',
name: 'textColor',
component: 'color',
defaultValue: '#ffffff',
},
],
},
];
export default settings;
```
---
# Hosting Storefront and Shop
By default, Pack storefronts or shops will be hosted using Shopify's Oxygen platform. Shopify Oxygen provides robust, scalable, and efficient hosting solutions specifically tailored for Shopify storefronts.
For detailed information and guidance on how to host your storefront or shop with Shopify Oxygen, please refer to the [Shopify Oxygen documentation](https://shopify.dev/docs/custom-storefronts/hydrogen/getting-started).
---
# Adding Localization to Your Storefront
For a full overview of localization in Pack Admin and the Customizer, see [Localization](/create-manage-content/localization).
## GraphQL Queries
* Queries now accept two optional parameters—`language` and `country`—which will return translated content when available, or default to your primary locale.
* You must also use the `@inContext` directive. For example, fetching a page by handle becomes:
```graphql
query PageByHandle(
$handle: String!
$language: String
$country: String
) @inContext(language: $language, country: $country) {
pageByHandle(handle: $handle) {
id
handle
}
}
```
* To request the French-Canadian version of “About Us,” pass these variables:
```json
{
"handle": "about-us",
"language": "fr",
"country": "CA"
}
```
***
## Fallback Resolution
When querying localized content, Pack resolves content in this order:
1. **Requested locale** — Exact match for the `language` + `country` provided
2. **Fallback locale** — The locale's configured fallback (if set)
3. **Primary locale** — The store's primary locale
4. **Non-localized content** — Legacy content created before localization (has no locale)
If no content is found at any level, the query returns `null`.
### Example Fallback Chain
Request: fr-CA (French Canadian)
↓ not found
Fallback: fr-FR (French)
↓ not found
Primary: en-US (English)
↓ found
Returns: en-US content
This means you can launch new locales immediately—untranslated content automatically falls back without requiring code changes.
***
## Handle Uniqueness
Content handles are unique per locale. This means you can have:
* `/pages/about` in `en-US`
* `/pages/about` in `fr-FR`
* `/pages/about` in `de-DE`
Each is a separate piece of content, linked together as translations.
***
## SEO Considerations
Storefronts implementing localization **must handle hreflang tags correctly** to avoid SEO issues:
* Localization creates duplicate versions of the same content for each locale.
* If SEO settings or meta tags are not updated for each localized version, search engines may flag them as **duplicate content**.
* Be explicit about content intent and ensure localized URLs (e.g. `/fr-CA/...`) and hreflang tags are implemented.
* Risks of not doing so:
* Diluted search rankings if search engines can’t determine which version is canonical.
* Google may surface the wrong language version (e.g. `en-US` instead of `fr-CA`), leading to bounce and poor customer experience.
* **Action:** Always update localized meta tags, titles, and hreflang references. Ensure Pack Admin / Customizer changes carry through to your storefront implementation.
***
## Fallback Handling & Direct Visitors
Localization introduces complexity for direct visitors and mismatched URLs:
* An implementor in Remix must handle redirects smartly:
* Example: `/fr-CA/pages/about-us` → `/fr-CA/pages/a-propos-de-nous` if a translated handle exists.
* If no translation exists, redirect to the **primary locale** instead of a 404.
* Common issues to account for:
* Users guessing URLs (`/fr-CA/...`) or following old links.
* Links in localized markdown pointing to English handles (`/en-US/...`). These should redirect intelligently.
* **Recommendation:**
* Decide how storefronts should handle falling back: exact match, redirect, or 404.
* Default best practice: **redirect to the primary locale equivalent page** if no localized version exists.
***
## Known Issues & Performance Notes
* Duplicate SEO pages can occur when localization is enabled but SEO meta settings aren’t updated per locale.
* This is not a bug in Pack but a configuration issue - the frontend code should control what values are passed in the GraphQL queries and ensure proper locale handling.
---
# Metaobjects: Implementing custom data models with Pack and Hydrogen
Metaobjects in Shopify empower merchants to define and manage custom data models on their storefronts or shops.
Metaobjects facilitate the addition and storage of structured data within the storefront or shop. These custom data models are vital for dynamically integrating content like product specifications, artist bios, and more across various site pages, ensuring consistency and streamlined management.

## Use Cases
Metaobjects are ideal for content that demands uniformity and minimal updates.
**Common use cases for metaobjects include:**
* Artist/designer profiles
* Size charts
* Product specifications
* Ingredient lists
* Manufacturing information
* Content blocks for landing pages
These applications demonstrate the utility of metaobjects in maintaining content consistency.
## Implementation Process
Implementing metaobjects in a Pack-powered Hydrogen storefront involves these key steps:
1. Create the metaobject definition in Shopify
2. Create metaobject entries in Shopify admin
3. Create a [section schema](https://docs.packdigital.com/section-schema-api) in your Pack project
4. Set up data fetching in your [route loaders](https://docs.packdigital.com/create-manage-content/templates)
5. Implement the component to render metaobject content
### Create Metaobject Definition in Shopify
First, create your metaobject definition in Shopify:
1. In Shopify admin, go to **Settings > Custom data** (or alternatively, **Content > Metaobjects**)
2. Click **Add definition**
3. Fill in the fields to define your metaobject:
* **Name** - The name of your metaobject definition
* **Fields** - Add fields to define your object's structure (text, rich text, image, etc.)
* **Access Options** - Make sure to check "Storefronts" to make it available
4. Save the definition
For more detailed instructions on creating metaobject definitions, refer to [Shopify's guide on building a metaobject](https://help.shopify.com/en/manual/custom-data/metaobjects/building-a-metaobject).

### Create Metaobject Entries
> **Note**: The feature to add a new metaobject directly in Pack without having to go to
> the Shopify admin is coming soon!
Next, create entries for your metaobject definition:
1. In Shopify admin, go to **Content > Metaobjects**
2. Click on the metaobject definition you created
3. Click **Add entry**
4. Fill in the field values for your entry
5. Save the entry
6. Repeat for any additional entries you need

### Create a Pack Section Schema
In your Pack project, create a [section](https://docs.packdigital.com/create-manage-content/sections) that will display your metaobject content. This involves creating a schema file that defines how Pack should interact with the metaobject data. For more details on creating schemas, refer to the [Section Schema API](https://docs.packdigital.com/section-schema-api).
1. Create a new section folder in app/sections with the following structure:
* `MetaobjectTextBlock.schema.ts` containing your component's schema. Use `dataSource` to pull in the metaobject data from Shopify.
```tsx
export function Schema() {
return {
category: 'Text',
label: 'Text Block',
key: 'metaobject-text-block',
previewSrc:
'https://cdn.shopify.com/s/files/1/0671/5074/1778/files/text-block-preview.jpg?v=1675730349',
dataSource: {
source: 'shopify', // where the data is coming from, in this case Shopify
type: 'metaobject', // what type of data it is, in this case a metaobject
reference: {
type: 'text_block', // what the definition is called in Shopify
},
},
fields: [],
}
}
```
* `MetaobjectTextBlock.tsx` containing your component's code. You can reference the `cms` prop to get the fields needed based off of what you defined in Shopify for the metaobject.
```tsx
export function MetaobjectTextBlock({ cms }) {
const { button_link, button_link_text, heading, subtext } = cms
return (
)
}
```
* `index.ts` exporting your new section component.
```tsx
export { MetaobjectTextBlock } from './MetaobjectTextBlock'
```
2. Register the new section in `app/sections/index.tsx`:
```tsx
import { MetaObjectTextBlock } from './MetaObjectTextBlock'
export function registerSections() {
registerSection(MetaObjectTextBlock, { name: 'meta-object-text-block' })
}
```
3. Now, when you navigate to Pack's Customizer, you should see the new metaobject section available to add to your page. Once you add the section to your page, you will have access to all the entries you created in the Shopify admin.

## Use metaobject sections in Pack's Customizer
Once you've added a new metaobject section to your codebase, you can use it in Pack's Customizer to add content to your pages.
1. Navigate to Pack's Customizer
2. Click on **+** to add a new section to your page
3. Select **Add New Section**
4. Select the **Metaobjects** tab
5. Select the metaobject section you created and click **Add Selected**
6. Click on the newly added section to select the metaobject entry you want to display
The selected metaobject content will now display on your page. You can [preview your changes](/create-manage-content/previewing) using Pack's preview mode to see how they'll look on different devices before publishing.
## Advanced Usage: Metaobject Collections
For more complex scenarios, you might want to display multiple metaobject entries. This is especially useful for creating team member listings, product feature comparisons, or other collection-style displays. You can modify your [section schema](https://docs.packdigital.com/section-schema-api) to allow selection of multiple metaobjects:
### Schema for multiple metaobjects
```tsx
export function Schema() {
return {
category: 'Collection',
label: 'Team Members',
key: 'team-members',
previewSrc: 'https://example.com/preview.jpg',
fields: [
{
label: 'Heading',
name: 'heading',
component: 'text',
defaultValue: 'Our Team',
},
{
label: 'Team Members',
name: 'members',
component: 'group-list',
itemProps: {
label: '{{item.member.handle}}',
},
fields: [
{
name: 'member',
component: 'metaobjectSearch',
label: 'Team Member',
metaobjectType: 'team_member', // Corrected from team\_member
},
],
defaultValue: [],
},
],
};
}
```
## Usage in Routes
In your [route component](/create-manage-content/templates), you need to render sections that include metaobjects. Pack provides built-in components like RenderSections to help display your content:
### Example route component
```tsx
export default function PageTemplate() {
const { page, metaobjects } = useLoaderData();
return (
{page.sections.map((section) => {
// For metaobject sections, pass the corresponding metaobject data
if (
section.type === 'metaobject-text-block' &&
section.settings?.reference?.handle
) {
const metaobjectHandle = section.settings.reference.handle;
const metaobjectData = metaobjects[metaobjectHandle];
if (metaobjectData) {
// Transform the metaobject fields into a more usable format
const fieldValues = metaobjectData.fields.reduce((acc, field) => {
acc[field.key] = field.value ? field.value : field.reference;
return acc;
}, {});
return (
);
}
}
// Handle other section types
// ...
return null;
})}
);
}
```
## Troubleshooting
### Metaobject Not Appearing in Pack
If your metaobject isn't appearing in Pack's Customizer:
1. Verify the metaobject type in your schema matches exactly with Shopify (including underscores)
2. Check that your metaobject has the "publishable" capability enabled
3. Ensure your metaobject entries are set to "Active" status
4. Make sure "Storefronts" is checked in the Access Options
### Field Values Not Rendering
If field values aren't displaying correctly:
1. Check field types match between Shopify and your component implementation
2. For reference fields (like images), make sure you're accessing the nested data correctly
3. Use console logging to inspect the data structure coming from your GraphQL query
## Conclusion
Metaobjects provide a powerful way to create reusable, structured content in your [Pack-powered Hydrogen storefront](https://docs.packdigital.com/getting-started/storefront-setup). By following this guide, you've learned how to:
1. Set up metaobject definitions in Shopify
2. Create a [Pack section](/developer-resources/blueprint-sections) that references metaobjects
3. Fetch and display metaobject content in your [routes](/create-manage-content/templates)
4. Allow content editors to select metaobjects through [Pack's Customizer](/create-manage-content/customizer)
With these tools, you can create dynamic, maintainable content that can be updated in a single place while appearing consistently across your entire storefront.
For more information about Shopify metaobjects, refer to [Shopify's official metaobjects documentation](https://help.shopify.com/en/manual/custom-data/metaobjects).
---
# Connect AI Tools to Pack with MCP
Pack's Model Context Protocol (MCP) server lets compatible AI tools read and manage content in your Pack storefront. OAuth is the recommended way to connect: you sign in to Pack in your browser, choose the storefront the client can access, and approve the connection without copying a Pack access token into the client.
Use this MCP server URL for production connections:
```text
https://pack-agent.packdigital.workers.dev/mcp
```
> **Warning**: Pack MCP tools can change customer-visible content, including publishing and
> unpublishing it. Review the proposed changes and confirm the target storefront
> and content environment before allowing an AI client to perform a write.
## Connect with OAuth
You need a Pack account with access to at least one storefront and an MCP client that supports OAuth for remote MCP servers.
1. Add `https://pack-agent.packdigital.workers.dev/mcp` as a remote MCP server in your client.
2. Choose **Connect** or start authentication. Your client opens Pack in your browser.
3. Sign in to Pack if prompted.
4. Review the client name, callback URL, and requested `pack:mcp` scope.
5. Select the organization and storefront the client should access.
6. Choose **Allow access**. Pack returns you to the MCP client to finish the connection.
The authorization applies only to the storefront you select. To connect another storefront, add a separate MCP connection and complete the flow again.
## Claude Code
Add the Pack MCP server without custom authentication headers:
```sh
claude mcp add --transport http pack https://pack-agent.packdigital.workers.dev/mcp
```
Claude opens the browser authorization flow when the connection authenticates. Check the connection afterward:
```sh
claude mcp list
```
## Claude Desktop
In Claude Desktop, add a custom connector and enter this URL:
```text
https://pack-agent.packdigital.workers.dev/mcp
```
Choose **Connect**, complete the Pack authorization flow in your browser, and return to Claude Desktop. OAuth connections do not require Node.js, `npx`, custom request headers, or manual changes to the Claude Desktop configuration file.
## Access and permissions
Each OAuth connection:
* is limited to the Pack user and storefront approved during authorization;
* uses the single `pack:mcp` scope;
* gives the MCP client a short-lived access token and a refresh token instead of a Pack access token; and
* stops working if the authorization is disconnected or the user loses access to the storefront.
The Pack actor token remains encrypted in the Pack MCP service and is never sent to the MCP client.
OAuth connections currently expose guarded tools for:
* reading, updating, publishing, and unpublishing pages, product pages, collection pages, articles, blogs, and templates;
* reading, publishing, and unpublishing sections; and
* reading, updating, and publishing site settings.
Some operations are temporarily unavailable on OAuth connections, including content creation and deletion, section upserts, A/B testing tools, advanced operations, and media management. The tools returned by your client's MCP tool list are the source of truth for the current connection.
Pack MCP is stateless and does not add its own confirmation step before a tool runs. Your MCP client is responsible for asking for confirmation before destructive or customer-visible changes.
## Content environments
If the connection does not specify a content environment, Pack uses the selected storefront's primary environment for both reads and writes.
Clients that support custom headers can pin the connection to another environment by sending the environment handle in this header:
```text
x-pack-content-environment-id:
```
For example, Claude Code can create an OAuth connection pinned to a non-primary environment:
```sh
claude mcp add --transport http pack-preview \
https://pack-agent.packdigital.workers.dev/mcp \
--header "x-pack-content-environment-id: hydrogen-preview"
```
Use a separate named connection for each environment when you need to work across multiple environments. Clients that cannot add a custom header use the primary environment.
> **Note**: Content environment selection belongs to the MCP connection, not an individual
> tool call. This keeps every read and write in a session on the same
> environment.
## Manage connected apps
To review or remove MCP access:
1. In Pack, open **User settings**.
2. Find **Connected apps**.
3. Review the client, organization, storefront, connection date, and scope.
4. Choose **Disconnect** next to an app and confirm.
Disconnecting an app takes effect immediately. Remove the connection from the MCP client as well if you no longer plan to use it.
To switch a connection to another storefront, disconnect it and authorize a new connection for the correct storefront.
## Legacy authentication
Existing integrations that send Pack credentials in request headers remain supported during the OAuth migration. Use OAuth for new desktop and remote connections.
Legacy clients must send:
```text
Authorization: Bearer
x-pack-store-id:
x-pack-content-environment-id:
```
`x-pack-content-environment-id` is optional. If it is omitted, Pack uses the storefront's primary content environment. The access token determines the storefront it can access; `x-pack-store-id` cannot be used to select a different storefront.
> **Warning**: Treat a Pack access token as a secret. Do not commit it, paste it into shared
> documentation, or place it directly in a client configuration when the OAuth
> flow is available.
## Troubleshooting
### The browser authorization flow does not open
Confirm that the client supports OAuth for remote MCP servers, dynamic client registration, and S256 PKCE. Remove the incomplete connection, add the production MCP URL again, and restart authentication.
### No storefronts are available to select
The signed-in Pack user does not have access to a storefront that can be connected. Ask an organization administrator to grant access, then restart the authorization flow.
### The connection uses the wrong storefront
Open **User settings** in Pack, disconnect the app under **Connected apps**, and authorize a new connection. Select the intended organization and storefront in the consent screen.
### The connection uses the wrong content environment
Connections without an environment header use the storefront's primary environment. If your client supports custom headers, create a separate connection with `x-pack-content-environment-id` set to the intended environment handle.
### Authentication expires or is revoked
OAuth-compatible clients refresh access automatically. If the connection continues to request authentication, remove it from the client and connect again. If the app was disconnected in Pack or the user lost storefront access, a new authorization is required.
---
# Manage Preview URLs
Preview URLs let you isolate and view changes in a sandboxed environment, typically corresponding to a specific git branch or set of code changes. This allows for testing and sharing development changes without affecting the live storefront or shop.

> **Warning**: Ensure your branch names are not excessively long. This can cause issues with
> the preview URL and Customizer not loading, as the maximum length of a domain
> label is 63 characters, per the [DNS size
> limit](https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.4).
## Edit Custom Preview URLs
Custom preview URLs can be added based on how you deploy your storefront or shop (e.g., `http://localhost:8080/`)
1. Select **Customizer** in Pack’s admin.
2. Select the **preview URL** dropdown menu.
3. Click on **Edit preview URLs**.
4. Add a new URL and click **Save**.

## Add a Preview URL for Git Branches
To set up preview URLs for Git branches, follow these steps:
1. Navigate to **Shopify Admin**.
2. Select **Hydrogen** under **Sales Channels**.
3. Click on the Hydrogen Storefront you want to edit.
4. Click on **Storefront settings**
5. Choose **Environments and Variables**.
6. Create a new Hydrogen environment linked to your Git branch, ensuring it is set to **public**.
7. Follow the steps for editing custom preview URLs to add the new URL for the Git branch.

## Edit the Preview URL for Production
1. Navigate to Pack’s Admin.
2. Select **Settings**.
3. Select **Developer**.
4. Click on and edit the production URL.

---
# Properties Panel Implementation
Properties Panel is a powerful tool that allows merchants to customize the look and feel of their content. It provides a user-friendly interface for editing various properties of elements on your page, making it easy to create visually appealing designs without needing any coding knowledge.
> **Warning**: The Properties Panel is not yet available for storefront on the A/B testing branch of `@pack/react`. We are working on integrating it and it will be available soon.
## Setup
1. Use @pack/react\@2.2.0 or higher.
```bash
npm install @pack/react@2.2.0
```
2. Add `styles` to `SectionFragment GQL` in `app/data/graphql/pack/settings.ts`:
export const SECTION_FRAGMENT = `
fragment SectionFragment on Section {
id
title
status
data
styles // Add styles field here
publishedAt
createdAt
updatedAt
}
---
# Section Schema API Reference: Building Custom Components
A section is the backbone of how a page shows its content. It holds structured data and decides which fields you content editors can interact with in the [Customizer](/create-manage-content/customizer). The magic happens thanks to the section schema, attached to the React component in your storefront code.
## The section schema
The section schema integrates with the prototype of your React component. This schema dictates the structure of the final data object and its accessor keys. Now, let's dive into crafting a simple schema and section to see this in action.
* **label** (string): A human readable label for your section.
* **key** (string): A unique identifier for your section.
* **category** (string): An optional string that lets you categorize this section. For example, "Heroes" or "Text Blocks".
* **fields** (Array\): An array of supported fields that make up your section.
* **datasource** (DataSource): At the moment we only support Shopify Metaobjects, with more supported sources coming in the future.An optional configuration to allow your section to pull from 3rd party data sources.
```ts {{ title: 'Schema Definition' }}
type Schema = SchemaObject | SchemaFunction
interface SchemaObject {
label: string
key: string
category?: string
fields?: Array
datasource?: DataSource
}
interface SchemaFunction {
(): SchemaObject
}
interface DataSource {
source: 'shopify'
type: 'metaobject'
reference: {
type: string
}
}
```
```js {{ title: 'Example Component' }}
export const HelloWorldBanner = ({ cms }) => {
return (
Hello World, my name is {cms?.yourName}
)
}
HelloWorldBanner.Schema = {
label: 'My Hello World Banner',
key: 'helloWorldBanner',
fields: [
{
component: 'text',
name: 'yourName',
label: 'What is your name?',
description: 'The name which the banner will greet you.',
},
],
}
```
***
## Text Field

A Text field is a single text input. You can use this field for short strings such as titles, handles, or headings.
* **component** (string): The name of the component for this field. In this case text.
* **name** (string): The name of the accessor key for the final content.
* **label** (string): A human readable description of what the field is.
* **description** (string): An optional string to allow you to add more context about the field.
* **defaultValue** (string): An optional string to set the default text value.
* **validate** (object): An optional object that can include:required: a boolean indicating if the field is required.regex: a string with a regular expression for validation.errorMessage: a string to display when regex validation fails.
```ts {{ title: 'Interface' }}
interface TextField {
component: 'text'
name: string
label: string
description?: string
defaultValue?: string
validate?: {
required?: boolean,
regex?: string,
errorMessage?: string
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'text',
name: 'heading',
label: 'Hero Heading',
description: 'Main text for Hero',
defaultValue: 'Hello world',
validate: {
regex: "^(?=.{5,10}$)(?!.*\\d).$",
errorMessage: "Heading must be 5-10 characters long and contain no numbers"
}
}
```
```js {{ title: 'Return' }}
{
heading: 'Hello world'
}
```
***
## Text Area Field

A Text Area field is multi-line text input. You can use this field for longer strings such as copy, descriptions, or details.
* **component** (string): The name of the component for this field. In this case textarea.
* **name** (string): The name of the accessor key for the final content.
* **label** (string): A human readable description of what the field is.
* **description** (string): An optional string to allow you to add more context about the field.
* **defaultValue** (string): An optional string to set the default text value.
* **validate** (object): An optional object that has a required key with a boolean value to set if this field is required or not.
```ts {{ title: 'Interface' }}
interface TextAreaField {
component: 'textarea'
name: string
label: string
description?: string
defaultValue?: string
validate?: {
required: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'textarea',
name: 'faqAnswer',
label: 'FAQ Answer',
description: 'Short description for the question',
}
```
```js {{ title: 'Return' }}
{
faqAnswer: 'Do this thing!'
}
```
***
## Image Field
Select an image from the media manager.
* **component** (string): The name of the component for this field. In this case image.
* **name** (string): The name of the accessor key for the final content.
* **label** (string): A human readable description of what the field is.
* **description** (string): An optional string to allow you to add more context about the field.
* **defaultValue** (string): An optional string to set the default image value.
* **validate** (object): An optional object that has a required key with a boolean value to set if this field is required or not.
```ts {{ title: 'Interface' }}
interface ImageField {
component: 'image'
name: string
label: string
description?: string
defaultValue?: string
validate?: {
required: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'image',
name: 'heroImage',
label: 'Hero Image',
description: 'Image for hero banner',
}
```
```js {{ title: 'Return' }}
{
id: "gid://shopify/MediaImage/27076979032264",
src: "https://cdn.shopify.com/s/files/1/0629/5519/2520/files/men-posing-in-outerwear.jpg?v=1701459236",
size: 550781,
type: "file",
width: 3000,
format: "image/jpeg",
height: 1989,
altText: "",
filename: "men-posing-in-outerwear.jpg?v=1701459236",
directory: "",
previewSrc: "https://cdn.shopify.com/s/files/1/0629/5519/2520/files/men-posing-in-outerwear.jpg?v=1701459236",
aspectRatio: 1.508295625942685
}
```
***
## Rich Text Field

You can use this field to write rich text content. The output is an HTML string.
> **Warning**: Your component will need to be set up to render an HTML string, and it is important that you should *sanitize the HTML output*. Our example from our Blueprint theme can be found [here](https://github.com/packdigital/pack-hydrogen-theme-blueprint/tree/main/app/sections/RichText/RichText.schema.ts).
* **component** (string): The name of the component for this field. In this case richText.
* **name** (string): The name of the accessor key for the final content.
* **label** (string): A human readable description of what the field is.
* **description** (string): An optional string to allow you to add more context about the field.
* **defaultValue** (string): An optional string to set the default markdown value.
```ts {{ title: 'Interface' }}
interface RichTextField {
component: 'rich-text'
name: string
label: string
description?: string
defaultValue?: string
}
```
```js {{ title: 'Example' }}
{
component: 'rich-text',
name: 'productInfoBlurb',
label: 'Product Info Blurb',
description: 'Short details and information about the product',
}
```
```js {{ title: 'Return' }}
```
***
## Markdown Field

You can use this field to write markdown text.
> **Warning**: Your component will need to be set up to render a markdown string. For example, a library like [react-markdown](https://github.com/remarkjs/react-markdown) can be used.
* **component** (string): The name of the component for this field. In this case markdown.
* **name** (string): The name of the accessor key for the final content.
* **label** (string): A human readable description of what the field is.
* **description** (string): An optional string to allow you to add more context about the field.
* **defaultValue** (string): An optional string to set the default markdown value.
* **validate** (object): An optional object that has a required key with a boolean value to set if this field is required or not.
```ts {{ title: 'Interface' }}
interface MarkdownField {
component: 'markdown'
name: string
label: string
description?: string
defaultValue?: string
validate?: {
required: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'markdown',
name: 'privacyInfo',
label: 'Privacy Notes',
description: 'Details about product\'s privacy details',
}
```
```js {{ title: 'Return' }}
{
privacyInfo: "This is some **content**.",
}
```
***
## Link Field

This field can be used to select pages in your storefront or link to external sites.
```ts {{ title: 'Interface' }}
interface LinkField {
component: 'link'
name: string
label: string
description?: string
defaultValue?: {
text: string
url: string
}
}
```
```js {{ title: 'Example' }}
{
component: 'link',
name: 'linkName',
label: 'Our company page',
defaultValue: {
text: 'Our mission',
url: '/pages/about-us'
}
}
```
```js {{title: "Return"}}
{
linkName: {
"url": "/pages/about-us",
"text": "Our mission",
"type": "isExternal",
"newTab": true
}
}
```
***
## Number Field

This field accepts a number as an input.
![]()
To make the number input a slider, set `variant` to `"slider"`.
By default, the slider will have a `min` value of `0` and a `max` value of `100`. To change these values, provide custom values in the `validate` object.
![]()
To enable a range slider, set `variant` to `"slider"` and set `defaultValue` to an array of two numbers.
```ts {{ title: 'Interface' }}
interface NumberField {
component: 'number'
name: string
label: string
description?: string
defaultValue?: number | number[]
validate?: {
required: boolean
min: number
max: number
} | ((...args: unknown[]) => unknown)
required?: boolean
step?: number
variant?: "input" | "slider" // default is "input"
}
```
```js {{ title: 'Example' }}
{
component: 'number',
name: 'productCount',
label: 'Product Count',
description: 'How many products in the carousel',
}
```
```js {{ title: 'Return' }}
{
productCount: 5
}
```
```js {{ title: 'Slider Example' }}
{
component: 'number',
name: 'secondsDelay',
label: 'Seconds Delay',
description: 'How many seconds for each slide',
variant: "slider",
validate: {
min: 1,
max: 10,
}
}
```
```js {{ title: 'Return' }}
{
secondsDelay: 3
}
```
```js {{ title: 'Range Example' }}
{
component: 'number',
name: 'priceRange',
label: 'Price Range',
defaultValue: [250, 500],
variant: "slider",
validate: {
min: 0,
max: 1000,
}
}
```
```js {{ title: 'Return' }}
{
priceRange: [500, 750]
}
```
***
## Dimension Field
This field allows you to define a dimension with a value and unit.
```ts {{ title: 'Interface' }}
interface DimensionField {
component: 'dimension'
name: string
label: string
description?: string
defaultValue?: {
value: string
unit: string
}
}
```
```js {{ title: 'Example' }}
{
component: 'dimension',
name: 'productHeight',
label: 'Product Height',
description: 'Height of the product',
}
```
```js {{ title: 'Return' }}
{
productHeight: {
"value": "12",
"unit": "INCHES"
}
}
```
***
## Volume Field
This field allows you to define a volume with a value and unit.
```ts {{ title: 'Interface' }}
interface VolumeField {
component: 'volume'
name: string
label: string
description?: string
defaultValue?: {
value: string
unit: string
}
}
```
```js {{ title: 'Example' }}
{
component: 'volume',
name: 'productVolume',
label: 'Product Volume',
description: 'Volume of the product',
}
```
```js {{ title: 'Return' }}
{
productVolume: {
"value": "12",
"unit": "PINTS"
}
}
```
***
## Weight Field
This field allows you to define a weight with a value and unit.
```ts {{ title: 'Interface' }}
interface WeightField {
component: 'weight'
name: string
label: string
description?: string
defaultValue?: {
value: string
unit: string
}
}
```
```js {{ title: 'Example' }}
{
component: 'weight',
name: 'productWeight',
label: 'Product Weight',
description: 'Weight of the product',
}
```
```js {{ title: 'Return' }}
{
productWeight: {
"value": "12",
"unit": "POUNDS"
}
}
```
***
## Money Field
This field accepts an amount.
The currencyCode defaults to "USD" if not specified.
```ts {{ title: 'Interface' }}
interface MoneyField {
component: 'money'
name: string
label: string
description?: string
defaultValue?: {
amount?: string | number,
currencyCode?: "USD" | "EUR" | "GBP" | "CAD"
}
validate?: {
required: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'money',
name: 'price',
label: 'Price',
description: 'How much does this product cost?',
}
```
```js {{ title: 'Return' }}
{
price: {
amount: "100",
currencyCode: "USD"
}
}
```
***
## Product Field

This field allows you to select a product.
```ts {{ title: 'Interface' }}
interface ProductSearchField {
component: 'productSearch'
name: string
label: string
description?: string
validate?: {
required: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'productSearch',
name: 'upsellProduct',
label: 'Upsell Product',
description: 'Select product to upsell',
}
```
```js {{ title: 'Return'}}
{
upsellProduct: {
"handle": "the-collection-snowboard-liquid",
"id": "gid://shopify/Product/8694422569233",
"data": {
"title": "The Collection Snowboard: Liquid",
"handle": "the-collection-snowboard-liquid",
"images": [
{
"__typename": "ShopifyImage",
"originalSrc": "https://cdn.shopify.com/s/files/1/0807/6515/7649/files/photo-1614358536373-1ce27819009e.jpg?v=1691640943"
},
{
"__typename": "ShopifyImage",
"originalSrc": "https://cdn.shopify.com/s/files/1/0807/6515/7649/products/Main_b13ad453-477c-4ed1-9b43-81f3345adfd6.jpg?v=1691620844"
}
],
"__typename": "ProductData",
"productType": ""
},
"__typename": "Product"
}
}
```
***
## Collection Field

This field allows you to select a collection.
```ts {{ title: 'Interface' }}
interface CollectionsField {
component: 'collections'
name: string
label: string
description?: string
validate?: {
required: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'collections',
name: 'collectionRow',
label: 'Collection Row',
}
```
```js {{ title: 'Return'}}
{
collectionRow: {
"id": "018a67a8-bd21-759f-adc1-a15a8c513f53",
"title": "On Sale 25%",
"__typename": "CollectionPage",
"description": "",
"sourceCollection": {
"image": null,
"__typename": "ShopifyCollection"
}
}
}
```
***
## Product Bundles Field

T
This field allows you to select a product bundle from Pack.
```ts {{ title: 'Interface' }}
interface ProductBundlesField {
component: 'productBundles'
name: string
label: string
description?: string
}
```
```js {{ title: 'Example' }}
{
component: 'productBundles',
name: 'upsellBundle',
label: 'Upsell Bundle',
description: 'Select a bundle'
}
```
```js {{ title: 'Return'}}
{
upsellBundle: {
"id": "3e014358-1f4d-4b85-a2dc-9f9cede56852",
"title": "Upsell Bundle",
"products": [
{
"id": "gid://shopify/Product/8694422536465",
"handle": "selling-plans-ski-wax",
...
},
{
"id": "gid://shopify/Product/8694422569233",
"handle": "the-collection-snowboard-liquid",
...
},
{
"id": "gid://shopify/Product/8694422503697",
"handle": "the-collection-snowboard-oxygen",
...
}
],
"__typename": "Bundle",
"description": ""
}
}
```
***
## HTML Field

Allows you to write HTML.
> **Warning**: This field will return raw HTML. It is important that your component is set up to sanitize and render the markup accordingly.
```ts {{ title: 'Interface' }}
interface HTMLField {
component: 'html'
name: string
label: string
description?: string
defaultValue?: string
validate?: {
required: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'html',
name: 'markup',
label: 'HTML Markup',
description: 'HTML for content',
defaultValue: '
Hello world!
',
}
```
```js {{ title: 'Return' }}
{
markup : "
Hello world!
"
}
```
***
## Group Field

Lets you group a set of fields into a group of values.
```ts {{ title: 'Interface' }}
interface GroupField {
component: 'group'
name: string
fields: Array
label: string
description?: string
defaultValue?: object
}
```
```js {{ title: 'Example' }}
{
component: 'group',
name: 'details',
label: 'Your Details',
description: 'Info about you',
fields: [
{
component: 'text',
name: 'firstName'
},
{
component: 'number',
name: 'age'
},
]
defaultValue: {
firstName: 'Andrew',
age: 28
},
}
```
```js {{ title: 'Return' }}
{
details : {
firstName: 'Andrew',
age: 28
}
}
```
***
## List Field

Lets you create a list of a single field type. For example, you can create a list of text fields or number fields.
```ts {{ title: 'Interface' }}
interface ListField {
component: 'list'
name: string
label: string
fields: {
component: string
}
description?: string
defaultValue?: Array
validate?: {
maxItems?: number
required?: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'list',
name: 'navLabels',
label: 'Navigation Labels',
field: {
component: 'text',
},
defaultValue: ['hello', 'world'],
}
```
```js {{ title: 'Return' }}
{
navLabels : [
'hello',
'world'
]
}
```
***
## Group List Field

Lets you create a list of groups. For example, you can create a list of groups that holds a text field, number field, and product bundle field.
The `itemProps.label` configuration allows you to give readable values that appear in the list. In the example below:
```js
label: "Friend: {{item.firstName}}"
```
You can choose any accessor `name` in the list of fields for this group like `lastName` or `number`.
```ts {{ title: 'Interface' }}
interface GroupListField {
component: 'group-list'
name: string
label: string
fields: Array
description?: string
defaultValue?: Array
itemProps?: {
label: string
} | ((...args: unknown[]) => unknown)
validate?: {
maxItems?: number
required?: boolean
} | ((...args: unknown[]) => unknown)
required?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'group-list',
name: 'friends',
label: 'My Friends',
itemProps: {
label: "Friend: {{item.firstName}}"
},
fields: [
{
component: 'text',
name: 'firstName',
label: 'First Name'
},
{
component: 'text',
name: 'number',
label: 'Phone Number'
},
],
defaultValue: [
{
firstName: 'John',
number: '(777) 777-7777'
}
],
}
```
```js {{ title: 'Return' }}
{
friends : [
{
"firstName": "John",
"number": "(777) 777-7777"
},
{
"firstName": "Jane",
"number": "(333) 333-3333"
},
]
}
```
***
## Blocks Field

Blocks is a flexible field that let you create a set of unique fields, making it easy to build a variety of components. For example, you can use them to quickly create a customizable form with different types of inputs, or to put together a content section with various elements like images or accordions. This system simplifies the process of designing and arranging components to suit your specific requirements.
### Nested sections
Setting the optional `sectionTypes` property on a blocks field lets editors add **whole sections** as blocks — alongside any inline templates you define. This enables the [Nested Sections](/create-manage-content/sections#nested-sections) feature in the customizer.
Each entry in `sectionTypes` must match the `key` of a registered section schema in your storefront. The customizer's block picker will surface those section types as options, and editors can also use "Insert from Library" to embed existing sections. Nested sections can be nested up to three levels deep.
### Rendering nested sections
When the parent section is fetched, nested-section refs are inlined into its `blocks` array. Walk the array and dispatch by `_template`:
* **Registered section types** (from `sectionTypes`) — render with `` from `@pack/react`. It looks up the component in the registry populated by `registerSection`.
* **Inline templates** (from the field's `templates` map) — render those explicitly; they aren't registered sections.
Requires `@pack/react@^4.3.0`.
```ts {{ title: 'Interface' }}
interface BlocksField {
component: 'blocks'
name: string
label: string
fields: Array
description?: string
templates: {
[string]: {
label: string,
key: string,
fields: Array,
}
}
/** Optional. Section schema keys that editors can embed as blocks. */
sectionTypes?: string[]
}
```
```js {{ title: 'Example' }}
{
component: 'blocks',
name: 'varietyList',
label: 'List Of Things',
templates: {
human: {
label: 'Human Thing',
key: 'human',
itemProps: {
label: 'Human says : {{item.noise}}',
},
fields: [
{
label: 'Noise',
name: 'noise',
component: 'text',
},
]
},
cat: {
label: 'Cat Thing',
key: 'cat',
itemProps: {
label: 'Cat says : {{item.noise}}',
},
fields: [
{
label: 'Noise',
name: 'noise',
component: 'text',
},
]
},
dog: {
label: 'Dog Thing',
key: 'dog',
itemProps: {
label: 'Dog says : {{item.noise}}',
},
fields: [
{
label: 'Noise',
name: 'noise',
component: 'text',
},
]
},
}
}
```
```js {{ title: 'Nested Sections Example' }}
{
component: 'blocks',
name: 'contentBlocks',
label: 'Content Blocks',
sectionTypes: ['promo-banner', 'feature-card'],
templates: {
heading: {
label: 'Heading',
key: 'heading',
fields: [
{
label: 'Text',
name: 'text',
component: 'text',
},
],
},
},
}
```
```js {{ title: 'Return' }}
{
varietyList: [
{
"_template": "human",
"noise": "Gah"
},
{
"_template": "cat",
"noise": "meow"
},
{
"_template": "dog",
"noise": "bark bark"
}
]
}
```
```tsx {{ title: 'Rendering Nested Sections' }}
import {RenderSection} from '@pack/react';
import {Link} from '~/components/Link';
export function NestedBlock({block}) {
const {_template: type} = block;
// Inline templates — handled explicitly.
if (type === 'heading') {
return
{block.text}
;
}
if (type === 'button') {
return (
{block.link?.text}
);
}
// Registered section types — looked up via the section registry.
return ;
}
```
***
## Toggle Field

The Toggle Field is a boolean toggle. You can set its value for content that needs a true and or false state.
```ts {{ title: 'Interface' }}
interface ToggleField {
component: 'toggle'
name: string
label: string
toggleLabels?: {
true?: string | boolean,
false?: string | boolean
}
description?: string
defaultValue?: boolean
}
```
```js {{ title: 'Example' }}
{
component: 'toggle',
name: 'amICool',
label: 'Am I Cool?',
toggleLabels: {
true: 'Yah',
false: 'Nah',
},
defaultValue: false,
}
```
```js {{ title: 'Return' }}
{
amICool : false
}
```
***
## Select Field

This is a select / dropdown input field. It will return the value of your selected option.
```ts {{ title: 'Interface' }}
interface SelectField {
component: 'select'
name: string
label: string
options?: Array<
{
label: string,
value: string
}
>
description?: string
defaultValue?: string
}
```
```js {{ title: 'Example' }}
{
component: 'select',
name: 'bundleQty',
label: 'Bundle Qty',
options: [
{
label: 'One Pack',
value: 'one-pack-sku'
},
{
label: 'Two Pack',
value: 'two-pack-sku'
},
{
label: 'Three Pack',
value: 'three-pack-sku'
},
],
defaultValue: 'three-pack-sku',
}
```
```js {{ title: 'Return' }}
{
bundleQty : 'two-pack-sku'
}
```
***
## Radio Group Field

This is a field of radio inputs. Returns the value of your selected option. This field also has the option to be displayed vertically or horizontally, or as radios or buttons.
```ts {{ title: 'Interface' }}
interface RadioGroupField {
component: 'radio-group'
name: string
label: string
options?: Array<
{
label: string,
value: string
}
>
description?: string
defaultValue?: string
variant?: "radio" | "button"
direction?: "vertical" | "horizontal"
}
```
```js {{ title: 'Example' }}
{
component: 'radio-group',
name: 'radioName',
label: 'Radio Group Field',
description: 'A radio field',
options: [
{
label: 'Yes',
value: 'yesValue'
},
{
label: 'Maybe',
value: 'maybeValue'
},
{
label: 'No',
value: 'noValue'
},
],
defaultValue: 'maybeValue',
}
```
```js {{ title: 'Return' }}
{
radioName : 'maybeValue'
}
```
***
## Tags Field

This field will render an input where you can create tags by inputting your tag and hitting enter. The return value will be an array of strings.
```ts {{ title: 'Interface' }}
interface TagsField {
component: 'tags'
name: string
label: string
description?: string
defaultValue?: Array
}
```
```js {{ title: 'Example' }}
{
name: 'tagsName',
label: 'Tags Field',
component: 'tags',
defaultValue: ['default-tag-1', 'default-tag-2'],
}
```
```js {{ title: 'Return' }}
{
tagsName : ['default-tag-1', 'default-tag-2']
}
```
***
## Color Field

This field will render an input where you can select a color.
```ts {{ title: 'Interface' }}
interface ColorField {
component: 'color'
name: string
label?: string
description?: string
colorFormat?: 'hex' | 'rgb' // Defaults to "hex"
colors?: string[]
widget?: 'sketch' | 'block' // Defaults to "sketch
defaultValue?: Array
}
```
```js {{ title: 'Example' }}
{
component: 'color',
name: 'colorName',
label: 'Color Field',
description: 'Pick a color',
colorFormat: 'hex',
colors: ['#FF0000', '#00FF00', '#0000FF'],
widget: 'sketch',
defaultValue: "#00FF00",
}
```
```js {{ title: 'Return' }}
{
color : "#00FF00"
}
```
***
## Date Field
This field will render an input where you can select a date.

```ts {{ title: 'Interface' }}
interface DateField {
component: 'date'
name: string
label: string
description?: string
timeFormat?: boolean | string // Accepts Moment.js date format
dateFormat?: boolean | string // Accepts Moment.js date format
}
```
```js {{ title: 'Example' }}
{
component: 'date',
name: 'date',
label: 'Date',
timeFormat: true,
dateFormat: 'MM/DD/YYYY',
}
```
```js {{ title: 'Return' }}
{
date : "2021-09-01T00:00:00.000Z"
}
```
---
# Templates in Pack: Understanding and Managing Page Structures
> **Warning**: Templates are only available on Pack storefronts.
Templates are React components that you can directly edit to add or change a given route's (e.g., page, product, collection...) structure and layout.

Each template has access to a `renderSections` prop which is a function that will render all the dynamic sections added in the Customizer or Pack Admin for that template.
## Available Types
You can find all available template types inside your storefront's Github repository in the `/app/routes` directory.
* Home — renders on `/` home page visits
* Page — renders on `/pages/:handle` visits
* Blog — renders on `/blogs/:handle` visits
* Article — renders on `/articles/:handle` visits
* Product — renders on `/products/:handle` visits
* Collection — renders on `/collections/:handle` visits
* Account — renders on `/account/:subroute` visits
* 404 — renders on `invalid-routes` visits
### Default Templates
When you create a store, you’ll find several templates that are already created for you by default. Let’s review each template.
### Home
This template will be used for your storefront's homepage.
### Page
This template will be used for all pages that follow the `/pages/` route in your storefront.
> **Note**: `RenderSections` will render any customizer CMS sections defined for the given
> `/pages/:handle` route. Use `useSections` instead to return the section data
> in an object to give you more control of how you want to render the sections.
```jsx
// app/routes/($locale).pages.$handle.tsx
export default function PageRoute() {
const { page } = useLoaderData();
return (
);
}
```
### Blog
This template will be used for all blog pages that follow the `/blogs/` route in your storefront.
> **Note**: `RenderSections` will render any customizer CMS sections defined for the given
> `/blogs/:handle` route. Use `useSections` instead to return the section data
> in an object to give you more control of how you want to render the sections.
```jsx
// app/routes/($locale).blogs.$handle.tsx
export default function BlogRoute() {
const { blog, siteTitle, url } = useLoaderData();
return (
);
}
```
### Article
This template will be used for all article pages that follow the `/blogs/:blogHandle/:articleHandle` route in your storefront.
> **Note**: `RenderSections` will render any customizer CMS sections defined for the given
> `/blogs/:blogHandle/:articleHandle` route. Use `useSections` instead to return
> the section data in an object to give you more control of how you want to
> render the sections.
```jsx
// app/routes/($locale).articles.$handle.tsx
export default function ArticleRoute() {
const { article, siteTitle, url } = useLoaderData();
return (
{/* ... */}
);
}
```
### Product
This template will be used for all product pages that follow the `/products/:handle` route in your storefront.
> **Note**: `RenderSections` will render any customizer CMS sections defined for the given
> `/products/:handle` route. Use `useSections` instead to return the section
> data in an object to give you more control of how you want to render the
> sections.
```jsx
// app/routes/($locale).products.$handle.tsx
export default function ProductRoute() {
const { product, productPage, selectedVariant, siteTitle, url } =
useLoaderData();
return (
);
}
```
### Collection
This template will be used for all collection pages that follow the `/collections/:handle` route in your storefront.
> **Note**: `RenderSections` will render any customizer CMS sections defined for the given
> `/collections/:handle` route. Use `useSections` instead to return the section
> data in an object to give you more control of how you want to render the
> sections.
```jsx
// app/routes/($locale).collections.$handle.tsx
export default function CollectionRoute() {
// ... //
const [collection, setCollection] = useState(resolveFirstCollection)
const [allProductsLoaded, setAllProductsLoaded] = useState(false)
return (
{collectionPage && }
)
}
```
### Account
The account template comprises multiple other sub-templates for routes such as sign-in, order history, address book, payment methods, and more.
All available sub-templates for the account template can be found in the `/app/routes` directory.
### 404
This template will be used for all 404 pages in your storefront.
> **Note**: `RenderSections` will render any customizer CMS sections defined for the given
> `/404` route. Use `useSections` instead to return the section data in an
> object to give you more control of how you want to render the sections.
```jsx
// app/routes/$.tsx
export default function Route404() {
return null
}
```
* [Templates](/create-manage-content/templates): Learn more about templates
* [Sections](/create-manage-content/sections): Learn more about sections
* [Localhost Setup](/developer-resources/blueprint-setup): Learn how to set up your localhost
---
# Documentation Overview
This page provides a complete sitemap of Pack's documentation.
---
# Frequently Asked Questions & Troubleshooting Guide
## 1. Preview Mode shows the wrong content environment
Your preview always loads the last content environment you selected in Pack Admin or the Customizer.\
To switch environments: go to **Pack Admin**, choose a different content environment, then refresh your site preview.
Or open an Incognito window to see the default environment.
***
## 2. Product updates in Shopify aren’t appearing in Pack
Pack syncs your products/collections automatically, but it can take a moment.\
To force a sync: go to **Pack Admin → Products → Sync products**.
***
## 3. Sections visible in the Customizer don’t appear in Preview Mode
There's a limit of 25 sections per page.\
Remove unused sections in **Pack Admin → Sections** to stay within the limit.
***
## 4. A created page/section/article is missing
You may have saved it in a different content environment.\
Use the dropdown to switch to the correct environment in Pack Admin or the Customizer.
***
## 5. Customizer is stuck loading a non-existent page
Try resetting the Customizer:
1. Go to **Pack Admin → Pages → Homepage**
2. Click the three dots → **Edit in Customizer**
***
## 6. Customizer won’t connect
**Permissions / Env variables:**
* Ensure your Hydrogen app has the required scopes (see Hydrogen setup guide)
* Add the correct environment variables (see Env vars guide)
**RenderSections component:**
* Verify `` is on every route in your codebase—Customizer hooks into it.
**Product/collection pages:**
* In Shopify Admin → Products, enable **Pack**, **Online Store**, and **Hydrogen** sales channels.
* Ensure the product is active and not in draft/archive.
***
## 7. Template section edits don’t apply globally
You edited a localized template section—only that page gets updates.\
Other pages use their own localized template sections, so they won’t inherit global template changes.
***
## 8. A red message “An error has occurred on this page” in the Customizer
This is likely a frontend bug in the codebase.\
Ask your agency or developer to check recent code changes.
Or view your Hydrogen deployment logs:\
**Shopify Admin → Hydrogen → All Deployments → Latest production → Runtime logs**
***
## 9. Syncing staging and production content environments
Direct syncing isn’t supported yet. Options:
* Delete and recreate your staging environment to match production.
* Use the Pack Content API to copy pages or sections between environments (see API reference).
***
## 10. Can’t add a new content environment
Check for duplicate page and article handles: **Pack Admin → Pages/Articles**.\
If there are no duplicates and you still get an error, please contact Pack support.
***
## 11. I accidentally deleted a section from a page—can I restore it?
Yes. Sections aren’t removed from Pack unless you delete them in **Pack Admin → Sections**.\
To restore: re-add the section to your page in the Customizer.
***
## 12. Updated SEO settings aren’t showing on my site
After editing SEO in **Pack Admin → Settings**, you must publish the changes:
1. Open the Customizer
2. Click the cog wheel in the right-side menu → **Publish Storefront Settings**
***
## 13. Section edits in the Customizer disappeared
If you have A/B testing enabled, confirm you edited the variant you’re previewing (e.g., Variant A vs. Variant B).
---
# Introduction to Pack Digital: Platform Overview and Benefits
## What is Pack?
Pack Digital (more commonly known as Pack) is the digital experience platform that's purpose-built for Shopify Hydrogen. We bridge the gap between traditional Shopify themes and fully custom headless commerce implementations, giving you all the speed and flexibility of headless architecture with the user-friendly experience you've come to expect from Shopify's theme editor.
Simply put, Pack enables growing brands to create, manage, and optimize their Shopify Hydrogen storefronts without the usual headaches of headless commerce. Whether you're a marketer looking to launch new content in minutes or a developer wanting to build custom features without limitation, Pack provides the tools you need to move fast and drive results.
## Core Concepts
Let's break down some key concepts you'll want to understand as you dive into Pack:
### Shopify Hydrogen
Shopify Hydrogen is Shopify's React-based framework for building custom storefronts. It provides hooks, utilities, and components for creating blazing-fast commerce experiences. While Hydrogen is incredibly powerful (and free), it typically requires development expertise to implement and maintain. That's where Pack comes in – we make Hydrogen accessible to both developers and marketers. Learn more about [Hydrogen + Pack Integration](/developer-resources/add-to-hydrogen).
### Customizer
The [Customizer](/create-manage-content/customizer) is our visual editing interface – think of it as Shopify's theme editor on steroids. It gives marketers and content creators the power to update content without needing to involve developers. Make changes, preview them in real-time, and publish when you're ready or release updates on a schedule – all with a familiar, intuitive interface.
### Sections
[Sections](/create-manage-content/sections) are the modular building blocks of your Pack-powered storefront. These components can be easily added, removed, and rearranged through the Customizer. Developers create section schemas using the [Section Schema API](/section-schema-api) that define what can be edited, while marketers configure these properties to build unique content experiences.
### Templates
In Pack, [templates](/create-manage-content/templates) define the structure and layout for different page types in your storefront. While the standard page types (product pages, collections, blog posts, etc.) are fixed in the Hydrogen framework, Pack provides several ways to customize how these templates behave and appear:
**Page Templates**\
Page templates are the base templates for standard page types in your Shopify Hydrogen storefront. These include:
* Home page templates
* Product page templates
* Collection page templates
* Blog post templates
* Article templates
-404 page templates
**Section Templates**\
Section templates are reusable content blocks that can be consistently applied across multiple pages of the same type. For example you can:
* Create a section template that appears on all product pages
* Establish global content blocks that appear in specific locations on certain page types
* Auto-assign section templates to product pages based on product tags
* Auto-assign section templates based on product types or categories
Section templates help maintain design consistency while reducing the need to recreate the same content structures repeatedly. You can also choose to override a template on a specific page where needed.
### Content Environments
[Content environments](/create-manage-content/content-environments) let you manage different versions of your content at the same time. Test changes in a staging environment before publishing to your live site, or prepare seasonal content variations without affecting your current storefront.
### Content Releases
[Content Releases](/create-manage-content/content-releases) let editors stage related CMS changes and publish them together. A release can include page, section, template, product, collection, blog, article, and settings edits, so teams can prepare campaigns or launches without exposing partial changes.
### Blueprint
[Blueprint](/developer-resources/blueprint-overview) is our open-source Hydrogen starter theme – it's like getting a head start on your custom storefront. With pre-built components and configurations that you can easily customize, Blueprint helps you get up and running quickly without sacrificing quality or flexibility. See how to [set it up here](/developer-resources/blueprint-setup).
## How Pack Differs from Traditional Shopify
Here's how Pack compares to traditional Shopify themes:
**Traditional Shopify Themes:**
* Built on Liquid templating language
* Limited customization options
* Often relies heavily on numerous third-party apps
* Performance decreases as customizations and apps pile up
* Need developer help for substantial changes or custom templates
**Pack Digital:**
* Built on React and Shopify Hydrogen
* Extensive customization capabilities
* Integrated functionality reduces app dependency
* Maintains high performance regardless of customization
* Visual editing lets marketers make changes independently
* AI-enabled and compatible
## How Pack Differs from Traditional Headless Solutions
Pack also stands apart from traditional headless commerce implementations:
**Traditional Headless Commerce:**
* Requires multiple separate systems (CMS, front end, middleware)
* High technical complexity
* Demands significant development resources
* Long implementation timelines (6-12 months)
* Challenging for non-technical users to manage
**Pack Digital:**
* All-in-one platform combining CMS, front end, and developer tools
* Reduced technical complexity
* Faster implementation (ready in weeks)
* Visual interface accessible to non-technical users
* Built specifically for Shopify integration
## Pack Components and Features
Pack brings together three key components to create a comprehensive platform:
### 1. Visual Content Management System
Our CMS makes [content management](/create-manage-content/content-management) a breeze with:
* **Visual Page Builder:** Drag-and-drop interface ([Customizer](/create-manage-content/customizer)) for creating and editing pages
* **Section Templates:** Reusable content blocks for consistent experiences
* **Media Management:** Centralized [asset library](/create-manage-content/media-manager) for images and videos
* **Content Scheduling:** Time-based [publishing](/create-manage-content/publishing) and [scheduling](/create-manage-content/scheduling) for promotions and seasonal content
* **Content Releases:** Coordinated [release workflows](/create-manage-content/content-releases) for staging, reviewing, and publishing related changes together
* **Content Environments:** Isolated [staging areas](/create-manage-content/content-environments) for testing changes
* **AI-Powered Experiences:** Intelligent content creation and optimization (coming soon)
### 2. Developer Tools
For developers, Pack provides everything needed to build exceptional experiences:
* **Section Schema API:** Framework for creating customizable components ([Schema API Reference](/developer-resources/section-schema-api))
* **Blueprint Theme:** Open-source starter kit for Hydrogen storefronts ([Blueprint Architecture](/developer-resources/blueprint-overview))
* **React Component Library:** Pre-built UI components ([@pack/react](/api-reference/pack-react))
* **SDK & API Access:** Programmatic content management capabilities ([Content API](/api-reference/content-management-api), [@pack/client](/api-reference/pack-client), [@pack/hydrogen](/api-reference/pack-hydrogen))
* **Local Development Environment:** Tools for building and testing locally
* **storefront.dev Integration:** AI-powered development acceleration (coming soon)
### 3. CRO Tools
Pack includes powerful tools for optimizing and measuring your storefront's performance:
* **A/B Testing:** Server-side testing without performance impact
* **AI-Powered Test Recommendations:** Smart analysis of which elements to test (coming soon)
* **Automated Variant Creation:** AI-generated test variants based on your goals (coming soon)
* **KPI Tracking:** Integration with analytics platforms
## Use Cases for Pack by Role
Pack supports the different needs of your team members:
### For Marketers and Content Creators
* Create and edit pages without developer assistance using the [Customizer](/create-manage-content/customizer)
* Launch promotional content and campaigns
* Test different content variations with AI-recommended [A/B tests](/testing-analytics/best-practices) (AI powered coming soon)
* Manage [product information](/create-manage-content/products) and collections
* [Schedule content publication](/create-manage-content/scheduling)
* Stage multi-page launches with [Content Releases](/create-manage-content/content-releases)
* Leverage AI to automate content creation and optimization (coming soon)
### For Developers
* Create custom [sections](/create-manage-content/sections) and components using the [Schema API](/section-schema-api)
* Implement design systems and UI patterns
* Integrate with third-party services using our [APIs & SDKs](/content-management-api)
* Optimize performance and loading times
* Extend functionality with custom code
* Use storefront.dev to accelerate development workflows
## Benefits of Using Pack
Pack delivers several key benefits for growing e-commerce brands:
### Performance Improvements
Storefronts built on Pack typically see major improvements in key metrics:
* **Faster Page Loads:** 2-3x speed improvements over traditional Shopify themes
* **SEO Benefits:** Better rankings due to improved Core Web Vitals ([SEO Guide](/create-manage-content/seo))
* **Conversion Rate Increases:** 5-26% lift in conversion rates on average
* **Lower Bounce Rates:** Fewer visitors leaving due to slow loading times
### Team Efficiency
Pack streamlines workflows between technical and non-technical team members:
* **Reduced Developer Bottlenecks:** Marketers can make changes independently
* **Faster Content Deployment:** Changes published in minutes vs. days ([Publishing Guide](/create-manage-content/publishing))
* **Improved Collaboration:** Clear separation of roles between team members ([Organization Management](/create-manage-content/organizations-storefronts))
* **Simplified Onboarding:** Familiar interfaces reduce training time
* **AI Acceleration:** Automated content generation and optimization
### Cost Optimization
Pack helps reduce the total cost of running your custom storefront:
* **App Consolidation:** Built-in functionality reduces need for third-party apps
* **Reduced Development Costs:** Less ongoing maintenance required
* **Faster Time-to-Market:** Shorter implementation cycles for new features
* **Developer Efficiency:** More time for innovation vs. routine content updates
* **AI-Powered Migration:** Quickly transition from Liquid to Hydrogen with storefront.dev ([Migration Guide](/implementation-guides/hydrogen-migration))
## Getting Started with Pack
Ready to dive in? Here's how to get started:
1. **Sign Up for Pack:** Create an account and set up your organization.
[Sign up here](https://app.packdigital.com/signup)
2. **Connect to Shopify:** Link your Shopify store to Pack. Follow the [Quickstart Guide](/getting-started/quickstart) or our comprehensive ([Storefront Setup Guide](/getting-started/storefront-setup)).
3. **Choose Your Approach:**
* Start with [Blueprint](/developer-resources/blueprint-overview) for a quick implementation ([Blueprint Setup Guide](/developer-resources/blueprint-setup)).
* [Integrate Pack with an existing Hydrogen project](/developer-resources/add-to-hydrogen).
* Work with a Pack partner agency for a custom implementation.
* Use storefront.dev to accelerate your migration from Liquid (coming soon)
## Next Steps
Depending on your role and goals, you might want to explore different aspects of Pack:
* **For Marketers:** Check out the [Customizer](/create-manage-content/customizer), [Content Management](/create-manage-content/content-management), and [A/B Testing](/testing-analytics/ab-testing).
* **For Developers:** Explore [Section Schemas](/developer-resources/section-schema-api), the [Blueprint Theme](/developer-resources/blueprint-overview), and the [Pack SDKs](/api-reference/pack-client).
* **For Technical Decision Makers:** Review [Implementation Guides](/implementation-guides/hydrogen-migration).
## Fast, Flexible, and AI-Ready: The Pack Digital Difference
Pack represents a new approach to e-commerce development, combining the performance benefits of modern web technologies with the ease of use you'd expect from Shopify. By bridging the gap between traditional themes and fully custom implementations, Pack enables growing brands to create exceptional shopping experiences without the typical tradeoffs between performance, flexibility, and usability.
But we're just getting started. Our vision for Pack goes beyond simply making Hydrogen accessible – we're building an AI-powered platform that fundamentally transforms how brands create and optimize their storefronts.
With our upcoming AI capabilities, Pack will analyze your traffic patterns, shopper behavior, and engagement metrics to automatically identify the best A/B testing opportunities. The system will then generate test variants based on this data, saving you countless hours of manual work and guesswork.
Content creation is also getting the AI treatment. Say goodbye to manual copy-pasting – our AI-assisted content tools will streamline the entire process, helping you create on-brand content faster than ever.
Perhaps most exciting is Pack's integration with storefront.dev, our AI-powered development tool. This powerful combination will help brands speed up migrations from Liquid ([Migration Guide](/implementation-guides/hydrogen-migration)) and create on-brand shopping experiences in minutes rather than weeks. By leveraging AI throughout the development process, you'll be able to build precisely what your brand needs without getting bogged down in technical complexity.
Whether you're launching your first Hydrogen storefront or optimizing an existing implementation, Pack provides the tools and workflows you need to move fast, build better, and drive results – today and tomorrow.
---
# Quickstart Guide: Launch Your First Pack Project
This guide will get you all set up and ready to manage your Hydrogen storefront or shop in Pack.
Check out our [demo video](https://packdigital.com/pages/demo) to learn more about what Hydrogen is and how Pack makes Hydrogen easy to use.
## Sign Up
First, you'll need to create a Pack account.
[Sign up here](https://app.packdigital.com/signup)
## Create Your Organization
Once your account is created, you can create an organization within Pack. This is where you'll manage all your storefronts and invite your teammates to collaborate.
## Create a Storefront
After you've created your organization, you can create a new storefront.
**[Storefronts](/resources/storefront)** are the backbone of your e-commerce business. They offer full control over your entire online storefront, from managing products and content to designing the overall site experience, and can replace a Shopify theme or a custom headless storefront / CMS. They’re designed to be easy to use—just like a theme—while taking full advantage of Hydrogen’s flexibility.

### Connect Your Shopify Store
Pack connects to Shopify via a secure OAuth integration. During setup, provide your Shopify store URL and the Pack team will prepare a dedicated app for your store. Once installed, tokens are exchanged automatically — no manual API key copying required.
If you already have a Custom App with API tokens, you can also connect using the manual token flow.
[Check out our full Shopify connection guide](/getting-started/storefront-setup)
Once connected, Pack will automatically sync your products and collections data so you can add them to your storefront or shop.

## Connect Your GitHub Repository
Following the next steps, link your GitHub repository to your Pack organization. This is where your storefront or shop code will live.
## Connect your Hydrogen Storefront
The last step is to create a new Hydrogen storefront on Shopify, and link it to Pack. Or connect an existing Hydrogen storefront to Pack.
## Using Our CLI Tool for Quick Setup
**If you're setting up a storefront**, our CLI tool offers an efficient way to get started quickly without needing a Pack account initially. This tool allows you to clone Bluprint, Pack's Hydrogen theme, install necessary packages, and run your project locally using default Shopify data, simplifying the initial setup process.
To get started with your storefront, simply run the following command in your terminal:
npx @pack/create-hydrogen@latest
For more details on using our CLI tool, refer to the [Blueprint Setup](/developer-resources/blueprint-setup#using-our-cli-tool) guide.
## What's Next?
Great, you're now set up with a Pack storefront or shop. Here are a few links that might be handy as you start exploring your new setup:
* [Learn how to manage your content](/create-manage-content/content-management)
* [Invite Teammates to Your Storefront or Shop](/create-manage-content/organizations-storefronts)
* [Set Up Your Local Development Environment](/developer-resources/blueprint-setup)
* [Get Help and Support](https://support.packdigital.com/)
---
# Storefront Setup: Connecting Shopify and Pack
Pack connects to your Shopify store via a secure OAuth integration. The Pack team will set up a dedicated Shopify app for your store and provide you with an install link. Once installed, Pack automatically handles the token exchange — no manual copying of API keys required.
## Connecting Your Shopify Store
### Step 1: Request Your Connection
During storefront setup in Pack Admin, you'll be prompted to connect your Shopify store. Provide your Shopify store URL (e.g., `your-store.myshopify.com`) and the Pack team will prepare your connection.
Once the app is ready, the **Install Pack on my Shopify store** button will become available and you can continue setup.
### Step 2: Install the Pack App
1. Click the **Install Pack on my Shopify store** button in the storefront setup flow.
2. You'll be redirected to Shopify's authorization screen.
3. Review the requested permissions and click **Install**.
4. You'll be redirected back to Pack automatically.
### Step 3: Confirm Connection
After authorization, Pack will automatically:
* Exchange and securely store your API tokens
* Validate that all required permissions are granted
* Begin syncing your products and collections
You'll see a success confirmation in Pack Admin. Click **Success, Continue** to proceed with the rest of your storefront setup.
### Required Permissions
Pack requests the following Shopify permissions during installation. These are configured automatically — no manual scope selection is needed.
**Admin API:**
* Files (read and write)
* Metaobject definitions (read)
* Metaobject entries (read)
* Products (read)
* Shopify Markets (read and write)
* Store content (read)
**Storefront API:**
* Metaobject entries (read)
***
## Manual Setup (Legacy)
> **Note**: The manual setup flow is available for stores that were connected before the OAuth integration was introduced, or for stores that have not yet been configured with a Pack app. For new storefronts, use the OAuth flow described above.
You can watch the video below to learn how to set up your Storefront in Pack and link your Shopify store to Pack using the manual token flow.
## Creating a Custom App for Your Pack Account
> **Note**: **Enable Shopify Custom Apps**: - From your Shopify admin, click **Settings >
> Apps and sales channels**. - Click **Develop apps**. - Click **Allow custom
> app development**. - Review the provided information and click **Allow custom
> app development**.
To create a new storefront in Pack, link your Shopify store to Pack by creating and installing a Custom App in Shopify.
### Steps to Create and Install a Custom App:
1. From your Shopify admin, click **Settings > Apps and sales channels**.
2. Click **Develop apps**.
3. Click **Create an app**.
4. Enter the App name and select an App developer in the modal window.
5. Click **Create app**.

[Learn more about Custom Apps](https://help.shopify.com/en/manual/apps/app-types/custom-apps)
## Configuring Your Custom App Permissions
To ensure Pack functions correctly, configure the following API scopes:
### Admin API Scopes
Pack uses these scopes to synchronize and manage your store's products and assets.
#### Steps to Configure Admin API Scopes:
1. In your Custom App, navigate to **Configuration**.
2. Click **Configure Admin API integration**.
3. Configure the following properties:
* Files: `write_files`, `read_files`
* Metaobject definitions: `write_metaobject_definitions`, `read_metaobject_definitions`
* Metaobject entries: `write_metaobject`, `read_metaobjects`
* Products: `read_products`
* Shopify Markets (optional): `read_markets`, `write_markets`
* Store content: `read_content`
4. Verify all required scopes are selected and click **Save**.
### Storefront API Scopes
These scopes are necessary for the Shopify Storefront API features, such as Metaobjects, Cart, Customer Account, and Market.
#### Steps to Configure Storefront API Scopes:
1. In your Custom App, navigate to **Configuration**.
2. Click **Configure Storefront API integration**.
3. Configure the following properties:
* Metaobject entries: `unauthenticated_read_metaobjects`
* Customers: Select all scopes
4. Verify all required scopes are selected and click **Save**.

## Installing Your Custom App and Obtaining API Keys
After configuring your API scopes, install the custom app on your Shopify store to obtain your API keys.
### Steps to Install Custom App and Obtain API Keys:
1. Click **Install app**.
2. In the modal window, click **Install**.
3. Navigate to **API Credentials**.
4. Note down the Admin API access token and Storefront API access token.
5. Remember, the Admin API access token is displayed only once.
## Linking Your Shopify Store to Pack
After installing your Custom App in Shopify, create your new Storefront in Pack Admin.
### Steps to Link Shopify Store to Pack:
1. Navigate to Pack Admin, click on your current instance, and select **Create new instance**.
2. Select 'Create a storefront', name your storefront, and click next.
3. Enter your shop URL `your-store.myshopify.com`.
4. Paste your Admin API access token.
5. Paste your Storefront API access token.
6. Click **Connect** and wait for confirmation.
7. After confirmation, click **Success, Continue** to continue setting up your Storefront.

## Preparing Your GitHub Repository
You will be prompted to connect your GitHub repository to Pack.
### Steps to Connect GitHub Repository:
1. Click **Connect**.
2. Create a repository and name it.
3. Click **Create**.
4. Click **Success, Continue** to proceed.

## Setting Up Your Hydrogen Storefront
The final step is to create a new Hydrogen storefront on Shopify.
### Steps to Set Up Hydrogen Storefront:
1. Navigate to Shopify Admin and ensure you have the Hydrogen sales channel installed.
2. Click **Hydrogen**.
3. Click **Create storefront**.
4. Name your storefront.
5. Select the repository you created in the previous step.
6. Click **Create**. This may take a moment.

7. Click **Storefront Settings > Environment and Variables**, and copy your Pack environment variables.
* Click on **Add variable** in **Custom Variables** section: Add the key, value, and select all environments. Click **Save**.
* Repeat this step for all variables shown in Pack.

8. Navigate to your GitHub repository and merge the pull request created by Shopify.
9. Once deployment is complete, navigate back to Pack and click **Continue**.
10. In **Your Storefront Details**, paste the following:
* **Public Storefront URL**: Your Hydrogen Storefront URL. Navigate to your **Hydrogen Store** > **Storefront Settings** > **Environment and Variables** > **Production**, set the URL as public, and click **Save**.

* **Storefront ID**: Your Hydrogen Storefront ID. Find it in the URL bar of your Hydrogen Storefront right after the `hydrogen/` part of the URL.
11. Click **Done, Continue** to finish the setup.
## Hydrogen Storefront Required Scopes
All permissions except 'Gates' are required for the Hydrogen Storefront API.
Ensure `unauthenticated_read_customer_tags` is selected for the Customer Accounts experience.

### Steps to Configure Hydrogen Storefront Permissions:
1. Navigate to **Shopify Admin > Sales Channels > Hydrogen**.
2. Click on your Hydrogen Storefront.
3. Click **Storefront Settings**.
4. Click **Storefront API** or **Customer Account API** to configure the permissions.

## Environment Variables
To edit your Hydrogen Storefront environment variables, follow these steps:
1. Navigate to **Shopify Admin > Sales Channels > Hydrogen**.
2. Click on your Hydrogen Storefront.
3. Click **Storefront Settings**.
4. Click **Environments and Variables**.

## GTM Setup
To add your Google Tag Manager (GTM) container ID to your Hydrogen Storefront, follow these steps:
1. Navigate to **Shopify Admin > Sales Channels > Hydrogen**.
2. Click on your Hydrogen Storefront.
3. Click **Storefront Settings**.
4. Click **Environments and Variables**.
5. Click **Add Variable**:
* Key: `PUBLIC_GTM_CONTAINER_ID`
* Value: `GTM-XXXXXXX`
* Environments: Select all available.
6. Click **Save**.

## Redirecting Traffic to Your Hydrogen Storefront
When your Hydrogen storefront is ready to be set as the primary, public storefront, you'll need to redirect traffic from the online store to your Hydrogen storefront.
#### Steps to Set Hydrogen Storefront as Primary:
For detailed guidance, refer to the Shopify documentation:
[Redirect traffic to the Hydrogen storefront](https://shopify.dev/docs/custom-storefronts/hydrogen/migrate/redirect-traffic)
This will ensure your Hydrogen storefront becomes the main entry point for your customers.
---
# Preview URL Management
Preview URLs provide a means to isolate and view changes in a sandboxed environment, typically corresponding to a specific git branch or set of code changes.
This allows for testing and sharing developments without affecting the live storefront.
> **Warning**: Ensure your branch names are not excessively long. This can cause issues with
> the preview URL and Customizer not loading, as the maximum length of a domain
> label is 63 characters, per the [DNS size
> limit](https://datatracker.ietf.org/doc/html/rfc1035#section-2.3.4).
All preview URLs are automatically generated from Netlify.
Feature branches are supported: a new branch added to your GitHub repository is associated with a new preview URL once the branch is automatically built and deployed.
---
# Managing URL redirects via Pack's Admin
If you have any old URLs you want to point to your new site, please follow the steps below to add a new redirect:
1. Navigate to Pack’s admin.
2. Click on **Settings > URL**.
3. Click on **Create redirect**.
4. Input the URL you want to redirect from and the URL you want to redirect to, and choose the status code to use. Check 'force' to force the redirect to occur even if the source route exists.
5. Click on **Create redirect**.
6. Allow up to 5 minutes for the redirect to take effect.
## Managing URL redirects via Shopify’s Admin
If you don’t want to automatically redirect a URL to your Pack site, you can do so by following the steps below:
1. Navigate to Shopify’s admin > **Online Store** > **Themes** > Three dots > **Edit code**.
2. Click on `theme.liquid`.
3. Insert the following snippet:
```javascript
```
4. Click on 'Save'.
5. Allow up to 5 minutes for the redirect to take effect.
---
# Manage content in Pack’s Customizer
Access Pack’s customizer to visually manage the content of your storefront or shop.
With:
* Real-time visual previews as you type
* Flexible, drag-and-drop sections that you can reuse across your storefront or shop
* Tools for speedy content entry
* Quick and easy publishing and scheduling workflows
Members on your account will be able to push content live and make edits to your site quickly and easily without needing to rely on a developer for every change.
This article explains how to create and manage [pages](#pages), [blogs](#blogs), [articles](#articles), and [sections](#sections) in the Customizer.
## Customizer tools and settings
## Pages
A page refers to content on your storefront or shop that is not directly related to individual products or collections. Examples include 'About Us', 'Contact', 'FAQ', 'Privacy Policy', and 'Terms of Service' pages.
These pages are distinct from product or collection pages, which are automatically generated for each product or collection you add to your store or shop.
### Creating a New Page
1. Open the **Customizer** from Pack’s admin.
2. Click on **+ Create** in the top nav on the left.
3. Choose **Page**.
4. Fill in the page details:
* Page Handle (unique identifier for the page, used in the page's URL).
* SEO Title (title for search engine results).
* SEO Description (brief description for search results).
* No Index (prevent indexing by search engines).
* No Follow (instruct search engines not to follow links on the page).
5. Click "Save" to create the page.

> **Warning**: **Non-Creatable and Non-Deletable Pages** Certain pages cannot be created or
> deleted in the Customizer, though they can be customized by changing their
> template and adding sections inside Customizer. These pages include: - Home
> (default necessity). - 404 (error page). - Product (automatically generated
> for Shopify products). - Collection (automatically generated for Shopify
> collections). - Duplicate URLs (you cannot use the same URL for multiple
> pages. If you get an error when creating a page, it’s most likely because the
> URL is exactly the same as an existing URL).
You can rename, duplicate, or delete your page by clicking the three dots next to the **Page** dropdown menu—located in the left panel at the very top.
## Blogs
> **Warning**: Blogs are only available on Pack storefronts.
A blog is a dedicated section on a storefront's website that houses articles.
These articles are typically focused on content that's relevant to your audience, such as product announcements, usage tips, industry insights, and related stories that might interest potential customers.
### Creating a New Blog
1. Select Customizer from Pack’s admin.
2. Click on **+ Create** in the top nav on the left.
3. Choose **Blog.**
4. Fill in your blog details:
* Blog Handle (unique identifier for the page, used in the page's URL).
* SEO Title (title for search engine results).
* SEO Description (brief description for search results).
5. Click "Save" to create the Blog.

You can edit, duplicate, or delete your blog by clicking the three dots next to the Page dropdown menu—located in the left panel at the very top.
> **Warning**: **Deleting Blogs** Articles within a blog will not be deleted when the blog is
> deleted, but you will need to reassign them to a new blog.
## Articles
> **Warning**: Articles are only available on Pack storefronts.
An article is a page that resides within a blog. A blog can house multiple articles on various topics, each contributing to the overarching theme or purpose of the blog.
### Creating a New Article
1. Select **Customizer** from Pack’s admin.
2. Click on **+ Create** in the left nav at the top.
3. Choose **Article**.
4. Fill in the article details:
* Article Handle (unique identifier for the page, used in the page's URL).
* SEO Title (title for search engine results).
* SEO Description (brief description for search results).
* Author
* Blog (Assign the article to a previously created blog within your storefront)
* Category
* Excerpt
* Tags
5. Click "Save" to create the Article.

You can edit, duplicate, or delete your article by clicking the three dots next to the Page dropdown menu—located in the left panel at the top.
## Sections
Learn more about sections [here](/create-manage-content/sections).
### Template Sections
> **Warning**: Templates are only available on Pack storefronts.
Template sections are replicated across all pages that share the same template. This is ideal for content that should appear on all pages of a certain type, like all product pages.
Template sections apply to these 5 templates:
* /pages
* /products
* /collections
* /blogs
* /articles
For example, you might want to include a product details section every time you create a /products page.
To add a template section:
1. Select **Customizer** in Pack’s admin.
2. Select a page with the template you want to add a section to.
3. Click on the template name (e.g., Page) — in the **Template** menu in the left panel.
4. Choose **+ Add Section** in the left panel.
5. Select a section or metaobject
6. Click **Add Selected**.
7. This section will now appear on all pages using that template.
Note: Now that you’ve added a new section to all pages within a given template (this section appears on all “Product” or “Article” pages), you can open each page individually to customize the section each time it appears.
If you’d like to add a section where content updates are automatically reflected across all instances, you’ll want to create a linked section.

## Local Sections
Local sections are unique to the page they're added to. When you create a section on a page, it's a local section by default, appearing only on that specific page.
1. Select **Customizer** in Pack's admin.
2. Find the **Sections** menu on the left panel and click the **+**
3. Click **Add New Section**
4. Choose the section(s) you want to add; multiple selections are possible.
5. Advanced users have the option to add an HTML section to create a section from scratch using HTML.
6. Click **Add Selected**
7. Click on the new section to start editing it.

You can edit, duplicate, or delete your section by clicking the three dots next to the Page dropdown menu—located at the top of the left-hand panel.
## Copying a Section
You can create an independent copy of an existing, populated section from another page. This process creates a distinct version of the section. When you make edits to the copy of your section, your changes will not affect the original version.
1. Select **Customizer** in Pack's admin.
2. Find the **Sections** menu on the left panel and click the **+**
3. Select **Copy Existing Section**
4. Find the section you want to copy by name and select it to create a unique copy.
5. Click on the new section to start editing it.

## Linking a Section
When you link a section, you’re adding a pre-existing, populated section from another page to your current page. This creates a link between the sections, displaying identical content on both pages.
Any edits made to a linked section will be reflected on all pages where it's displayed, due to its 'linked' nature.
1. Select **Customizer** in Pack’s admin.
2. Find the **Sections** menu on the left panel and click the **+**
3. Select **Link Existing Section**
4. Search for the existing section by name and click to link it.

> **Warning**: When you delete a linked section, you’ll need to re-add it, customize it, and re-link it to all of your pages.
>
> If you want to remove a linked section from a page, we recommend [**hiding the section instead of deleting it**](/create-manage-content/sections#hiding-a-section) so that it doesn’t impact your other linked sections.
## Publishing Pages
You’ll need to publish your content to make it visible to customers on your live storefront or shop.
When you publish a page, you’ll push all the changes and updates you've made to the page in the Customizer live for your customers to see
.
The time it takes for this content to be visible will vary based on your caching settings.
> **Warning**: It may take longer for changes to your site to populate depending on how you’ve set your caching policy in your Hydrogen storefront.
>
> By default, Hydrogen storefronts are set to cache every 24 hours, which means you won’t see changes to your storefront update until 24 hours have passed. To change your caching policy, refer to the [Hydrogen documentation](https://shopify.dev/docs/custom-storefronts/hydrogen/caching#:~:text=Hydrogen%20and%20Oxygen%20provide%20built,Hydrogen's%20built%2Din%20API%20client).
To Publish a page:
1. Select **Customizer** in Pack's admin.
2. In the **Page** dropdown menu, select the page you wish to publish.
3. Click the **Publish** button located in the upper right corner of the Customizer.
4. The storefront or shop will rebuild to incorporate the latest changes on that page.

You can unpublish a page by following the same steps as above. The storefront or shop will rebuild, and the page will no longer be live (regardless of whether auto-publish is enabled). You’ll still be able to access and edit pages you’ve unpublished in the customizer.
---
# Section Templates
Section Templates simplify content management for your e-commerce site by letting you apply and update key sections—like banners, product highlights, or promotions—across multiple pages at once. Make a change in one place, and it instantly updates everywhere, ensuring a consistent shopping experience while saving you time and effort.

## Applying a Section Template to a page
You can simply change or apply a section template to a page by following these steps:
1. Go to the **Customizer**.
2. Click on the **Section Template** button above the sections.
3. Select the desired section template from the list.
4. Click **Save** button.

## Making section changes apply for all pages
When you want to make changes to a section that is part of a Section Template and have it apply to all pages that use the template, you can do so editing the section in the Section Template list.
To do this, click the **Section Template** button above the sections, find the section you want to edit, customize the content, and save.

## Making changes apply to specific page
You can customize the content of a section within a Section Template for a specific page without impacting other pages.
To do this, in the page’s section list, find the section that belongs to the Section Template, customize the content to fit your needs, and save.
You will get an notification that the section has been customized for this page.

The section will now be marked as customized, indicated by the dashed border and broken chain icon, and changes made to the section is unique to this page.

### Reverting specific page changes
If you have customized a section for a specific page and want to revert the changes to match the Section Template, find the section in the page's section list, click the action menu, and select **Reset changes**.

## Section Template Section Visibility
When you apply a Section Template to a page, all sections within the template will be added to the page. You can choose to hide the section per page or for all pages that use the template.
### Hiding a section for a specific page
To hide a section for a specific page, find the section in the page's section list, click the action menu, and select **Hide section on page**.

### Hiding a section for all pages that use the template
To hide a section on all pages, click the **Section Template** button above the sections, find the section you want to hide, click the action menu, and select **Hide section**.

* [Managing Sections](/create-manage-content/sections): Learn more about sections.
---
# Shop Setup
Pack connects to your Shopify store via a secure OAuth integration. The Pack team will set up a dedicated Shopify app for your store and provide you with an install link. Once installed, Pack automatically handles the token exchange — no manual copying of API keys required.
## Connecting Your Shopify Store
### Step 1: Request Your Connection
During shop setup in Pack Admin, you'll be prompted to connect your Shopify store. Provide your Shopify store URL (e.g., `your-store.myshopify.com`) and the Pack team will prepare your connection.
Once the app is ready, the **Install Pack on my Shopify store** button will become available and you can continue setup.
### Step 2: Install the Pack App
1. Click the **Install Pack on my Shopify store** button in the shop setup flow.
2. You'll be redirected to Shopify's authorization screen.
3. Review the requested permissions and click **Install**.
4. You'll be redirected back to Pack automatically.
### Step 3: Confirm Connection
After authorization, Pack will automatically:
* Exchange and securely store your API tokens
* Validate that all required permissions are granted
* Begin syncing your products and collections
You'll see a success confirmation in Pack Admin. Click **Success, Continue** to proceed with the rest of your shop setup.
### Required Permissions
Pack requests the following Shopify permissions during installation. These are configured automatically — no manual scope selection is needed.
**Admin API:**
* Files (read and write)
* Metaobject definitions (read)
* Metaobject entries (read)
* Products (read)
* Shopify Markets (read and write)
* Store content (read)
**Storefront API:**
* Metaobject entries (read)
> **Note**: Shops do not require Customer scopes under the Storefront API.
***
## Manual Setup (Legacy)
> **Note**: The manual setup flow is available for stores that were connected before the OAuth integration was introduced, or for stores that have not yet been configured with a Pack app. For new shops, use the OAuth flow described above.
You can watch the video below to learn how to set up your shop in Pack and link your Shopify store to Pack using the manual token flow. Please note you don't need to select 'Customer scopes' for a shop under Storefront API Scopes, even though the video below shows it.
## Creating a Custom App for your Pack Account
> **Note**: **Enable Shopify Custom Apps**: - From your Shopify admin, click **Settings >
> Apps and sales channels**. - Click **Develop apps**. - Click **Allow custom
> app development**. - Review the provided information and click **Allow custom
> app development**.
To create a new Shop in Pack, link your Shopify store to Pack by creating and installing a Custom App in Shopify.
### Steps to Create and Install a Custom App:
1. From your Shopify admin, click **Settings > Apps and sales channels**.
2. Click **Develop apps**.
3. Click **Create an app**.
4. Enter the App name and select an App developer in the modal window.
5. Click **Create app**.

[Learn more about Custom Apps](https://help.shopify.com/en/manual/apps/app-types/custom-apps)
## Configuring Your Custom App Permissions
To ensure Pack functions correctly, configure the following API scopes:
### Admin API Scopes
Pack uses these scopes to synchronize and manage your store's products and assets.
#### Steps to Configure Admin API Scopes:
1. In your Custom App, navigate to **Configuration**.
2. Click **Configure Admin API integration**.
3. Configure the following properties:
* Files: `write_files`, `read_files`
* Metaobject definitions: `write_metaobject_definitions`, `read_metaobject_definitions`
* Metaobject entries: `write_metaobject`, `read_metaobjects`
* Products: `read_products`
* Shopify Markets (optional): `read_markets`, `write_markets`
* Store Content: `read_content`
4. Verify all required scopes are selected and click **Save**.
### Storefront API Scopes
These scopes are necessary for the Shopify Storefront API features, such as Metaobjects, Cart, Customer Account, and Market.
#### Steps to Configure Storefront API Scopes:
1. In your Custom App, navigate to **Configuration**.
2. Click **Configure Storefront API integration**.
3. Configure the following properties:
* Metaobject entries: `unauthenticated_read_metaobjects`
4. Verify all scopes are selected and click **Save**.

## Installing Your Custom App to Shopify and Obtaining API Keys
After configuring your API scopes, install the custom app on your Shopify store to obtain your API keys.
### Steps to Install Custom App and Obtain API Keys:
1. Click **Install app**.
2. In the modal window, click **Install**.
3. Navigate to **API Credentials**.
4. Note down the Admin API access token and Storefront API access token.
5. Remember, the Admin API access token is displayed only once.
## Linking Your Shopify Store to Pack
After installing your Custom App in Shopify, create your new Shop in Pack Admin.
### Steps to Link Shopify Store to Pack:
1. Navigate to Pack Admin, click on your current instance, and select **Create new instance**.
2. Select 'Create a shop', name your shop, and click next.
3. Enter your shop URL `your-store.myshopify.com`.
4. Paste your Admin API access token.
5. Paste your Storefront API access token.
6. Click **Connect** and wait for confirmation.
7. After confirmation, click **Success, Continue** to continue setting up your shop.

## Preparing Your GitHub Repository
You will be prompted to connect your GitHub repository to Pack.
### Steps to Connect GitHub Repository:
1. Click **Connect**.
2. Create a repository and name it.
3. Click **Create**.
4. Click **Success, Continue** to proceed.

## Setting Up Your Hydrogen Storefront
The final step is to create a new Hydrogen Storefront on Shopify.
### Steps to Set Up Hydrogen Storefront:
1. Navigate to Shopify Admin and ensure you have the Hydrogen sales channel installed.
2. Click **Hydrogen**.
3. Click **Create storefront**.
4. Name your storefront.
5. Select the repository you created in the previous step.
6. Click **Create**. This may take a moment.

7. Click **Storefront Settings > Environment and Variables**, and copy your Pack environment variables.
* Click on **Add variable** in **Custom Variables** section: Add the key, value, and select all environments. Click **Save**.
* Repeat this step for all variables shown in Pack.

8. Navigate to your GitHub repository and merge the pull request created by Shopify.
9. Once deployment is complete, navigate back to Pack and click **Continue**.
10. In **Your Storefront Details**, paste the following:
* **Public Storefront URL**: Your Hydrogen Storefront URL. Navigate to your **Hydrogen Store** > **Storefront Settings** > **Environment and Variables** > **Production**, set the URL as public, and click **Save**.

* **Storefront ID**: Your Hydrogen Storefront ID. Find it in the URL bar of your Hydrogen Storefront right after the `hydrogen/` part of the URL.
11. Click **Done, Continue** to finish the setup.
## Hydrogen Storefront Required Scopes
All permissions except 'Gates' are required for the Hydrogen Storefront API.
Ensure `unauthenticated_read_customer_tags` is selected for the Customer Accounts experience.

### Steps to Configure Hydrogen Storefront Permissions:
1. Navigate to **Shopify Admin > Sales Channels > Hydrogen**.
2. Click on your Hydrogen Storefront.
3. Click **Storefront Settings**.
4. Click **Storefront API** or **Customer Account API** to configure the permissions.

## Environment Variables
To edit your Hydrogen Storefront environment variables, follow these steps:
1. Navigate to **Shopify Admin > Sales Channels > Hydrogen**.
2. Click on your Hydrogen Storefront.
3. Click **Storefront Settings**.
4. Click **Environments and Variables**.

## GTM Setup
To add your Google Tag Manager (GTM) container ID to your Hydrogen Storefront, follow these steps:
1. Navigate to **Shopify Admin > Sales Channels > Hydrogen**.
2. Click on your Hydrogen Storefront.
3. Click **Storefront Settings**.
4. Click **Environments and Variables**.
5. Click **Add Variable**:
* Key: `PUBLIC_GTM_CONTAINER_ID`
* Value: `GTM-XXXXXXX`
* Environments: Select all available.
6. Click **Save**.

## Redirecting Traffic to Your Hydrogen Storefront
When your Hydrogen store is ready to be set as the primary, public storefront, you'll need to redirect traffic from the online store to your Hydrogen storefront.
#### Steps to Set Hydrogen Store as Primary:
For detailed guidance, refer to the Shopify documentation:
[Redirect traffic to the Hydrogen storefront](https://shopify.dev/docs/custom-storefronts/hydrogen/migrate/redirect-traffic)
This will ensure your Hydrogen storefront becomes the main entry point for your customers.
---
# B2B Integration
Follow these steps to integrate Shopify's B2B functionality. If the repo was created before Blueprint version `1.14.0`, then the New Customer Account API migration is required before continuing.
## Code Migration
The commit links (paired with some steps) direct to a Blueprint PR used only for reference purposes.
> **Warning**: When opening a link, it may take 2-5 seconds for it to scroll to the intended block of code.
### Return `buyer` from root loader
Update the `root.tsx` `loader` to fetch and return `buyer` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-4133eb55408b25b35b8e07c696a9dfc97b741c555d7407e8c86d0845e5eecc28R127-R175)]
***
### Storefront API `buyer` query variable for Graphql
Add `buyer` variable to ***every*** Storefront API Graphql query for ***only*** **product**, **collection**, and **search**. Do not add to queries for the Pack API \[[example](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-aea2460c5294edead2dbde348d598a2f724b60746738974dc5512525bcf5bc4bL288-R423)]
> **Note**: Search for `@inContext(country: $country, language: $language)` to see where Storefront API queries exist.
For each query:
1. Add `$buyer: BuyerInput` to the variables
2. Add `buyer: $buyer` into `@inContext()`
For example, this product query:
```graphql
export const PRODUCT_QUERY = `#graphql
query Product(
$handle: String!
$country: CountryCode
$language: LanguageCode
) @inContext(country: $country, language: $language) {
...
}
`;
```
Becomes:
```graphql
export const PRODUCT_QUERY = `#graphql
query Product(
$handle: String!
$country: CountryCode
$language: LanguageCode
$buyer: BuyerInput
) @inContext(country: $country, language: $language, buyer: $buyer) {
...
}
`;
```
***
### Storefront API `buyer` query variable for client
Add `buyer` variable to ***every*** `storefront` client query for **product**, **collection**, and **search**. This does *not* apply to the `pack` or `admin` clients.
1. In `app/lib` create a new file called `b2b.server.ts` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-5f222992f595bfaaabf3bf3b9f9c5a13429619c5534ec1e56d67b355f16f0577R1-R15)]
```ts
import type {AppLoadContext} from '@shopify/remix-oxygen';
/* Buyer contextualization for B2B */
export const getBuyerVariables = async (context: AppLoadContext) => {
const {customerAccount} = context;
const buyer = await customerAccount.getBuyer();
return buyer?.companyLocationId && buyer?.customerAccessToken
? {
buyer: {
companyLocationId: buyer.companyLocationId,
customerAccessToken: buyer.customerAccessToken,
},
}
: null;
};
```
2. Search the codebase for every instance of `storefront.query`, which will occur in `loader` or server logic
3. If the storefront query is related to product, collection or search, the `buyer` variable needs to be passed in \[[example](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-3df4b14c314c88015a0a0aad89c8b507193871eecb78c1a17d55e939f912d7c2R8-R46)]
* In Blueprint, the routes that had this change were: `api.collection`, `api.predictive-search`, `api.product-by-id`, `api.product`, `api.recommendations`, `api.search`, `collections.$handle`, `products.$handle` and `search`
* And files `products.server.ts` and `server.utils.ts`,
Example:
```ts
import {getBuyerVariables} from '~/lib/b2b.server';
...
export async function loader({context, request}: LoaderFunctionArgs) {
...
const buyerVariables = await getBuyerVariables(context);
let {product} = await storefront.query(PRODUCT_QUERY, {
variables: {
handle,
selectedOptions,
country: storefront.i18n.country,
language: storefront.i18n.language,
...buyerVariables,
},
cache: storefront.CacheShort(),
});
```
> **Warning**: For the instances in `server.utils.ts`, `getBuyerVariables` cannot be imported because of a React Refresh rule. Instead just fetch and generate the `buyer` variable in the same code block \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-f4461132bbc696b721fe3ab2bb97035ba64eeeb59158bb6d940731880f0207a9R157-R456)]
* There is also a change to the props for `getFilters` in `server.utils.ts` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-f4461132bbc696b721fe3ab2bb97035ba64eeeb59158bb6d940731880f0207a9R142-R157)], which gives way to a change in `collections.$handle` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-b4f9c82ea7d2d09f453d004589d57612c0ae979a91515da83946e5dcdee48d74R40-L42)] and `search` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-dda0dd7744db850e56922b6b83d46e6e1ffbd0764fd8a11dac86be0ad641e084R63-L65)]
***
### B2B Provider and Components
1. Add route `($locale).api.b2blocations.tsx` to the `app/routes` folder \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-0fb77c95dbb5c38ea7d92c5a4c9820cde84aa9ab450ebf183466766bb6ccc83cR1-R53)]
2. [Download this `B2B` folder](https://drive.google.com/file/d/15mUhF5-gOjel_Y2NCIz1Rir07hDmf_EJ/view?usp=sharing) and paste folder into the `app/components` folder
3. Add `` alongside where all other `Provider`'s wrap the html \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-8602330fce3d308cce968ad4793067d6b4c3a51533642a085c3a7726da80b104R3-R21)]
* The provider also will automatically open `B2BLocationSelectorModal` if the customer has not yet selected a location. The modal will only close once a location is selected. Changes to `Modal` are made to accommodate this new logic (instructions in later steps)
* Depending on the Blueprint version, `openModal` in `B2BLocationProvider` and `closeModal` in `B2BLocationSelectorModal` will come from either `useMenu` or `useGlobal`. If it's from `useGlobal`, correct accordingly
4. Add `` and `` under the PDP `AddToCart` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-60428b04c7aacb8512a0a911c7b1d05edae553e820447feb607ea4d5035afe18R4-R65)]
* These display the quantity rules and price breaks, respectively for the selected variant
5. Add `` to the customer account menu \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-d4c6932e54ec2ebdf8abd59b7d64b9d48a20b4d3347bf5567240419bd03e36d6R12-R62)]
* This displays the customer's location and a button to open the `B2BLocationSelectorModal` to switch location
* `openModal` from `useMenu` will also need to be corrected if comes from `useGlobal`
***
### Add `quantityRule` and `quantityPriceBreaks` fields
1. Add `quantityRule` and `quantityPriceBreaks` to `VARIANT_FRAGMENT` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-aea2460c5294edead2dbde348d598a2f724b60746738974dc5512525bcf5bc4bR78-R91)]
2. Add `quantityRule` and `quantityPriceBreaks` inside `merchandise` in `CART_LINE_FRAGMENT` and `CART_LINE_COMPONENT_FRAGMENT` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-9de184c0554ff03c4b86bda6247706852a61745a9bd36a82fb9044d13bf4f61fR121-R288)]
***
### Add `quantityRule` logic for quantity and quantity selectors
Apply the `increment`, `minimum` and `maximum` settings to all quantity and quantity selectors
1. Change default PDP `quantity` from `1` to the `minimum` value \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-f8f58e8ec930592aaaad3159978920d3c71457f2b1b69e6f0db624f6fdf3c828L31-R44)]
2. Add `quantityRule` logic for the PDP `QuantitySelector` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-1b42c3736ea13ad627f23ed6bcc8bdf59344da1c558ac31d47e8b3f399f34235R1-R56)]
3. Add `quantityRule` logic for the `useCartLine` hook \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-1b42c3736ea13ad627f23ed6bcc8bdf59344da1c558ac31d47e8b3f399f34235R1-R56)]
4. Pass `disableDecrement` and `disableIncrement` to the cart line `QuantitySelector` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-8eddf27ae9f0d3ae2e1319c710f8ba64c608fd98ed51ad2b017da28a6eeb93c2L18-R93)]
5. If `ProductModalPanel.tsx` exists, add the `quantityRule` logic \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-c8960df333d462c9119ec51efb9781d8b0b7247fcfd954bbd69b687de68dbe33L1-R141)]
6. If `ShoppableSocialVideoProductCard.tsx` exists, add the `quantityRule` logic \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-36016f0caddc8ccfa156149701dae5d965bcfd823bccf9255d9525174713fb9fL55-R318)]
7. Wherever else there is a default product `quantity`, e.g. quick shop, apply the same change from step 1
8. Wherever else `QuantitySelector` is used, apply the same logic from step 2 or 3
***
### Customer types
1. Add `customer.types.ts` to the `app/lib/types` folder \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-7d8e46e4c8885be33c9daba480163c5cb54f42206d299a5e11789dad3d0db7b3R1-R31)]
2. Add its export to the `index.ts` file \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-6a3e5492aa3f7eb97a242dbe3d4d96963e053f240d8678184da26ab665bbe658R2)]
***
### Add `disableClose` prop to `Modal`
This additional `Modal` setting is to disable the customer from closing the modal when asked to choose a location, if a location has not yet been selected
1. In either `MenuProvider.tsx` or `GlobalProvider.tsx` (depending on what version Blueprint the store was built on), update `defaultModal` and `openModal` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-4bdc9037158e1029a354dbbdae48105953fa1102eeffaba66795e7b1f38a7719L8-R137)]
2. In either `context.types.ts` or `global.types.ts`, add `disableClose` type to `Modal` and update `openModal` type \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-7f17d2defffc665e1bc715a1db1a293ec71e93d2b3cddc91bb0282f461b6cffbR10-R37)]
3. In `Modal.tsx`, update the `onClose` attribute for `Dialog` and wrap the close button in a `disableClose` conditional \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-b2a008ee3360a0b25e320c5073c4953bbc3a9fb79662f19dbe51acc9dd058a06L20-R69)]
***
### Update `buyerIdentity`
Add `purchasingCompany` field inside `buyerIdentity` in `CART_FRAGMENT` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/106/files#diff-9de184c0554ff03c4b86bda6247706852a61745a9bd36a82fb9044d13bf4f61fR431-R444)]
---
# New Customer Account API Migration
Follow these steps to migrate from the legacy customer accounts to the new Customer Account API.
This migration is required before integrating the Shopify B2B logic into the codebase.
**Migration Caveats:**
* The logged in experience will no longer be supported while ***in*** the Pack Customizer due to the nature of customer authentication
* Customer accounts doesn't support the use of `localhost` due to security concerns. In order to use customer accounts while in development, localhost must be hosted on a public URL via **ngrok** (see below for instructions)
* Multipass is currently not supported, though if a customer is logged in and goes to checkout, logging in will be a one click action
## Local Development and Hydrogen Settings
For official Shopify documentation for Customer Account API with Hydrogen, it can be referenced [here](https://pack-hydrogen-essentials.myshopify.com/), but the pertinent steps are re-outlined below.
### Set up ngrok
[ngrok](https://ngrok.com/) has a free plan but is limited by the number of HTTP requests that can be made monthly. The free plan may be sufficient to get through this migration, but upgrading to a paid plan may be a likely scenario for the long term maintenance of the store, due to the added convenience to create your own domain and to not be limited by your usage. In later steps, the ngrok domain will need to be added officially into Hydrogen settings.
1. Set up an [ngrok](https://ngrok.com/) account
2. If you have a paid account, [add a static domain](https://ngrok.com/blog-post/free-static-domains-ngrok-users) in your ngrok settings. If you are using the free plan, a random domain is generated for you after every instance as seen in the terminal, e.g. `https://abc123def456.ngrok-free.app`
3. Install the [ngrok CLI](https://ngrok.com/download)
4. In a terminal, start ngrok using the following command:
Paid account: `ngrok http --domain= 8080`
Free account: `ngrok http 8080`
Notes:
* Working off ngrok is only needed for using customer accounts, all other development can still be viewed on `localhost` directly
* Stopping or restarting `localhost` does *not* kill the ngrok instance
* If you using the free plan and the ngrok instance is stopped, upon starting again, a new random domain will be generated, thus this new domain will need to replaced with the one added in Hydrogen settings
***
### Update the application setup
For the Customer Account API to recognize your domain as a valid authentication host, edit your Customer Account API settings.
1. The panel can be found in `Storefront settings` for the Hydrogen store, then under `Customer Account API`
2. Under `Application setup`, click `Edit` to edit the endpoints
3. Under `Callback URI(s)`, click `Add Callback URI`, add all the `/account/authorize` url's prefixed with their domain
* First add `https://.com/account/authorize`, e.g. `https://storefront.com/account/authorize`
* Next add one with the ngrok domain as in `https://.app/account/authorize`, e.g. `https://abc123def456.ngrok-free.app/account/authorize`
> **Warning**: If you are using the free ngrok plan, this domain will repeatedly change on every instance, so this value will continually be replaced during development. This is when the paid account will prove convenient.
4. Under `JavaScript origin(s)`, click `Add origin`, and all your domains
* First add the main domain, e.g. `https://storefront.com`
* Next add the ngrok domain, e.g. `https://abc123def456.ngrok-free.app`
* As mentioned above, this value will likely be replaced out during development if using a free ngrok account
5. Under `Logout URI`, click `Add Logout URI`, and all your domains
* First add the main domain, e.g. `https://storefront.com`
* Next add the ngrok domain, e.g. `https://abc123def456.ngrok-free.app`
* As mentioned above, this value will likely be replaced out during development if using a free ngrok account
***
## Code Migration
Most steps will link to part of the Blueprint release that it is in reference to. These steps will require copying these code changes as a guide of what exactly to do.
> **Warning**: When opening a link, it may take **2-5 seconds** for it to scroll to the intended block of code.
### Required `@shopify/hydrogen` session.commit() migration:
Only applicable if this migration has not already been implemented. Per migration notes for `2024-07` `@shopify/hydrogen`, all instances of `session.commit()` in routes are removed and instead exists only in `server.ts`. Additional logic is also added into `AppSession` [\[commit\]](https://github.com/packdigital/pack-hydrogen-theme-blueprint/commit/25414e691af8bf3d5c578aed3b458708427cff12)
***
### Env variables:
Add both `PUBLIC_CUSTOMER_ACCOUNT_API_CLIENT_ID` and `PUBLIC_CUSTOMER_ACCOUNT_API_URL` to the `.env` file, if not already added. The values can be found under the "Ready-only variables" in Hydrogen settings `Environment and variables`
***
### Add Customer Account client:
1. In `server.ts`, add `createCustomerAccountClient` and pass `customerAccount` into `getLoadContext` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-1ba718c1eb8aa39cd20c2562d92523068c734d75f54655e97d652b992d9b4259R6-R142)]
2. In `root.tsx`, remove `customerAccessToken` logic and replace with `customerAccount` client logic \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-4133eb55408b25b35b8e07c696a9dfc97b741c555d7407e8c86d0845e5eecc28L19-L181)]
3. In `remix.env.d.ts` or `env.d.ts`, add new Env and client types \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-cbba94305c93c8cd5198a4a8554a09e9e5c21243800b0b2e33edb803f93636d0L4-R81)]
4. In `useCustomer.ts`, remove logic pertaining to `previewModeCustomer` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-e5f323e9c7a5bd2086b37eba0a1268ec9f389328480493ac1c5e4a251327dfaeL3-R23)]
5. Create a new file for the new Customer Account API Graphql queries \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-c0b2d60cd1e7fcd2e6a1fbae699ecc3d3d18762d3a3ddd7972595eae3bc5e673R1-R298)]
* For Blueprint, the new Graphql file exists in a new `customer-account` folder created under `app/data/graphql`, but matching this folder structure is not important, rather just ensure all Graphql imports from this file are imported correctly after the migration
***
### Customer folder and hooks bulk actions:
1. Delete the entire `customer` folder in the `app/lib` folder\*\*
2. [Download this `customer` folder](https://drive.google.com/file/d/1Dk913QZS9rj1D3l-pVMTKOLTbTtWoeCQ/view?usp=sharing) and paste the folder into the `app/lib` folder
3. For customer hooks, [download this `customer` folder](https://drive.google.com/file/d/1QHQri1pS5sD9t5HGTXf3htUGjQknqWYd/view?usp=sharing) and paste the folder into the `app/hooks` folder; then add the export to the `index.ts` file \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-2d7f41296ea2e32a034fed8464bc5674259c7b0855af44207cb5b0ca45c33300R2)]
4. For the remaining hooks, update the imports from `~/lib/customer` to from `~/hooks`, e.g. convert `import {useCustomerCreateAddress} from '~/lib/customer';` to `import {useCustomerCreateAddress} from '~/hooks';`
* The hooks include: `useCustomerCreateAddress`, `useCustomerDeleteAddress`, `useCustomerLogOut`, `useCustomerUpdateAddress`, `useCustomerUpdateProfile`
> **Warning**: When deleting the entire `app/lib/customer` folder at once, the assumption is that additional logic has ***not*** been added to these files or folders to accommodate custom functionality, i.e. code that is not default on Blueprint. Check your git changes to ensure these deleted files do not contain any pertinent code custom to the store. If so, manual intervention will be required to incorporate them into the migration.
Examples include:
* Additional fields queried with Graphql for the customer. Reference the new [Customer Account API `Customer` object](https://shopify.dev/docs/api/customer/latest/objects/customer) to see what Graphql fields are accepted
* Additional hooks or logic added for additional customer integrations
***
### Cleanup account components:
1. Delete `GuestAccountLayout.tsx` from the `app/components/AccountLayout` folder; then delete its export from the `index.ts` file \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-b69c05e829f6f6304227a37788eb0dfd3aa597d1bf8bdeb09e70f972cfe927e4L2)]
2. Delete the `Login` folder, `Register` folder, `Activate.tsx` file and `ResetPassword.tsx` file from the `app/components/Account` folder; then delete their exports from the `index.ts` file \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-991a87ae2923037893516d1c42ec9c029d3325f10b854fd71fcc2b2c18aa1d51L1-L7)]
3. In `Layout.tsx`, remove `usePreviewModeCustomerInit()` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-9aab0cbc1dd4a0f760b394681e7bdda83399e648b5651249ec19db79bfb49748L11-L22)]
4. In `CustomerAccountLayout.tsx`, `customerPending` logic can be removed because it is no longer relevant \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-d4c6932e54ec2ebdf8abd59b7d64b9d48a20b4d3347bf5567240419bd03e36d6L1-L53)]
***
### Delete old customer routes and replace with new ones:
1. Delete all the current customer routes:
* `($locale).account.$.tsx` (will be added back in next step)
* `($locale).account.addresses.tsx` (will be added back in next step)
* `($locale).account.orders._index.tsx` (will be added back in next step)
* `($locale).account.orders.$id.tsx` (will be added back in next step)
* `($locale).account.profile.tsx` (will be added back in next step)
* `($locale).account.tsx` (will be added back in next step)
* `($locale).account.activate.$id.$activationToken.tsx`
* `($locale).account.login.multipass.tsx`
* `($locale).account.login.tsx`
* `($locale).account.logout.tsx`
* `($locale).account.register.tsx`
* `($locale).account.reset.$id.$resetToken.tsx`
* `($locale).api.customer.tsx`
2. [Download new/updated account routes](https://drive.google.com/file/d/1cu3idwxC3Uvqck-hvuAV7bJGb6fDmUQl/view?usp=sharing) and paste each route into the `app/routes` folder
> **Warning**: When deleting the files, ensure that any custom functionality specific to the store has not just been wiped by checking the git changes. If so, add it back into the code accordingly.
***
### Update instances of customer email:
1. Find instances of `customer?.email` or `customer.email` and replace the `.email` with `.emailAddress?.emailAddress`, e.g. `customer.emailAddress?.emailAddress`
* In Blueprint, these changes happen in the following files. They may exist elsewhere for different repos:
* `BackInStockModal.tsx` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-90c095c53b5fe9834a5cce9f8f6e8d79bb8bc1fe57adc8a343eda7e71e1bf3b0L25-R27)]
* `Profile.tsx` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-031dbea8e96b2565928c57f85d9316f9389011aa2379c71b0dba95350a2f38adL87-R86)]
* `CustomerAccountLayout.tsx` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-d4c6932e54ec2ebdf8abd59b7d64b9d48a20b4d3347bf5567240419bd03e36d6L67-R45)]
***
### Add `useProductById` hook:
1. In the `app/hooks/product` folder, add `useProductById.ts` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-32ea18eda76d0b05b462575be152faafffab183164d16bcbe8abd1586a92d722R1-R31)]
2. Add the export to the `index.ts` file \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-ab5487672b4fb12473815d13f146789e5836950b03b977a377c61a86f60e3c37R5)]
3. Add `($locale).api.product-by-id.tsx` to the `app/routes` folder \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-d83d3800e0a49b9fd7b41b41348a4f8df25c8cce3a327ea1d8ad89ec74561034R1-R48)]
4. Add this new Graphql query to the Storefront API graphql file \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-aea2460c5294edead2dbde348d598a2f724b60746738974dc5512525bcf5bc4bR313-R327)]
***
### Update address properties:
1. In `AddressForm.tsx` make [these changes to the code](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-12305978a3a35135d6a7d5b3ee9c60daa841840b121ded4cfa9cab646bb19598L3-R225). Changes include:
* Update `province` and `country` useState default
* Update `countries` useMemo
* Add `countriesByShortCode` useMemo
* Update `provinces` useMemo
* Add `provincesByShortCode` useMemo
* Update the `useEffect`
* Update `name` for inputs for `province`, `country`, and `phone` accordingly
* Update `label` for `selectedOption` for each `Select` input
2. In `AddressesItem.tsx` make [these changes to the code](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-595f5d30d934e992b5f3dd4c3f11621c6bdd31aef96d83b3684cb86436683d11L2-R50). Changes include:
* Update properties destructured from `address`
* Update `name` to `firstName` and `lastName`
* Map out new `formatted` address
***
### Update order and order properties:
1. In `Order.tsx`, destructure `order` from `useLoaderData` instead of `useCustomerOrder`, which is now deprecated \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-be1abbec320753adb9f0a218055b6a1bd65d8878886ed1659726de89e6998160R2-R65)]
2. In `OrderAddressAndStatus.tsx` (or wherever address and status info is displayed), make [these changes to the code](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-45f4fc87fe226b9e72b2a764f792a939776c1b1bcdf1d170a7505c80aa7d6099L1-R50). Changes include:
* Update properties destructured from `order` and `shippingAddress`
* Update `shippingUrl`
* Map out new `formatted` address
* Update old variables with corresponding new properties
3. In `OrderItem.tsx`, make [these changes to the code](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-04c3612f47b15994bd77de9e720e69d5285b24b82779d038cfbbd4b3e4f85230L3-R90). Changes include:
* Update properties destructured from `order` and `item`
* Update `originalPrice` and `discountedPrice` useMemo
* Fetch full product using the new `useProductById` hook
* Update old variables with corresponding new properties
4. In `OrderTotals.tsx`, make [these changes to the code](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-3d7a03a13a4fcbbd4bad59697cd3d5f9781344407e674250d9ab1855c6a0921eL4-R43). Changes include:
* Update `currencyCode`
* Update `totals` useMemo
5. In `Orders.tsx`, grab customer orders from `customer` from `useCustomer` instead of `useCustomerOrders`, which is now deprecated \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-3f741d16251b9dc835ca971817338de04a5a5eeee4044ce8624ffac218612e7dR3-R14)]
***
### Add new constants:
1. Add new constants to the `constants` file or folder \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-46b5b7d2e67a10dcefc3ab46c009a6497fd42ae2a4bdb8c7d7f06ed66399a5f9R17-R24)]
***
### Remove Multipass checkout button and authenticate Checkout URL:
1. In `CartTotals.tsx` replace `MultipassCheckoutButton` with a `Link`, which links to the `checkoutUrl` \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-3943c21d72a8249db09f0e4e0e3e2ad02fb709f352fb224a2c1019216bde0cb0R10-R124)]
2. **Addendum to step 1**: Authenticate the `checkoutUrl` and instead pass in the `authenticatedCheckoutUrl` to the new `Link` [\[commit\]](https://github.com/packdigital/pack-hydrogen-theme-blueprint/commit/045690339d745511a82b590462ddac492ca1f593)
3. Delete `MultipassCheckoutButton` from `app/components/Cart` folder
***
### Update customer types:
1. Replace types from `@shopify/hydrogen/storefront-api-types` with types from `@shopify/hydrogen/customer-account-api-types`, i.e.
* `Customer` (e.g. `root.ts`, `useCustomer.ts`)
* `Order` (check the order related pages)
* `CustomerAddress` (formerly `MailingAddress`) (check the addressses related pages)
***
### Remove deprecated global state variables:
1. In either `SettingsProvider.tsx` or `GlobalProvider.tsx` (depending on what version Blueprint the store was built on), remove deprecated `previewModeCustomer` variables \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/commits/edc5f50d24dbbd048be8d2fd20450bb1018f90a6#diff-427591b6dfdbf09de42831273461ed18ecb3981aad0b7b7a54508294b13834bbL3-L55)]
2. If it exists, delete `usePreviewMode.ts`
3. In either `context.types.ts` or `global.types.ts`, delete deprecated types \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/commits/edc5f50d24dbbd048be8d2fd20450bb1018f90a6#diff-7f17d2defffc665e1bc715a1db1a293ec71e93d2b3cddc91bb0282f461b6cffbL70-R73)]
***
### Update Analytics components to accommodate new customer object:
1. In `Analytics.tsx`, `customerPending` can be removed because it is no longer relevant \[[commit](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-7b72ca9ae1dc0d49db380bfb20eac9efaac01e649353d6118dc83cd2e66e24fbL41-R111)]
2. For any in use `XYZEvents.tsx`, update the `customer` prop type from generic `Record` to `Customer` type imported from `@shopify/hydrogen/customer-account-api-types`. [Example](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-339f817f1585731b25fbd031293c81ed4cc8d0e6397e9c480ac971efb0941a1aR2-R33)
3. In any relevant `events.ts` or `utils.ts` files, swap the following customer properties with the new corresponding property. [Example](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-df08ee9f735a3f2dcbafe9473b0d043a899e6f93e43572f27c2fef64dc7496ecR6-R92)
* `customer.email` -> `customer.emailAddress?.emailAddress`
* `customer.numberOfOrders` -> `flattenConnection(customer.orders)?.length`
* `customer.defaultAddress?.countryCodeV2` -> `customer.defaultAddress?.territoryCode`
* `customer.defaultAddress?.phone` - > `customer.defaultAddress?.phoneNumber`
* `customer.defaultAddress?.provinceCode` -> `customer.defaultAddress?.zoneCode`
4. Explicitly type `customer` where needed. [Example](https://github.com/packdigital/pack-hydrogen-theme-blueprint/pull/104/files#diff-260128cfd537cf5e2d1d6fa0f326b7046ebe7fcc9de15ff0bdb5a64b644c3090R113)
---
# Common Hydrogen Issues: Solutions and Workarounds
When migrating to Hydrogen, there are a number of small issues to consider, which Fueled has seen brands forget about until after they go live. Here’s a list of gotchas that brands should plan for when migrating to Hydrogen.
## Client-Side URL Redirects
For Shopify Liquid Storefronts, you can set up server-side redirects. If a shopper hits an old URL, Shopify automatically renders the new URL as part of the response.
With Hydrogen, these redirects are typically handled client-side. For example, if someone clicks a link to `shop.example.com/product/xyz`, that URL will load, and then redirect to `www.example.com/product/xyz`.
These redirects can break attribution. For example, Google Analytics (and even Shopify Analytics) will see the referring URL as `shop.example.com/product/xyz`, not the URL of the advertisement or external link that sent the traffic to your website. This will lead to a high level of traffic being attributed as **“Direct.”**
### Where Do Client-Side Redirects Impact Hydrogen Sites?
This issue can surface in a number of places:
1. **Google Merchant Center and Other Catalog Feeds**
For some reason, when you migrate to Hydrogen, Shopify uses the checkout URL for the product URL in its built-in integrations with Google Merchant Center, Facebook Catalog, TikTok Catalog, and so forth.
There is no solution for this out of the box. Brands really need to leverage a dedicated Product Feed Management tool like **Feedonomics**, **GoDataFeed**, or **DataFeedWatch**.
2. **Klaviyo’s Product Catalog**
The same issue impacts Klaviyo’s built-in Shopify product catalog. However, you can email Klaviyo support, and they can resolve the issue on their end.
3. **New Product URL Paths**
A lot of merchants are excited to launch SEO-friendly product URLs that include human-friendly variant names as URL paths and search strings, as opposed to the standard Shopify Liquid URL pattern of `/product-name?variant=1234567`.
However, you need to make sure that the old URL paths are still routed to the new product URLs to avoid 404 errors. Again, be mindful of client-side redirects that can break attribution.
4. **Site Map URLs**
Note: You will also need to consider these URL pattern changes in your Hydrogen site map.
## Shopify Analytics Cookies Are Set Client-Side
When leveraging Shopify Liquid Storefront, Shopify’s `_shopify_y` cookie is set with a header request when a page loads. As such, it’s an **ITP compliant cookie** (i.e., persists for up to 12 months on iOS 17 devices).
But for Hydrogen sites, this cookie is set client-side, in the browser. As such:
* It’s not compliant with iOS 17 and will be deleted after 7 days of inactivity.
* It cannot be used as a persistent identifier by ID graphs, like Klaviyo’s new ID Graph.
**Solution:**
Fueled has worked with Pack to set our own iOS 17 compliant cookie. We use this cookie for our ID Graph to extend attribution windows. Brands can generate and set server-side cookies too, with a little bit of Node.js code.
## Cookie Domains
For Shopify Liquid Storefronts, the website experience and checkout are managed on the same subdomain. This isn’t the case with Hydrogen sites, as described above.
As a result:
* Developers need to ensure that cookies are set on the **root domain**, not the subdomain for the site.
* This can affect the `_shopify_y` cookie as well, breaking Shopify Analytics’ tracking between the content site and checkout.
## localStorage Variables
`localStorage` variables are set per subdomain. They do not support cross-subdomain use cases. This needs to be considered for JavaScript libraries that use `localStorage` variables.
**Solution:**
Extra steps need to be taken to pass data server-side to support `localStorage` variable data being passed between `www.*` and `shop.*`.
## Attribution
This article was written by **Fueled**, Pack's partner and a leading agency specializing in collecting and organizing 1st-party eCommerce customer data.
For more information, visit their [homepage](https://fueled.io/).
---
# Hydrogen Migration Guide: Content and Tracking Audits
## Sitemap Audit
Completing a sitemap audit, especially for content pages and blog articles, is the clearest way to determine what content needs to be recreated in Pack.
### How to Audit
Assuming it’s a Shopify store, simply append `/sitemap.xml` to the root URL (e.g., `https://yoursite.com/sitemap.xml`). For a pages sitemap audit, enter `https://yoursite.com/sitemap_pages_1.xml`. For blog articles, enter `https://yoursite.com/sitemap_blogs_1.xml`.
### Considerations
The main goal of a sitemap audit is to identify what content needs to be recreated on the new website.\
If pages do not need to be recreated, then a redirect should be set up so that the URL does not lead to a 404.
## Pixel and Tracking Audit
When transitioning to Hydrogen, it's essential to perform a pixel and tracking audit to ensure all necessary tracking services are included on the new site. Many brands use third-party services like Elevar or Fueled to manage their data performance through a single application. Partnering with experts can be very beneficial due to the increasing technical complexity of marketing.
### The Pixel and Tracking Audit Involves:
* **Theme.liquid**: For brands using a Shopify theme, pixels are often hard-coded in the head of the theme.liquid layout file, providing insight into the pixels being used.
* **Google Tag Manager (GTM)**: GTM simplifies tag and pixel management, so reviewing it gives a clear picture of the brand's tracking services.
* **Shopify Customer Events**: This newer architecture is becoming the recommended method for integrating pixels with Shopify themes. Ensure you access Customer Events to account for any necessary pixels.
* **Sales Channel Apps**: Major services like Meta and TikTok have native sales channels in Shopify. Review their implementation to avoid duplicate pixel firing, which can occur with Hydrogen sites using both GTM and native Sales Channel pixels.
## App Audit
When transitioning to Hydrogen, it's a good time to review the installed apps on the brand’s Shopify store. Often, you'll find multiple apps that serve the same purpose (like Yotpo Reviews and Junip Reviews), so check which ones are active and consider uninstalling the duplicates.\
While many apps only handle back-end logistics and don't affect the front-end experience, understanding all the tools your brand uses is still important.
## Documentation
In our experience, using Google Sheets and having a tab for each audit that the brand can review and contribute to is very effective. It provides a single, robust artifact for discovery that helps determine the solution architecture needed for the move to Hydrogen!
---
# React Router 7 Migration
This guide outlines the necessary steps to migrate a Pack Hydrogen storefront with Remix to one with React Router 7. This will include:
* Updating `@shopify/hydrogen` and `@shopify/hydrogen-react` to version `^2026.1.0`
* Updating `@pack/hydrogen` and `@pack/react` to `^3.1.0` and `^4.0.0`, respectively
* Replacing imports from `@remix-run` with `react-router`
* All required code, config or import changes
> **Warning**: Before proceeding, the following migrations ***must*** be done beforehand:
>
> 1. [Vite Migration](https://docs.packdigital.com/implementation-guides/vite-migration)
> 2. [React Router 7 Preparation](https://docs.packdigital.com/implementation-guides/react-router-7-preparation)
> 3. [SSR Cart Migration](https://docs.packdigital.com/implementation-guides/ssr-cart-migration)
## Things to Consider
For every task under `Manual Setup` , there is a corresponding prompt under `LLM Prompts` that may be passed into an LLM for automating each task. These prompts have been tested internally using the IDE [Cursor](https://cursor.com/en) or the [Claude Code extension for VS Code](https://code.claude.com/docs/en/vs-code)
Adjust the chat settings accordingly to allow agent to write files without asking for permission each time:
If using Cursor:
1. Turn on `Agent` mode
2. Turn on `Auto-Run Mode`
If using Claude Code extension for VS Code:
1. Type in `/` into chat to open menu, then click on `General config...`
2. Enable `Claude Code: Allow Dangerously Skip Permissions`
> **Warning**: Note: These LLM prompts are tested with Pack storefronts that:
>
> 1. Are built off Pack's Blueprint template
> 2. Have not deviated significantly from the Blueprint template
>
> In case of more custom builds, review each prompt and edit as needed to work within the codebase. Or follow the manual steps and ensure changes are made to the appopriate files and blocks of code.
After the completing the migration, and running `npm run dev`, the terminal may have a warning reading `Hydrogen requires React Router 7.9.x for proper functionality.`. This can be ignored as latest Hydrogen ***does*** support `react-router` `7.12.0` and the warning has not been updated on Shopify's side.
## Manual Setup
### Dependency Updates
1. Update or add the following packages accordingly:
* "@shopify/hydrogen": "^2026.1.0"
* "@shopify/hydrogen-react": "^2026.1.0"
* "@shopify/cli": "^3.90.0"
* "@shopify/cli-hydrogen": "^11.1.9"
* "react-router": "7.12.0"
* "react-router-dom": "7.12.0"
* "@react-router/dev": "7.12.0"
* "@react-router/fs-routes": "7.12.0" (in devDependencies)
* "@shopify/mini-oxygen": "^4.0.0"
* "vite": "^6.2.4"
* "@pack/hydrogen": "^3.1.0"
* "@pack/react": "^4.0.0"
* "@pack/types" : "^0.1.4"
2. Remove following packages:
* "@remix-run/react"
* "@shopify/remix-oxygen"
* "@remix-run/dev"
* "@remix-run/eslint-config"
* "@remix-run/fs-routes"
* "@remix-run/route-config"
3. Update node version to 20, so:
* In `package.json`, change: `"node": ">=18.0.0"` to `"node": ">=20.0.0"`
* In `.nvmrc`, change `v18` to `v20`
4. Delete `node_modules` and `package-lock.json`
5. Run `npm install`
6. Ensure your project terminal is using Node 20, i.e. `nvm use 20`
***
### React Router Config File
Add `react-router.config.ts` to the root of the repo:
```tsx
import type {Config} from '@react-router/dev/config';
import {hydrogenPreset} from '@shopify/hydrogen/react-router-preset';
/**
* This configuration uses the official Hydrogen preset to provide optimal
* React Router settings for Shopify Oxygen deployment. The preset enables
* validated performance optimizations while ensuring compatibility.
*/
export default {
presets: [hydrogenPreset()],
future: {
// Disable middleware to use legacy context pattern (context.storefront vs context.get())
// This is required until route files are migrated to use context.get(hydrogenContext.*)
v8_middleware: false,
},
} satisfies Config;
```
***
### Server.ts Update
In `server.ts`:
1. Replace `import * as remixBuild from 'virtual:remix/server-build';` with `import * as serverBuild from 'virtual:react-router/server-build';`
2. Update the import for `createRequestHandler` and `getStorefrontHeader` to be from `@shopify/hydrogen/oxygen` opposed to `@shopify/remix-oxygen`
3. Replace `build: remixBuild` with `build: serverBuild` within `createRequestHandler`
4. If `testSession` is not being passed into `createPackClient` do the following. Note, this is not a required step for RR7 migration, but to address the eslint error:
* Import `PackTestSession` from `@pack/hydrogen`
* Fetch `packTestSession`
```tsx
const [cache, session, packSession, packTestSession] = await Promise.all([
caches.open('hydrogen'),
AppSession.init(request, [env.SESSION_SECRET]),
PackSession.init(request, [env.SESSION_SECRET]),
PackTestSession.init(request, [env.SESSION_SECRET]),
]);
```
* Pass in `packTestSession` as `testSession` to \`createPackClient
***
### Vite Config
In `vite.config.ts`
1. Replace `import {vitePlugin as remix} from '@remix-run/dev';` with `import {reactRouter} from '@react-router/dev/vite';`
2. Remove the `remix({...})` plugin from the `plugins` array, and add `reactRouter()` instead
3. Remove any `remix` related dependencies from the `ssr.optimizeDeps.include` and `optimizeDeps.include` arrays, e.g. `@remix-run/dev/server-build`
4. Add `react-router` to the `ssr.optimizeDeps.include` array
5. Add in this `resolve` config to the `ssr` object
```tsx
resolve: {
conditions: ['workerd', 'worker', 'browser'],
externalConditions: ['workerd', 'worker'],
},
```
6. Add this custom plugin above `export default defineConfig`
```tsx
/**
* Plugin to redirect vfile's Node.js imports to browser-compatible versions.
* This fixes "No such module node:path" errors on Oxygen deployment.
*/
function vfileBrowserPlugin(): Plugin {
return {
name: 'vfile-browser',
enforce: 'pre',
resolveId(id, importer) {
// Redirect vfile's internal imports to browser versions
if (importer?.includes('node_modules/vfile/')) {
// Get the vfile package root directory
const vfileRoot = importer.substring(
0,
importer.indexOf('node_modules/vfile/') +
'node_modules/vfile/'.length,
);
if (id === '#minpath') {
return {id: vfileRoot + 'lib/minpath.browser.js'};
}
if (id === '#minproc') {
return {id: vfileRoot + 'lib/minproc.browser.js'};
}
if (id === '#minurl') {
return {id: vfileRoot + 'lib/minurl.browser.js'};
}
}
return null;
},
};
}
```
7. Add the import `import type {Plugin} from 'vite';` then add `vfileBrowserPlugin()` to the `plugins` array
8. If `@pack/types` is a dependency in `package.json` then ensure adding `@pack/types` to the `ssr.optimizeDeps.include` array
9. Add `server` into root of `defineConfig` below `optimizeDeps`:
```tsx
server: {
headers: {
// Allow Private Network Access from public origins (e.g., hosted iframe)
'Access-Control-Allow-Private-Network': 'true',
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': '*',
},
allowedHosts: [],
}
```
***
### Tsconfig Change
Swap all the lines in `tsconfig.json` with this instead:
```json
{
"include": [
"env.d.ts",
"app/**/*.ts",
"app/**/*.tsx",
"app/**/*.d.ts",
"*.ts",
"*.tsx",
"*.d.ts",
".graphqlrc.ts",
".react-router/types/**/*"
],
"exclude": ["node_modules", "dist", "build", "packages/**/dist/**/*"],
"compilerOptions": {
"lib": ["DOM", "DOM.Iterable", "ES2022"],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"resolveJsonModule": true,
"module": "ES2022",
"target": "ES2022",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true,
"baseUrl": ".",
"types": [
"@shopify/oxygen-workers-types",
"react-router",
"@shopify/hydrogen/react-router-types",
"vite/client",
"@types/gtag.js",
"@types/grecaptcha"
],
"paths": {
"~/_": ["app/_"]
},
"noEmit": true,
"rootDirs": [".", "./.react-router/types"],
"incremental": true,
"composite": false,
"verbatimModuleSyntax": true
}
}
```
***
### ESLint Update
In `.eslintrc.cjs`
1. Remove `@remix-run/eslint-config` from the `extends` array
2. Add `import` to the `plugins` array
3. Add `pathGroups` array in the `import/order` object (below `groups`): `pathGroups: [{pattern: '~/**', group: 'internal'}]`
***
### Route Type Pattern Changes
Find all the instances of `ActionFunctionArgs`, `LoaderFunctionArgs`, and `MetaArgs` in the codebase and do the following with the corresponding file:
1. Import `Route` like this, where right of the last `/` is the name of the file itself. For example, if the file name is `($locale).products.$handle.tsx` then the import for `Route` would look like `import type {Route} from './+types/($locale).products.$handle’;`
2. Update `ActionFunctionArgs` to `Route.ActionArgs`; and `LoaderFunctionArgs` to `Route.LoaderArgs`
3. Update each `export const meta` to be:
```tsx
export const meta: Route.MetaFunction = ({matches}) => {
return (
getSeoMeta(...matches.map((match) => (match?.loaderData as any).seo)) || []
);
};
```
***
### Import Updates
1. In `entry.client.ts`
* Replace the import `import {RemixBrowser} from '@remix-run/react';` with `import {HydratedRouter} from 'react-router/dom';`
* Replace `` with ``
2. In `entry.server.ts`
* Replace `import {RemixServer} from '@remix-run/react';` with `import {ServerRouter} from 'react-router';`
* Replace `import type {EntryContext} from '@shopify/remix-oxygen';` with `import type {EntryContext} from 'react-router';`
* Replace `import type {AppLoadContext} from '@shopify/remix-oxygen';` with `import type {HydrogenRouterContextProvider} from '@shopify/hydrogen';`
* Replace the line `remixContext: EntryContext` with `reactRouterContext: EntryContext`
* Replace `` with ``
* Replace the line `context: AppLoadContext` with `context: HydrogenRouterContextProvider`
3. In `routes.ts`
* Replace `import {flatRoutes} from '@remix-run/fs-routes';` with `import {flatRoutes} from '@react-router/fs-routes';`
* Replace `import type {RouteConfig} from '@remix-run/route-config';` with `import type {RouteConfig} from '@react-router/dev/routes';`
* Replace `export default hydrogenRoutes([...(await flatRoutes())]) satisfies RouteConfig;` with
```tsx
export default hydrogenRoutes([
...(await flatRoutes()),
// Manual route definitions can be added to this array, in addition to or instead of using the `flatRoutes` file-based routing convention.
// See https://reactrouter.com/api/framework-conventions/routes.ts#routests
]) satisfies RouteConfig;
```
4. Find all instances where components or types are imported from `@shopify/remix-oxygen` and change the import to be from `react-router` instead
* For example: `import type {AppLoadContext, ActionFunctionArgs} from '@shopify/remix-oxygen';` becomes `import type {AppLoadContext, ActionFunctionArgs} from 'react-router';`
* This includes `data` and `redirect`, for example: `import {data as dataWithOptions, redirect} from '@shopify/remix-oxygen';` becomes `import {data as dataWithOptions, redirect} from 'react-router';`
5. Find all instances where components or types are imported from `@remix-run/react`, and change the import to be from `react-router` instead
***
### env.d.ts File
In `env.d.ts`:
1. First if there is no `env.d.ts` but instead `remix.env.d.ts`, rename `remix.env.d.ts` to `env.d.ts`
2. Replace all the `/// `'s at the top with these:
```tsx
///
///
///
///
```
3. Replace `declare module '@shopify/remix-oxygen'` with `declare module 'react-router'`
***
### Remove `displayName` Definition From All Route Files
For all the `app/routes` files, if a `displayName` is defined:
* Replace the `data-comp` attribute values from `ComponentName.displayName` to the actual display name. For example, `data-comp={ProductRoute.displayName}` becomes `data-comp="ProductRoute"`
* Remove the `.displayName` defintion at the bottom
***
### Misc
1. If the `DEFAULT_STOREFRONT_API_VERSION` constant exists, update it to be `2026-01`; if not, find where `ShopifyProvider` is rendered, and update the fallback for `storefrontApiVersion` to be `2026-01`
2. In `Link.tsx`
* Update `RemixLink` to be `ReactRouterLink`; and `RemixLinkProps` to be `ReactRouterLinkProps`
* Update the comment link to the docs to use the url `https://api.reactrouter.com/v7/functions/react_router.Link.html`
* Change `remix property` to `react router property`
3. Add `.react-router` to `.gitignore`
4. Remove `uuid` from `package.json` and from `vite.config.ts` if it exists; then find all instances where `uuid` is being imported, e.g. `import {v4 as uuidv4} from 'uuid';`, then do the following. At the end run `npm install`:
* Replace the use of `uuidv4()` with `crypto.randomUUID()`
* Remove the import from the top
5. Find where `useMatches` is being used and change any match using `.data` to `.loaderData`
6. In `useRootLoaderData`, update type `RootLoaderData` to be following; then remove the `SerializeFrom` import
```tsx
type UnwrapData = T extends {data: infer U} ? U : T;
export type RootLoaderData = Exclude<
UnwrapData>>,
Response
>;
```
7. In `Scripts.tsx`, remove the line `IMPORTANT: Third party scripts rendered directly, i.e. as