brightsy-logo-horz
  • Documentation
  • Pricing
  • FAQ
  • Contact
Sign In
Sign Up

Schema Reference

Complete reference for Brightsy JSON Schema extensions: field types, formats, relationships, localization, rich text, and more.

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.

On this page

  • Base field types
  • String formats
  • Relationships
  • Localized strings
  • Rich text (TipTap)
  • Nested objects
  • Arrays & tags
  • Search metadata
  • UI Schema (widgets, order, labels)
  • $ref & $defs
  • Combiners (allOf / anyOf)
  • Full example

Base Field Types

All standard JSON Schema primitive types are supported. Every field should include a human-readable title.

TypeDescriptionExample use
stringText valuesTitle, slug, email
numberNumeric values (float)Price, rating, latitude
integerWhole numbersQuantity, sort order
booleanTrue / falseFeatured, published
objectNested structure or Brightsy formatAddress, relationship, richtext
arrayOrdered listTags, 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"]
}

String Formats

Use the format keyword on string fields to control validation and the widget rendered in forms.

format valueWidgetNotes
emailEmail inputValidates email syntax
uriURL input + file pickerAlso shows file manager button
imageURL input + image previewRenders inline preview
fileURL input + file pickerGeneric file attachment
dateDate pickerStores ISO date string (YYYY-MM-DD)
date-timeDate + time pickerStores ISO datetime string
markdownMarkdown editorFull markdown editor widget
textareaMulti-line textPlain long-text area
passwordPassword inputMasked; 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"
  }
}

Relationships

Brightsy supports two relationship types. Both require recordType to be the related record type's UUID (not its slug).

has-one — single reference

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"
  }
}
  • Stored value is a plain UUID string (the related record's ID).
  • The CMS form renders a searchable relationship picker.
  • Allowed at root level and inside nested objects.

has-many — reverse lookup

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"
    }
  }
}
PropertyRequiredDescription
recordTypeyesUUID of the child record type
foreignKeyyesField name on the child that holds the parent ID
defaultSort.fieldnoChild field to sort by in CMS forms
defaultSort.orderno"asc" or "desc" (default: "asc")

Localized Strings

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" }

Rich Text (TipTap JSON)

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"
  }
}

Nested Objects

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" }
        }
      }
    }
  }
}

Arrays & Tags

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" }
  }
}

Search Metadata

Control how individual fields influence semantic search and embeddings using Brightsy-specific extension keywords prefixed with x-.

KeywordValuesDefaultDescription
x-search-importance"high" | "normal" | "low""normal"Weight this field higher or lower in embeddings
x-searchabletrue | falsetrueSet 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
  }
}

UI Schema

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.

Root-level keys

These live at the top level of the UI Schema object and apply to the whole record type.

KeyTypeDescription
ui:orderstring[]Ordered list of every top-level property key. Controls render order. Fields missing from this list are appended at the end.
ui:listFieldsstring[]Fields rendered as columns in the records list / table view.
ui:labelFieldsstring[]Fields used to build the display label when this record is referenced via a relationship picker.

Per-field keys

Each field key in the UI Schema is a map from a property name to an object of per-field overrides: { "fieldName": { "ui:title": "..." } }.

KeyTypeDescription
ui:titlestringOverride the field label shown in the form (falls back to schema title, then property key).
ui:descriptionstringOverride the help text shown below the input (falls back to schema description).
ui:placeholderstringInput placeholder text.
ui:disabledbooleanRender the field as disabled / read-only in the form.
ui:classNamesstringExtra CSS class names applied to the form item wrapper element.
ui:enumNamesstring[]Human-readable labels for enum options, parallel to the schema enum array.
ui:widgetstringOverride the widget type — see table below.

ui:widget values

ValueEffect
"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.

Complete example

// 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.

$ref & $defs

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" }
  }
}

Schema Combiners (allOf / anyOf / oneOf)

Brightsy supports JSON Schema combiner keywords, mirroring RJSF's approach:

KeywordSemanticsForm Behaviour
allOfMust satisfy all subschemasSubschemas are shallow-merged into one resolved schema before rendering (transparent — no extra UI)
anyOfMust satisfy at least one subschemaRenders a dropdown to choose the active subschema; the selected subschema's fields are displayed
oneOfMust satisfy exactly one subschemaSame as anyOf — renders a dropdown selector
notMust NOT satisfy the subschemaValidation-only; not rendered in the form UI

allOf — schema composition

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.

anyOf / oneOf — discriminated unions

{
  "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.

Full Example — Blog Post

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"]
}