Cartble’s plugin system is built around a small set of architectural rules that keep each plugin self-contained, visually consistent with the admin, and safe from cross-platform data collisions. Following these patterns ensures your plugin feels like a native part of the platform from day one, and that it remains maintainable as the codebase evolves.
The plugin system is designed exclusively for admin-side extensions. Plugins add pages, sidebar navigation, and UI slots inside the Cartble admin shell. They do not modify the customer-facing storefront or the public checkout experience.
Modularity principle
Every plugin is a fully self-contained directory under src/plugins/[plugin-name]/. All code that belongs to a plugin — pages, hooks, services, types, utilities, and local components — lives inside that directory. Plugins must not import from each other’s directories, and they must not modify files outside their own scope.
This rule enforces clear ownership: if something is inside src/plugins/your-plugin/, it belongs to that plugin. If it is outside, the plugin should consume it as a shared resource (UI components, hooks) rather than duplicating it.
UI consistency
Your plugin’s UI should be indistinguishable from the rest of the Cartble admin. Follow these rules to achieve that consistency.
Use shared UI components
Do not create custom buttons, inputs, or modal dialogs. If an equivalent component already exists in src/components/ui/, use it.
| Need | Use |
|---|
| Buttons | src/components/ui/Button.tsx |
| Text inputs | src/components/ui/Input.tsx |
| Confirmation dialogs | src/components/ui/ConfirmModal.tsx |
Match the admin design system
Apply the same Tailwind CSS color palette, spacing scale, border radius conventions, and typography that the main admin uses. Do not introduce custom color variables or override global styles. Reference existing admin pages to understand which Tailwind classes are standard.
Every monetary value your plugin displays must be formatted using formatPrice from the useTranslation hook. This function applies the correct currency symbol and regional number formatting for the platform’s locale. Raw number output or custom formatting functions will produce inconsistent results across locales.
Firestore data model
Cartble’s Firestore database is organized around a platform-scoped hierarchy. Every document your plugin reads or writes must live under the platforms/{platformId}/ path. Access outside this scope is not permitted.
Collection conventions
| Data type | Firestore path | When to use |
|---|
| Products and primary assets | platforms/{platformId}/resources/ | When your plugin reads or updates product catalog entries |
| Orders, transactions, and logs | platforms/{platformId}/records/ | When your plugin records financial or operational events |
| Plugin-private data | platforms/{platformId}/[plugin_name]_data/ | For data that only your plugin reads and writes |
When writing directly to the resources collection, your documents must conform to the platform’s existing resource schema. Do not add fields that conflict with or shadow existing resource properties.
All data access must be scoped to the platform the current admin session belongs to. Use the usePlatform() hook to retrieve the platformId and any platform-level settings at runtime. Never construct a Firestore path using a hardcoded string.
Hardcoded platform IDs will work in development but silently corrupt data in production when multiple platforms share the same deployment.
React Query patterns
Use React Query as the standard for managing server-side state in your plugin. Your plugin’s withBridge HOC must provide its own QueryClient instance so that the plugin’s cache is entirely isolated from the main application.
Always include platformId as the first element of every query key. This prevents stale data from one platform appearing in another platform’s admin when a user switches accounts within the same session.
Plugin type system
Plugins are defined using the Plugin interface from src/plugins/core/types.ts. The key fields you will work with are:
Define adminApps for every page your plugin exposes. Each entry requires a unique id, a display label, a Lucide icon, and a component wrapped with your withBridge HOC.