Create PagibleAI Content Elements

Define custom content elements in a theme package's schema.json, then add one Blade view with the same element name. PagibleAI merges the schema when the theme service provider calls Schema::register().

This guide uses a theme named mytheme. Its quote entry is registered as mytheme::quote, so it cannot collide with an element from another theme.

Source compatibility: This guide was verified against PagibleAI commit 3c149a3 on 2026-08-01. If you use a tagged release, check its bundled field components before relying on an option.

Current implementation references: base content schema, admin field registry, field components and schema registry.

Create an element

Add the element below the top-level content key in your theme's schema.json. The surrounding theme file also contains metadata and page-type definitions:

{
    "label": "My Theme",
    "description": "Theme for the company website",
    "maintainer": "My Company",
    "email": "info@example.com",
    "website": "https://example.com",
    "types": {
        "page": {
            "sections": ["main", "footer"]
        }
    },
    "content": {
        "quote": {
            "group": "content",
            "icon": "<svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M7 7h4v10H5V9a2 2 0 0 1 2-2Zm8 0h4v10h-6V9a2 2 0 0 1 2-2Z\"/></svg>",
            "fields": {
                "text": {
                    "type": "text",
                    "label": "Quote",
                    "min": 1,
                    "default": "Add the quotation."
                },
                "author": {
                    "type": "string",
                    "label": "Author",
                    "max": 100
                }
            }
        }
    }
}

Create views/quote.blade.php in the same theme package:

<figure class="quote">
    <blockquote>@text($data->text ?? '')</blockquote>

    @if($data->author ?? null)
        <figcaption>{{ $data->author }}</figcaption>
    @endif
</figure>

After Composer discovers the theme provider, select the theme on a page. The editor will offer Quote in the content-element dialog. The stored type is mytheme::quote; the renderer tries mytheme::quote, then the base cms::quote view, and finally cms::invalid.

Element properties

Each entry below content uses:

  • group: category shown in the element chooser, such as basic, content, media or forms
  • icon: SVG markup for the chooser; use fill="currentColor" so it follows light and dark modes
  • fields: object keyed by the names stored below the element's data property

Every field requires a type. label, default, validation settings and selector options depend on that type. Reusing familiar names such as title, text and file also lets PagibleAI carry compatible values across when an editor changes an element's type.

Common field settings

  • label: translated label shown in the admin panel
  • default: value used before an editor stores a value
  • required: prevents an empty value where the field supports it
  • min and max: length, item-count, column-count or numeric bounds depending on the field
  • placeholder: hint shown in an empty control

Defaults must match the value type. Use a JSON array for items, images, range and multiple-choice fields, an object for one selected file, and a scalar for ordinary text or number fields.

Text fields

string

Short text control with min, max, placeholder, default and an optional class. Use pattern for a regular-expression check and uppercase: true to normalize stored input.

plaintext

Multi-line plain text with min, max, placeholder, default and an optional class applied to the control.

text

CKEditor with limited inline formatting. It supports min, max and default.

markdown

Full Markdown editor with min, max and default.

html

Raw HTML textarea. The current field requires a non-empty value.

url

URL input with allowed, placeholder, required and default. allowed is an array of scheme names.

Combined text-field schema example:

{
    "title": {
        "type": "string",
        "label": "Title",
        "min": 5,
        "max": 100,
        "placeholder": "Enter a title"
    },
    "summary": {
        "type": "text",
        "label": "Summary",
        "max": 500
    },
    "body": {
        "type": "markdown",
        "label": "Body",
        "min": 1
    },
    "website": {
        "type": "url",
        "label": "Website",
        "allowed": ["http", "https"],
        "required": false
    }
}

Choice fields

checkbox

One checkbox with optional on, off and default values.

switch

Toggle with optional on, off and default values.

radio

Selects one value from options; supports required and default.

select

Dropdown using options; supports multiple, placeholder, required and default.

combobox

Searchable dropdown that also accepts typed input. It supports the remote-data settings documented below.

autocomplete

Suggestion picker populated from initial options, GraphQL or REST.

options is an array of objects containing label and value. Combined choice-field example:

{
    "category": {
        "type": "select",
        "label": "Category",
        "options": [
            {"label": "Technology", "value": "tech"},
            {"label": "Design", "value": "design"}
        ],
        "required": true,
        "default": "tech"
    },
    "featured": {
        "type": "switch",
        "label": "Featured",
        "on": true,
        "off": false,
        "default": false
    }
}

Remote autocomplete and combobox

Set api-type to REST or GQL. Use _term_ in the URL or trusted GraphQL query; PagibleAI replaces it with the escaped search term. list-key, item-title and item-value are slash-separated paths into the returned JSON.

REST example:

{
    "repository": {
        "type": "autocomplete",
        "label": "Repository",
        "api-type": "REST",
        "url": "/api/repos?filter=_term_",
        "list-key": "items",
        "item-title": "name",
        "item-value": "url",
        "placeholder": "Start typing",
        "empty-text": "No repositories found",
        "required": true
    }
}

GraphQL example:

{
    "parent-page": {
        "type": "autocomplete",
        "label": "Parent page",
        "api-type": "GQL",
        "query": "query { pages(filter: {title: _term_}) { data { id title } } }",
        "list-key": "pages/data",
        "item-title": "title",
        "item-value": "id"
    }
}

Number, range, date and color fields

number

Numeric input with min, max, step, precision, placeholder, required and default.

slider

One numeric value with min, max, step and default.

range

Two numeric values with min, max, step and an array default.

date

One or multiple dates with allowed, min, max, multiple, placeholder, required and default.

color

Hexadecimal color value with required and default.

Combined numeric, date and color example:

{
    "price": {
        "type": "number",
        "label": "Price",
        "min": 0,
        "step": 0.01,
        "precision": 2,
        "required": true
    },
    "price-range": {
        "type": "range",
        "label": "Price range",
        "min": 0,
        "max": 1000,
        "step": 10,
        "default": [100, 500]
    },
    "event-date": {
        "type": "date",
        "label": "Event date",
        "min": "2026-01-01",
        "max": "2026-12-31"
    },
    "accent": {
        "type": "color",
        "label": "Accent",
        "default": "#2563EB"
    }
}

Repeated data

items

Ordered list of structured objects. Define the row fields below item; identity names an optional generated stable property. min, max and default apply to the outer array.

table

Editable rows and columns. min and max constrain the column count; default is a two-dimensional array.

An items default must be an array of objects. Combined repeated-data example:

{
    "members": {
        "type": "items",
        "label": "Team members",
        "identity": "id",
        "min": 1,
        "max": 6,
        "default": [
            {
                "name": "Jane Doe",
                "position": "Editor"
            }
        ],
        "item": {
            "name": {
                "type": "string",
                "label": "Name",
                "min": 1
            },
            "position": {
                "type": "string",
                "label": "Position"
            }
        }
    },
    "comparison": {
        "type": "table",
        "label": "Comparison",
        "min": 2,
        "max": 4,
        "default": [
            ["Feature", "Plan"],
            ["Support", "Included"]
        ]
    }
}

File and media fields

file

Selects one file of any accepted MIME type. Supports accept and required.

image

Selects one image. Supports accept and required.

images

Selects an ordered image list. Use min and max for the allowed item count and accept for MIME types.

media

Selects one image or video and exposes the media editing controls. Supports accept and required.

audio

Selects one audio file. Supports accept and required.

video

Selects one video file. Supports accept and required.

Use accept to restrict MIME types. Single-file fields support required; images uses min when at least one image is required. Combined file-field example:

{
    "document": {
        "type": "file",
        "label": "PDF",
        "accept": "application/pdf",
        "required": true
    },
    "cover": {
        "type": "image",
        "label": "Cover image",
        "accept": "image/jpeg,image/webp"
    },
    "gallery": {
        "type": "images",
        "label": "Gallery",
        "accept": "image/*",
        "min": 2,
        "max": 8
    },
    "feature-media": {
        "type": "media",
        "label": "Image or video",
        "accept": "image/*,video/*"
    }
}

Hidden fields and actions

hidden

Stores a fixed schema value without showing a control. Its primary use is selecting a trusted action handler.

{
    "action": {
        "type": "hidden",
        "value": "\\App\\Cms\\LatestArticles"
    }
}

During rendering, PagibleAI resolves the handler from the trusted registered schema for the element type. It does not execute a callable supplied only in stored page content. See Add Dynamic Content to Pages for the action class and query patterns.

Troubleshoot schema fields

  • The element is absent from the chooser: validate schema.json, confirm the theme provider is loaded and select that theme on the page. Clear cached configuration and views with php artisan optimize:clear.
  • A field does not render: its type must match a registered component name from admin/js/fields; field names are case-sensitive after conversion to the component name.
  • A default is rejected: match the value shape to the field—scalar, object or array—and keep min, max and required consistent with it.
  • A remote chooser stays empty: inspect the REST or GraphQL response, CORS policy, api-type, list-key, item-title and item-value.
  • An older element lacks a new field: keep a default in the schema or Blade view. Existing immutable page versions are not rewritten when the schema changes.