diff --git a/.changeset/visitor-consent-incontext.md b/.changeset/visitor-consent-incontext.md new file mode 100644 index 0000000000..f60981073d --- /dev/null +++ b/.changeset/visitor-consent-incontext.md @@ -0,0 +1,15 @@ +--- +'@shopify/hydrogen': minor +'@shopify/hydrogen-react': minor +--- + +Add `visitorConsent` support to `@inContext` directive for Storefront API parity + +**Note: Most Hydrogen storefronts do NOT need this feature.** + +This API addition provides Storefront API 2025-10 parity for the `visitorConsent` parameter in `@inContext` directives. However, if you're using Hydrogen's analytics provider or Shopify's Customer Privacy API (including third-party consent services integrated with it), consent is already handled automatically and you don't need to use this. + +This feature is primarily intended for Checkout Kit and other non-Hydrogen integrations that manage consent outside of Shopify's standard consent flow. + +**What it does:** +When explicitly provided, `visitorConsent` encodes buyer consent preferences (analytics, marketing, preferences, saleOfData) into the cart's `checkoutUrl` via the `_cs` parameter. diff --git a/packages/hydrogen-react/src/cart-queries.ts b/packages/hydrogen-react/src/cart-queries.ts index 45f9554f0a..e6737b2c81 100644 --- a/packages/hydrogen-react/src/cart-queries.ts +++ b/packages/hydrogen-react/src/cart-queries.ts @@ -5,7 +5,13 @@ export const CartLineAdd = (cartFragment: string): string => /* GraphQL */ ` $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartLinesAdd(cartId: $cartId, lines: $lines) { cart { ...CartFragment @@ -22,7 +28,13 @@ export const CartCreate = (cartFragment: string): string => /* GraphQL */ ` $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartCreate(input: $input) { cart { ...CartFragment @@ -40,7 +52,13 @@ export const CartLineRemove = (cartFragment: string): string => /* GraphQL */ ` $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartLinesRemove(cartId: $cartId, lineIds: $lines) { cart { ...CartFragment @@ -58,7 +76,13 @@ export const CartLineUpdate = (cartFragment: string): string => /* GraphQL */ ` $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartLinesUpdate(cartId: $cartId, lines: $lines) { cart { ...CartFragment @@ -76,7 +100,13 @@ export const CartNoteUpdate = (cartFragment: string): string => /* GraphQL */ ` $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartNoteUpdate(cartId: $cartId, note: $note) { cart { ...CartFragment @@ -96,7 +126,13 @@ export const CartBuyerIdentityUpdate = ( $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) { cart { ...CartFragment @@ -116,7 +152,13 @@ export const CartAttributesUpdate = ( $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartAttributesUpdate(attributes: $attributes, cartId: $cartId) { cart { ...CartFragment @@ -136,7 +178,13 @@ export const CartDiscountCodesUpdate = ( $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) { cart { ...CartFragment @@ -153,7 +201,13 @@ export const CartQuery = (cartFragment: string): string => /* GraphQL */ ` $numCartLines: Int = 250 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) + @inContext( + country: $country + language: $language + visitorConsent: $visitorConsent + ) { cart(id: $id) { ...CartFragment } diff --git a/packages/hydrogen/src/cart/queries/cart-types.ts b/packages/hydrogen/src/cart/queries/cart-types.ts index ce5bdd07e1..1c51549b0f 100644 --- a/packages/hydrogen/src/cart/queries/cart-types.ts +++ b/packages/hydrogen/src/cart/queries/cart-types.ts @@ -8,6 +8,7 @@ import type { MetafieldsSetUserError, MetafieldDeleteUserError, CartWarning, + VisitorConsent, } from '@shopify/hydrogen-react/storefront-api-types'; import type {StorefrontApiErrors, Storefront} from '../../storefront'; import {CustomerAccount} from '../../customer/types'; @@ -28,6 +29,21 @@ export type CartOptionalInput = { * @default storefront.i18n.language */ language?: LanguageCode; + /** + * Visitor consent preferences for the Storefront API's @inContext directive. + * + * **Most Hydrogen storefronts do NOT need this.** If you're using Hydrogen's + * analytics provider or Shopify's Customer Privacy API (including third-party + * consent services integrated with it), consent is handled automatically. + * + * This option exists for Storefront API parity and is primarily intended for + * non-Hydrogen integrations like Checkout Kit that manage consent outside + * Shopify's standard consent flow. + * + * When provided, consent is encoded into the cart's checkoutUrl via the _cs parameter. + * @see https://shopify.dev/docs/storefronts/headless/building-with-the-storefront-api/in-context + */ + visitorConsent?: VisitorConsent; }; export type MetafieldWithoutOwnerId = Omit; diff --git a/packages/hydrogen/src/cart/queries/cartAttributesUpdateDefault.ts b/packages/hydrogen/src/cart/queries/cartAttributesUpdateDefault.ts index 4bd821e262..c40ebce1df 100644 --- a/packages/hydrogen/src/cart/queries/cartAttributesUpdateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartAttributesUpdateDefault.ts @@ -28,6 +28,7 @@ export function cartAttributesUpdateDefault( variables: { cartId: optionalParams?.cartId || options.getCartId(), attributes, + ...optionalParams, }, }); return formatAPIResult(cartAttributesUpdate, errors); @@ -40,7 +41,10 @@ export const CART_ATTRIBUTES_UPDATE_MUTATION = ( mutation cartAttributesUpdate( $cartId: ID! $attributes: [AttributeInput!]! - ) { + $language: LanguageCode + $country: CountryCode + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartAttributesUpdate(cartId: $cartId, attributes: $attributes) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartBuyerIdentityUpdateDefault.ts b/packages/hydrogen/src/cart/queries/cartBuyerIdentityUpdateDefault.ts index 0b9dab62b4..2ebead1376 100644 --- a/packages/hydrogen/src/cart/queries/cartBuyerIdentityUpdateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartBuyerIdentityUpdateDefault.ts @@ -57,7 +57,8 @@ export const CART_BUYER_IDENTITY_UPDATE_MUTATION = ( $buyerIdentity: CartBuyerIdentityInput! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartBuyerIdentityUpdate(cartId: $cartId, buyerIdentity: $buyerIdentity) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartCreateDefault.ts b/packages/hydrogen/src/cart/queries/cartCreateDefault.ts index bd2db7f45f..f7dd188c47 100644 --- a/packages/hydrogen/src/cart/queries/cartCreateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartCreateDefault.ts @@ -53,7 +53,8 @@ export const CART_CREATE_MUTATION = ( $input: CartInput! $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartCreate(input: $input) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartDeliveryAddressesAddDefault.tsx b/packages/hydrogen/src/cart/queries/cartDeliveryAddressesAddDefault.tsx index 48084f54a0..e6f74a848d 100644 --- a/packages/hydrogen/src/cart/queries/cartDeliveryAddressesAddDefault.tsx +++ b/packages/hydrogen/src/cart/queries/cartDeliveryAddressesAddDefault.tsx @@ -69,7 +69,8 @@ export const CART_DELIVERY_ADDRESSES_ADD_MUTATION = ( $addresses: [CartSelectableAddressInput!]!, $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartDeliveryAddressesAdd(addresses: $addresses, cartId: $cartId) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartDeliveryAddressesRemoveDefault.tsx b/packages/hydrogen/src/cart/queries/cartDeliveryAddressesRemoveDefault.tsx index ab2aa74d7f..baf70f7736 100644 --- a/packages/hydrogen/src/cart/queries/cartDeliveryAddressesRemoveDefault.tsx +++ b/packages/hydrogen/src/cart/queries/cartDeliveryAddressesRemoveDefault.tsx @@ -65,7 +65,8 @@ export const CART_DELIVERY_ADDRESSES_REMOVE_MUTATION = ( $addressIds: [ID!]!, $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartDeliveryAddressesRemove(addressIds: $addressIds, cartId: $cartId) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartDeliveryAddressesUpdateDefault.tsx b/packages/hydrogen/src/cart/queries/cartDeliveryAddressesUpdateDefault.tsx index ada1e0e315..ee222e89a3 100644 --- a/packages/hydrogen/src/cart/queries/cartDeliveryAddressesUpdateDefault.tsx +++ b/packages/hydrogen/src/cart/queries/cartDeliveryAddressesUpdateDefault.tsx @@ -87,7 +87,8 @@ export const CART_DELIVERY_ADDRESSES_UPDATE_MUTATION = ( $addresses: [CartSelectableAddressUpdateInput!]!, $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartDeliveryAddressesUpdate(addresses: $addresses, cartId: $cartId) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartDiscountCodesUpdateDefault.ts b/packages/hydrogen/src/cart/queries/cartDiscountCodesUpdateDefault.ts index d6add1f857..e22cc69990 100644 --- a/packages/hydrogen/src/cart/queries/cartDiscountCodesUpdateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartDiscountCodesUpdateDefault.ts @@ -48,7 +48,8 @@ export const CART_DISCOUNT_CODE_UPDATE_MUTATION = ( $discountCodes: [String!] $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartDiscountCodesUpdate(cartId: $cartId, discountCodes: $discountCodes) { ... @defer { cart { diff --git a/packages/hydrogen/src/cart/queries/cartGetDefault.ts b/packages/hydrogen/src/cart/queries/cartGetDefault.ts index 26901b5d15..71ddee722b 100644 --- a/packages/hydrogen/src/cart/queries/cartGetDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartGetDefault.ts @@ -5,6 +5,7 @@ import type { Cart, CountryCode, LanguageCode, + VisitorConsent, } from '@shopify/hydrogen-react/storefront-api-types'; type CartGetProps = { @@ -28,6 +29,20 @@ type CartGetProps = { * @default 100 */ numCartLines?: number; + /** + * Visitor consent preferences for the Storefront API's @inContext directive. + * + * **Most Hydrogen storefronts do NOT need this.** If you're using Hydrogen's + * analytics provider or Shopify's Customer Privacy API (including third-party + * consent services integrated with it), consent is handled automatically. + * + * This option exists for Storefront API parity and is primarily intended for + * non-Hydrogen integrations like Checkout Kit that manage consent outside + * Shopify's standard consent flow. + * + * When provided, consent is encoded into the cart's checkoutUrl via the _cs parameter. + */ + visitorConsent?: VisitorConsent; }; export type CartGetFunction = ( @@ -77,7 +92,8 @@ const CART_QUERY = (cartFragment = DEFAULT_CART_FRAGMENT) => `#graphql $numCartLines: Int = 100 $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cart(id: $cartId) { ...CartApiQuery } diff --git a/packages/hydrogen/src/cart/queries/cartGiftCardCodeUpdateDefault.ts b/packages/hydrogen/src/cart/queries/cartGiftCardCodeUpdateDefault.ts index f5f5c1025d..77db16f082 100644 --- a/packages/hydrogen/src/cart/queries/cartGiftCardCodeUpdateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartGiftCardCodeUpdateDefault.ts @@ -55,7 +55,8 @@ export const CART_GIFT_CARD_CODE_UPDATE_MUTATION = ( $giftCardCodes: [String!]! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartGiftCardCodesUpdate(cartId: $cartId, giftCardCodes: $giftCardCodes) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartGiftCardCodesRemoveDefault.ts b/packages/hydrogen/src/cart/queries/cartGiftCardCodesRemoveDefault.ts index 1c1c2707c6..bd2c320f06 100644 --- a/packages/hydrogen/src/cart/queries/cartGiftCardCodesRemoveDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartGiftCardCodesRemoveDefault.ts @@ -43,7 +43,8 @@ export const CART_GIFT_CARD_CODES_REMOVE_MUTATION = ( $appliedGiftCardIds: [ID!]! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartGiftCardCodesRemove(cartId: $cartId, appliedGiftCardIds: $appliedGiftCardIds) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartLinesAddDefault.ts b/packages/hydrogen/src/cart/queries/cartLinesAddDefault.ts index 0bca0be04b..f0b1435589 100644 --- a/packages/hydrogen/src/cart/queries/cartLinesAddDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartLinesAddDefault.ts @@ -45,7 +45,8 @@ export const CART_LINES_ADD_MUTATION = ( $lines: [CartLineInput!]! $country: CountryCode = ZZ $language: LanguageCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartLinesAdd(cartId: $cartId, lines: $lines) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartLinesRemoveDefault.ts b/packages/hydrogen/src/cart/queries/cartLinesRemoveDefault.ts index 9cd146ad5d..38b8cc38f1 100644 --- a/packages/hydrogen/src/cart/queries/cartLinesRemoveDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartLinesRemoveDefault.ts @@ -46,7 +46,8 @@ export const CART_LINES_REMOVE_MUTATION = ( $lineIds: [ID!]! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartLinesRemove(cartId: $cartId, lineIds: $lineIds) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartLinesUpdateDefault.ts b/packages/hydrogen/src/cart/queries/cartLinesUpdateDefault.ts index b9f28ed5e7..1f09da4c09 100644 --- a/packages/hydrogen/src/cart/queries/cartLinesUpdateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartLinesUpdateDefault.ts @@ -47,7 +47,8 @@ export const CART_LINES_UPDATE_MUTATION = ( $lines: [CartLineUpdateInput!]! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartLinesUpdate(cartId: $cartId, lines: $lines) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartMetafieldDeleteDefault.ts b/packages/hydrogen/src/cart/queries/cartMetafieldDeleteDefault.ts index eba775922f..7f2df95329 100644 --- a/packages/hydrogen/src/cart/queries/cartMetafieldDeleteDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartMetafieldDeleteDefault.ts @@ -31,6 +31,7 @@ export function cartMetafieldDeleteDefault( ownerId, key, }, + ...optionalParams, }, }); return formatAPIResult( @@ -49,7 +50,10 @@ export function cartMetafieldDeleteDefault( export const CART_METAFIELD_DELETE_MUTATION = () => `#graphql mutation cartMetafieldDelete( $input: CartMetafieldDeleteInput! - ) { + $language: LanguageCode + $country: CountryCode + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartMetafieldDelete(input: $input) { userErrors { code diff --git a/packages/hydrogen/src/cart/queries/cartMetafieldsSetDefault.ts b/packages/hydrogen/src/cart/queries/cartMetafieldsSetDefault.ts index 4b4191af91..e61431a2a2 100644 --- a/packages/hydrogen/src/cart/queries/cartMetafieldsSetDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartMetafieldsSetDefault.ts @@ -32,7 +32,7 @@ export function cartMetafieldsSetDefault( }; errors: StorefrontApiErrors; }>(CART_METAFIELD_SET_MUTATION(), { - variables: {metafields: metafieldsWithOwnerId}, + variables: {metafields: metafieldsWithOwnerId, ...optionalParams}, }); return formatAPIResult( @@ -53,7 +53,8 @@ export const CART_METAFIELD_SET_MUTATION = () => `#graphql $metafields: [CartMetafieldsSetInput!]! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartMetafieldsSet(metafields: $metafields) { userErrors { code diff --git a/packages/hydrogen/src/cart/queries/cartNoteUpdateDefault.ts b/packages/hydrogen/src/cart/queries/cartNoteUpdateDefault.ts index 6c653d9b73..c4561448ab 100644 --- a/packages/hydrogen/src/cart/queries/cartNoteUpdateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartNoteUpdateDefault.ts @@ -43,7 +43,8 @@ export const CART_NOTE_UPDATE_MUTATION = ( $note: String! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartNoteUpdate(cartId: $cartId, note: $note) { cart { ...CartApiMutation diff --git a/packages/hydrogen/src/cart/queries/cartSelectedDeliveryOptionsUpdateDefault.ts b/packages/hydrogen/src/cart/queries/cartSelectedDeliveryOptionsUpdateDefault.ts index 04c4b8ff43..fbb324e1d1 100644 --- a/packages/hydrogen/src/cart/queries/cartSelectedDeliveryOptionsUpdateDefault.ts +++ b/packages/hydrogen/src/cart/queries/cartSelectedDeliveryOptionsUpdateDefault.ts @@ -45,7 +45,8 @@ export const CART_SELECTED_DELIVERY_OPTIONS_UPDATE_MUTATION = ( $selectedDeliveryOptions: [CartSelectedDeliveryOptionInput!]! $language: LanguageCode $country: CountryCode - ) @inContext(country: $country, language: $language) { + $visitorConsent: VisitorConsent + ) @inContext(country: $country, language: $language, visitorConsent: $visitorConsent) { cartSelectedDeliveryOptionsUpdate(cartId: $cartId, selectedDeliveryOptions: $selectedDeliveryOptions) { cart { ...CartApiMutation