| title | Plugins |
|---|
Object UI supports a powerful plugin system that allows you to extend the framework with additional components. Plugins are separate packages that load on-demand, keeping your main application bundle small while providing rich functionality.
Plugins are lazy-loaded component packages that:
- Auto-register components when imported
- Lazy-load heavy dependencies on-demand
- Keep bundles small - only load when needed
- Are type-safe with full TypeScript support
- Follow best practices with built-in loading states
Object UI provides 14+ official plugins for common use cases:
Data visualization components powered by Recharts.
- Bar, line, area, and pie charts
- Responsive design
- Customizable colors
- Lazy-loaded (~80 KB)
Dashboard layouts with metric cards and widgets.
- Dashboard grid layouts
- Metric/KPI cards with trends
- Widget system
- Lazy-loaded (~22 KB)
Timeline component with multiple layout variants.
- Vertical, horizontal layouts
- Customizable markers
- Date formatting
- Lazy-loaded (~20 KB)
Gantt chart for project visualization.
- Task dependencies
- Progress tracking
- ObjectQL integration
- Lazy-loaded (~40 KB)
Calendar visualization for events.
- Month/week/day views
- Event management
- ObjectQL integration
- Lazy-loaded (~25 KB)
Map visualization with markers.
- Interactive maps
- Location markers
- ObjectQL integration
- Lazy-loaded (~60 KB)
Advanced data grid with sorting, filtering, and pagination.
- Column sorting and filtering
- Pagination controls
- Row selection
- Lazy-loaded (~45 KB)
Advanced form builder with validation.
- Multi-step forms
- Field validation
- Custom field types
- Lazy-loaded (~28 KB)
ObjectQL-integrated views for automatic CRUD.
- Auto-generated forms and grids
- CRUD operations
- Field mapping
- Lazy-loaded (~35 KB)
Code editor component powered by Monaco Editor.
- Syntax highlighting for 100+ languages
- IntelliSense and code completion
- Multiple themes
- Lazy-loaded (~120 KB)
Markdown renderer with GitHub Flavored Markdown support.
- GitHub Flavored Markdown
- XSS protection
- Code syntax highlighting
- Lazy-loaded (~30 KB)
Chat interface component.
- Message history
- User and assistant roles
- Timestamps and avatars
- Responsive floating panel for console assistants
- Inline responding, stop, and retry states
- Lazy-loaded (~35 KB)
Kanban board component with drag-and-drop powered by @dnd-kit.
- Drag and drop cards between columns
- Column limits (WIP limits)
- Card badges for status/priority
- Lazy-loaded (~100 KB)
A plugin's JavaScript is only half of what it renders with. @object-ui/plugin-grid and @object-ui/plugin-kanban publish a style.css of their own, and an app that installs one must import it after the base sheets:
/* src/index.css */
@import "tailwindcss";
@import "@object-ui/components/style.css";
@import "@object-ui/fields/style.css";
@import "@object-ui/plugin-grid/style.css";
@import "@object-ui/plugin-kanban/style.css";Each plugin sheet is compiled against the components theme and then has every rule that sheet already ships subtracted from it, so it carries only what the plugin adds. That includes the themed utilities (bg-muted/10, bg-card/60, ring-primary/40) which no consumer-side configuration can produce — the @theme block declaring their tokens lives in package source that is not published, so scanning node_modules cannot reach it (#4929). Skip the import and the view renders unstyled.
Add a line only for the plugins you install. The other @object-ui/plugin-* packages do not publish a stylesheet yet; the build step above is the pattern each of them will adopt when it needs one.
Plugins use React's lazy() and Suspense to load heavy dependencies on-demand:
// The plugin structure
import React, { Suspense } from 'react'
import { Skeleton } from '@object-ui/components'
// Lazy load the heavy implementation
const LazyEditor = React.lazy(() => import('./MonacoImpl'))
export const CodeEditorRenderer = (props) => (
<Suspense fallback={<Skeleton className="w-full h-[400px]" />}>
<LazyEditor {...props} />
</Suspense>
)Benefits:
- Smaller initial bundle: Main app loads faster
- Progressive loading: Components load when needed
- Better UX: Loading skeletons while chunks download
- Automatic code splitting: Vite handles chunking
| Plugin | Initial Load | Lazy Load | Description |
|---|---|---|---|
| plugin-editor | ~0.2 KB | ~120 KB | Monaco editor |
| plugin-charts | ~0.2 KB | ~80 KB | Recharts visualization |
| plugin-kanban | ~0.2 KB | ~100 KB | Drag-and-drop board |
| plugin-markdown | ~0.2 KB | ~30 KB | Markdown rendering |
| plugin-dashboard | ~0.2 KB | ~22 KB | Dashboard layouts |
| plugin-form | ~0.2 KB | ~28 KB | Form builder |
| plugin-grid | ~0.2 KB | ~45 KB | Data grid |
| plugin-view | ~0.2 KB | ~35 KB | ObjectQL views |
| plugin-timeline | ~0.2 KB | ~20 KB | Timeline layouts |
| plugin-chatbot | ~0.2 KB | ~35 KB | Chat interface |
| plugin-calendar | ~0.2 KB | ~25 KB | Calendar views |
| plugin-gantt | ~0.2 KB | ~40 KB | Gantt charts |
| plugin-map | ~0.2 KB | ~60 KB | Map visualization |
Without lazy loading, all this code would be in your main bundle!
Plugins automatically register their components when imported:
// In the plugin's index.tsx
import { ComponentRegistry } from '@object-ui/core'
ComponentRegistry.register('code-editor', CodeEditorRenderer)You just need to import the plugin once:
// In your App.tsx or main.tsx
import '@object-ui/plugin-editor'
import '@object-ui/plugin-charts'
import '@object-ui/plugin-kanban'
import '@object-ui/plugin-markdown'
import '@object-ui/plugin-dashboard'
import '@object-ui/plugin-form'
import '@object-ui/plugin-grid'
// ... import other plugins as neededNow all plugin components are available in your schemas!
You can create your own plugins following the same pattern:
mkdir -p packages/plugin-myfeature/src
cd packages/plugin-myfeature// src/MyFeatureImpl.tsx
import HeavyLibrary from 'heavy-library'
export default function MyFeatureImpl(props) {
return <HeavyLibrary {...props} />
}// src/index.tsx
import React, { Suspense } from 'react'
import { ComponentRegistry } from '@object-ui/core'
import { Skeleton } from '@object-ui/components'
// Lazy load implementation
const LazyFeature = React.lazy(() => import('./MyFeatureImpl'))
// Create renderer with Suspense
export const MyFeatureRenderer = (props) => (
<Suspense fallback={<Skeleton className="w-full h-[300px]" />}>
<LazyFeature {...props} />
</Suspense>
)
// Auto-register
ComponentRegistry.register('my-feature', MyFeatureRenderer)
// Export for manual use
export const myFeatureComponents = {
'my-feature': MyFeatureRenderer
}// src/types.ts
import type { BaseSchema } from '@object-ui/types'
export interface MyFeatureSchema extends BaseSchema {
type: 'my-feature'
customProp?: string
}// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { resolve } from 'path'
export default defineConfig({
plugins: [react()],
build: {
lib: {
entry: resolve(__dirname, 'src/index.tsx'),
name: 'ObjectUIPluginMyFeature',
fileName: (format) => `index.${format}.js`
},
rollupOptions: {
external: [
'react',
'react-dom',
'@object-ui/components',
'@object-ui/core'
],
output: {
globals: {
react: 'React',
'react-dom': 'ReactDOM'
}
}
}
}
}){
"name": "@object-ui/plugin-myfeature",
"version": "1.0.0",
"type": "module",
"main": "./dist/index.umd.js",
"module": "./dist/index.es.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.es.js",
"require": "./dist/index.umd.js",
"types": "./dist/index.d.ts"
}
},
"files": ["dist"],
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly"
},
"peerDependencies": {
"react": "^18.0.0 || ^19.0.0",
"react-dom": "^18.0.0 || ^19.0.0"
},
"dependencies": {
"heavy-library": "^1.0.0"
},
"devDependencies": {
"@object-ui/components": "workspace:*",
"@object-ui/core": "workspace:*",
"@object-ui/types": "workspace:*",
"@vitejs/plugin-react": "^6.0.5",
"typescript": "^6.0.3",
"vite": "^8.2.1"
}
}The main index file should only contain:
- Lazy loading wrapper
- Component registration
- Type exports
Heavy imports go in the *Impl.tsx file.
Always show a meaningful skeleton while loading:
<Suspense fallback={
<Skeleton className="w-full h-[400px]" />
}>
<LazyComponent {...props} />
</Suspense>Make your plugin type-safe:
export type { MyFeatureSchema } from './types'Include a README with:
- Installation instructions
- Usage examples
- Schema API reference
- Bundle size information
Verify that:
- The main bundle is small (~200 bytes)
- The lazy chunk is separate
- Components load correctly when rendered
pnpm build
ls -lh dist/Use a Plugin when:
- The component depends on large libraries (>50 KB)
- Not all apps will use this component
- You want on-demand loading
Use regular Components when:
- The component is lightweight
- Most apps will use it
- It's part of core functionality
Check that you imported it in your app:
import '@object-ui/plugin-myfeature'Make sure types are exported:
export type { MyFeatureSchema } from '@object-ui/plugin-myfeature'Check that the implementation is in a separate file:
✅ src/index.tsx (light, uses React.lazy)
✅ src/MyFeatureImpl.tsx (heavy, imported lazily)
Check that ComponentRegistry.register() is called at the module level:
// ✅ Good - runs on import
ComponentRegistry.register('my-feature', MyFeatureRenderer)
// ❌ Bad - never runs
export function registerComponents() {
ComponentRegistry.register('my-feature', MyFeatureRenderer)
}- Component Registry - Understanding the registry
- Schema Rendering - How schemas become UI
- Custom Plugin Development - Component development
- Create Plugin Utility - Scaffold new plugins quickly
- CLI Tool - Test plugins with the CLI
- All Utilities - Complete toolkit for development
- Install official plugins you need
- Try creating a custom plugin
- Share your plugins with the community
- Contribute new plugins to Object UI