Brightsy schemas are valid JSON Schema (Draft 7). All standard keywords — type, properties, required, enum, pattern, minimum / maximum, minLength / maxLength, $ref, $defs, and more — are supported. The extensions documented here are additions on top of that foundation, not replacements.
All standard JSON Schema primitive types are supported. Every field should include a human-readable title.
| Type | Description | Example use |
|---|---|---|
string | Text values | Title, slug, email |
number | Numeric values (float) | Price, rating, latitude |
integer | Whole numbers | Quantity, sort order |
boolean | True / false | Featured, published |
object | Nested structure or Brightsy format | Address, relationship, richtext |
array | Ordered list | Tags, image gallery |
{
"type": "object",
"title": "Blog Post",
"properties": {
"title": { "type": "string", "title": "Title" },
"price": { "type": "number", "title": "Price" },
"featured": { "type": "boolean", "title": "Featured" }
},
"required": ["title"]
}Use the format keyword on string fields to control validation and the widget rendered in forms.
| format value | Widget | Notes |
|---|---|---|
email | Email input | Validates email syntax |
uri | URL input + file picker | Also shows file manager button |
image | URL input + image preview | Renders inline preview |
file | URL input + file picker | Generic file attachment |
date | Date picker | Stores ISO date string (YYYY-MM-DD) |
date-time | Date + time picker | Stores ISO datetime string |
markdown | Markdown editor | Full markdown editor widget |
textarea | Multi-line text | Plain long-text area |
password | Password input | Masked; not stored encrypted |
Common JSON Schema validation keywords also apply to string fields:
{
"slug": {
"type": "string",
"title": "Slug",
"pattern": "^[a-z0-9-]+$",
"minLength": 2,
"maxLength": 100
},
"status": {
"type": "string",
"title": "Status",
"enum": ["draft", "published", "archived"],
"default": "draft"
}
}Brightsy supports two relationship types. Both require recordType to be the related record type's UUID (not its slug).
Stores the ID of one related record. Can be placed at any nesting level.
{
"author": {
"type": "string",
"format": "has-one",
"title": "Author",
"recordType": "author-record-type-uuid"
}
}Defines a reverse relationship. Data lives on the child records; nothing is stored in the parent. Only allowed at the root level of the schema.
{
"comments": {
"type": "object",
"format": "has-many",
"title": "Comments",
"recordType": "comment-record-type-uuid",
"foreignKey": "post",
"defaultSort": {
"field": "created_at",
"order": "asc"
}
}
}| Property | Required | Description |
|---|---|---|
recordType | yes | UUID of the child record type |
foreignKey | yes | Field name on the child that holds the parent ID |
defaultSort.field | no | Child field to sort by in CMS forms |
defaultSort.order | no | "asc" or "desc" (default: "asc") |
Multi-language fields use format: "localized-string" on an object with one property per locale code.
{
"title": {
"type": "object",
"format": "localized-string",
"title": "Title",
"defaultLocale": "en",
"fallbackLocale": "en",
"properties": {
"en": { "type": "string", "title": "EN" },
"de": { "type": "string", "title": "DE" },
"fr": { "type": "string", "title": "FR" }
}
}
}Stored value:
{ "en": "Hello World", "de": "Hallo Welt", "fr": "Bonjour le monde" }Use format: "richtext" (or format: "html") on an object field to render a full TipTap rich-text editor. The TipTap document JSON is stored directly as the field value. You can also trigger the editor via UI Schema using ui:widget: "richtext".
{
"content": {
"type": "object",
"format": "richtext",
"title": "Content"
}
}Plain object fields with properties create collapsible sub-forms. Nesting is fully recursive. Nested objects may contain has-one relationships but not has-many.
{
"address": {
"type": "object",
"title": "Address",
"properties": {
"street": { "type": "string", "title": "Street" },
"city": { "type": "string", "title": "City" },
"country": { "type": "string", "title": "Country" },
"coords": {
"type": "object",
"title": "Coordinates",
"properties": {
"lat": { "type": "number", "title": "Latitude" },
"lng": { "type": "number", "title": "Longitude" }
}
}
}
}
}Standard JSON Schema array fields are supported. Add format: "tags" to the array (not the items) to render a chip-style tag input.
{
"labels": {
"type": "array",
"format": "tags",
"title": "Labels",
"items": { "type": "string" }
},
"scores": {
"type": "array",
"title": "Scores",
"items": { "type": "number" }
}
}Control how individual fields influence semantic search and embeddings using Brightsy-specific extension keywords prefixed with x-.
| Keyword | Values | Default | Description |
|---|---|---|---|
x-search-importance | "high" | "normal" | "low" | "normal" | Weight this field higher or lower in embeddings |
x-searchable | true | false | true | Set false to exclude a field from embeddings entirely |
{
"title": {
"type": "string",
"title": "Title",
"x-search-importance": "high"
},
"internalNotes": {
"type": "string",
"title": "Internal Notes",
"x-searchable": false
}
}The UI Schema is a separate JSON object stored alongside the JSON Schema that controls how the CMS form renders — field order, labels, widgets, and display options. It does not affect stored data.
These live at the top level of the UI Schema object and apply to the whole record type.
| Key | Type | Description |
|---|---|---|
ui:order | string[] | Ordered list of every top-level property key. Controls render order. Fields missing from this list are appended at the end. |
ui:listFields | string[] | Fields rendered as columns in the records list / table view. |
ui:labelFields | string[] | Fields used to build the display label when this record is referenced via a relationship picker. |
Each field key in the UI Schema is a map from a property name to an object of per-field overrides: { "fieldName": { "ui:title": "..." } }.
| Key | Type | Description |
|---|---|---|
ui:title | string | Override the field label shown in the form (falls back to schema title, then property key). |
ui:description | string | Override the help text shown below the input (falls back to schema description). |
ui:placeholder | string | Input placeholder text. |
ui:disabled | boolean | Render the field as disabled / read-only in the form. |
ui:classNames | string | Extra CSS class names applied to the form item wrapper element. |
ui:enumNames | string[] | Human-readable labels for enum options, parallel to the schema enum array. |
ui:widget | string | Override the widget type — see table below. |
ui:widget values| Value | Effect |
|---|---|
"date" | Date-only picker. Stored as ISO date string (YYYY-MM-DD). |
"datetime" | Date + time picker. Stored as ISO datetime string. |
"date-time" | Alias for "datetime". |
"textarea" | Multi-line plain-text area instead of single-line input. Also activates multiline mode for localized-string fields. |
"richtext" | TipTap rich-text editor (same as format: "richtext" but can be set via UI Schema). |
"number" | Force a number input for a field that does not declare type: "number" in the schema. |
// schema_ui
{
"ui:order": ["title", "slug", "status", "publishedAt", "author", "content", "tags"],
"ui:listFields": ["title", "status", "publishedAt"],
"ui:labelFields": ["title"],
"title": {
"ui:title": "Headline",
"ui:description": "The public-facing title of the post."
},
"slug": {
"ui:placeholder": "my-post-slug"
},
"status": {
"ui:enumNames": ["Draft", "Published", "Archived"]
},
"publishedAt": {
"ui:widget": "datetime",
"ui:title": "Publish Date & Time"
},
"content": {
"ui:widget": "richtext"
},
"tags": {
"ui:description": "Add one or more topic tags."
}
}⚠️ ui:order must include every property you want rendered. Any key present in the schema but absent from ui:order is appended at the end in declaration order — but relying on that is not recommended.
Standard JSON Schema $defs / $ref are supported. Use them to share a definition across multiple fields. Local document references (starting with #/) are resolved at render time. Sibling keywords alongside a $ref are merged with the resolved definition.
{
"$defs": {
"seoMeta": {
"type": "object",
"title": "SEO Meta",
"properties": {
"metaTitle": { "type": "string", "title": "Meta Title", "maxLength": 60 },
"metaDescription": { "type": "string", "title": "Meta Description", "maxLength": 160 }
}
}
},
"type": "object",
"properties": {
"title": { "type": "string", "title": "Title" },
"seo": { "$ref": "#/$defs/seoMeta" }
}
}Brightsy supports JSON Schema combiner keywords, mirroring RJSF's approach:
| Keyword | Semantics | Form Behaviour |
|---|---|---|
allOf | Must satisfy all subschemas | Subschemas are shallow-merged into one resolved schema before rendering (transparent — no extra UI) |
anyOf | Must satisfy at least one subschema | Renders a dropdown to choose the active subschema; the selected subschema's fields are displayed |
oneOf | Must satisfy exactly one subschema | Same as anyOf — renders a dropdown selector |
not | Must NOT satisfy the subschema | Validation-only; not rendered in the form UI |
The most common use-case is extending a shared base type via $ref:
{
"$defs": {
"baseContent": {
"properties": {
"createdAt": { "type": "string", "format": "date-time", "title": "Created" },
"status": { "type": "string", "enum": ["draft", "published"], "title": "Status" }
}
}
},
"type": "object",
"allOf": [
{ "$ref": "#/$defs/baseContent" },
{
"properties": {
"title": { "type": "string", "title": "Title" },
"body": { "type": "object", "format": "richtext", "title": "Body" }
}
}
]
}Brightsy merges all subschemas (resolving any $ref first) into a single flat schema — identical to RJSF's shallowAllOfMerge pipeline.
{
"type": "object",
"properties": {
"contact": {
"title": "Contact",
"anyOf": [
{
"title": "Email",
"properties": { "email": { "type": "string", "format": "email", "title": "Email" } }
},
{
"title": "Phone",
"properties": { "phone": { "type": "string", "title": "Phone Number" } }
}
]
}
}
}A select dropdown appears above the field group. Choosing an option swaps in that subschema's fields. Give each subschema a title to produce readable option labels.
A complete record type schema combining the major features:
// schema
{
"type": "object",
"title": "Blog Post",
"properties": {
"title": {
"type": "object",
"format": "localized-string",
"title": "Title",
"defaultLocale": "en",
"properties": {
"en": { "type": "string" },
"de": { "type": "string" }
},
"x-search-importance": "high"
},
"slug": {
"type": "string",
"title": "Slug",
"pattern": "^[a-z0-9-]+$"
},
"author": {
"type": "string",
"format": "has-one",
"title": "Author",
"recordType": "author-record-type-uuid"
},
"content": {
"type": "object",
"format": "richtext",
"title": "Content"
},
"tags": {
"type": "array",
"format": "tags",
"title": "Tags",
"items": { "type": "string" }
},
"comments": {
"type": "object",
"format": "has-many",
"title": "Comments",
"recordType": "comment-record-type-uuid",
"foreignKey": "post",
"defaultSort": { "field": "created_at", "order": "asc" }
}
},
"required": ["title", "slug"]
}
// schema_ui
{
"ui:order": ["title", "slug", "author", "content", "tags", "comments"],
"ui:listFields": ["slug", "author"],
"ui:labelFields": ["title"]
}