@thebigrick/catalyst-pluginizr is a drop-in pluginization system designed specifically for Catalyst (BigCommerce). It enables developers to enhance, override, or augment functionality at multiple integration points within a Catalyst-based application without modifying core code.
With this tool, you can:
- Extend UI Components: Wrap existing React components with additional markup, styling, or logic
- Enrich Server Actions: Intercept and modify server actions and other functions
- Integrate Third-Party Logic: Inject external business rules, analytics, or A/B testing
BETA Notice: This package is currently in beta. While stable and functional, it may undergo changes before the final release.
Just a Catalyst-based project (see Catalyst on Github).
You can install Catalyst Pluginizr either automatically using our installation script or manually following step-by-step instructions.
-
Clone the package in your
packagesfolder as submodule:cd /path-to-catalyst git submodule add https://github.com/thebigrick/catalyst-pluginizr.git packages/catalyst-pluginizr pnpm i -
Run the installation script:
cd /path-to-catalyst/packages/catalyst-pluginizr npm run setup
The script will automatically:
- Add the necessary dependencies
- Configure your workspace
- Update Next.js and Tailwind configurations
- Install all dependencies
If you prefer to install manually or need more control over the installation process, follow these steps:
-
Clone the package in your
packagefolder:cd /path-to-catalyst/packages git clone https://github.com/thebigrick/catalyst-pluginizr.git -
Add the pluginizr dependency in
core/package.jsonfile:{ "dependencies": { "@thebigrick/catalyst-pluginizr": "workspace:*" } } -
Add a new workspace for plugins in
pnpm-workspace.yamlfile in your root folder:packages: - core - packages/* - plugins/* # <-- Add this line
-
Configure Next.js to use the pluginizr in
core/next.config.ts:// Add this line at beginning import withCatalystPluginizr from '@thebigrick/catalyst-pluginizr/with-catalyst-pluginizr'; // ... (leave the content as is) nextConfig = withNextIntl(nextConfig); nextConfig = withCatalystPluginizr(nextConfig); // <-- Add this line after withNextIntl // ... (leave the content as is)
-
Configure tailwind to use pluginizr in
core/tailwind.config.js(only legacy versions of Catalyst):// Add the following line at the beginning of the file const withTailwindPluginizr = require('@thebigrick/catalyst-pluginizr/with-tailwind-pluginizr'); // ... (leave the main content as is) // Replace the default module.exports line at the end with the following: module.exports = withTailwindPluginizr(config);
-
Install dependencies:
cd /path-to-catalyst pnpm install
Catalyst Pluginizr allows you to enhance your application in three main ways:
-
Component Enhancement:
- Wrap React components with additional functionality
- Add UI elements without modifying original components
- Modify component behavior consistently across your application
-
Function Modification:
- Intercept and modify function calls
- Add pre- and post-processing logic
- Transform input and output data
-
Value Transformation:
- Transform static values and configuration
- Modify application constants
- Override default settings
-
Component Plugins (using
componentPlugin):- Wrap React components with custom logic
- Add UI elements (headers, footers, wrappers)
- Modify component props or behavior
- Access to the original component through
WrappedComponent
-
Function Plugins (using
functionPlugin):- Modify function inputs and outputs
- Add logging, analytics, or monitoring
- Transform data before or after function execution
- Access to original function and all its arguments
-
Value Plugins (using
valuePlugin):- Transform configuration values
- Modify constants and defaults
- Chain multiple transformations
- Access to original value
-
Set up the plugin structure:
cd /path-to-catalyst mkdir -p plugins/my-first-plugin cd plugins/my-first-plugin
-
Create basic configuration files:
Sample
package.json(adapt as needed):{ "name": "my-first-plugin", "version": "1.0.0", "dependencies": { "@thebigrick/catalyst-pluginizr": "workspace:*", "@bigcommerce/catalyst-core": "workspace:*", "react": "^18.3.1" }, "devDependencies": { "@types/node": "^20.17.6", "@types/react": "^18.3.12", "typescript": "^5.6.3" } }Sample
tsconfig.json(you will likely need to adjust this):{ "$schema": "https://json.schemastore.org/tsconfig", "display": "Default", "compilerOptions": { "baseUrl": "src", "composite": false, "declaration": true, "declarationMap": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "inlineSources": false, "incremental": true, "isolatedModules": true, "moduleResolution": "node", "noUnusedLocals": false, "noUnusedParameters": false, "preserveWatchOutput": true, "skipLibCheck": true, "strict": true, "tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json", "resolveJsonModule": true, "jsx": "react" }, "exclude": [ "node_modules" ] } -
Create your plugin:
Each plugin should be in a separate file within the
pluginsdirectory at your package's baseUrl. The file must export a default value created using one of the plugin helper functions.Example structure:
my-first-plugin/ ├── src/ │ └── plugins/ │ ├── header-wrapper.tsx │ ├── search-logger.ts │ └── product-card-modifier.tsExample of a component plugin (
header-wrapper.tsx):import React from "react"; import { componentPlugin } from "@thebigrick/catalyst-pluginizr"; import { Header } from "@bigcommerce/catalyst-core/components/header"; export default componentPlugin<typeof Header>({ name: "header-wrapper", resourceId: "@bigcommerce/catalyst-core/components/header:Header", wrap: ({ WrappedComponent, ...props }) => ( <div className="w-full"> <div className="font-bold text-center">Special Offer Today!</div> <WrappedComponent {...props} /> </div> ), });
Plugins execute based on their sortOrder value:
- Lower values run first (e.g., -10 before 0)
- Default value is 0
- Same values have no guaranteed order
Example of multiple plugins with order:
// First plugin (runs first)
export default componentPlugin({
name: "header-promo",
sortOrder: -10,
// ...
});
// In another file
export default componentPlugin({
name: "header-analytics",
sortOrder: 0,
// ...
});
// In yet another file
export default componentPlugin({
name: "header-customization",
sortOrder: 10,
// ...
});If you need to extend the Next.js configuration from a plugin, you can create a next.wrapper.cjs file in your plugin directory with the following example content:
// plugins/my-first-plugin/next.wrapper.cjs
const configWrapper = (nextConfig) => {
return {
...nextConfig,
// Add your custom configuration here
};
};
module.exports = configWrapper;Follow these conventions for plugin identification:
-
Plugin Names:
- Use descriptive, kebab-case names
- Include purpose in name (e.g., "header-promo-banner")
- Keep names unique within your plugin
-
Resource IDs:
- Format:
@packageName/pathor@packageName/path:exportName - Example:
- For named exports
@bigcommerce/catalyst-core/components/header:Header - For default exports
@bigcommerce/catalyst-core/components/header/cart-icon
- For named exports
- Format:
-
File Structure:
- Keep each plugin in a separate file within the
pluginsdirectory - Use consistent naming for plugin files
- Group plugins logically in subdirectories if needed
- Keep each plugin in a separate file within the
With function plugins, you can intercept and modify function calls.
Example of a search term logger (search-term-logger.ts):
import { functionPlugin } from "@thebigrick/catalyst-pluginizr";
import { getSearchResults } from "@bigcommerce/catalyst-core/components/header/_actions/get-search-results";
export default functionPlugin<typeof getSearchResults>({
name: "search-term-logger",
resourceId: "@bigcommerce/catalyst-core/components/header/_actions/get-search-results:getSearchResults",
sortOrder: -10,
wrap: (fn, searchTerm) => {
console.log(`Search term: ${searchTerm}`);
return fn(searchTerm);
},
});With component plugins, you can wrap React components with custom logic.
Example of a header analytics plugin (header-analytics.tsx):
import React from "react";
import { componentPlugin } from "@thebigrick/catalyst-pluginizr";
import { Header } from "@bigcommerce/catalyst-core/components/header";
export default componentPlugin<typeof Header>({
name: "header-analytics",
resourceId: "@bigcommerce/catalyst-core/components/header:Header",
wrap: ({ WrappedComponent, ...props }) => {
return (
<TrackedComponent>
<WrappedComponent {...props} />
</TrackedComponent>
);
},
});With value plugins, you can transform static values and configuration.
Example of adding SKU to product card fragment (product-card-sku.ts):
import { valuePlugin } from "@thebigrick/catalyst-pluginizr";
import { PricingFragment } from '@bigcommerce/catalyst-core/client/fragments/pricing';
import { ProductCardFragment } from "@bigcommerce/catalyst-core/components/product-card/fragment";
import { graphql } from '@bigcommerce/catalyst-core/client/graphql';
import { AddToCartFragment } from '@bigcommerce/catalyst-core/components/product-card/add-to-cart/fragment';
export default valuePlugin<typeof ProductCardFragment>({
name: "add-product-sku",
resourceId: "@bigcommerce/catalyst-core/components/product-card/fragment:ProductCardFragment",
wrap: (value) => {
return graphql(
`
fragment ProductCardFragment on Product {
entityId
sku # <-- Added SKU field
name
defaultImage {
altText
url: urlTemplate(lossy: true)
}
path
brand {
name
path
}
reviewSummary {
numberOfReviews
averageRating
}
...AddToCartFragment
...PricingFragment
}
`,
[AddToCartFragment, PricingFragment],
);
},
});We welcome contributions! Please follow these steps:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
For bugs or feature requests:
- Open an issue on GitHub
- Provide detailed reproduction steps
- Include relevant code examples
This project is licensed under the MIT License. See the LICENSE.md file for details.
Copyright (c) 2024 The BigRick