Releases: bigcommerce/catalyst
@bigcommerce/catalyst-makeswift@1.4.2
Patch Changes
- #2845
7f82903Thanks @chanceaclark! - Pulls in changes from the@bigcommerce/catalyst-core@1.4.2patch.
@bigcommerce/catalyst-makeswift@1.4.1
Patch Changes
- #2839
db27c0fThanks @jorgemoya! - Pulls in changes from the@bigcommerce/catalyst-core@1.4.1patch.
@bigcommerce/catalyst-core@1.4.2
Patch Changes
- #2842
aadc1e3Thanks @chanceaclark! - Addresses https://vercel.com/changelog/summary-of-cve-2026-23864 by bumping React and Next.js
@bigcommerce/catalyst-core@1.4.1
Patch Changes
-
#2827
49b1097Thanks @jorgemoya! - Filter out child cart items (items withparentEntityId) from cart and cart analytics to prevent duplicate line items when products have parent-child relationships, such as product bundles.Migration steps
Step 1: GraphQL Fragment Updates
The
parentEntityIdfield has been added to both physical and digital cart item fragments to identify child items.Update
core/app/[locale]/(default)/cart/page-data.ts:export const PhysicalItemFragment = graphql(` fragment PhysicalItemFragment on CartPhysicalItem { entityId quantity productEntityId variantEntityId + parentEntityId listPrice { currencyCode value } } `); export const DigitalItemFragment = graphql(` fragment DigitalItemFragment on CartDigitalItem { entityId quantity productEntityId variantEntityId + parentEntityId listPrice { currencyCode value } } `);Step 2: Cart Display Filtering
Cart line items are now filtered to exclude child items when displaying the cart.
Update
core/app/[locale]/(default)/cart/page.tsx:const lineItems = [ ...cart.lineItems.giftCertificates, ...cart.lineItems.physicalItems, ...cart.lineItems.digitalItems, - ]; + ].filter((item) => !('parentEntityId' in item) || !item.parentEntityId);Step 3: Analytics Data Filtering
Analytics data collection now only includes top-level items to prevent duplicate tracking.
Update
core/app/[locale]/(default)/cart/page.tsxin thegetAnalyticsDatafunction:- const lineItems = [...cart.lineItems.physicalItems, ...cart.lineItems.digitalItems]; + const lineItems = [...cart.lineItems.physicalItems, ...cart.lineItems.digitalItems].filter( + (item) => !item.parentEntityId, // Only include top-level items + );
Step 4: Styling Update
Cart subtitle text color has been updated for improved contrast.
Update
core/vibes/soul/sections/cart/client.tsx:- <span className="text-[var(--cart-subtext-text,hsl(var(--contrast-300)))] contrast-more:text-[var(--cart-subtitle-text,hsl(var(--contrast-500)))]"> + <span className="text-[var(--cart-subtext-text,hsl(var(--contrast-400)))] contrast-more:text-[var(--cart-subtitle-text,hsl(var(--contrast-500)))]"> {lineItem.subtitle} </span>
-
#2811
b57bffaThanks @chanceaclark! - Fix pagination cursor persistence when changing sort order. Thebeforeandafterquery parameters are now cleared when the sort option changes, preventing stale pagination cursors from causing incorrect results or empty pages. -
#2833
a520dbcThanks @jamesqquick! - Add placeholders for gift certificate inputs and remove redundant placeholders in the gift certificate purchase form. -
#2818
74e4dd1Thanks @jordanarldt! - Disable product filters that are no longer available based on the selection.Migration steps
Step 1
Update the
facetsTransformerfunction incore/data-transformers/facets-transformer.tsto handle disabled filters:return allFacets.map((facet) => { const refinedFacet = refinedFacets.find((f) => f.displayName === facet.displayName); + if (refinedFacet == null) { + return null; + } + if (facet.__typename === 'CategorySearchFilter') { const refinedCategorySearchFilter = - refinedFacet?.__typename === 'CategorySearchFilter' ? refinedFacet : null; + refinedFacet.__typename === 'CategorySearchFilter' ? refinedFacet : null; return { type: 'toggle-group' as const, paramName: 'categoryIn', label: facet.displayName, defaultCollapsed: facet.isCollapsedByDefault, options: facet.categories.map((category) => { const refinedCategory = refinedCategorySearchFilter?.categories.find( (c) => c.entityId === category.entityId, ); const isSelected = filters.categoryEntityIds?.includes(category.entityId) === true; + const disabled = refinedCategory == null && !isSelected; + const productCountLabel = disabled ? '' : ` (${category.productCount})`; + const label = facet.displayProductCount + ? `${category.name}${productCountLabel}` + : category.name; return { - label: facet.displayProductCount - ? `${category.name} (${category.productCount})` - : category.name, + label, value: category.entityId.toString(), - disabled: refinedCategory == null && !isSelected, + disabled, }; }), }; } if (facet.__typename === 'BrandSearchFilter') { const refinedBrandSearchFilter = - refinedFacet?.__typename === 'BrandSearchFilter' ? refinedFacet : null; + refinedFacet.__typename === 'BrandSearchFilter' ? refinedFacet : null; return { type: 'toggle-group' as const, paramName: 'brand', label: facet.displayName, defaultCollapsed: facet.isCollapsedByDefault, options: facet.brands.map((brand) => { const refinedBrand = refinedBrandSearchFilter?.brands.find( (b) => b.entityId === brand.entityId, ); const isSelected = filters.brandEntityIds?.includes(brand.entityId) === true; + const disabled = refinedBrand == null && !isSelected; + const productCountLabel = disabled ? '' : ` (${brand.productCount})`; + const label = facet.displayProductCount + ? `${brand.name}${productCountLabel}` + : brand.name; return { - label: facet.displayProductCount ? `${brand.name} (${brand.productCount})` : brand.name, + label, value: brand.entityId.toString(), - disabled: refinedBrand == null && !isSelected, + disabled, }; }), }; } if (facet.__typename === 'ProductAttributeSearchFilter') { const refinedProductAttributeSearchFilter = - refinedFacet?.__typename === 'ProductAttributeSearchFilter' ? refinedFacet : null; + refinedFacet.__typename === 'ProductAttributeSearchFilter' ? refinedFacet : null; return { type: 'toggle-group' as const, paramName: `attr_${facet.filterKey}`, label: facet.displayName, defaultCollapsed: facet.isCollapsedByDefault, options: facet.attributes.map((attribute) => { const refinedAttribute = refinedProductAttributeSearchFilter?.attributes.find( (a) => a.value === attribute.value, ); const isSelected = filters.productAttributes?.some((attr) => attr.values.includes(attribute.value)) === true; + const disabled = refinedAttribute == null && !isSelected; + const productCountLabel = disabled ? '' : ` (${attribute.productCount})`; + const label = facet.displayProductCount + ? `${attribute.value}${productCountLabel}` + : attribute.value; + return { - label: facet.displayProductCount - ? `${attribute.value} (${attribute.productCount})` - : attribute.value, + label, value: attribute.value, - disabled: refinedAttribute == null && !isSelected, + disabled, }; }), }; } if (facet.__typename === 'RatingSearchFilter') { const refinedRatingSearchFilter = - refinedFacet?.__typename === 'RatingSearchFilter' ? refinedFacet : null; + refinedFacet.__typename === 'RatingSearchFilter' ? refinedFacet : null; const isSelected = filters.rating?.minRating != null; return { type: 'rating' as const, paramName: 'minRating', label: facet.displayName, disabled: refinedRatingSearchFilter == null && !isSelected, defaultCollapsed: facet.isCollapsedByDefault, }; } if (facet.__typename === 'PriceSearchFilter') { const refinedPriceSearchFilter = - refinedFacet?.__typename === 'PriceSearchFilter' ? refinedFacet : null; + refinedFacet.__typename === 'PriceSearchFilter' ? refinedFacet : null; const isSelected = filters.price?.minPrice != null || filters.price?.maxPrice != null; return { type: 'range' as const, minParamName: 'minPrice', maxPara...
@bigcommerce/catalyst-makeswift@1.4.0
Minor Changes
- #2808
d6e2f1bThanks @matthewvolk! - This release includes all changes included in thecanary1.4.0 release (see the 1.4.0 changelog for more details: https://github.com/bigcommerce/catalyst/blob/44c682ef988030d7500275f3e4e4503a3a1af63c/core/CHANGELOG.md#140)
Patch Changes
- #2791
bd30ed3Thanks @migueloller! - Fix sort order ofadditionalProductsprop inProductsCarouselMakeswift component.
@bigcommerce/catalyst-core@1.4.0
Minor Changes
-
#2806
becb67dThanks @chanceaclark! - Upgrade c15t to 1.8.2, migrate from custom mode to offline mode, refactor consent cookie handling to use c15t's compact format, add script location support for HEAD/BODY rendering, and add privacy policy link support to CookieBanner.What Changed
- Upgraded
@c15t/nextjsto version1.8.2 - Changed consent manager mode from
custom(with endpoint handlers) toofflinemode- Removed custom
handlers.tsimplementation
- Removed custom
- Added
enabledprop toC15TConsentManagerProviderto control consent manager functionality - Removed custom consent cookie encoder/decoder implementations (
decoder.ts,encoder.ts) - Added
parse-compact-format.tsto handle c15t's compact cookie format- Compact format:
i.t:timestamp,c.necessary:1,c.functionality:1,etc...
- Compact format:
- Updated cookie parsing logic in both client and server to use the new compact format parser
- Scripts now support
locationfield from BigCommerce API and can be rendered in<head>or<body>based on thetargetproperty CookieBannernow supports theprivacyPolicyUrlfield from BigCommerce API and will be rendered in the banner description if available.
Migration Path
Consent Manager Provider Changes
The
ConsentManagerProvidernow usesofflinemode instead ofcustommode with endpoint handlers. The provider configuration has been simplified:Before:
<C15TConsentManagerProvider options={{ mode: 'custom', consentCategories: ['necessary', 'functionality', 'marketing', 'measurement'], endpointHandlers: { showConsentBanner: () => showConsentBanner(isCookieConsentEnabled), setConsent, verifyConsent, }, }} > <ClientSideOptionsProvider scripts={scripts}> {children} </ClientSideOptionsProvider> </C15TConsentManagerProvider>
After:
<C15TConsentManagerProvider options={{ mode: 'offline', storageConfig: { storageKey: CONSENT_COOKIE_NAME, crossSubdomain: true, }, consentCategories: ['necessary', 'functionality', 'marketing', 'measurement'], enabled: isCookieConsentEnabled, }} > <ClientSideOptionsProvider scripts={scripts}> {children} </ClientSideOptionsProvider> </C15TConsentManagerProvider>
Key changes:
modechanged from'custom'to'offline'- Removed
endpointHandlers- no longer needed in offline mode - Added
enabledprop to control consent manager functionality - Added
storageConfigfor cookie storage configuration
Cookie Handling
If you have custom code that directly reads or writes consent cookies, you'll need to update it:
Before:
The previous implementation used custom encoding/decoding. If you were directly accessing consent cookie values, you would have needed to use the custom decoder.After:
The consent cookie now uses c15t's compact format. The public API for reading cookies remains the same:import { getConsentCookie } from '~/lib/consent-manager/cookies/client'; // client-side // or import { getConsentCookie } from '~/lib/consent-manager/cookies/server'; // server-side const consent = getConsentCookie();
The
getConsentCookie()function now internally usesparseCompactFormat()to parse the compact format cookie string. If you were directly parsing cookie values, you should now use thegetConsentCookie()helper instead.getConsentCookienow returns a compact version of the consent values:{ i.t: 123456789, c.necessary: true, c.functionality: true, c.marketing: false, c.measurment: false }
Updated instances where
getConsentCookieis used to reflect this new schema.Removed
setConsentCookiefrom server and client since this is now handled by the c15t library.Script Location Support
Scripts now support rendering in either
<head>or<body>based on thelocationfield from the BigCommerce API:// Scripts transformer now includes target based on location target: script.location === 'HEAD' ? 'head' : 'body';
The
ScriptsFragmentGraphQL query now includes thelocationfield, allowing scripts to be placed in the appropriate DOM location.FOOTERlocation is still not supported.Privacy Policy
The
RootLayoutMetadataQueryGraphQL query now includes theprivacyPolicyUrlfield, which renders a provicy policy link in theCookieBannerdescription.<CookieBanner privacyPolicyUrl="https://example.com/privacy-policy" // ... other props />
The privacy policy link:
- Opens in a new tab (
target="_blank") - Only renders if
privacyPolicyUrlis provided as a non-empty string
Add translatable
privacyPolicyfield toComponents.ConsentManager.CookieBannertranslation namespace for the privacy policy link text. - Upgraded
-
#2806
becb67dThanks @chanceaclark! - Conditionally display product ratings in the storefront based onsite.settings.display.showProductRating. The storefront logic when this setting is enabled/disabled matches exactly the logic of Stencil + Cornerstone. -
#2806
becb67dThanks @chanceaclark! - Adds product review submission functionality to the product detail page via a modal form with validation for rating, title, review text, name, and email fields. Integrates with BigCommerce's GraphQL API using Conform and Zod for form validation and real-time feedback. -
#2806
becb67dThanks @chanceaclark! - Introduce displayName and displayKey fields to facets for improved labeling and filteringFacet filters now use the
displayNamefield for more descriptive labels in the UI, replacing the deprecatednamefield. Product attribute facets now support thefilterKeyfield for consistent parameter naming. The facet transformer has been updated to usedisplayNamewith a fallback tofilterNamewhendisplayNameis not available. -
#2806
becb67dThanks @chanceaclark! - Updated product and brand pages to include the number of reviews in the product data. Fixed visual spacing within product cards. Enhanced the Rating component to display the number of reviews alongside the rating. Introduced a new RatingLink component for smooth scrolling to reviews section on PDP. -
#2806
becb67dThanks @chanceaclark! - Make newsletter signup component on homepage render conditionally based on BigCommerce settings.What Changed
- Newsletter signup component (
Subscribe) on homepage now conditionally renders based onshowNewsletterSignupsetting from BigCommerce. - Added
showNewsletterSignupfield toHomePageQueryGraphQL query to fetch newsletter settings. - Newsletter signup now uses
Streamcomponent withStreamablepattern for progressive loading.
Migration
To make newsletter signup component render conditionally based on BigCommerce settings, update your homepage code:
1. Update GraphQL Query (
page-data.ts)Add the
newsletterfield to yourHomePageQuery:const HomePageQuery = graphql( ` query HomePageQuery($currencyCode: currencyCode) { site { // ... existing fields settings { inventory { defaultOutOfStockMessage showOutOfStockMessage showBackorderMessage } newsletter { showNewsletterSignup } } } } `, [FeaturedProductsCarouselFragment, FeaturedProductsListFragment], );
2. Update Homepage Component (
page.tsx)Import
Streamand create a streamable for newsletter settings:import { Stream, Streamable } from '@/vibes/soul/lib/streamable'; // Inside your component, create the streamable: const streamableShowNewsletterSignup = Streamable.from(async () => { const data = await streamablePageData; const { showNewsletterSignup } = data.site.settings?.newsletter ?? {}; return showNewsletterSignup; }); // Replace direct rendering with conditional Stream: <Stream fallback={null} value={streamableShowNewsletterSignup}> {(showNewsletterSignup) => showNewsletterSignup && <Subscribe />} </Stream>
Before:
<Subscribe />
After:
<Stream fallback={null} value={streamableShowNewsletterSignup}> {(showNewsletterSignu...
- Newsletter signup component (
@bigcommerce/catalyst-makeswift@1.3.8
Patch Changes
-
#2773
b475a36Thanks @chanceaclark! - Catalyst has been upgraded to Next.js 15.5.9. This is a patch version upgrade that requires migration steps for existing stores to fix a security vulnerability.🔒 Security Update
This upgrade addresses a security vulnerability (CVE-2025-55184 + CVE-2025-55183) that affects React Server Components. These vulnerabilities allow a Denial of Service attack and Source Code Exposure attach. This upgrade includes:
- Next.js 15.5.9 with the security patch
- React 19.1.4 and React DOM 19.1.4 with the security patch
All users are strongly encouraged to upgrade immediately.
Key Changes
- ⚡ Next.js 15.5.9: Upgraded from Next.js 15.5.7 to 15.5.9
- ⚛️ React 19: Upgraded to React 19.1.4 and React DOM 19.1.4
Migration Guide
Update Dependencies
If you're maintaining a custom Catalyst store, update your
package.json:{ "dependencies": { "next": "15.5.9", "react": "19.1.4", "react-dom": "19.1.4" }, "devDependencies": { "@next/bundle-analyzer": "15.5.9", "eslint-config-next": "15.5.9" } }Then run:
pnpm install
@bigcommerce/catalyst-makeswift@1.3.7
Patch Changes
-
#2764
83c5b75Thanks @chanceaclark! - # Next.js 15.5.8 UpgradeCatalyst has been upgraded to Next.js 15.5.8. This is a patch version upgrade that requires migration steps for existing stores to fix a security vulnerability.
🔒 Critical Security Update
This upgrade addresses a critical security vulnerability (CVE-2025-55184 + CVE-2025-55183) that affects React Server Components. These vulnerabilities allow a Denial of Service attack and Source Code Exposure attach. This upgrade includes:
- Next.js 15.5.8 with the security patch
- React 19.1.3 and React DOM 19.1.3 with the security patch
All users are strongly encouraged to upgrade immediately.
Key Changes
- ⚡ Next.js 15.5.8: Upgraded from Next.js 15.5.7 to 15.5.8
- ⚛️ React 19: Upgraded to React 19.1.3 and React DOM 19.1.3
Migration Guide
Update Dependencies
If you're maintaining a custom Catalyst store, update your
package.json:{ "dependencies": { "next": "15.5.8", "react": "19.1.3", "react-dom": "19.1.3" }, "devDependencies": { "@next/bundle-analyzer": "15.5.8", "eslint-config-next": "15.5.8" } }Then run:
pnpm install
@bigcommerce/catalyst-core@0.24.4
Patch Changes
- #2775
442ae1bThanks @bookernath! - Bump Next.js to 15.5.9 to address security vulnerability
@bigcommerce/catalyst-core@1.3.6
Patch Changes
-
#2762
7f3a184Thanks @chanceaclark! - # Next.js 15.5.8 UpgradeCatalyst has been upgraded to Next.js 15.5.8. This is a patch version upgrade that requires migration steps for existing stores to fix a security vulnerability.
🔒 Critical Security Update
This upgrade addresses a critical security vulnerability (CVE-2025-55184 + CVE-2025-55183) that affects React Server Components. These vulnerabilities allow a Denial of Service attack and Source Code Exposure attach. This upgrade includes:
- Next.js 15.5.8 with the security patch
- React 19.1.3 and React DOM 19.1.3 with the security patch
All users are strongly encouraged to upgrade immediately.
Key Changes
- ⚡ Next.js 15.5.8: Upgraded from Next.js 15.5.7 to 15.5.8
- ⚛️ React 19: Upgraded to React 19.1.3 and React DOM 19.1.3
Migration Guide
Update Dependencies
If you're maintaining a custom Catalyst store, update your
package.json:{ "dependencies": { "next": "15.5.8", "react": "19.1.3", "react-dom": "19.1.3" }, "devDependencies": { "@next/bundle-analyzer": "15.5.8", "eslint-config-next": "15.5.8" } }Then run:
pnpm install