Custom Components
The builder ships with manifests for all standard JSON Forms element types, but you can register your own custom components to extend the palette with domain-specific controls.
Creating a Component Manifest
A manifest tells the builder how to display the component in the palette, what default node properties to use, and which category to group it under.
The builder's internal manifest format is:
interface ComponentManifest {
/** Unique manifest ID used to create nodes from palette drags */
id: string
/** Display name shown in the palette */
name: string
/** Optional longer description */
description?: string
/** Icon identifier shown in the palette */
icon: string
/** Category for palette grouping */
category: ManifestCategory // 'layout' | 'control' | 'display'
/** The SchemaNode type this manifest creates */
nodeType: SchemaNodeType
/** Default SchemaNode property overrides */
defaults: SchemaNodeDefaults
/** JSON Schema + UI Schema pair for the property panel */
propertySchema: {
jsonSchema: Record<string, unknown>
uiSchema: Record<string, unknown>
}
/** Optional matching criteria for existing schema imports */
schemaMatch?: {
types?: JsonSchemaType[]
formats?: string[]
}
/** Optional placement constraints */
placement?: {
allowedParents?: SchemaNodeType[]
isContainer?: boolean
allowedChildren?: SchemaNodeType[]
}
/** Optional custom preview renderer */
previewRenderer?: {
tester: Record<string, unknown>
component: unknown
}
}
interface SchemaNodeDefaults {
propertyName?: string
schemaProperties?: JsonSchemaFragment
options?: Record<string, unknown>
label?: string | boolean
}Registering Custom Manifests
Register custom manifests before the builder renders. Typically you do this during app setup or in a composable:
import { useComponentRegistry } from '@banclo/jsonforms-core'
const registry = useComponentRegistry()
registry.register({
id: 'star-rating',
name: 'Star Rating',
icon: 'star',
category: 'control',
nodeType: 'Control',
defaults: {
schemaProperties: {
type: 'integer',
minimum: 0,
maximum: 5,
},
options: {
format: 'rating',
},
},
propertySchema: {
jsonSchema: {},
uiSchema: {},
},
})After registration, "Star Rating" appears in the palette under the "control" category. When dragged to the canvas, the builder creates a Control node with type: 'integer', minimum: 0, maximum: 5, and an options.format of "rating".
Defining a Meta-Schema for Property Editing
By default, the property panel shows generic fields for the node. You can provide a propertySchema in the manifest to power a richer property editing experience. The propertySchema is a JSON Schema + UI Schema pair that the property panel renders using JSON Forms:
registry.register({
id: 'star-rating',
name: 'Star Rating',
icon: 'star',
category: 'control',
nodeType: 'Control',
defaults: {
schemaProperties: {
type: 'integer',
minimum: 0,
maximum: 5,
},
options: {
format: 'rating',
maxStars: 5,
color: '#fbbf24',
allowHalf: false,
},
},
propertySchema: {
jsonSchema: {
type: 'object',
properties: {
maxStars: { type: 'integer', minimum: 1, maximum: 10 },
color: { type: 'string' },
allowHalf: { type: 'boolean' },
},
},
uiSchema: {
type: 'VerticalLayout',
elements: [
{ type: 'Control', scope: '#/properties/maxStars' },
{ type: 'Control', scope: '#/properties/color' },
{ type: 'Control', scope: '#/properties/allowHalf' },
],
},
},
})The property panel will display editors for options.maxStars, options.color, and options.allowHalf alongside the standard label and validation editors.
Example: Star Rating Component
Here is a complete example of registering a star rating custom component.
1. Register the Manifest
// src/custom-components/star-rating.ts
import type { ComponentManifest } from '@banclo/jsonforms-core'
export const starRatingManifest: ComponentManifest = {
id: 'star-rating',
name: 'Star Rating',
description: 'A star-based rating input control',
icon: 'star',
category: 'control',
nodeType: 'Control',
defaults: {
schemaProperties: {
type: 'integer',
minimum: 0,
maximum: 5,
default: 0,
},
options: {
format: 'rating',
maxStars: 5,
color: '#fbbf24',
allowHalf: false,
size: 'md',
},
label: 'Rating',
},
propertySchema: {
jsonSchema: {
type: 'object',
properties: {
maxStars: { type: 'integer', minimum: 1, maximum: 10 },
color: { type: 'string' },
allowHalf: { type: 'boolean' },
size: { type: 'string', enum: ['sm', 'md', 'lg'] },
},
},
uiSchema: {
type: 'VerticalLayout',
elements: [
{ type: 'Control', scope: '#/properties/maxStars' },
{ type: 'Control', scope: '#/properties/color' },
{ type: 'Control', scope: '#/properties/allowHalf' },
{ type: 'Control', scope: '#/properties/size' },
],
},
},
}2. Register at App Startup
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { useComponentRegistry } from '@banclo/jsonforms-core'
import { starRatingManifest } from './custom-components/star-rating'
import App from './App.vue'
const app = createApp(App)
app.use(createPinia())
const registry = useComponentRegistry()
registry.register(starRatingManifest)
app.mount('#app')3. Provide a Custom Preview Renderer (Optional)
If you want the live preview to render your custom component, you need a JSON Forms renderer that handles the rating format. See Preview Adapters for details on creating custom renderers.
<!-- StarRatingRenderer.vue -->
<script setup lang="ts">
import { computed } from 'vue'
import { useJsonFormsControl, type RendererProps } from '@jsonforms/vue'
const props = defineProps<RendererProps<any>>()
const { control, handleChange } = useJsonFormsControl(props)
const maxStars = computed(() => control.value.uischema.options?.maxStars ?? 5)
const currentValue = computed(() => control.value.data ?? 0)
function setRating(value: number) {
handleChange(control.value.path, value)
}
</script>
<template>
<div class="star-rating-control">
<label>{{ control.label }}</label>
<div class="stars">
<button
v-for="i in maxStars"
:key="i"
type="button"
:class="{ filled: i <= currentValue }"
@click="setRating(i)"
>
★
</button>
</div>
</div>
</template>Example: Color Picker Component
Another example showing a custom color picker control:
1. Register the Manifest
// src/custom-components/color-picker.ts
import type { ComponentManifest } from '@banclo/jsonforms-core'
export const colorPickerManifest: ComponentManifest = {
id: 'color-picker',
name: 'Color Picker',
icon: 'palette',
category: 'control',
nodeType: 'Control',
defaults: {
schemaProperties: {
type: 'string',
pattern: '^#[0-9a-fA-F]{6}$',
},
options: {
format: 'color',
showHex: true,
showAlpha: false,
presetColors: [
'#ef4444', '#f97316', '#eab308',
'#22c55e', '#3b82f6', '#8b5cf6',
],
},
label: 'Color',
},
propertySchema: {
jsonSchema: {
type: 'object',
properties: {
showHex: { type: 'boolean' },
showAlpha: { type: 'boolean' },
},
},
uiSchema: {
type: 'VerticalLayout',
elements: [
{ type: 'Control', scope: '#/properties/showHex' },
{ type: 'Control', scope: '#/properties/showAlpha' },
],
},
},
}2. Register at Startup
import { useComponentRegistry } from '@banclo/jsonforms-core'
import { colorPickerManifest } from './custom-components/color-picker'
const registry = useComponentRegistry()
registry.register(colorPickerManifest)3. Generated Schema Output
When a color picker is placed on the canvas with property name brandColor, the generated schemas look like:
JSON Schema:
{
"type": "object",
"properties": {
"brandColor": {
"type": "string",
"pattern": "^#[0-9a-fA-F]{6}$"
}
}
}UI Schema:
{
"type": "VerticalLayout",
"elements": [
{
"type": "Control",
"scope": "#/properties/brandColor",
"label": "Color",
"options": {
"format": "color",
"showHex": true,
"showAlpha": false,
"presetColors": ["#ef4444", "#f97316", "#eab308", "#22c55e", "#3b82f6", "#8b5cf6"]
}
}
]
}Best Practices
- Use descriptive, kebab-case manifest IDs (
star-rating, notStarRating) - Set sensible defaults in
schemaPropertiesso the control works immediately after being dropped - Use the
optionsobject for UI-specific settings that do not belong in the JSON Schema - Group related custom components under a shared category name
- Keep manifest definitions in separate files for maintainability
- Provide a
propertySchemawith bothjsonSchemaanduiSchemato give users a rich editing experience in the property panel - Test that your generated schemas validate correctly by using
validateGeneratedSchemas()from@banclo/jsonforms-core