Version 4.3 of OctoberCMS ships a broad set of features across the frontend, the backend, and developer tooling. The document form design brings the Tailor editor layout to every plugin, partials as components introduces attribute-bag patterns to CMS templates, view transitions enable native animated page navigations, and the release adds an official debugbar, a new theme translation editor, and October Boost for AI-assisted development.
Table of Contents
- How to Upgrade to v4.3
- Document Form Design
- Partials as Components
- File Attachment Import & Export
- View Transitions
- Per-Site Plugin Management
- AI Development with Boost
- Debugbar
- Theme Translation Editor
- Translatable Sync for Repeaters
- Notable Minor Changes
How to Upgrade to v4.3
There are two ways to upgrade, by clicking the Check for Updates button in the admin panel, or via console commands. For command line interface, please use the following commands:
composer update
php artisan october:migrate
php artisan october:util set build
In the event that you find some incompatibilities with your plugins due to this release, lock your composer file to the previous version (v4.2) by modifying your composer file below and then run composer update.
"require": {
"october/all": "4.2.*",
"october/rain": "4.2.*"
}
Document Form Design
The FormController behavior gains a new document display mode that renders a full-height document layout with a Vue-powered toolbar, outside fields header, and secondary tabs in a popover. This layout was previously implemented in the Tailor entries controller as a bespoke _edit.php view. It is now a reusable form design that any backend controller can adopt.
Enabling:
Set design.displayMode in your config_form.yaml:
# config_form.yaml
name: Blog Post
form: $/acme/blog/models/post/fields.yaml
modelClass: Acme\Blog\Models\Post
design:
displayMode: document
secondaryLabel: Settings # optional - popover button label, defaults to "Options"
Then call formRenderDesign() from your create.php / update.php views:
<?= $this->formRenderDesign() ?>
This single call renders the entire form including outside fields header, primary tabs, secondary fields popover, Vue toolbar, and any drawer slot content.
Default toolbar: Save, Save & Close, and Delete buttons. The save buttons disable automatically while a request is in flight and re-enable when the change monitor reports unsaved changes.
Adaptive toolbar wiring: Any form widget with span: adaptive automatically participates in the document toolbar. The FieldProcessor sets externalToolbarBus: 'document' on adaptive fields during form configuration, so widget JS (rich editor, markdown editor, file upload, media finder, repeater) injects its own toolbar buttons into the document toolbar without any manual configuration.
fields:
content:
type: richeditor
span: adaptive # toolbar buttons appear in document toolbar automatically
Secondary fields popover: When a form has secondary tabs, the document layout renders a button in the header that opens the secondary fields in a popover. The button label is read from design.secondaryLabel (defaulting to __("Options")).
Customizing the toolbar: Document toolbars use the same _form_buttons.php override mechanism as the other form designs. Drop a _form_buttons.php partial into your controller's views directory and declare your buttons with the familiar Ui::ajaxButton and Ui::button factories - the framework adapts them into the Vue toolbar automatically. No custom JS, no VueDocumentForm extension required.
<?php // plugins/acme/blog/controllers/posts/_form_buttons.php ?>
<?= Ui::ajaxButton(
label: __("Save"),
handler: 'onSave',
icon: 'icon-save-cloud',
primary: true,
hotkey: ['ctrl+s', 'cmd+s'],
dataRequestData: "redirect: 0"
) ?>
<?= Ui::ajaxButton(
label: __("Save & Close"),
handler: 'onSave',
icon: 'icon-keyboard-return',
hotkey: ['ctrl+enter', 'cmd+enter'],
dataBrowserRedirectBack: true,
dataRequestData: "close: 1"
) ?>
<?php if (!empty($pageUrl)): ?>
<?= Ui::button(
label: __("Preview"),
href: Url::to($pageUrl),
icon: 'icon-crosshairs',
target: '_blank'
) ?>
<?php endif ?>
<?= Ui::ajaxButton(
label: __("Delete"),
handler: 'onDelete',
icon: 'icon-delete',
hotkey: ['shift+option+d'],
class: 'pull-right',
dataRequestConfirm: __("Delete this post?")
) ?>
A <div class="toolbar-divider"></div> between buttons becomes a separator in the toolbar. Buttons with class: 'pull-right' are pinned to the right edge. Plain Ui::button calls with an href (such as a Preview link) work as link-type toolbar elements that open in a new tab when activated by hotkey.
For toolbars that need behavior beyond static buttons - bespoke Vue components, drawers, dynamic dropdowns - you can still provide a _form_mode_document.php partial in your controller's views directory and ship your own Vue control. Tailor's entries controller uses this escape hatch (see below).
Tailor adoption: The Tailor entries controller now uses this shared infrastructure. The previous _edit.php view has been removed and replaced with a _form_mode_document.php override that adds entry-specific header controls (publish button, draft notes) and the vue-entry-document control variant. Behavior in the Tailor editor is unchanged.
Partials as Components
CMS partials now support a {% props %} tag that separates parameters into props (template data) and attributes (HTML pass-through). This brings the same component pattern used by Laravel Blade Components and the backend Ui:: system to CMS theme templates.
Declaring props:
The {% props %} tag is placed at the top of a partial .htm file using Twig hash syntax. Keys become template variables with defaults, and everything else flows into an attributes variable:
{% props {title: null, icon: null, size: 'md'} %}
The attributes variable:
When {% props %} is present, the template receives an attributes variable - an instance of Laravel's ComponentAttributeBag. It supports smart class merging where the caller's classes are appended to the partial's defaults:
{# partials/ui/alert.htm #}
{% props {type: 'info', dismissible: false} %}
<div {{ attributes.merge({class: 'alert alert-' ~ type, role: 'alert'}) }}>
<div class="alert-content">{{ body }}</div>
{% if dismissible %}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
{% endif %}
</div>
{# Usage in a page #}
{% partial "ui/alert"
type="warning"
dismissible=true
class="mb-3"
body %}
<strong>Warning!</strong> Something needs your attention.
{% endpartial %}
Rendered output:
<div class="alert alert-warning mb-3" role="alert">
<div class="alert-content">
<strong>Warning!</strong> Something needs your attention.
</div>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
The attribute bag supports all standard methods:
| Method | Description |
|---|---|
attributes.merge({class: 'btn'}) |
Merge defaults - smart-appends class and style |
attributes.only('class', 'id') |
Keep only specific attributes |
attributes.except('class') |
Exclude specific attributes |
attributes.has('id') |
Check if an attribute exists |
attributes.get('id') |
Get a single attribute value |
An empty {% props {} %} means all parameters go to attributes - useful for wrapper elements.
Composable partials:
The existing {% put %} / {% placeholder %} system works alongside {% props %} for partials that need multiple content sections:
{# Caller #}
{% partial "card" class="shadow" body %}
{% put header %}
<h2>Card Title</h2>
{% endput %}
<p>Body content</p>
{% endpartial %}
{# card.htm #}
{% props {} %}
<div {{ attributes.merge({class: 'card'}) }}>
<div class="card-header">
{% placeholder header %}
</div>
<div class="card-body">{{ body }}</div>
</div>
Backwards compatibility: Partials without {% props %} work exactly as before - all parameters remain plain Twig variables. Existing body, only, {% put %}, and {% placeholder %} behavior is unchanged.
File Attachment Import & Export
The ImportModel and ExportModel base classes now support file attachment relations (attachOne and attachMany) natively. File handling is implemented via two new traits - EncodesZip and DecodesZip - following the same pattern as the existing EncodesCsv/EncodesJson and DecodesCsv/DecodesJson traits.
Exporting: When an export includes file attachment columns, the output is automatically packaged as a ZIP archive containing the data file (data.json or data.csv) and a files/ directory with the attachment files using human-readable filenames. Filename collisions are handled with _2, _3 suffixes. If no file attachments are included, the export behaves as before (plain JSON or CSV).
Use encodeFileRelation($model, $attr) in your export model to encode a file relation as relative paths and register the files for ZIP packaging:
public function exportData($columns, $sessionKey = null)
{
$records = Product::with(['images'])->get();
$result = [];
foreach ($records as $record) {
$item = $record->toArray();
$item['images'] = $this->encodeFileRelation($record, 'images');
$result[] = $item;
}
return $result;
}
Importing: The import file upload field now accepts .zip files. When a ZIP is uploaded, it is extracted automatically and the data file inside is located (data.json, data.csv, or any JSON/CSV file in the root). The file_format is auto-detected from the data file extension.
Use decodeFileRelation($model, $attr, $value, $sessionKey) in your import model to resolve file paths and create attachments:
public function importData($results, $sessionKey = null)
{
foreach ($results as $row => $data) {
$record = MyModel::create($data);
if ($imagesValue = array_get($data, 'images')) {
$this->decodeFileRelation($record, 'images', $imagesValue, $sessionKey);
}
}
}
File path resolution follows a priority chain:
- ZIP paths - Values starting with
files/resolve from the extracted ZIP directory - Source prefix - If
setSourcePrefix()has been called (theme seed context), paths resolve relative to that prefix - Media library - Paths resolve from
Storage::disk('media')
Theme seeding: The setSourcePrefix() method is already called by the theme seed system, so plugin import models that extend ImportModel can resolve file paths from the theme directory or media library without any additional setup. See the theme seeding documentation for examples.
Tailor entries: The RecordImport and RecordExport models handle file attachments automatically - no additional code is needed. File attachment fields defined in blueprints are encoded/decoded during import and export.
View Transitions
The AJAX framework now supports the View Transitions API, enabling smooth animated transitions between turbo-routed page navigations. When enabled, the browser captures the old and new page states and cross-fades between them natively - no JavaScript animation libraries required.
Enabling: Add the turbo-view-transition meta tag alongside the existing turbo router tag:
<head>
<meta name="turbo-visit-control" content="enable" />
<meta name="turbo-view-transition" content="same-origin" />
</head>
Browsers without View Transitions API support fall back to the standard instant page swap with no errors.
Customization: Transitions are controlled entirely through CSS using the ::view-transition-old() and ::view-transition-new() pseudo-elements. You can adjust duration, easing, or replace the default cross-fade with slides, scales, or any CSS animation.
::view-transition-old(root),
::view-transition-new(root) {
animation-duration: 0.3s;
animation-timing-function: ease-in-out;
}
Directional animations: The turbo router sets data-turbo-visit-direction on the <html> element during navigation (forward, back, or none), allowing directional slide animations that respond to navigation direction.
html[data-turbo-visit-direction="forward"]::view-transition-old(root) {
animation: slide-out-left 0.3s ease-in-out;
}
html[data-turbo-visit-direction="forward"]::view-transition-new(root) {
animation: slide-in-right 0.3s ease-in-out;
}
See the View Transitions documentation for CSS examples and usage details.
Per-Site Plugin Management
Plugins can now be enabled or disabled per site or per site group. This is useful when running multiple websites from a single installation where certain plugins should only be active on specific sites - for example, a shop plugin that should only run on shop.domain.com.
Enabling: Set the feature flag in config/multisite.php:
'features' => [
'system_plugin_sites' => true, // Per individual site
// or
'system_plugin_site_groups' => true, // Per site group
]
Clear the application cache after changing the configuration.
Usage: Navigate to Settings → Updates & Plugins → Manage Plugins. The existing enable/disable toggle becomes site-aware - it applies to whichever site is currently selected in the site picker. Switch sites to configure each one independently.
When a plugin is disabled for a site:
- Its backend navigation items and settings are hidden
- Its controllers return 404
- The site picker dropdown on plugin pages only shows sites where the plugin is enabled
The two feature flags are mutually exclusive. Use system_plugin_sites for per-site control, or system_plugin_site_groups to manage plugins at the site group level where all sites in a group share the same plugin configuration.
AI Development with Boost
The october/boost Composer package extends Laravel Boost with October CMS-specific guidelines, skills, and MCP tools for AI-assisted development. It works with Claude Code, Cursor, GitHub Copilot, Codex, Gemini CLI, and Junie.
Installation:
composer require october/boost --dev
php artisan boost:install
Select october/boost when prompted during installation. The installer configures your AI agent automatically - writing guideline files, registering the MCP server, and copying skills.
Guidelines are compiled into the agent's configuration file (e.g. CLAUDE.md) and teach the AI to follow October CMS conventions: array-based model relationships, the Validation trait, YAML-configured backend behaviors, Twig themes, and the AJAX framework. The AI will not suggest Livewire, Inertia, Blade components, or other patterns that don't apply to October CMS.
Skills are on-demand knowledge modules that activate when the AI recognizes a specific domain:
| Skill | Activation |
|---|---|
| Plugin Development | Plugin.php, migrations, version.yaml, scaffolding |
| Tailor Development | Blueprints, content fields, entry records |
| Backend Controllers | FormController, ListController, RelationController, YAML configs |
| Theme Development | Pages, layouts, partials, Twig, CMS components |
| AJAX Framework | data-request attributes, jax API, handlers, partial updates |
| Model Development | Relationships, validation, traits, model events |
MCP tools give the AI real-time access to the application:
| Tool | Purpose |
|---|---|
| SearchOctoberDocs | Search official documentation for Laravel, October CMS, Larajax, and Meloncart from their GitHub-hosted repos |
| GetBlueprints | List and inspect Tailor blueprint definitions and fields |
| GetPluginRegistration | List installed plugins with components, permissions, and navigation |
| GetThemeStructure | Inspect the active theme's pages, layouts, partials, and content files |
The SearchOctoberDocs tool replaces Laravel Boost's default documentation search with a version that searches the correct doc repositories - Laravel (version-matched), October CMS, Larajax, and Meloncart - using keyword matching with local caching. Results can be filtered by package.
See the AI Development with Boost documentation for usage details.
Debugbar
The october/debugbar Composer package integrates Laravel Debugbar (fruitcake) with October CMS, replacing the legacy rainlab/debugbar-plugin.
Installation:
composer require october/debugbar --dev
The debugbar is enabled automatically when APP_DEBUG=true. Override with the DEBUGBAR_ENABLED environment variable.
October CMS collectors: In addition to the standard Laravel Debugbar collectors (queries, timeline, session, request, mail, etc.), the following October-specific collectors are included:
| Collector | Description |
|---|---|
| Backend | Backend controller, action, parameters, and AJAX handler with file location |
| CMS | CMS page, URL, AJAX handler, and page properties with file location |
| Components | All components from the page and layout with their class and properties |
AJAX support: AJAX requests are captured automatically and displayed in the toolbar dropdown. The package includes an InterpretsAjaxExceptions middleware that catches AJAX exceptions and embeds debugbar data in response headers, controlled by the capture_ajax setting in config/debugbar.php.
Configuration: Publish the configuration file with php artisan vendor:publish --provider="October\Debugbar\ServiceProvider" --tag=config, or create config/debugbar.php manually. The configuration extends fruitcake's default config with sensible defaults for October CMS (e.g. views and events collectors disabled by default).
See the Debugbar documentation for full usage details.
Theme Translation Editor
The CMS editor now includes a Languages section for managing theme translation files directly from the backend. Theme translations live in the lang/ directory as JSON files (e.g. en.json, fr.json) and are used by the Twig __() and trans() functions to localize frontend content.
Spreadsheet editor: Language files open in a key/value spreadsheet rather than a raw JSON editor. Each row represents a translation key and its value, making it easy to see and edit all translations at a glance. Rows can be added and removed via toolbar buttons.
Search: A toggleable search bar filters the spreadsheet in real time, highlighting matching cells. Press Enter to cycle through results.
New file pre-population: When creating a new language file, the editor automatically loads the translation keys from the theme's default language file, so translators start with the complete set of keys ready to fill in.
Toolbar: Save, rename, delete, insert/delete rows, search, and template info - all accessible from the document toolbar with keyboard shortcuts.
The Languages section appears in the CMS editor sidebar alongside Pages, Layouts, Partials, Content, and Assets. Access is controlled by the editor.cms_langs permission.
Translatable Sync for Repeaters
Tailor repeater fields now support a translatable: sync mode that keeps the repeater structure (items, order) synced across all sites while allowing individual sub-fields to have different values per site. This solves a common need in multilingual content - for example, a FAQ where the questions and answers are translated but the list of items stays consistent across languages.
Blueprint configuration:
handle: Blog\Post
type: stream
multisite: sync
fields:
attachments:
type: repeater
translatable: sync
form:
fields:
file_title:
type: text
translatable: true # Different value per site
file_desc:
type: textarea # Shared across all sites
file:
type: fileupload
mode: file
maxFiles: 1 # Shared file across all sites
localized_image:
type: fileupload
mode: image
maxFiles: 1
translatable: true # Different file per site
How it works:
The translatable property on repeater fields now accepts three values:
| Value | Behavior |
|---|---|
true (default) |
Each site has fully independent repeater items |
false |
All sites share the exact same repeater items (shared rows) |
sync |
Items are replicated per site with structure synced - adding, removing, or reordering items on one site propagates to all others |
When translatable: sync is set, sub-fields control their own translatability:
- Scalar sub-fields with
translatable: truehave independent values per site (e.g. translated text). Sub-fields withouttranslatable: trueare shared and their values propagate across sites. - Relation sub-fields (file uploads, entries) with
translatable: truehave independent attachments per site. Non-translatable relation sub-fields share attachments across all site variants automatically.
Translatable sub-fields display a globe icon in the backend form, consistent with how translatable fields work on the parent entry.
Propagation: Structural changes propagate when the parent record is saved, using the same propagation mechanism as other multisite-synced content. The "leader" site (whichever saves) pushes its structure to all other sites. Non-translatable scalar values are copied; translatable values are left untouched on each site.
Notable Minor Changes
Twig translation functions
Theme translations now use the __() and trans() Twig functions instead of the |_ and |trans filters, bringing Twig syntax closer to the familiar PHP __() and trans() helpers.
{{ __("Hello World") }}
The function syntax takes an English string as the key and resolves translations from JSON files in the theme's lang/ directory (e.g. fr.json). The trans_choice() function is also available for pluralization. The old filter syntax ({{ "text"|_ }}, {{ "text"|trans }}, {{ "text"|transchoice }}) is deprecated.
RainLab.Blog plugin refresh
The official rainlab/blog plugin has been updated to align with v4.3 patterns: posts and categories now use the new displayMode: document form design, controller view files have been migrated from .htm to .php, toolbar markup uses the Ui:: component factories, and the blog settings model has been renamed to the singular Setting convention.
The post edit screen ships a custom _form_buttons.php partial that adds a Preview button to the document toolbar - a working reference for plugin authors adopting the new override mechanism.
This is the end of the document, you may read the announcement blog post or visit the changelog for more information.