Skip to main content
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.
NeedUse
Buttonssrc/components/ui/Button.tsx
Text inputssrc/components/ui/Input.tsx
Confirmation dialogssrc/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.

Format all prices with formatPrice

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 typeFirestore pathWhen to use
Products and primary assetsplatforms/{platformId}/resources/When your plugin reads or updates product catalog entries
Orders, transactions, and logsplatforms/{platformId}/records/When your plugin records financial or operational events
Plugin-private dataplatforms/{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.

Platform awareness

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.