--- title: Product and variant publishing description: >- Learn how to publish and unpublish products and variants across sales channels and catalogs using the GraphQL Admin API. source_url: html: 'https://shopify.dev/docs/apps/build/sales-channels/product-publishing' md: 'https://shopify.dev/docs/apps/build/sales-channels/product-publishing.md' --- # Product and variant publishing Publishing controls which products and variants are visible to buyers on a given sales channel, market, or B2B context. The publishing framework is built on **catalogs** and **publications**. Every selling context on a Shopify store has a catalog: * **AppCatalog** represents sales channels like the Online Store, Point of Sale, or custom channels. * **MarketCatalog** represents international markets such as North America or Europe. * **CompanyLocationCatalog** represents B2B company locations. Each catalog can have an associated **publication**, which is the group of products and collections visible to buyers in that context. To make a product visible, you publish it to that catalog's publication. To hide it, you unpublish it. The [`Publishable`](https://shopify.dev/docs/api/admin-graphql/latest/interfaces/Publishable) interface is implemented by `Product`, `ProductVariant`, and `Collection`, giving you the same publishing controls across all three resource types. *** ## Requirements * Your app can make authenticated requests to the [GraphQL Admin API](https://shopify.dev/docs/api/admin-graphql). * Your app has the `read_publications` and `write_publications` [access scopes](https://shopify.dev/docs/api/usage/access-scopes). You also need `read_products` to query products. * Your store has existing products. * Your store has at least one active sales channel. Most stores have the Online Store by default. *** ## Discovering catalogs and publications Before you can publish products, you need to know what publications exist on the shop. You can discover them in two ways: by querying catalogs, or by querying publications directly. ### Query publications directly Use the `publications` query for a flat list of all publications on the shop. This is simpler when you just need publication IDs for mutations. ## POST /api/2026-07/graphql.json ##### GraphQL query ```graphql query Publications { publications(first: 20) { nodes { id autoPublish supportsFuturePublishing catalog { id title } } } } ``` ##### JSON response ```json { "data": { "publications": { "nodes": [ { "id": "gid://shopify/Publication/123", "autoPublish": true, "supportsFuturePublishing": true, "catalog": { "id": "gid://shopify/AppCatalog/1", "title": "Online Store" } }, { "id": "gid://shopify/Publication/456", "autoPublish": false, "supportsFuturePublishing": false, "catalog": { "id": "gid://shopify/AppCatalog/2", "title": "Point of Sale" } } ] } } } ``` **Info:** You can filter publications by catalog type using the `catalogType` argument. For example, `publications(first: 20, catalogType: MARKET)` returns only publications associated with market catalogs. ### Query catalogs Use the `catalogs` query to discover what selling contexts exist on the shop. The response can include app catalogs, market catalogs, and B2B company location catalogs. ## POST /api/2026-07/graphql.json ##### GraphQL query ```graphql query { catalogs(first: 3) { nodes { id title status } } } ``` ##### JSON response ```json { "data": { "catalogs": { "nodes": [ { "id": "gid://shopify/MarketCatalog/1068177679", "title": "NA Market", "status": "ACTIVE" }, { "id": "gid://shopify/CompanyLocationCatalog/1068177680", "title": "B2B Example 1", "status": "DRAFT" }, { "id": "gid://shopify/AppCatalog/1068177681", "title": "App Catalog Example", "status": "ARCHIVED" } ] } } } ``` *** ## Publishing products to sales channels and catalogs Use the [`publishablePublish`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishablePublish) mutation to publish a product to one or more publications. Pass the product's GID and an array of publication IDs. If the product is already published to a given publication, the mutation succeeds with no change. ## POST /api/2026-07/graphql.json ##### GraphQL mutation ```graphql mutation PublishProduct($id: ID!, $input: [PublicationInput!]!) { publishablePublish(id: $id, input: $input) { publishable { ... on Product { id title } availablePublicationsCount { count } resourcePublicationsCount { count } } userErrors { field message } } } ``` ##### Variables ```json { "id": "gid://shopify/Product/1", "input": [ { "publicationId": "gid://shopify/Publication/123" }, { "publicationId": "gid://shopify/Publication/456" } ] } ``` ##### JSON response ```json { "data": { "publishablePublish": { "publishable": { "id": "gid://shopify/Product/1", "title": "Classic Snowboard", "availablePublicationsCount": { "count": 3 }, "resourcePublicationsCount": { "count": 3 } }, "userErrors": [] } } } ``` The `input` field accepts an array, so you can publish to multiple publications in a single mutation call. ### Unpublish a product Use the [`publishableUnpublish`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishableUnpublish) mutation to remove a product from one or more publications. The product isn't deleted. It remains in your store but becomes unavailable to buyers on those channels. ## POST /api/2026-07/graphql.json ##### GraphQL mutation ```graphql mutation UnpublishProduct($id: ID!, $input: [PublicationInput!]!) { publishableUnpublish(id: $id, input: $input) { publishable { ... on Product { id title } availablePublicationsCount { count } resourcePublicationsCount { count } } userErrors { field message } } } ``` ##### Variables ```json { "id": "gid://shopify/Product/1", "input": [ { "publicationId": "gid://shopify/Publication/456" } ] } ``` ##### JSON response ```json { "data": { "publishableUnpublish": { "publishable": { "id": "gid://shopify/Product/1", "title": "Classic Snowboard", "availablePublicationsCount": { "count": 3 }, "resourcePublicationsCount": { "count": 2 } }, "userErrors": [] } } } ``` **Info:** If your app is itself a sales channel, you can use the [`publishablePublishToCurrentChannel`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishablePublishToCurrentChannel) and [`publishableUnpublishToCurrentChannel`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishableUnpublishToCurrentChannel) convenience mutations to publish resources to your app's own channel without specifying a publication ID. This guide focuses on publishing to any publication using explicit IDs. *** ## Reading publication state After publishing or unpublishing products, you can verify their publication state using several fields on the `Product` object. ### Check a specific publication Use `publishedOnPublication` to check whether a product is published to a specific publication. ## POST /api/2026-07/graphql.json ##### GraphQL query ```graphql query ProductPublicationCheck($id: ID!, $publicationId: ID!) { product(id: $id) { title publishedOnPublication(publicationId: $publicationId) } } ``` ##### Variables ```json { "id": "gid://shopify/Product/1", "publicationId": "gid://shopify/Publication/123" } ``` ##### JSON response ```json { "data": { "product": { "title": "Classic Snowboard", "publishedOnPublication": true } } } ``` ### List all publications for a product Use `resourcePublicationsV2` to get a list of all publications a product is published to, along with the publication date. ## POST /api/2026-07/graphql.json ##### GraphQL query ```graphql query ProductPublications($id: ID!) { product(id: $id) { title resourcePublicationsV2(first: 20) { nodes { publication { id } isPublished publishDate } } } } ``` ##### Variables ```json { "id": "gid://shopify/Product/1" } ``` ##### JSON response ```json { "data": { "product": { "title": "Classic Snowboard", "resourcePublicationsV2": { "nodes": [ { "publication": { "id": "gid://shopify/Publication/123" }, "isPublished": true, "publishDate": "2026-01-15T10:00:00Z" }, { "publication": { "id": "gid://shopify/Publication/456" }, "isPublished": true, "publishDate": "2026-01-15T10:00:00Z" } ] } } } } ``` ### List publications where a product is not published Use `unpublishedPublications` to find publications where the product could be published but currently isn't. ## POST /api/2026-07/graphql.json ##### GraphQL query ```graphql query ProductUnpublished($id: ID!) { product(id: $id) { title unpublishedPublications(first: 20) { nodes { id } } } } ``` ##### Variables ```json { "id": "gid://shopify/Product/1" } ``` ##### JSON response ```json { "data": { "product": { "title": "Classic Snowboard", "unpublishedPublications": { "nodes": [ { "id": "gid://shopify/Publication/789" } ] } } } } ``` *** ## Managing publications at scale The mutations above are resource-centric: you pick a product and manage which publications it appears in. For the inverse pattern, picking a publication and managing which products it contains, use the [`publicationUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publicationUpdate) mutation. ## POST /api/2026-07/graphql.json ##### GraphQL mutation ```graphql mutation UpdatePublication($id: ID!, $input: PublicationUpdateInput!) { publicationUpdate(id: $id, input: $input) { publication { id } userErrors { field message } } } ``` ##### Variables ```json { "id": "gid://shopify/Publication/123", "input": { "publishablesToAdd": [ "gid://shopify/Product/1", "gid://shopify/Product/2" ], "publishablesToRemove": [ "gid://shopify/Product/3" ] } } ``` ##### JSON response ```json { "data": { "publicationUpdate": { "publication": { "id": "gid://shopify/Publication/123" }, "userErrors": [] } } } ``` Use `publishablePublish` when managing a single product's channel presence. Use `publicationUpdate` when managing a publication's product catalog. *** ## Variant-level publishing Variant-level publishing lets you control visibility at the individual variant level. By default, when a product is published to a sales channel or catalog, all of its variants are visible to buyers. With variant-level publishing, you can publish or unpublish specific variants independently. This is useful for scenarios like: * **Product launches**: Prepare new colorways or sizes without exposing them to buyers until you're ready. * **Region-based restrictions**: Hide variants from certain markets due to regulatory or shipping constraints. * **Channel-specific catalogs**: Show different variant selections on different sales channels. * **Lifecycle management**: Retire variants from storefronts without deleting them, preserving historical data and analytics. ### How it works The [`ProductVariant`](https://shopify.dev/docs/api/admin-graphql/latest/objects/ProductVariant) object implements the [`Publishable`](https://shopify.dev/docs/api/admin-graphql/latest/interfaces/Publishable) interface, giving it the same publishing controls that exist for products and collections. By default, all variants are published on all channels and catalogs where their parent product is published. This ensures apps and merchants can continue their current product creation and publishing flow without implementing variant publishing support. ### Visibility rules A variant is visible to buyers on a given sales channel or catalog when the following are all true: 1. The product has an Active status (or Unlisted on supported channels). 2. The product is published to that channel or catalog. 3. The variant is published to that channel or catalog. | Product state | Variant state | Visible to buyers | | - | - | - | | Published | Published | Yes | | Published | Unpublished | No | | Unpublished | Published | No | | Unpublished | Unpublished | No | ### Independent state Variant and product publishing states are independent: * Publishing or unpublishing a variant doesn't affect the parent product's publication state. * Publishing or unpublishing a product doesn't change any variant's publication state. Variant publishing state persists across product publishing changes, so you can configure variant visibility before publishing the product. ### Query variant publication status The `ProductVariant` object includes all fields from the `Publishable` interface. You can check whether a variant is published to a specific channel, list all of its publications, or count its available publications. ## POST /api/2026-07/graphql.json ##### GraphQL query ```graphql query VariantPublicationStatus { productVariant(id: "gid://shopify/ProductVariant/123") { title publishedOnPublication( publicationId: "gid://shopify/Publication/456" ) resourcePublicationsCount { count } } } ``` ##### JSON response ```json { "data": { "productVariant": { "title": "Red / Small", "publishedOnPublication": true, "resourcePublicationsCount": { "count": 3 } } } } ``` Use `resourcePublicationsV2` and `unpublishedPublications` to get the full picture of where a variant is and isn't published: ## POST /api/2026-07/graphql.json ##### GraphQL query ```graphql query VariantPublications { productVariant(id: "gid://shopify/ProductVariant/123") { title resourcePublicationsV2(first: 10) { nodes { publication { id } isPublished publishDate } } unpublishedPublications(first: 10) { nodes { id } } } } ``` ##### JSON response ```json { "data": { "productVariant": { "title": "Red / Small", "resourcePublicationsV2": { "nodes": [ { "publication": { "id": "gid://shopify/Publication/123" }, "isPublished": true, "publishDate": "2026-01-15T10:00:00Z" } ] }, "unpublishedPublications": { "nodes": [ { "id": "gid://shopify/Publication/789" } ] } } } } ``` ### Publish a variant to a sales channel Use the [`publishablePublish`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishablePublish) mutation with a `ProductVariant` GID to publish a variant to one or more publications. If the variant is already published to that publication, the mutation succeeds with no change. ## POST /api/2026-07/graphql.json ##### GraphQL mutation ```graphql mutation PublishVariant($id: ID!, $input: [PublicationInput!]!) { publishablePublish(id: $id, input: $input) { publishable { ... on ProductVariant { id title } } userErrors { field message } } } ``` ##### Variables ```json { "id": "gid://shopify/ProductVariant/123", "input": [ { "publicationId": "gid://shopify/Publication/456" } ] } ``` ##### JSON response ```json { "data": { "publishablePublish": { "publishable": { "id": "gid://shopify/ProductVariant/123", "title": "Red / Small" }, "userErrors": [] } } } ``` ### Unpublish a variant from a sales channel Use the [`publishableUnpublish`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishableUnpublish) mutation to remove a variant from a specific publication. The variant data isn't deleted. You can republish the variant at any time. ## POST /api/2026-07/graphql.json ##### GraphQL mutation ```graphql mutation UnpublishVariant($id: ID!, $input: [PublicationInput!]!) { publishableUnpublish(id: $id, input: $input) { publishable { ... on ProductVariant { id title } } userErrors { field message } } } ``` ##### Variables ```json { "id": "gid://shopify/ProductVariant/123", "input": [ { "publicationId": "gid://shopify/Publication/456" } ] } ``` ##### JSON response ```json { "data": { "publishableUnpublish": { "publishable": { "id": "gid://shopify/ProductVariant/123", "title": "Red / Small" }, "userErrors": [] } } } ``` ### Create a variant in an unpublished state You can create a variant that's unpublished from all publications by setting `published` to `false`. This prevents any buyer exposure before you explicitly publish the variant. Use the `published` field on the variant input when calling [`productVariantsBulkCreate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productVariantsBulkCreate) or [`productSet`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/productSet). ## POST /api/2026-07/graphql.json ##### GraphQL mutation ```graphql mutation CreateUnpublishedVariant( $productId: ID! $variants: [ProductVariantsBulkInput!]! ) { productVariantsBulkCreate( productId: $productId variants: $variants ) { productVariants { id title } userErrors { field message } } } ``` ##### Variables ```json { "productId": "gid://shopify/Product/1", "variants": [ { "price": "29.99", "optionValues": [ { "optionName": "Color", "name": "Midnight Blue" } ], "published": false } ] } ``` ##### JSON response ```json { "data": { "productVariantsBulkCreate": { "productVariants": [ { "id": "gid://shopify/ProductVariant/999", "title": "Midnight Blue" } ], "userErrors": [] } } } ``` After creating an unpublished variant, you can selectively publish it to specific channels using [`publishablePublish`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishablePublish). **Note:** If you don't set `published`, or set it to `true`, the variant is created with the default state of published to all channels and catalogs where the parent product is published. *** ## Other publishable resources `Collection` also implements the [`Publishable`](https://shopify.dev/docs/api/admin-graphql/latest/interfaces/Publishable) interface. The same `publishablePublish`, `publishableUnpublish`, and `publicationUpdate` mutations shown in this guide work with Collection GIDs to control which sales channels and catalogs a collection is visible in. *** ## Next steps * Learn about [scheduled product publishing](https://shopify.dev/docs/apps/build/sales-channels/scheduled-product-publishing) * Explore the [`Publishable` interface](https://shopify.dev/docs/api/admin-graphql/latest/interfaces/Publishable) reference * Explore the [`publishablePublish`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishablePublish) mutation reference * Explore the [`publishableUnpublish`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publishableUnpublish) mutation reference * Explore the [`publicationUpdate`](https://shopify.dev/docs/api/admin-graphql/latest/mutations/publicationUpdate) mutation reference * Learn about [catalogs for different markets](https://shopify.dev/docs/apps/build/markets/catalogs-different-markets) * Learn about [building sales channels](https://shopify.dev/docs/apps/build/sales-channels) ***