Skip to main content

Documentation Index

Fetch the complete documentation index at: https://help.cartble.com/llms.txt

Use this file to discover all available pages before exploring further.

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.
import { useTranslation } from '@/src/i18n';

const { formatPrice } = useTranslation();

// Correct
<span>{formatPrice(product.price)}</span>

// Incorrect — bypasses locale-aware formatting
<span>$ {product.price.toFixed(2)}</span>

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.
import { usePlatform } from '@/src/hooks/usePlatform';

const { platformId } = usePlatform();
const path = `platforms/${platformId}/your_plugin_data`;
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.
// Correct — platformId scopes the cache entry
queryKey: [platformId, 'your-plugin', 'settings']

// Incorrect — cache is not scoped, data can leak between platforms
queryKey: ['your-plugin', 'settings']

Plugin type system

Plugins are defined using the Plugin interface from src/plugins/core/types.ts. The key fields you will work with are:
interface Plugin {
  id: string;                        // Unique identifier, e.g. 'your-plugin'
  name: string;                      // Display name shown in the Plugins Manager
  description?: string;              // Short description shown in the plugin card
  version: string;                   // Semantic version string
  category: PluginCategory;          // 'payment' | 'shipping' | 'marketing' | 'analytics' | 'other'
  isNative?: boolean;                // True for first-party plugins bundled with the platform
  icon?: React.ComponentType<any>;   // Lucide icon shown in the Plugins Manager card
  settingsComponent?: React.ComponentType<any>; // Inline settings panel shown in the Plugins Manager
  adminApps?: AdminApp[];            // Sidebar navigation entries and their page components
  slots?: Partial<Record<SlotID, React.ComponentType<any>>>; // UI slot injections
  hooks?: Partial<Record<HookID, (data: any) => void>>;      // Platform event listeners
  translations?: Record<string, any>;                        // i18n strings
}
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.