@shopify/hydrogen 2026.10.0-preview.2 → 2026.10.0-preview.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (404) hide show
  1. package/README.md +34 -3
  2. package/dist/cli/index.mjs +578 -85
  3. package/dist/client/client.mjs +2 -2
  4. package/dist/client/errors.d.mts +19 -19
  5. package/dist/client/errors.mjs +19 -16
  6. package/dist/client/errors.mjs.map +1 -1
  7. package/dist/client/types.d.mts +91 -10
  8. package/dist/core/analytics/cart-tracker.d.mts +14 -0
  9. package/dist/core/analytics/cart-tracker.mjs +15 -1
  10. package/dist/core/analytics/cart-tracker.mjs.map +1 -1
  11. package/dist/core/analytics/custom-consent.mjs +12 -0
  12. package/dist/core/analytics/custom-consent.mjs.map +1 -0
  13. package/dist/core/analytics/events.d.mts +7 -0
  14. package/dist/core/analytics/events.mjs +7 -0
  15. package/dist/core/analytics/events.mjs.map +1 -1
  16. package/dist/core/analytics/index.d.mts +1 -1
  17. package/dist/core/analytics/types.d.mts +152 -16
  18. package/dist/core/cart/actions.d.mts +47 -3
  19. package/dist/core/cart/actions.mjs +29 -0
  20. package/dist/core/cart/actions.mjs.map +1 -1
  21. package/dist/core/cart/attach-quantity-input.d.mts +26 -0
  22. package/dist/core/cart/attach-quantity-input.mjs +26 -0
  23. package/dist/core/cart/attach-quantity-input.mjs.map +1 -1
  24. package/dist/core/cart/cart.d.mts +106 -6
  25. package/dist/core/cart/cart.mjs +57 -6
  26. package/dist/core/cart/cart.mjs.map +1 -1
  27. package/dist/core/cart/cookie.d.mts +15 -0
  28. package/dist/core/cart/cookie.mjs +15 -0
  29. package/dist/core/cart/cookie.mjs.map +1 -1
  30. package/dist/core/cart/form.d.mts +61 -0
  31. package/dist/core/cart/form.mjs +18 -0
  32. package/dist/core/cart/form.mjs.map +1 -1
  33. package/dist/core/cart/get-cart.d.mts +43 -2
  34. package/dist/core/cart/get-cart.mjs +33 -0
  35. package/dist/core/cart/get-cart.mjs.map +1 -1
  36. package/dist/core/cart/index.d.mts +1 -1
  37. package/dist/core/cart/quantity.d.mts +17 -0
  38. package/dist/core/cart/quantity.mjs +17 -0
  39. package/dist/core/cart/quantity.mjs.map +1 -1
  40. package/dist/core/cart/queries.d.mts +402 -369
  41. package/dist/core/cart/queries.mjs +2 -1
  42. package/dist/core/cart/queries.mjs.map +1 -1
  43. package/dist/core/cart/server-handlers.d.mts +69 -0
  44. package/dist/core/cart/server-handlers.mjs +2 -10
  45. package/dist/core/cart/server-handlers.mjs.map +1 -1
  46. package/dist/core/cart/state.d.mts +133 -0
  47. package/dist/core/cart/state.mjs +8 -0
  48. package/dist/core/cart/state.mjs.map +1 -1
  49. package/dist/core/collection/collection.d.mts +18 -2
  50. package/dist/core/collection/collection.mjs +11 -0
  51. package/dist/core/collection/collection.mjs.map +1 -1
  52. package/dist/core/collection/index.d.mts +1 -1
  53. package/dist/core/collection/reconciler.d.mts +13 -4
  54. package/dist/core/collection/reconciler.mjs.map +1 -1
  55. package/dist/core/collection/state.d.mts +7 -2
  56. package/dist/core/collection/state.mjs.map +1 -1
  57. package/dist/core/collection/url.d.mts +3 -0
  58. package/dist/core/collection/url.mjs.map +1 -1
  59. package/dist/core/constants.d.mts +1 -1
  60. package/dist/core/constants.mjs +2 -2
  61. package/dist/core/constants.mjs.map +1 -1
  62. package/dist/core/headers.mjs +2 -2
  63. package/dist/core/headers.mjs.map +1 -1
  64. package/dist/core/index.d.mts +5 -5
  65. package/dist/core/logging/logging.mjs.map +1 -1
  66. package/dist/core/money/format.mjs +6 -3
  67. package/dist/core/money/format.mjs.map +1 -1
  68. package/dist/core/observable.mjs +0 -3
  69. package/dist/core/observable.mjs.map +1 -1
  70. package/dist/core/predictive-search/form.d.mts +1 -0
  71. package/dist/core/predictive-search/form.mjs +1 -0
  72. package/dist/core/predictive-search/form.mjs.map +1 -1
  73. package/dist/core/predictive-search/index.d.mts +1 -1
  74. package/dist/core/predictive-search/queries.d.mts +40 -4
  75. package/dist/core/predictive-search/queries.mjs +7 -1
  76. package/dist/core/predictive-search/queries.mjs.map +1 -1
  77. package/dist/core/predictive-search/search.d.mts +43 -10
  78. package/dist/core/predictive-search/search.mjs +34 -0
  79. package/dist/core/predictive-search/search.mjs.map +1 -1
  80. package/dist/core/predictive-search/server-handlers.d.mts +31 -5
  81. package/dist/core/predictive-search/server-handlers.mjs +5 -3
  82. package/dist/core/predictive-search/server-handlers.mjs.map +1 -1
  83. package/dist/core/predictive-search/store.d.mts +3 -1
  84. package/dist/core/predictive-search/store.mjs.map +1 -1
  85. package/dist/core/predictive-search/url.d.mts +50 -8
  86. package/dist/core/predictive-search/url.mjs +9 -0
  87. package/dist/core/predictive-search/url.mjs.map +1 -1
  88. package/dist/core/product/form.d.mts +21 -2
  89. package/dist/core/product/form.mjs +13 -0
  90. package/dist/core/product/form.mjs.map +1 -1
  91. package/dist/core/product/index.d.mts +1 -1
  92. package/dist/core/product/options.d.mts +2 -2
  93. package/dist/core/product/options.mjs +2 -2
  94. package/dist/core/product/options.mjs.map +1 -1
  95. package/dist/core/product/product-form.d.mts +33 -1
  96. package/dist/core/product/product-form.mjs +30 -0
  97. package/dist/core/product/product-form.mjs.map +1 -1
  98. package/dist/core/product/state.d.mts +41 -0
  99. package/dist/core/product/url.d.mts +6 -0
  100. package/dist/core/product/url.mjs.map +1 -1
  101. package/dist/core/request-context.d.mts +8 -5
  102. package/dist/core/request-context.mjs +17 -39
  103. package/dist/core/request-context.mjs.map +1 -1
  104. package/dist/core/request-routing/handle-shopify-routes.mjs +4 -1
  105. package/dist/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  106. package/dist/core/request-routing/interceptors/ajax-api.mjs +1 -1
  107. package/dist/core/request-routing/interceptors/api-proxy.mjs +1 -1
  108. package/dist/core/request-routing/interceptors/checkout.mjs +22 -6
  109. package/dist/core/request-routing/interceptors/checkout.mjs.map +1 -1
  110. package/dist/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
  111. package/dist/core/request-routing/interceptors/proxy.mjs +36 -10
  112. package/dist/core/request-routing/interceptors/proxy.mjs.map +1 -1
  113. package/dist/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
  114. package/dist/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
  115. package/dist/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
  116. package/dist/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
  117. package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
  118. package/dist/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
  119. package/dist/core/request-routing/interceptors/well-known.mjs +1 -1
  120. package/dist/core/shopify-scripts/analytics.mjs +2 -2
  121. package/dist/core/shopify-scripts/analytics.mjs.map +1 -1
  122. package/dist/core/shopify-scripts/constants.mjs +4 -2
  123. package/dist/core/shopify-scripts/constants.mjs.map +1 -1
  124. package/dist/core/shopify-scripts/global.mjs +9 -4
  125. package/dist/core/shopify-scripts/global.mjs.map +1 -1
  126. package/dist/core/shopify-scripts/index.d.mts +1 -0
  127. package/dist/core/shopify-scripts/index.mjs +14 -12
  128. package/dist/core/shopify-scripts/index.mjs.map +1 -1
  129. package/dist/core/shopify-scripts/initialize.d.mts +1 -0
  130. package/dist/core/shopify-scripts/initialize.mjs +10 -3
  131. package/dist/core/shopify-scripts/initialize.mjs.map +1 -1
  132. package/dist/core/shopify-scripts/types.d.mts +6 -0
  133. package/dist/core/url.mjs +21 -2
  134. package/dist/core/url.mjs.map +1 -1
  135. package/dist/customer-account/client.d.mts +128 -4
  136. package/dist/customer-account/client.mjs +27 -1
  137. package/dist/customer-account/client.mjs.map +1 -1
  138. package/dist/customer-account/errors.d.mts +46 -0
  139. package/dist/customer-account/errors.mjs +46 -0
  140. package/dist/customer-account/errors.mjs.map +1 -1
  141. package/dist/customer-account/graphql.d.mts +68 -0
  142. package/dist/customer-account/graphql.mjs +47 -0
  143. package/dist/customer-account/graphql.mjs.map +1 -1
  144. package/dist/customer-account/session.d.mts +114 -23
  145. package/dist/customer-account/session.mjs +27 -12
  146. package/dist/customer-account/session.mjs.map +1 -1
  147. package/dist/customer-account/type-resolver.d.mts +23 -0
  148. package/dist/customer-account-api-types.d.ts +570 -374
  149. package/dist/customer-account.schema.json +2981 -1527
  150. package/dist/development/client/client.mjs +2 -2
  151. package/dist/development/client/errors.d.mts +19 -19
  152. package/dist/development/client/errors.mjs +20 -20
  153. package/dist/development/client/errors.mjs.map +1 -1
  154. package/dist/development/client/types.d.mts +91 -10
  155. package/dist/development/core/analytics/cart-tracker.d.mts +14 -0
  156. package/dist/development/core/analytics/cart-tracker.mjs +15 -1
  157. package/dist/development/core/analytics/cart-tracker.mjs.map +1 -1
  158. package/dist/development/core/analytics/custom-consent.mjs +12 -0
  159. package/dist/development/core/analytics/custom-consent.mjs.map +1 -0
  160. package/dist/development/core/analytics/events.d.mts +7 -0
  161. package/dist/development/core/analytics/events.mjs +7 -0
  162. package/dist/development/core/analytics/events.mjs.map +1 -1
  163. package/dist/development/core/analytics/index.d.mts +1 -1
  164. package/dist/development/core/analytics/types.d.mts +152 -16
  165. package/dist/development/core/cart/actions.d.mts +47 -3
  166. package/dist/development/core/cart/actions.mjs +29 -0
  167. package/dist/development/core/cart/actions.mjs.map +1 -1
  168. package/dist/development/core/cart/attach-quantity-input.d.mts +26 -0
  169. package/dist/development/core/cart/attach-quantity-input.mjs +26 -0
  170. package/dist/development/core/cart/attach-quantity-input.mjs.map +1 -1
  171. package/dist/development/core/cart/cart.d.mts +106 -6
  172. package/dist/development/core/cart/cart.mjs +57 -6
  173. package/dist/development/core/cart/cart.mjs.map +1 -1
  174. package/dist/development/core/cart/cookie.d.mts +15 -0
  175. package/dist/development/core/cart/cookie.mjs +15 -0
  176. package/dist/development/core/cart/cookie.mjs.map +1 -1
  177. package/dist/development/core/cart/form.d.mts +61 -0
  178. package/dist/development/core/cart/form.mjs +18 -0
  179. package/dist/development/core/cart/form.mjs.map +1 -1
  180. package/dist/development/core/cart/get-cart.d.mts +43 -2
  181. package/dist/development/core/cart/get-cart.mjs +33 -0
  182. package/dist/development/core/cart/get-cart.mjs.map +1 -1
  183. package/dist/development/core/cart/index.d.mts +1 -1
  184. package/dist/development/core/cart/quantity.d.mts +17 -0
  185. package/dist/development/core/cart/quantity.mjs +17 -0
  186. package/dist/development/core/cart/quantity.mjs.map +1 -1
  187. package/dist/development/core/cart/queries.d.mts +345 -312
  188. package/dist/development/core/cart/queries.mjs +2 -1
  189. package/dist/development/core/cart/queries.mjs.map +1 -1
  190. package/dist/development/core/cart/server-handlers.d.mts +69 -0
  191. package/dist/development/core/cart/server-handlers.mjs +2 -10
  192. package/dist/development/core/cart/server-handlers.mjs.map +1 -1
  193. package/dist/development/core/cart/state.d.mts +133 -0
  194. package/dist/development/core/cart/state.mjs +8 -0
  195. package/dist/development/core/cart/state.mjs.map +1 -1
  196. package/dist/development/core/collection/collection.d.mts +18 -2
  197. package/dist/development/core/collection/collection.mjs +11 -0
  198. package/dist/development/core/collection/collection.mjs.map +1 -1
  199. package/dist/development/core/collection/index.d.mts +1 -1
  200. package/dist/development/core/collection/reconciler.d.mts +13 -4
  201. package/dist/development/core/collection/reconciler.mjs.map +1 -1
  202. package/dist/development/core/collection/state.d.mts +7 -2
  203. package/dist/development/core/collection/state.mjs.map +1 -1
  204. package/dist/development/core/collection/url.d.mts +3 -0
  205. package/dist/development/core/collection/url.mjs.map +1 -1
  206. package/dist/development/core/constants.mjs +1 -1
  207. package/dist/development/core/constants.mjs.map +1 -1
  208. package/dist/development/core/development.d.mts +5 -5
  209. package/dist/development/core/headers.mjs +2 -2
  210. package/dist/development/core/headers.mjs.map +1 -1
  211. package/dist/development/core/index.d.mts +4 -4
  212. package/dist/development/core/logging/logging.mjs.map +1 -1
  213. package/dist/development/core/money/format.mjs +6 -3
  214. package/dist/development/core/money/format.mjs.map +1 -1
  215. package/dist/development/core/observable.mjs +0 -3
  216. package/dist/development/core/observable.mjs.map +1 -1
  217. package/dist/development/core/predictive-search/form.d.mts +1 -0
  218. package/dist/development/core/predictive-search/form.mjs +1 -0
  219. package/dist/development/core/predictive-search/form.mjs.map +1 -1
  220. package/dist/development/core/predictive-search/index.d.mts +1 -1
  221. package/dist/development/core/predictive-search/queries.d.mts +38 -2
  222. package/dist/development/core/predictive-search/queries.mjs +7 -1
  223. package/dist/development/core/predictive-search/queries.mjs.map +1 -1
  224. package/dist/development/core/predictive-search/search.d.mts +43 -10
  225. package/dist/development/core/predictive-search/search.mjs +34 -0
  226. package/dist/development/core/predictive-search/search.mjs.map +1 -1
  227. package/dist/development/core/predictive-search/server-handlers.d.mts +31 -5
  228. package/dist/development/core/predictive-search/server-handlers.mjs +5 -3
  229. package/dist/development/core/predictive-search/server-handlers.mjs.map +1 -1
  230. package/dist/development/core/predictive-search/store.d.mts +3 -1
  231. package/dist/development/core/predictive-search/store.mjs.map +1 -1
  232. package/dist/development/core/predictive-search/url.d.mts +50 -8
  233. package/dist/development/core/predictive-search/url.mjs +9 -0
  234. package/dist/development/core/predictive-search/url.mjs.map +1 -1
  235. package/dist/development/core/product/form.d.mts +21 -2
  236. package/dist/development/core/product/form.mjs +13 -0
  237. package/dist/development/core/product/form.mjs.map +1 -1
  238. package/dist/development/core/product/index.d.mts +1 -1
  239. package/dist/development/core/product/options.d.mts +2 -2
  240. package/dist/development/core/product/options.mjs +2 -2
  241. package/dist/development/core/product/options.mjs.map +1 -1
  242. package/dist/development/core/product/product-form.d.mts +33 -1
  243. package/dist/development/core/product/product-form.mjs +30 -0
  244. package/dist/development/core/product/product-form.mjs.map +1 -1
  245. package/dist/development/core/product/state.d.mts +41 -0
  246. package/dist/development/core/product/url.d.mts +6 -0
  247. package/dist/development/core/product/url.mjs.map +1 -1
  248. package/dist/development/core/request-context.d.mts +8 -5
  249. package/dist/development/core/request-context.mjs +17 -39
  250. package/dist/development/core/request-context.mjs.map +1 -1
  251. package/dist/development/core/request-routing/handle-shopify-routes.mjs +4 -1
  252. package/dist/development/core/request-routing/handle-shopify-routes.mjs.map +1 -1
  253. package/dist/development/core/request-routing/interceptors/ajax-api.mjs +1 -1
  254. package/dist/development/core/request-routing/interceptors/api-proxy.mjs +1 -1
  255. package/dist/development/core/request-routing/interceptors/checkout.mjs +22 -6
  256. package/dist/development/core/request-routing/interceptors/checkout.mjs.map +1 -1
  257. package/dist/development/core/request-routing/interceptors/mcp-proxy.mjs +1 -1
  258. package/dist/development/core/request-routing/interceptors/proxy.mjs +36 -10
  259. package/dist/development/core/request-routing/interceptors/proxy.mjs.map +1 -1
  260. package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs +5 -11
  261. package/dist/development/core/request-routing/interceptors/query-param-redirect.mjs.map +1 -1
  262. package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs +19 -1
  263. package/dist/development/core/request-routing/interceptors/sfapi-proxy.mjs.map +1 -1
  264. package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs +35 -0
  265. package/dist/development/core/request-routing/interceptors/ucp-mcp-proxy.mjs.map +1 -0
  266. package/dist/development/core/request-routing/interceptors/well-known.mjs +1 -1
  267. package/dist/development/core/shopify-scripts/analytics.mjs +2 -2
  268. package/dist/development/core/shopify-scripts/analytics.mjs.map +1 -1
  269. package/dist/development/core/shopify-scripts/constants.mjs +4 -2
  270. package/dist/development/core/shopify-scripts/constants.mjs.map +1 -1
  271. package/dist/development/core/shopify-scripts/global.mjs +9 -4
  272. package/dist/development/core/shopify-scripts/global.mjs.map +1 -1
  273. package/dist/development/core/shopify-scripts/index.d.mts +1 -0
  274. package/dist/development/core/shopify-scripts/index.mjs +14 -12
  275. package/dist/development/core/shopify-scripts/index.mjs.map +1 -1
  276. package/dist/development/core/shopify-scripts/initialize.d.mts +1 -0
  277. package/dist/development/core/shopify-scripts/initialize.mjs +10 -3
  278. package/dist/development/core/shopify-scripts/initialize.mjs.map +1 -1
  279. package/dist/development/core/shopify-scripts/types.d.mts +6 -0
  280. package/dist/development/core/url.mjs +21 -2
  281. package/dist/development/core/url.mjs.map +1 -1
  282. package/dist/development/customer-account/session.d.mts +69 -13
  283. package/dist/development/customer-account/session.mjs +4 -0
  284. package/dist/development/customer-account/session.mjs.map +1 -1
  285. package/dist/development/globals.d.mts +6 -5
  286. package/dist/development/graphql/generated/graphql-env.d.mts +459 -16
  287. package/dist/development/graphql/generated/storefront-api-types.d.mts +1 -1
  288. package/dist/development/graphql/graphql.d.mts +33 -0
  289. package/dist/development/graphql/graphql.mjs +30 -0
  290. package/dist/development/graphql/graphql.mjs.map +1 -1
  291. package/dist/development/graphql/type-resolver.d.mts +14 -0
  292. package/dist/development/react/cart.d.mts +85 -14
  293. package/dist/development/react/cart.mjs +123 -3
  294. package/dist/development/react/cart.mjs.map +1 -1
  295. package/dist/development/react/collection.d.mts +2 -4
  296. package/dist/development/react/collection.mjs.map +1 -1
  297. package/dist/development/react/index.d.mts +8 -6
  298. package/dist/development/react/index.mjs +2 -2
  299. package/dist/development/react/predictive-search.d.mts +68 -7
  300. package/dist/development/react/predictive-search.mjs +54 -12
  301. package/dist/development/react/predictive-search.mjs.map +1 -1
  302. package/dist/development/react/product.d.mts +1 -0
  303. package/dist/development/react/product.mjs +5 -2
  304. package/dist/development/react/product.mjs.map +1 -1
  305. package/dist/development/react/shopify-scripts.mjs +1 -0
  306. package/dist/development/react/shopify-scripts.mjs.map +1 -1
  307. package/dist/development/vue/cart.d.mts +85 -6
  308. package/dist/development/vue/cart.mjs +95 -0
  309. package/dist/development/vue/cart.mjs.map +1 -1
  310. package/dist/development/vue/collection.d.mts +83 -5
  311. package/dist/development/vue/collection.mjs +52 -0
  312. package/dist/development/vue/collection.mjs.map +1 -1
  313. package/dist/development/vue/index.d.mts +6 -4
  314. package/dist/development/vue/predictive-search.d.mts +53 -6
  315. package/dist/development/vue/predictive-search.mjs +33 -0
  316. package/dist/development/vue/predictive-search.mjs.map +1 -1
  317. package/dist/development/vue/product.d.mts +6 -2
  318. package/dist/development/vue/product.mjs.map +1 -1
  319. package/dist/development/vue/shopify-scripts.d.mts +30 -3
  320. package/dist/development/vue/shopify-scripts.mjs +6 -0
  321. package/dist/development/vue/shopify-scripts.mjs.map +1 -1
  322. package/dist/globals.d.mts +6 -5
  323. package/dist/graphql/generated/customer-account-graphql-env.d.mts +1717 -1090
  324. package/dist/graphql/generated/graphql-env.d.mts +459 -16
  325. package/dist/graphql/generated/storefront-api-types.d.mts +1 -1
  326. package/dist/graphql/graphql.d.mts +33 -0
  327. package/dist/graphql/graphql.mjs +30 -0
  328. package/dist/graphql/graphql.mjs.map +1 -1
  329. package/dist/graphql/type-resolver.d.mts +14 -0
  330. package/dist/react/cart.d.mts +85 -14
  331. package/dist/react/cart.mjs +123 -3
  332. package/dist/react/cart.mjs.map +1 -1
  333. package/dist/react/collection.d.mts +2 -4
  334. package/dist/react/collection.mjs.map +1 -1
  335. package/dist/react/index.d.mts +8 -6
  336. package/dist/react/index.mjs +2 -2
  337. package/dist/react/predictive-search.d.mts +68 -7
  338. package/dist/react/predictive-search.mjs +54 -12
  339. package/dist/react/predictive-search.mjs.map +1 -1
  340. package/dist/react/product.d.mts +1 -0
  341. package/dist/react/product.mjs +5 -2
  342. package/dist/react/product.mjs.map +1 -1
  343. package/dist/react/shopify-scripts.mjs +1 -0
  344. package/dist/react/shopify-scripts.mjs.map +1 -1
  345. package/dist/storefront-api-types.d.ts +165 -20
  346. package/dist/storefront.schema.json +782 -57
  347. package/dist/vite/customer-account.mjs +191 -0
  348. package/dist/vite/customer-account.mjs.map +1 -0
  349. package/dist/vite/local-https.mjs +13 -29
  350. package/dist/vite/local-https.mjs.map +1 -1
  351. package/dist/vite/mkcert.mjs +1 -1
  352. package/dist/vue/cart.d.mts +85 -6
  353. package/dist/vue/cart.mjs +95 -0
  354. package/dist/vue/cart.mjs.map +1 -1
  355. package/dist/vue/collection.d.mts +83 -5
  356. package/dist/vue/collection.mjs +52 -0
  357. package/dist/vue/collection.mjs.map +1 -1
  358. package/dist/vue/index.d.mts +6 -4
  359. package/dist/vue/predictive-search.d.mts +53 -6
  360. package/dist/vue/predictive-search.mjs +33 -0
  361. package/dist/vue/predictive-search.mjs.map +1 -1
  362. package/dist/vue/product.d.mts +6 -2
  363. package/dist/vue/product.mjs.map +1 -1
  364. package/dist/vue/shopify-scripts.d.mts +31 -4
  365. package/dist/vue/shopify-scripts.mjs +6 -0
  366. package/dist/vue/shopify-scripts.mjs.map +1 -1
  367. package/package.json +5 -2
  368. package/skills/hydrogen-analytics/SKILL.md +6 -5
  369. package/skills/hydrogen-analytics/references/custom-consent.md +52 -0
  370. package/skills/hydrogen-analytics/references/react.md +1 -1
  371. package/skills/hydrogen-cart-metafields/SKILL.md +133 -0
  372. package/skills/hydrogen-cart-ui/SKILL.md +2 -1
  373. package/skills/hydrogen-customer-account/SKILL.md +3 -3
  374. package/skills/hydrogen-local-https/SKILL.md +22 -7
  375. package/skills/hydrogen-request-handlers/SKILL.md +5 -5
  376. package/skills/hydrogen-request-handlers/references/nuxt.md +0 -6
  377. package/skills/hydrogen-setup/references/analytics.md +24 -23
  378. package/skills/hydrogen-setup/steps/1-inspect-app.md +1 -1
  379. package/skills/hydrogen-setup/steps/11-verify.md +1 -0
  380. package/skills/hydrogen-setup/steps/2-scaffold.md +22 -1
  381. package/skills/hydrogen-smoke-test/SKILL.md +2 -2
  382. package/skills/hydrogen-storefront-client/SKILL.md +4 -2
  383. package/skills/hydrogen-variant-form/SKILL.md +10 -4
  384. package/ts-plugin/package.json +5 -0
  385. package/dist/core/shopify-scripts/consent.mjs +0 -17
  386. package/dist/core/shopify-scripts/consent.mjs.map +0 -1
  387. package/dist/core/shopify-scripts/deprecated-cookies.mjs +0 -97
  388. package/dist/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
  389. package/dist/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
  390. package/dist/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
  391. package/dist/core/shopify-scripts/utils/tracking-values.mjs +0 -65
  392. package/dist/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
  393. package/dist/core/shopify-scripts/utils/uuid.mjs +0 -17
  394. package/dist/core/shopify-scripts/utils/uuid.mjs.map +0 -1
  395. package/dist/development/core/shopify-scripts/consent.mjs +0 -17
  396. package/dist/development/core/shopify-scripts/consent.mjs.map +0 -1
  397. package/dist/development/core/shopify-scripts/deprecated-cookies.mjs +0 -97
  398. package/dist/development/core/shopify-scripts/deprecated-cookies.mjs.map +0 -1
  399. package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs +0 -43
  400. package/dist/development/core/shopify-scripts/utils/cookie-domain.mjs.map +0 -1
  401. package/dist/development/core/shopify-scripts/utils/tracking-values.mjs +0 -65
  402. package/dist/development/core/shopify-scripts/utils/tracking-values.mjs.map +0 -1
  403. package/dist/development/core/shopify-scripts/utils/uuid.mjs +0 -17
  404. package/dist/development/core/shopify-scripts/utils/uuid.mjs.map +0 -1
@@ -1,5 +1,5 @@
1
1
  import { ShopifyRoutesOptions, ShopifyScriptTagsOptions, ShopifyScriptsAnalyticsConfig, ShopifyScriptsI18n, ShopifyScriptsShop } from "../core/shopify-scripts/types.mjs";
2
- import { ConsentConfig } from "../core/analytics/types.mjs";
2
+ import { ConsentConfig, ConsentSetup } from "../core/analytics/types.mjs";
3
3
  import * as _$vue from "vue";
4
4
 
5
5
  //#region src/vue/shopify-scripts.d.ts
@@ -26,7 +26,16 @@ declare const ShopifyScripts: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
26
26
  default: undefined;
27
27
  };
28
28
  consent: {
29
- type: (new (...args: any[]) => ConsentConfig) | (() => ConsentConfig | undefined);
29
+ type: (new (...args: any[]) => {
30
+ mode?: "no-banner";
31
+ setup?: never;
32
+ } | {
33
+ mode: "default-banner";
34
+ setup?: never;
35
+ } | {
36
+ mode: "custom-banner";
37
+ setup: ConsentSetup;
38
+ }) | (() => ConsentConfig | undefined);
30
39
  default: undefined;
31
40
  };
32
41
  analytics: {
@@ -49,6 +58,10 @@ declare const ShopifyScripts: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
49
58
  type: () => boolean | undefined;
50
59
  default: undefined;
51
60
  };
61
+ account: {
62
+ type: () => boolean | undefined;
63
+ default: undefined;
64
+ };
52
65
  shopifyAnalytics: {
53
66
  type: BooleanConstructor;
54
67
  default: undefined;
@@ -77,7 +90,16 @@ declare const ShopifyScripts: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
77
90
  default: undefined;
78
91
  };
79
92
  consent: {
80
- type: (new (...args: any[]) => ConsentConfig) | (() => ConsentConfig | undefined);
93
+ type: (new (...args: any[]) => {
94
+ mode?: "no-banner";
95
+ setup?: never;
96
+ } | {
97
+ mode: "default-banner";
98
+ setup?: never;
99
+ } | {
100
+ mode: "custom-banner";
101
+ setup: ConsentSetup;
102
+ }) | (() => ConsentConfig | undefined);
81
103
  default: undefined;
82
104
  };
83
105
  analytics: {
@@ -100,6 +122,10 @@ declare const ShopifyScripts: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
100
122
  type: () => boolean | undefined;
101
123
  default: undefined;
102
124
  };
125
+ account: {
126
+ type: () => boolean | undefined;
127
+ default: undefined;
128
+ };
103
129
  shopifyAnalytics: {
104
130
  type: BooleanConstructor;
105
131
  default: undefined;
@@ -109,6 +135,8 @@ declare const ShopifyScripts: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
109
135
  required: true;
110
136
  };
111
137
  }>> & Readonly<{}>, {
138
+ nonce: string;
139
+ account: boolean | undefined;
112
140
  analytics: ShopifyScriptsAnalyticsConfig | undefined;
113
141
  consent: ConsentConfig | undefined;
114
142
  debug: {
@@ -116,7 +144,6 @@ declare const ShopifyScripts: _$vue.DefineComponent<_$vue.ExtractPropTypes<{
116
144
  } | undefined;
117
145
  i18n: ShopifyScriptsI18n;
118
146
  inbox: boolean | undefined;
119
- nonce: string;
120
147
  shopifyAnalytics: boolean;
121
148
  navigate: any;
122
149
  routes: any;
@@ -11,6 +11,7 @@ const consentProp = Object;
11
11
  const analyticsProp = Object;
12
12
  const debugProp = Object;
13
13
  const inboxProp = Boolean;
14
+ const accountProp = Boolean;
14
15
  const ShopifyScripts = defineComponent({
15
16
  name: "ShopifyScripts",
16
17
  props: {
@@ -50,6 +51,10 @@ const ShopifyScripts = defineComponent({
50
51
  type: inboxProp,
51
52
  default: void 0
52
53
  },
54
+ account: {
55
+ type: accountProp,
56
+ default: void 0
57
+ },
53
58
  shopifyAnalytics: {
54
59
  type: Boolean,
55
60
  default: void 0
@@ -62,6 +67,7 @@ const ShopifyScripts = defineComponent({
62
67
  setup(props) {
63
68
  onMounted(() => {
64
69
  initializeShopifyScripts({
70
+ consent: props.consent,
65
71
  navigate: props.navigate,
66
72
  routes: props.routes,
67
73
  webMcp: props.webMcp !== false
@@ -1 +1 @@
1
- {"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../src/vue/shopify-scripts.ts"],"sourcesContent":["import { defineComponent, h, onMounted, type PropType } from \"vue\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyScriptsShop,\n type ShopifyScriptsI18n,\n type ShopifyScriptTagsOptions,\n type ShopifyRoutesOptions,\n} from \"../core/shopify-scripts/index\";\n\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ntype CompletePropOptions<T> = {\n [K in keyof T]-?: unknown;\n};\n\nconst i18nProp: PropType<ShopifyScriptsI18n> = Object;\nconst navigateProp: PropType<NonNullable<ShopifyRoutesOptions[\"navigate\"]>> | null = null;\nconst routesProp: PropType<NonNullable<ShopifyRoutesOptions[\"routes\"]>> | null = null;\nconst shopProp: PropType<ShopifyScriptsShop> = Object;\nconst consentProp: PropType<ShopifyScriptTagsOptions[\"consent\"]> = Object;\nconst analyticsProp: PropType<ShopifyScriptTagsOptions[\"analytics\"]> = Object;\nconst debugProp: PropType<ShopifyScriptTagsOptions[\"debug\"]> = Object;\nconst inboxProp: PropType<ShopifyScriptTagsOptions[\"inbox\"]> = Boolean;\n\nexport const ShopifyScripts = defineComponent({\n name: \"ShopifyScripts\",\n props: {\n i18n: {\n type: i18nProp,\n default: undefined,\n },\n nonce: {\n type: String,\n default: undefined,\n },\n routes: {\n type: routesProp,\n default: undefined,\n },\n navigate: {\n type: navigateProp,\n default: undefined,\n },\n consent: {\n type: consentProp,\n default: undefined,\n },\n analytics: {\n type: analyticsProp,\n default: undefined,\n },\n debug: {\n type: debugProp,\n default: undefined,\n },\n webMcp: {\n type: Boolean,\n default: undefined,\n },\n inbox: {\n type: inboxProp,\n default: undefined,\n },\n shopifyAnalytics: {\n type: Boolean,\n default: undefined,\n },\n shop: {\n type: shopProp,\n required: true as const,\n },\n } satisfies CompletePropOptions<ShopifyScriptsProps>,\n setup(props) {\n onMounted(() => {\n void initializeShopifyScripts({\n navigate: props.navigate,\n routes: props.routes,\n webMcp: props.webMcp !== false,\n });\n });\n\n return () =>\n getShopifyScriptTags(props).tags.map(({ tagName, attributes, innerHTML }) =>\n h(tagName, {\n ...attributes,\n ...(innerHTML ? { innerHTML } : {}),\n }),\n );\n },\n});\n"],"mappings":";;;;;AAqBA,MAAM,WAAyC;AAC/C,MAAM,eAA+E;AACrF,MAAM,aAA2E;AACjF,MAAM,WAAyC;AAC/C,MAAM,cAA6D;AACnE,MAAM,gBAAiE;AACvE,MAAM,YAAyD;AAC/D,MAAM,YAAyD;AAE/D,MAAa,iBAAiB,gBAAgB;CAC5C,MAAM;CACN,OAAO;EACL,MAAM;GACJ,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,UAAU;GACR,MAAM;GACN,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,WAAW;GACT,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,kBAAkB;GAChB,MAAM;GACN,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,UAAU;GACX;EACF;CACD,MAAM,OAAO;AACX,kBAAgB;AACd,GAAK,yBAAyB;IAC5B,UAAU,MAAM;IAChB,QAAQ,MAAM;IACd,QAAQ,MAAM,WAAW;IAC1B,CAAC;IACF;AAEF,eACE,qBAAqB,MAAM,CAAC,KAAK,KAAK,EAAE,SAAS,YAAY,gBAC3D,EAAE,SAAS;GACT,GAAG;GACH,GAAI,YAAY,EAAE,WAAW,GAAG,EAAE;GACnC,CAAC,CACH;;CAEN,CAAC"}
1
+ {"version":3,"file":"shopify-scripts.mjs","names":[],"sources":["../../src/vue/shopify-scripts.ts"],"sourcesContent":["import { defineComponent, h, onMounted, type PropType } from \"vue\";\n\nimport {\n getShopifyScriptTags,\n initializeShopifyScripts,\n type ShopifyScriptsShop,\n type ShopifyScriptsI18n,\n type ShopifyScriptTagsOptions,\n type ShopifyRoutesOptions,\n} from \"../core/shopify-scripts/index\";\n\nexport type ShopifyScriptsProps = ShopifyScriptTagsOptions & {\n navigate?: ShopifyRoutesOptions[\"navigate\"];\n routes?: ShopifyRoutesOptions[\"routes\"];\n webMcp?: boolean;\n};\n\ntype CompletePropOptions<T> = {\n [K in keyof T]-?: unknown;\n};\n\nconst i18nProp: PropType<ShopifyScriptsI18n> = Object;\nconst navigateProp: PropType<NonNullable<ShopifyRoutesOptions[\"navigate\"]>> | null = null;\nconst routesProp: PropType<NonNullable<ShopifyRoutesOptions[\"routes\"]>> | null = null;\nconst shopProp: PropType<ShopifyScriptsShop> = Object;\nconst consentProp: PropType<ShopifyScriptTagsOptions[\"consent\"]> = Object;\nconst analyticsProp: PropType<ShopifyScriptTagsOptions[\"analytics\"]> = Object;\nconst debugProp: PropType<ShopifyScriptTagsOptions[\"debug\"]> = Object;\nconst inboxProp: PropType<ShopifyScriptTagsOptions[\"inbox\"]> = Boolean;\nconst accountProp: PropType<ShopifyScriptTagsOptions[\"account\"]> = Boolean;\n\nexport const ShopifyScripts = defineComponent({\n name: \"ShopifyScripts\",\n props: {\n i18n: {\n type: i18nProp,\n default: undefined,\n },\n nonce: {\n type: String,\n default: undefined,\n },\n routes: {\n type: routesProp,\n default: undefined,\n },\n navigate: {\n type: navigateProp,\n default: undefined,\n },\n consent: {\n type: consentProp,\n default: undefined,\n },\n analytics: {\n type: analyticsProp,\n default: undefined,\n },\n debug: {\n type: debugProp,\n default: undefined,\n },\n webMcp: {\n type: Boolean,\n default: undefined,\n },\n inbox: {\n type: inboxProp,\n default: undefined,\n },\n account: {\n type: accountProp,\n default: undefined,\n },\n shopifyAnalytics: {\n type: Boolean,\n default: undefined,\n },\n shop: {\n type: shopProp,\n required: true as const,\n },\n } satisfies CompletePropOptions<ShopifyScriptsProps>,\n setup(props) {\n onMounted(() => {\n void initializeShopifyScripts({\n consent: props.consent,\n navigate: props.navigate,\n routes: props.routes,\n webMcp: props.webMcp !== false,\n });\n });\n\n return () =>\n getShopifyScriptTags(props).tags.map(({ tagName, attributes, innerHTML }) =>\n h(tagName, {\n ...attributes,\n ...(innerHTML ? { innerHTML } : {}),\n }),\n );\n },\n});\n"],"mappings":";;;;;AAqBA,MAAM,WAAyC;AAC/C,MAAM,eAA+E;AACrF,MAAM,aAA2E;AACjF,MAAM,WAAyC;AAC/C,MAAM,cAA6D;AACnE,MAAM,gBAAiE;AACvE,MAAM,YAAyD;AAC/D,MAAM,YAAyD;AAC/D,MAAM,cAA6D;AAEnE,MAAa,iBAAiB,gBAAgB;CAC5C,MAAM;CACN,OAAO;EACL,MAAM;GACJ,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,UAAU;GACR,MAAM;GACN,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,WAAW;GACT,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,QAAQ;GACN,MAAM;GACN,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,SAAS;GACV;EACD,kBAAkB;GAChB,MAAM;GACN,SAAS;GACV;EACD,MAAM;GACJ,MAAM;GACN,UAAU;GACX;EACF;CACD,MAAM,OAAO;AACX,kBAAgB;AACd,GAAK,yBAAyB;IAC5B,SAAS,MAAM;IACf,UAAU,MAAM;IAChB,QAAQ,MAAM;IACd,QAAQ,MAAM,WAAW;IAC1B,CAAC;IACF;AAEF,eACE,qBAAqB,MAAM,CAAC,KAAK,KAAK,EAAE,SAAS,YAAY,gBAC3D,EAAE,SAAS;GACT,GAAG;GACH,GAAI,YAAY,EAAE,WAAW,GAAG,EAAE;GACnC,CAAC,CACH;;CAEN,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shopify/hydrogen",
3
- "version": "2026.10.0-preview.2",
3
+ "version": "2026.10.0-preview.4",
4
4
  "description": "Framework-agnostic Shopify storefront SDK and agent skills.",
5
5
  "license": "MIT",
6
6
  "author": "Shopify",
@@ -16,7 +16,8 @@
16
16
  "README.md",
17
17
  "bin",
18
18
  "dist",
19
- "skills"
19
+ "skills",
20
+ "ts-plugin"
20
21
  ],
21
22
  "type": "module",
22
23
  "sideEffects": false,
@@ -82,6 +83,7 @@
82
83
  "access": "public"
83
84
  },
84
85
  "dependencies": {
86
+ "cross-spawn": "7.0.6",
85
87
  "gql.tada": "1.9.2"
86
88
  },
87
89
  "devDependencies": {
@@ -89,6 +91,7 @@
89
91
  "@graphql-codegen/introspection": "^6.0.0",
90
92
  "@graphql-codegen/typescript": "^6.0.0",
91
93
  "@testing-library/react": "^16.3.2",
94
+ "@types/cross-spawn": "6.0.6",
92
95
  "@types/react": "^19.2.14",
93
96
  "@types/react-dom": "^19.2.3",
94
97
  "@vue/test-utils": "^2.4.0",
@@ -9,17 +9,17 @@ description: >
9
9
 
10
10
  # Storefront Analytics
11
11
 
12
- Hydrogen's analytics bus owns the event API, event normalization, and consent-gated destination replay. ShopifyScripts owns Shopify consent setup, analytics CDN loading, and deprecated cookie compatibility. App code owns when to publish route/view events and when to call cart delta tracking.
12
+ Hydrogen's analytics bus owns the event API, event normalization, and consent-gated destination replay. ShopifyScripts owns Shopify consent setup and analytics CDN loading. App code owns when to publish route/view events and when to call cart delta tracking.
13
13
 
14
14
  ## Framework References
15
15
 
16
16
  Before wiring route events, check whether this skill has a reference file for the app's framework in `references/`. If one exists, read it and use that framework's route-change and lifecycle primitives. If there is no matching reference, keep the core singleton below and adapt page-view, product-view, collection-view, search-view, and cart tracking to the app's own route lifecycle.
17
17
 
18
- Prerequisite: analytics depends on the same-origin SFAPI proxy (see `hydrogen-request-handlers`) so the browser can observe tracking values from Storefront API responses. Without it, analytics falls back to deprecated JavaScript-visible cookies and session continuity into checkout breaks — treat it as incomplete until the proxy is wired. Key consent setup: Shopify Customer Privacy controls destination delivery in production. Raw subscribers can observe events before consent; destinations receive only consent-allowed replay. Do not bypass Customer Privacy consent gating in production.
18
+ Prerequisite: analytics depends on the same-origin SFAPI proxy (see `hydrogen-request-handlers`) so the browser can observe tracking values from Storefront API responses. The proxy establishes the backend session cookies needed for analytics and checkout continuity; treat analytics as incomplete until it is wired. Key consent setup: Shopify Customer Privacy controls destination delivery in production. Register event consumers with `addDestination()`; live delivery and replay require analytics consent. Do not bypass Customer Privacy consent gating in production.
19
19
 
20
20
  ## Core Pattern
21
21
 
22
- Render Shopify runtime scripts once from the app root/document head with the same resolved market used by Storefront API requests. Use `ShopifyScripts` from your framework binding if it exports one, or `getShopifyScriptTags()` / `renderShopifyScriptTags()` from core in other framework heads. Pass the `shop` object matching the `ShopifyScriptsShop` type, and `i18n` matching the `ShopifyScriptsI18n` type and serialize them into ShopifyScripts. — declare them as constants with type annotations to keep errors located close to the source of writing. The analytics bus is created by default; pass `analytics` only for optional bus configuration such as `customData`. Do not pass market `country` or `language` through analytics consent config.
22
+ Render Shopify runtime scripts once from the app root/document head with the same resolved market used by Storefront API requests. Use `ShopifyScripts` from your framework binding if it exports one, or `getShopifyScriptTags()` / `renderShopifyScriptTags()` from core in other framework heads. Pass the `shop` object matching the `ShopifyScriptsShop` type, and `i18n` matching the `ShopifyScriptsI18n` type and serialize them into ShopifyScripts. — declare them as constants with type annotations to keep errors located close to the source of writing. The analytics bus is created by default; pass `analytics` only for optional bus configuration such as `customData`. `customData` isn't added to events you publish; destinations read it via `getConfig().customData`. Do not pass market `country` or `language` through analytics consent config.
23
23
 
24
24
  Read one browser-lazy singleton from the Shopify global created by `ShopifyScripts`:
25
25
 
@@ -66,7 +66,8 @@ Required product analytics fields include Shopify Product GID, ProductVariant GI
66
66
 
67
67
  - Let Shopify Customer Privacy control destination delivery in production.
68
68
  - Initialize one single bus per page lifetime through ShopifyScripts.
69
- - Raw subscribers can observe events before consent; destinations receive only consent-allowed replay.
69
+ - Register event consumers with `addDestination()` and use the `subscribe` function provided to its setup callback.
70
+ - For third-party banners, use `consent: {mode: "custom-banner", setup}` and read [Custom consent providers](references/custom-consent.md) for readiness, synchronization, and framework integration.
70
71
  - Render ShopifyScripts before route components publish events.
71
72
  - Do not dispatch server-side analytics through this browser bus.
72
73
  - Keep ShopifyScripts i18n aligned with the resolved market when the storefront uses markets.
@@ -75,6 +76,6 @@ Required product analytics fields include Shopify Product GID, ProductVariant GI
75
76
 
76
77
  - Page view fires on initial load and client navigations.
77
78
  - Product, collection, search, and cart view events fire once per relevant route data change.
78
- - Confirmed cart data changes flow through `trackCartAnalytics(cartStore)` (React/Vue bindings: `useCartAnalytics()`), and the cart query includes `updatedAt`.
79
+ - Confirmed cart data changes flow through `trackCartAnalytics(cartStore)` (React/Vue bindings: `useCartAnalytics()`).
79
80
  - Consent-denied visitors do not deliver destination events.
80
81
  - No browser module reads private or server-only env variables.
@@ -0,0 +1,52 @@
1
+ # Custom Consent Providers
2
+
3
+ Read this when connecting a third-party banner or app-owned consent UI through `consent: {mode: "custom-banner", setup}`. Other consent modes do not accept `setup`.
4
+
5
+ ## Setup Contract
6
+
7
+ Hydrogen calls `setup()` without arguments in the browser after Shopify's Customer Privacy API loads. Use `window.Shopify.customerPrivacy` directly. Destination events stay buffered from the inline bootstrap through hydration until the setup promise resolves, even if Shopify's regional defaults allow tracking.
8
+
9
+ The promise is the readiness signal. Subscribe to provider changes before reading saved consent, wait for a resolved provider choice or shopper interaction, and await `customerPrivacy.setTrackingConsent(choice)` before resolving setup. Registering a listener or showing a banner alone does not complete setup.
10
+
11
+ Map the provider's choice explicitly to all four boolean purposes in `ConsentPreferences`: `analytics`, `marketing`, `preferences`, and `sale_of_data`. Keep unknown provider state pending rather than submitting it as a denial. The provider owns its regional policy.
12
+
13
+ Once setup resolves, Hydrogen checks Shopify consent and replays allowed events or clears the buffer when denied. Later updates use the existing CTA consent events. This gate covers Hydrogen analytics destinations; independently loaded trackers need their own consent handling.
14
+
15
+ ## Integration Pattern
16
+
17
+ The provider methods, category mapping, and error handler below are app-owned adapters, not Hydrogen or provider SDK APIs. `whenConsentResolved()` waits for saved consent or interaction; `getResolvedConsent()` reads the latest choice. The subscription continues synchronizing later acceptance, rejection, and preference changes.
18
+
19
+ ```tsx
20
+ <ShopifyScripts
21
+ shop={shop}
22
+ consent={{
23
+ mode: "custom-banner",
24
+ async setup() {
25
+ const customerPrivacy = window.Shopify!.customerPrivacy!;
26
+ const sync = async (choice) => {
27
+ await customerPrivacy.setTrackingConsent(mapProviderConsent(choice));
28
+ };
29
+ const unsubscribe = provider.subscribe((choice) => {
30
+ void sync(choice).catch(reportConsentError);
31
+ });
32
+ try {
33
+ await provider.whenConsentResolved();
34
+ await sync(provider.getResolvedConsent());
35
+ } catch (error) {
36
+ unsubscribe();
37
+ throw error;
38
+ }
39
+ },
40
+ }}
41
+ />
42
+ ```
43
+
44
+ Keep the callback in client code and access browser-only provider globals inside setup. In Next.js App Router, define setup in an app-owned client component that renders `ShopifyScripts`; pass serializable `shop` and `i18n` data from the server. A function prop cannot cross the server-to-client boundary. Hydrogen serializes only the consent mode into HTML and does not expose setup to analytics destinations.
45
+
46
+ For frameworks using core script helpers, pass the same consent configuration to `getShopifyScriptTags()` / `renderShopifyScriptTags()` and `initializeShopifyScripts({consent})` during browser startup.
47
+
48
+ ## Lifecycle and Failures
49
+
50
+ Setup runs once per analytics bus and resolves without a return value. The first callback remains active across component unmounts, remounts, and React Strict Mode. Hydrogen owns the bus for the page's lifetime; component unmounts do not tear down the consent integration.
51
+
52
+ If setup fails, Hydrogen logs the failure and leaves destination delivery blocked for that bus. Hydrogen does not automatically rerun setup. If the provider needs retries, perform them inside setup while its promise remains pending. Handle consent write failures in the app, including later updates, and release any subscriptions created by setup if it fails.
@@ -147,7 +147,7 @@ if (term) {
147
147
 
148
148
  ## Cart Updates
149
149
 
150
- Use the `useCartAnalytics()` hook from the React binding — rendered inside `CartProvider`, it calls `trackCartAnalytics(store)` in an effect and cleans up on unmount. Outside the binding, call `trackCartAnalytics(store)` once with the cart store from a client-only effect (`useEffect`) — never at cart-store creation time, since it throws when `window.Shopify.analytics` is missing during SSR. It subscribes itself, skips pending/revalidating/note updates, derives cart delta events from confirmed cart changes, returns an unsubscribe function, and uses the global analytics bus created by ShopifyScripts. Do not manually publish `product_added_to_cart`.
150
+ Use the `useCartAnalytics()` hook from the React binding — rendered inside the `CartProvider` returned by `createCartComponents()` (the React entry exports no standalone `CartProvider`), it calls `trackCartAnalytics(store)` in an effect and cleans up on unmount. Outside the binding, call `trackCartAnalytics(store)` once with the cart store from a client-only effect (`useEffect`) — never at cart-store creation time, since it throws when `window.Shopify.analytics` is missing during SSR. It subscribes itself, skips pending/revalidating/note updates, derives cart delta events from confirmed cart changes, returns an unsubscribe function, and uses the global analytics bus created by ShopifyScripts. Do not manually publish `product_added_to_cart`.
151
151
 
152
152
  Publish `CART_VIEWED` when the cart page or drawer is viewed. The cart payload is `AnalyticsCart | null`: when a compatible cart is available, include `id`, `updatedAt`, and connection-shaped `lines`; otherwise pass `cart: null` instead of a partial cart.
153
153
 
@@ -0,0 +1,133 @@
1
+ ---
2
+ name: hydrogen-cart-metafields
3
+ description: >
4
+ Behavioral guide for reading and writing cart metafields with @shopify/hydrogen.
5
+ Use when adding, modifying, or reviewing cart metafield reads or writes, an
6
+ app-owned cart metafield route, cart-to-order metafield copying, or custom cart
7
+ data such as delivery instructions or gift preferences. Framework agnostic.
8
+ ---
9
+
10
+ # Cart Metafields
11
+
12
+ Cart metafields store custom data on the cart (for example delivery instructions or a gift preference). They are **server-only**: the Storefront cart ajax API (Standard Actions) does not support them, so they never flow through Hydrogen's optimistic cart store the way line, discount, note, and attribute mutations do.
13
+
14
+ Because of that, **do not add metafields to the core cart action/intent model.** Compose three pieces Hydrogen already provides:
15
+
16
+ 1. **Read** — a custom `CartFragment` on `createCartServerHandlers`, so metafields appear in cart state.
17
+ 2. **Write** — an app-owned route (`createShopifyRouteHandler`) calling `cartMetafieldsSet` / `cartMetafieldDelete`.
18
+ 3. **Re-sync** — `useCartActions().refresh()` after a successful write.
19
+
20
+ See `hydrogen-cart-ui` for cart state, `refresh()`, and form patterns, and `hydrogen-request-handlers` for registering the custom route.
21
+
22
+ ## Golden rule: decouple the write from the read
23
+
24
+ The mutation route returns **only the mutation's `userErrors`** — it must never refetch and return the cart. Re-syncing is a separate `refresh()` call.
25
+
26
+ ```
27
+ POST /api/cart/metafields → cartMetafieldsSet → { userErrors } (truthful: success is success)
28
+ then, on success:
29
+ refresh() → re-reads the cart via CartFragment
30
+ ```
31
+
32
+ If the write and a refetch were coupled in one request, a blip on the read would mask a successful save as a "network error." Decoupled, a failed re-sync surfaces as `state.errors.network` (a soft refresh error) and preserves the saved cart — it is never reported as a failed save.
33
+
34
+ ## Reading metafields
35
+
36
+ Select the metafields you need in a `CartFragment` named `CartFragment`:
37
+
38
+ ```ts
39
+ const CART_FRAGMENT = gql(`
40
+ fragment CartFragment on Cart {
41
+ metafields(identifiers: [{ namespace: "custom", key: "delivery_instructions" }]) {
42
+ namespace
43
+ key
44
+ type
45
+ value
46
+ }
47
+ }
48
+ `);
49
+
50
+ export const cartHandlers = createCartServerHandlers({ fragment: CART_FRAGMENT });
51
+ ```
52
+
53
+ They then appear in cart state. Read a **primitive** in the selector so referential equality can skip re-renders:
54
+
55
+ ```ts
56
+ const instructions = useCart(
57
+ (state) =>
58
+ state.data.metafields?.find(
59
+ (m) => m?.namespace === "custom" && m.key === "delivery_instructions",
60
+ )?.value ?? "",
61
+ );
62
+ ```
63
+
64
+ ## Writing metafields (app-owned route)
65
+
66
+ Register a custom `POST` route with `createShopifyRouteHandler` in `handleShopifyRoutes`. Do **not** extend `/api/cart` or open up its intents.
67
+
68
+ ```ts
69
+ async function handleCartMetafieldsPost({ request, storefrontClient }) {
70
+ const body = parseRequest(await request.json()); // validate at the boundary
71
+ const cartId = getCartId(request); // read the owner from the cookie, never the client body
72
+
73
+ // Mutate an existing cart only; never create one here. Two concurrent empty-cart
74
+ // writes would each call cartCreate and race — the last Set-Cookie wins,
75
+ // orphaning one cart and dropping its write.
76
+ if (!cartId) {
77
+ return { type: "error", error: { code: "missing_cart", message: "No cart exists." } };
78
+ }
79
+
80
+ if (body.intent === "delete") {
81
+ const result = await storefrontClient.graphql(CART_METAFIELD_DELETE_MUTATION, {
82
+ variables: { input: { ownerId: cartId, key: body.key } },
83
+ });
84
+ return { type: "json", data: { userErrors: result.data?.cartMetafieldDelete?.userErrors ?? [] } };
85
+ }
86
+
87
+ const result = await storefrontClient.graphql(CART_METAFIELDS_SET_MUTATION, {
88
+ variables: { metafields: body.metafields.map((m) => ({ ...m, ownerId: cartId })) },
89
+ });
90
+ return { type: "json", data: { userErrors: result.data?.cartMetafieldsSet?.userErrors ?? [] } };
91
+ }
92
+ ```
93
+
94
+ - **Existing cart** → `cartMetafieldsSet` with `ownerId` = the cart id from `getCartId(request)`.
95
+ - **No cart** → reject with a `missing_cart` error. Do **not** call `cartCreate` here: two concurrent empty-cart writes would each create a cart and race on the cookie, orphaning one and dropping its write. Require callers to add a line first.
96
+ - **Delete** → a single key via `cartMetafieldDelete`. The Storefront API deletes one metafield per call, so keep the contract single-key.
97
+
98
+ ## Re-syncing the store
99
+
100
+ On success, call `refresh()`:
101
+
102
+ ```ts
103
+ const { refresh } = useCartActions();
104
+ // after the POST resolves ok:
105
+ refresh();
106
+ ```
107
+
108
+ `refresh()` reconciles the `CartFragment` fields for the cart after a write. Never call `refresh()` after ordinary Hydrogen cart forms — their Standard Actions events already synchronize the store.
109
+
110
+ ## Copying to the order
111
+
112
+ To copy a cart metafield onto the order at checkout, the store must define the metafield with the cart-to-order copyable capability:
113
+ https://shopify.dev/docs/apps/build/metafields/use-metafield-capabilities#cart-to-order-copyable
114
+ Without the definition the cart write still succeeds, but nothing is copied to the order.
115
+
116
+ ## Rules
117
+
118
+ - **Server-only.** Never route metafields through Standard Actions or the optimistic cart store.
119
+ - **No core intents.** Do not add `metafields-set`/`metafield-delete` to the cart action model; use an app-owned route.
120
+ - **Mutation returns `userErrors` only.** Never refetch and return the cart in the same request.
121
+ - **Re-sync with `refresh()`** after success. Treat a refresh failure as a soft error (`errors.network`), not a failed save.
122
+ - **Inject `ownerId` server-side** from the cart cookie (`getCartId`). Never trust a client-supplied `ownerId`.
123
+ - **Require an existing cart.** Reject writes when no cart cookie is present; never call `cartCreate` in this route — it races on the cookie.
124
+ - **Validate the request body** at the route boundary before calling the Storefront API.
125
+ - **Delete is single-key**, matching `cartMetafieldDelete`.
126
+
127
+ ## Putting it together
128
+
129
+ Three pieces in your app, each owning one concern:
130
+
131
+ 1. **Cart handlers** — add the `CartFragment` (see *Reading metafields*) so the values load into cart state.
132
+ 2. **Metafields route** — an app-owned `POST` handler (see *Writing metafields*) that runs `cartMetafieldsSet` / `cartMetafieldDelete` and returns only `userErrors`.
133
+ 3. **Your component** — read with `useCart`, write via the route, then call `refresh()` on success (see *Re-syncing the store*).
@@ -41,7 +41,7 @@ On mutation:
41
41
 
42
42
  When overlapping mutations make response snapshots ambiguous, the store sets `state.revalidating` to `true`. It starts one authoritative refresh after those mutations settle and clears the flag when the refresh completes or fails. Until then, server-derived values such as costs remain at their last trustworthy value. A refresh failure preserves the locally reconciled cart and appears in `errors.network`.
43
43
 
44
- App-owned cart mutations outside Standard Actions do not emit the events the store normally observes. After such a mutation succeeds, call `CartStore.refresh()` directly or use the framework binding's `useCartActions().refresh()`. The refresh waits for active optimistic work, reconciles custom fragment fields from the configured cart endpoint, and loads the cart when none exists yet (e.g. one just created server-side). Do not call it after ordinary Hydrogen cart forms; their Standard Actions events already synchronize the store.
44
+ App-owned cart mutations outside Standard Actions do not emit the events the store normally observes. After such a mutation succeeds, call `CartStore.refresh()` directly or use the framework binding's `useCartActions().refresh()`. The refresh waits for active optimistic work, reconciles custom fragment fields from the configured cart endpoint, and loads the cart when none exists yet (e.g. one just created server-side). Do not call it after ordinary Hydrogen cart forms; their Standard Actions events already synchronize the store. For cart metafields specifically, see the `hydrogen-cart-metafields` skill.
45
45
 
46
46
  The store supersedes keyed mutations for the same line, discount batch, note, or complete attribute list. Relative additions remain independent so every submitted quantity reaches the server; their projections are reconciled together without disabling controls.
47
47
 
@@ -141,6 +141,7 @@ Errors survive unrelated cart work and clear when a new mutation begins for the
141
141
 
142
142
  ### Form structure
143
143
 
144
+ - **Optionally render visible line-item attributes.** Each cart line may carry `attributes` — an array of `{ key, value }` pairs set during add-to-cart. If your storefront uses line-item attributes (e.g. engraving text, gift messages, custom options), render them below the variant subtitle as secondary text. Filter out attributes whose key starts with `_` (these are internal/private, set by apps or Shopify systems). The same variant with different attributes creates separate cart lines — the optimistic layer uses attributes in line identity matching.
144
145
  - **Each line item is its own form.** This gives each line its own identity input and its own submit buttons. A single form containing multiple lines creates ambiguity about which line an action targets.
145
146
  - **Each line item form must preserve the progressive-enhancement shape.** The rendered structure will vary by framework and design system, but every line item quantity form needs the same Hydrogen contract: `register("set")`, `register("lineId", { value: line.id })`, and a real editable quantity input using `register("quantity", { value: line.quantity, interactive: true })`. Increase, decrease, and remove buttons are additional submit controls, not replacements for the set intent or the quantity input.
146
147
  - **The `set` control is a hidden submit button, not a hidden input.** `register("set")` already returns `{ type: "submit", hidden: true }`; render it on a `<button>`. Do not swap it for `<input type="hidden">` — that removes the submit button, so pressing Enter in the quantity input no longer submits the set action.
@@ -71,7 +71,7 @@ The Customer Account handlers own:
71
71
  - `GET /account/refresh`
72
72
  - `POST /account/logout`
73
73
 
74
- Do not reimplement login, authorize, refresh, or logout unless the app needs custom behavior that preserves the same safeguards: sanitized `return_to`, same-origin logout POST checks, `cache-control: no-store`, committed session cookies on the returned response, and request-context response headers.
74
+ Do not reimplement login, authorize, refresh, or logout unless the app needs custom behavior that preserves the same safeguards: `return_to` limited to the same origin, same-origin logout POST checks, `cache-control: no-store`, committed session cookies on the returned response, and request-context response headers.
75
75
 
76
76
  ## Server Rendering
77
77
 
@@ -81,7 +81,7 @@ Server-rendered account UI must keep session reads and token refresh separate:
81
81
  - Use `customerSession.getAccessToken()` before Customer Account GraphQL calls. It returns only a currently usable access token.
82
82
  - If `isLoggedIn()` is true but `getAccessToken()` returns `undefined`, redirect once to `/account/refresh?return_to=...` from a dynamic server route, then retry the account page after the refresh route commits cookies.
83
83
  - If `isLoggedIn()` is false, show login UI or redirect to `/account/login` instead of sending the user to `/account/refresh`.
84
- - Include a one-shot refresh guard in `return_to`; if the refreshed page still has no usable access token, fall back to login or an account error state.
84
+ - Include a one-shot refresh guard in the `return_to` path, such as `/account?refreshed=1`; if the refreshed page still has no usable access token, fall back to login or an account error state.
85
85
  - Server Components and layouts should only receive `ReadonlyCustomerSessionManager`, so they cannot call `getOrRefreshAccessToken()`.
86
86
 
87
87
  Wrap header/account-link UI in the framework's streaming primitive when possible so the shell can render before session state resolves.
@@ -104,4 +104,4 @@ The same `@shopify/hydrogen/ts-plugin` and `hydrogen gql check` setup from the `
104
104
 
105
105
  ## Local OAuth
106
106
 
107
- Customer Account OAuth needs a public HTTPS callback origin. For local examples, use a trusted local HTTPS hostname and register the exact `/account/authorize` callback URL in the Customer Account app configuration.
107
+ Customer Account OAuth needs a public HTTPS origin. For local development, follow the `hydrogen-local-https` skill; its Vite plugin provisions a trusted certificate and pushes the callback, JavaScript origin, and logout URLs through Shopify CLI outside CI.
@@ -30,7 +30,7 @@ import { localHttps } from "@shopify/hydrogen/vite";
30
30
  import { defineConfig } from "vite";
31
31
 
32
32
  const httpsOptions = {
33
- enabled: process.env.npm_lifecycle_event === "dev:https" || process.env.VITE_LOCAL_HTTPS === "1",
33
+ enabled: process.env.npm_lifecycle_event === "dev:https",
34
34
  };
35
35
 
36
36
  export default defineConfig({
@@ -57,7 +57,7 @@ Astro needs its own host and port in addition to the Vite plugin:
57
57
  import { LOCAL_HTTPS_DEFAULTS, localHttps } from "@shopify/hydrogen/vite";
58
58
  import { defineConfig } from "astro/config";
59
59
 
60
- const enabled = process.env.npm_lifecycle_event === "dev:https" || process.env.VITE_LOCAL_HTTPS === "1";
60
+ const enabled = process.env.npm_lifecycle_event === "dev:https";
61
61
  const httpsOptions = { enabled };
62
62
 
63
63
  export default defineConfig({
@@ -79,7 +79,7 @@ import type { NuxtConfig } from "nuxt/schema";
79
79
  type VitePlugin = NonNullable<NonNullable<NuxtConfig["vite"]>["plugins"]>[number];
80
80
 
81
81
  const httpsOptions = {
82
- enabled: process.env.npm_lifecycle_event === "dev:https" || process.env.VITE_LOCAL_HTTPS === "1",
82
+ enabled: process.env.npm_lifecycle_event === "dev:https",
83
83
  };
84
84
  const httpsPlugin = localHttps(httpsOptions);
85
85
 
@@ -100,23 +100,30 @@ import { defineConfig } from "@solidjs/start/config";
100
100
  import { localHttps } from "@shopify/hydrogen/vite";
101
101
 
102
102
  const httpsOptions = {
103
- enabled: process.env.npm_lifecycle_event === "dev:https" || process.env.VITE_LOCAL_HTTPS === "1",
103
+ enabled: process.env.npm_lifecycle_event === "dev:https",
104
104
  };
105
105
  const httpsPlugin = localHttps(httpsOptions);
106
106
  const devServer = httpsPlugin.api.getDevServerConfig();
107
107
 
108
+ // Vinxi reads its bind target from HOST/PORT when the listener starts, after
109
+ // this config has loaded. Setting them here works on every OS; a `HOST=...`
110
+ // prefix in the package script fails on Windows shells.
111
+ if (devServer) {
112
+ process.env.HOST = devServer.host;
113
+ process.env.PORT = String(devServer.port);
114
+ }
115
+
108
116
  export default defineConfig({
109
117
  server: { https: devServer?.https },
110
118
  vite: { plugins: [httpsPlugin] },
111
119
  });
112
120
  ```
113
121
 
114
- Vinxi also needs its bind target and port on startup:
115
-
116
122
  ```json
117
123
  {
118
124
  "scripts": {
119
- "dev:https": "vinxi dev --host local.tryhydrogen.dev --port 5173"
125
+ "dev": "vinxi dev",
126
+ "dev:https": "vinxi dev"
120
127
  }
121
128
  }
122
129
  ```
@@ -131,6 +138,14 @@ next dev --experimental-https --hostname local.tryhydrogen.dev --port 5173
131
138
 
132
139
  ## Shopify Admin
133
140
 
141
+ Outside CI, the `localHttps` Vite plugin uses the installed Shopify CLI to update Customer Account API settings when the server starts. Shopify CLI must include `@shopify/cli-hydrogen` 13.0.4 or later. If the project is not linked to a Hydrogen storefront, Shopify CLI starts the interactive linking flow before it pushes the callback, JavaScript origin, and logout URLs.
142
+
143
+ The plugin skips Shopify CLI in CI. It also falls back without stopping the development server when Shopify CLI is missing or outdated, linking is cancelled, or the settings cannot be pushed. In these cases, configure the values printed in the terminal manually.
144
+
145
+ Next.js does not use the Vite plugin, so configure its values manually too.
146
+
147
+ ### Manual Configuration
148
+
134
149
  In the Hydrogen or Headless sales channel, open the storefront's **Customer Account API settings** and configure:
135
150
 
136
151
  ```text
@@ -4,7 +4,7 @@ description: >
4
4
  Guide for wiring Hydrogen request handlers in server frameworks. Use when
5
5
  adding, modifying, or reviewing handleShopifyRoutes, handleShopifyRedirects,
6
6
  SFAPI proxy routes, cart, predictive search, and Customer Account server handlers,
7
- checkout redirects, cart permalinks, AJAX cart proxy routes, /admin redirects, Storefront URL redirects,
7
+ checkout redirects, cart and UCP buy permalinks, AJAX cart proxy routes, /admin redirects, Storefront URL redirects,
8
8
  requestContext response-header propagation, or framework middleware,
9
9
  not-found, and catch-all integration.
10
10
  ---
@@ -27,7 +27,7 @@ Request
27
27
  -> framework 404 page
28
28
  ```
29
29
 
30
- `handleShopifyRoutes` owns Hydrogen routes the framework should never see: SFAPI proxy URLs, the generic `/__shopify/*` API proxy, `/checkout`, cart permalinks like `/cart/{variantId}:{quantity}`, AJAX cart URLs like `/cart.js` and `/cart/add.js`, `/api/mcp`, `/graphiql` in development, Liquid-style `?variant=<numeric id>` product URLs, and app-registered handler groups such as `createCartServerHandlers()` or `createCustomerAccountServerHandlers()`.
30
+ `handleShopifyRoutes` owns Hydrogen routes the framework should never see: SFAPI proxy URLs, the generic `/__shopify/*` API proxy, `/checkout`, cart permalinks like `/cart/{variantId}:{quantity}`, UCP buy permalinks like `/buy/{itemId}:{quantity}`, AJAX cart URLs like `/cart.js` and `/cart/add.js`, `/api/mcp`, `/graphiql` in development, Liquid-style `?variant=<numeric id>` product URLs, and app-registered handler groups such as `createCartServerHandlers()` or `createCustomerAccountServerHandlers()`.
31
31
 
32
32
  ## Variant Id Redirects
33
33
 
@@ -44,7 +44,7 @@ const shopifyRoute = handleShopifyRoutes({
44
44
 
45
45
  Pass `routeTemplates` to `handleShopifyRoutes` so product `?variant=` links can be recognized before framework routing. `pathPrefix` is inferred from `requestContext.i18n.pathPrefix`, so localized product URLs stay in the localized tree.
46
46
 
47
- `handleShopifyRedirects` is a post-routing 404 check for `/admin`, configured standard route redirects, Storefront URL redirects, and same-origin query-param redirects. Do not run it on every request.
47
+ `handleShopifyRedirects` is a post-routing 404 check for `/admin`, configured standard route redirects, Storefront URL redirects, and same-origin `return_to`/`redirect` query params. Do not run it on every request.
48
48
 
49
49
  ## Standard Route Redirects
50
50
 
@@ -73,7 +73,7 @@ const redirect = await handleShopifyRedirects({
73
73
  - `handleShopifyRoutes` and `handleShopifyRedirects` apply request-context response headers before returning matched Shopify responses. Return those responses directly without calling `requestContext.applyResponseHeaders()` again.
74
74
  - Link and submit to Customer Account routes (`/account/login`, `/account/authorize`, `/account/refresh`, `/account/logout`) with plain HTML `<a>`/`<form>`, never the framework's client-side navigation component (`<Form>`/`<Link>` in React Router, `next/link` in Next.js, `NuxtLink` in Nuxt). The login and logout handlers return raw HTTP redirects to external Shopify URLs, which client-nav cannot process.
75
75
  - Apps authoring Customer Account API documents use the same packed Hydrogen TypeScript plugin as Storefront API documents. Add `@shopify/hydrogen/ts-plugin` to `tsconfig.json` `compilerOptions.plugins` and chain `hydrogen gql check` into a package script. Applies to every framework.
76
- - For custom or framework-routed responses, commit session headers once at the final response boundary, append those headers, then call `requestContext.applyResponseHeaders(response.headers)` so SFAPI cookies, `Server-Timing`, tracking fallback headers, and personalized-response cache safety survive.
76
+ - For custom or framework-routed responses, commit session headers once at the final response boundary, append those headers, then call `requestContext.applyResponseHeaders(response.headers)` so eligible SFAPI cookies and personalized-response cache safety are applied.
77
77
  - Wire this in production runtime code, not dev-only hooks. Only GraphiQL is dev-only.
78
78
 
79
79
  ## Imports
@@ -103,5 +103,5 @@ Run the app in dev and production modes, then check:
103
103
  5. `GET /admin` returns a redirect to the shop admin URL.
104
104
  6. An unknown path returns the framework 404 when no Shopify redirect exists.
105
105
  7. `GET /products/{handle}?variant={numeric id}` returns a 302 to the option-params URL when `routeTemplates` is passed to `handleShopifyRoutes`; `?variant=garbage` falls through to the product page.
106
- 8. Cart, predictive search, Customer Account, and SFAPI responses preserve `Set-Cookie` and `Server-Timing` headers where the framework exposes them.
106
+ 8. Cart, Customer Account, and consent responses preserve eligible `Set-Cookie` headers. Consent responses remain private and non-cacheable even when personal state is returned only in the body.
107
107
  9. Authenticated Customer Account responses do not preserve public or CDN cache-control headers.
@@ -109,15 +109,9 @@ export default defineNitroPlugin((nitroApp) => {
109
109
  copyHeader(event.node.res.getHeader("content-type"), (value) => {
110
110
  headers.set("content-type", value);
111
111
  });
112
- copyHeader(event.node.res.getHeader("server-timing"), (value) => {
113
- headers.append("server-timing", value);
114
- });
115
112
 
116
113
  requestContext.applyResponseHeaders(headers);
117
114
 
118
- const serverTiming = headers.get("server-timing");
119
- if (serverTiming) event.node.res.setHeader("server-timing", serverTiming);
120
-
121
115
  const setCookies = headers.getSetCookie();
122
116
  if (setCookies.length > 0) {
123
117
  event.node.res.setHeader("set-cookie", [