Elements
Elements (also called ElementTypes) define UI component specifications for rendering forms in the Admin Console and client applications. They map NodeType properties to visual field components, providing a schema-driven approach to building content editing interfaces.
What is an Element?
An Element defines how a property should be displayed and edited in a user interface:
name: rich-text-editor
component: RichTextEditor
properties:
placeholder:
type: string
toolbar:
type: array
maxLength:
type: number
defaultProps:
toolbar: [bold, italic, link, heading]
When you define a NodeType property, you can specify which Element to use:
name: blog:Article
properties:
body:
type: richtext
element: rich-text-editor # Maps to Element
elementProps:
placeholder: "Write your article..."
maxLength: 50000
The Admin Console automatically renders the appropriate UI component based on the Element definition.
Built-in Element Types
RaisinDB includes standard elements for common field types:
Text Input
name: text-input
component: TextInput
properties:
placeholder:
type: string
maxLength:
type: number
pattern:
type: string
autocomplete:
type: string
Usage:
title:
type: string
element: text-input
elementProps:
placeholder: "Enter title..."
maxLength: 200
Textarea
name: textarea
component: Textarea
properties:
placeholder:
type: string
rows:
type: number
maxLength:
type: number
Rich Text Editor
name: rich-text-editor
component: RichTextEditor
properties:
toolbar:
type: array
allowedFormats:
type: array
imageUpload:
type: boolean
Number Input
name: number-input
component: NumberInput
properties:
min:
type: number
max:
type: number
step:
type: number
precision:
type: number
Checkbox
name: checkbox
component: Checkbox
properties:
label:
type: string
helpText:
type: string
Select Dropdown
name: select
component: Select
properties:
options:
type: array
multiple:
type: boolean
searchable:
type: boolean
Usage:
status:
type: string
element: select
elementProps:
options:
- value: draft
label: Draft
- value: review
label: In Review
- value: published
label: Published
Date Picker
name: date-picker
component: DatePicker
properties:
format:
type: string
includeTime:
type: boolean
minDate:
type: datetime
maxDate:
type: datetime
Media Picker
name: media-picker
component: MediaPicker
properties:
accept:
type: array
maxSize:
type: number
multiple:
type: boolean
uploadPath:
type: string
Usage:
featuredImage:
type: media
element: media-picker
elementProps:
accept: [image/jpeg, image/png, image/webp]
maxSize: 5242880 # 5MB in bytes
Reference Picker
name: reference-picker
component: ReferencePicker
properties:
nodeType:
type: string
workspace:
type: string
multiple:
type: boolean
searchable:
type: boolean
Usage:
author:
type: reference
element: reference-picker
elementProps:
nodeType: user:Profile
workspace: default
searchable: true
Tag Input
name: tag-input
component: TagInput
properties:
suggestions:
type: array
allowCustom:
type: boolean
maxTags:
type: number
Color Picker
name: color-picker
component: ColorPicker
properties:
format:
type: string # hex, rgb, hsl
presets:
type: array
Geolocation Picker
name: geo-picker
component: GeoLocationPicker
properties:
mapProvider:
type: string
zoom:
type: number
searchEnabled:
type: boolean
Creating Custom Elements
Define custom elements in raisin:system.element_types:
-- Custom markdown editor element
INSERT INTO raisin:system.element_types (name, component, properties, defaultProps) VALUES (
'markdown-editor',
'MarkdownEditor',
'{
"placeholder": {"type": "string"},
"preview": {"type": "boolean"},
"syntax": {"type": "string"}
}',
'{
"preview": true,
"syntax": "github"
}'
);
Use in NodeType:
{
"content": {
"type": "string",
"element": "markdown-editor",
"elementProps": {
"placeholder": "Write markdown content..."
}
}
}
Element Properties
Common Properties
All elements support these common properties:
| Property | Type | Description |
|---|---|---|
label | string | Field label shown in UI |
helpText | string | Descriptive help text |
placeholder | string | Placeholder text |
disabled | boolean | Disable field editing |
required | boolean | Mark as required (visual indicator) |
validation | object | Custom validation rules |
Validation Rules
Elements can specify UI-level validation:
email:
type: email
element: text-input
elementProps:
label: Email Address
placeholder: "user@example.com"
validation:
pattern: "^[^@]+@[^@]+\\.[^@]+$"
message: "Please enter a valid email address"
Conditional Elements
Show/hide elements based on other field values:
properties:
hasExpiration:
type: boolean
element: checkbox
elementProps:
label: Content expires
expiresAt:
type: datetime
element: date-picker
elementProps:
label: Expiration date
condition:
field: hasExpiration
equals: true
The expiresAt field only appears when hasExpiration is true.
Element Groups
Group related fields together:
name: blog:Article
properties:
title:
type: string
element: text-input
body:
type: richtext
element: rich-text-editor
elementGroups:
- name: seo
label: SEO Settings
fields: [metaTitle, metaDescription, ogImage]
collapsible: true
collapsed: true
- name: publish
label: Publishing
fields: [published, publishedAt, author]
In the Admin Console, fields are organized into collapsible groups.
Element Layouts
Control field layout:
elementLayout:
columns: 2
fields:
title:
span: 2 # Full width
author:
span: 1 # Half width
category:
span: 1 # Half width
body:
span: 2 # Full width
Default Element Mapping
If no element is specified, RaisinDB uses defaults:
| Property Type | Default Element |
|---|---|
string | text-input |
number | number-input |
boolean | checkbox |
datetime | date-picker |
date | date-picker |
richtext | rich-text-editor |
array | tag-input |
reference | reference-picker |
media | media-picker |
geo | geo-picker |
Admin Console Integration
The Admin Console automatically generates forms based on Elements:
- User navigates to create/edit a node
- Console fetches the NodeType schema
- For each property, renders the specified Element
- Applies validation rules from both schema and element
- Submits validated data to RaisinDB
Example workflow:
-- 1. Define NodeType with elements
INSERT INTO raisin:system.node_types (name, properties) VALUES (
'blog:Article',
'{
"title": {
"type": "string",
"required": true,
"element": "text-input",
"elementProps": {"maxLength": 200}
},
"body": {
"type": "richtext",
"element": "rich-text-editor"
}
}'
);
-- 2. User opens Admin Console
-- 3. Navigates to create Article
-- 4. Form auto-renders with TextInput and RichTextEditor
-- 5. User fills form, clicks Save
-- 6. Console validates and inserts node
Custom Element Development
Build custom React components for elements:
// CustomSlugInput.jsx
import React from 'react';
export default function CustomSlugInput({ value, onChange, elementProps }) {
const handleChange = (e) => {
// Auto-convert to slug format
const slug = e.target.value
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
onChange(slug);
};
return (
<input
type="text"
value={value || ''}
onChange={handleChange}
placeholder={elementProps.placeholder}
maxLength={elementProps.maxLength}
/>
);
}
Register in RaisinDB:
INSERT INTO raisin:system.element_types (name, component, properties) VALUES (
'slug-input',
'CustomSlugInput',
'{
"placeholder": {"type": "string"},
"maxLength": {"type": "number"}
}'
);
Use in NodeType:
{
"slug": {
"type": "string",
"element": "slug-input",
"elementProps": {
"placeholder": "article-slug",
"maxLength": 100
}
}
}
Element Inheritance
Elements can extend other elements:
-- Base text input
INSERT INTO raisin:system.element_types (name, component, properties) VALUES (
'text-input',
'TextInput',
'{"placeholder": {"type": "string"}}'
);
-- Extended slug input
INSERT INTO raisin:system.element_types (name, extend, component, properties) VALUES (
'slug-input',
'text-input',
'SlugInput',
'{"autoGenerate": {"type": "boolean"}}'
);
slug-input inherits all properties from text-input and adds autoGenerate.
Querying Elements
List available elements:
-- Get all element types
SELECT name, component, properties
FROM raisin:system.element_types
ORDER BY name;
-- Find elements for a specific component
SELECT name, properties
FROM raisin:system.element_types
WHERE component = 'RichTextEditor';
Real-World Example
Complete blog article editing interface:
-- Define custom elements
INSERT INTO raisin:system.element_types (name, component, properties) VALUES
('slug-generator', 'SlugGenerator', '{"sourceField": {"type": "string"}}'),
('featured-image', 'FeaturedImagePicker', '{"aspectRatio": {"type": "string"}}');
-- Define NodeType with elements
INSERT INTO raisin:system.node_types (name, properties, elementGroups) VALUES (
'blog:Article',
'{
"title": {
"type": "string",
"required": true,
"element": "text-input",
"elementProps": {"maxLength": 200, "placeholder": "Article title"}
},
"slug": {
"type": "string",
"required": true,
"element": "slug-generator",
"elementProps": {"sourceField": "title"}
},
"excerpt": {
"type": "string",
"element": "textarea",
"elementProps": {"rows": 3, "maxLength": 500}
},
"body": {
"type": "richtext",
"required": true,
"element": "rich-text-editor",
"elementProps": {
"toolbar": ["heading", "bold", "italic", "link", "image"],
"imageUpload": true
}
},
"featuredImage": {
"type": "media",
"element": "featured-image",
"elementProps": {"aspectRatio": "16:9"}
},
"author": {
"type": "reference",
"required": true,
"element": "reference-picker",
"elementProps": {
"nodeType": "user:Profile",
"searchable": true
}
},
"tags": {
"type": "array",
"element": "tag-input",
"elementProps": {
"allowCustom": true,
"maxTags": 10
}
},
"published": {
"type": "boolean",
"element": "checkbox",
"elementProps": {"label": "Publish immediately"}
},
"publishedAt": {
"type": "datetime",
"element": "date-picker",
"elementProps": {
"includeTime": true,
"condition": {"field": "published", "equals": true}
}
}
}',
'[
{
"name": "content",
"label": "Content",
"fields": ["title", "slug", "excerpt", "body", "featuredImage"]
},
{
"name": "meta",
"label": "Metadata",
"fields": ["author", "tags", "published", "publishedAt"],
"collapsible": true
}
]'
);
This creates a professional content editing interface with:
- Auto-generating slug from title
- Rich text editor with media upload
- Featured image with aspect ratio enforcement
- Author reference picker
- Conditional publishing date
- Organized field groups
Element and Field Metadata
Element types carry a free-form meta map — both at the element-type level and on each individual field. RaisinDB stores and round-trips meta untouched; it never interprets the contents, so it's the right place for editor hints, layout flags, categorization, or integration configuration that isn't part of a field's value.
name: marketing:HeroBlock
meta:
category: "marketing"
previewImage: "/assets/hero-preview.png"
fields:
- $type: TextField
name: headline
label: Headline
meta:
group: "content"
maxPreviewChars: 60
- $type: MediaField
name: background
label: Background image
meta:
group: "appearance"
meta values may be strings, numbers, booleans, arrays, or objects. Both maps are editable in the Admin Console element editor — element-level meta lives under Element Settings, and per-field meta is in the field editor's metadata tab (the braces icon).
Element-type meta attaches to the whole element. Per-field meta attaches to a single field via the shared field base, so any field type (TextField, MediaField, CompositeField, …) can carry it.
Resolving inheritance
Element types support extends. To get an element type with its inheritance chain merged (base fields first, child overrides by name), use the resolved view — available on both the HTTP API and the JavaScript client:
const resolved = await db.elementTypes().getResolved('marketing:HeroBlock');
// { element_type, resolved_fields, resolved_layout, inheritance_chain, resolved_strict }
The same getResolved() exists for NodeTypes and Archetypes. Schemas are per-branch, so resolution runs against the database's branch — use db.onBranch('staging') to resolve on another branch. See Schema Management.
Best Practices
- Use semantic elements: Choose elements that match the data type
- Provide helpful props: Set placeholder, labels, and help text
- Validate at both levels: Schema validation + UI validation
- Group related fields: Use elementGroups for better UX
- Consider mobile: Choose elements that work on all devices
- Custom elements sparingly: Only when built-ins don't fit
- Test thoroughly: Ensure elements handle edge cases
Next Steps
- NodeTypes - Define schemas that use elements
- Admin Console Guide - Use the visual interface
- Archetypes - Reusable fields with elements
- Data Modeling Guide - Build complete content models