Version 4.4 introduces user content submissions for Tailor, brings Vue components to the frontend, completes the database-driven themes story for cloud deployments, and adds inline snippets for content editors.
Table of Contents
How to Upgrade to v4.4
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:
php artisan october:update
In the event that you find some incompatibilities with your plugins due to this release, lock your composer file to the previous version (v4.3) by modifying your composer file below and then run composer update.
"require": {
"october/all": "4.3.*",
"october/rain": "4.3.*"
}
User Content Submissions
Tailor gains a new submission blueprint type for accepting user generated content from the frontend, such as blog comments, contact form submissions and product reviews. The blueprint defines the fields, a new submission CMS component captures the input, and records arrive in the admin panel as a moderation queue.
handle: Blog\Comment
type: submission
name: Comment
submission:
titleTemplate: '{{ author_name }} on {{ record.post.title }}'
fields:
author_name:
label: Name
type: text
validation: required|min:2|max:100
author_email:
label: Email Address
type: email
validation: required|email
content:
label: Comment
type: textarea
validation: required|min:5|max:2000
post:
label: Post
type: entries
source: Blog\Post
maxItems: 1
Frontend component: the submission component renders a complete form generated from the blueprint fields, or the theme can supply custom markup posting to the onFormSubmit AJAX handler. Field validation rules from the blueprint are enforced on submission and returned as AJAX field errors. After a successful submission the formSubmitted and formModel variables become available to the partial.
[submission commentForm]
handle = "Blog\Comment"
{% component 'commentForm' %}
File upload fields are supported by adding the data-request-files attribute to the form tag, with uploads validated against the maximum upload size, the field fileTypes extension allowlist and the maxFiles count, including SVG sanitisation.
Moderation workflow: submissions arrive with a Pending status and stay hidden from the frontend until approved. The submissions list provides Approve and Reject bulk actions, where rejecting is a soft delete that can be restored. The Spam action also rejects other pending submissions received from the same IP address within a configurable window (spamSweepDays, default 30 days), and approved records are never affected by the sweep. Rejected submissions are deleted forever after a retention period (purgeRejectedDays, default 30 days), cleaned up automatically when viewing the submissions list.
Record titles: the titleTemplate property builds the record title from submitted values using Twig, with every field in scope and a record variable for accessing relations. Without a template, the title falls back to common fields (name, subject, author_name, full_name, email) before generating a random reference.
Spam protection: the component ships with a honeypot field and per-IP rate limiting (6 submissions per minute, override formGetThrottleRate on the component to change it). Every submission captures the visitor IP address and user agent in the submitted_ip and submitted_user_agent attributes, available as a list column, filter scope and read-only form fields.
Events: the new cms.form.beforeSubmit event fires before the record saves and throwing an exception rejects the submission, providing the integration point for spam scoring services, CAPTCHA verification and blocklists. The cms.form.submitSuccess event fires after the record saves, useful for sending notifications.
Event::listen('cms.form.beforeSubmit', function ($component, $model) {
if (SpamService::isSpam($model)) {
throw new ValidationException(['content' => 'Submission rejected.']);
}
});
See the submission component documentation for full details.
Vue Components in CMS Themes
The October-Vue component pattern that powers the admin panel is now available to CMS themes. A Vue component is a PHP class paired with a template partial and a JavaScript ES module, and a CMS component can register one during its life cycle. October delivers two things to the page, the Vue library and the registered components, and the theme stays in control of mounting the application.
Scaffolding: the new create:vuecomponent command generates the component class, template partial, and asset files, ready for use in the backend panel or a CMS theme.
php artisan create:vuecomponent Acme.Blog PostViewer
Registering: a CMS component calls registerVueComponent and the call forwards to the CMS controller, which keeps a single per-page registry with deduplication and automatic resolution of $require dependencies, exactly like a backend controller.
class MyComponent extends ComponentBase
{
public function init()
{
$this->registerVueComponent(\Acme\Blog\VueComponents\PostViewer::class);
}
}
Registering in the init method makes the component available during both page renders and AJAX requests. The onRun method also works when the component is only needed for the initial page render.
Twig tags: the feature is delivered by two decoupled tags. The vue option on the framework tag loads the Vue 3 library and exposes it as the Vue global, using the development build when debug mode is on. If the tag is omitted, the theme can include its own Vue 3 build and expose it as window.Vue.
The new {% vuecomponents %} tag outputs the component templates and registration code, together with the oc.createVueApp and oc.mountVueApp factory functions. It is placed near the end of the page, before any script that mounts an application.
<div id="app">
<acme-blog-post-viewer :post-id="7"></acme-blog-post-viewer>
</div>
{% vuecomponents %}
<script type="module">
oc.mountVueApp('#app');
</script>
AJAX support: Vue components registered during an AJAX request, for example by a component inside an updated partial, are sent through the AJAX asset pipeline and registered on the client before DOM patching occurs, matching the existing backend behavior. Mounting an application over the updated markup remains the responsibility of the page.
Component classes: frontend component JavaScript modules should read the Vue global (const { ref } = Vue) instead of using bare imports (import { ref } from 'vue'), since bare module specifiers only resolve in the backend panel.
As part of this change, the Vue infrastructure moved to the System module: Backend\Classes\VueComponentBase is now System\Classes\VueComponentBase and Backend\Traits\VueMaker is now System\Traits\VueMaker. The Backend classes remain in place as aliases, so existing plugins continue to work unchanged, although instanceof checks against the Backend class name will not match components extending the System base directly.
See the Vue components documentation for full details.
Database-Driven Theme Assets
Theme assets (CSS, JavaScript, images, fonts) can now be published to a shared storage disk with database tracking, completing the database-driven themes story for multi-instance deployments. Where CMS templates already persist to the database, asset edits previously only landed on the local filesystem of the instance that made them. With this feature enabled, an asset edited in the CMS editor is live across every instance immediately.
How it works: asset bytes are stored on a dedicated assets filesystem disk (typically S3 fronted by a CDN) and each change is tracked by a row in the cms_source_files table. The ASSET_URL environment variable points at the same origin as the disk, so URLs generated by asset() resolve to the published location with no changes to October's asset pipeline - no resolver layer, no combiner overrides, no PHP-served asset routes.
Enabling:
Three pieces need to be in place. First, define the assets disk in config/filesystems.php:
'assets' => [
'driver' => 's3',
'key' => env('AWS_ACCESS_KEY_ID'),
'secret' => env('AWS_SECRET_ACCESS_KEY'),
'region' => env('AWS_DEFAULT_REGION'),
'bucket' => env('AWS_ASSETS_BUCKET'),
'url' => env('ASSET_URL'),
'visibility' => 'public',
],
Second, point ASSET_URL at the same origin as the disk. Both read from the same variable so they cannot drift:
ASSET_URL=https://cdn.example.com
Third, enable the feature flag. It defaults to off, so a fresh checkout requires no cloud credentials to run:
For local development, swap the disk to a local driver pointed at a public path with ASSET_URL aligned to its URL. The editor save path and console commands are unchanged, only the disk implementation swaps.
Editor behavior: when the layer is enabled, all asset operations in the CMS editor route through the disk and database instead of the local filesystem:
| Operation |
Behavior |
| Save |
Bytes are written to the disk and a tracking row is upserted |
| Upload |
Same as save, with SVG sanitisation and mime detection preserved |
| Delete |
The row is tombstoned and the disk object removed |
| Rename / Move |
Contents are re-keyed on the disk, the old path is tombstoned |
| New directory |
A placeholder row is stored so the folder appears on every instance |
Reads check the database first and fall back to the filesystem, so files that ship with the deploy continue to be served without a row. A tombstone hides the filesystem copy from listings and reads, so deletes propagate across instances even when the on-disk copy cannot be removed. Directory renames and moves re-key every file beneath the prefix, so shipped assets keep working at their new URLs on all instances without touching the local filesystem.
Publishing on deployment: the october:mirror command gains a --disk option that uploads all theme, module, plugin, and app asset directories to a filesystem disk. Run it from the deployment pipeline so the disk always reflects the current codebase:
php artisan october:mirror --disk=assets
The upload is additive only - files are created or overwritten, never deleted, which removes any risk of a mirror run taking down a live asset. Orphaned keys can be cleaned up with object storage lifecycle rules if desired. Unchanged files are skipped using a size comparison against a single remote listing. Supporting options:
| Option |
Description |
--checksum |
Compare content hashes instead of file sizes |
--force |
Upload every file, even when unchanged |
--dry-run |
List what would be uploaded without uploading |
The command shares its path inventory and the system.console.mirror.extendPaths event with the existing symlink mode, so plugins that extend the mirror paths are published automatically. In disk mode, storage/* paths and root files (index.php, .htaccess) are excluded after the event fires, since these never belong in an asset bucket.
CDN cache invalidation: every asset change fires the cms.asset.invalidate event with the theme and the changed disk keys. The core stays CDN-agnostic; listen to the event to purge your provider:
Event::listen('cms.asset.invalidate', function ($theme, $diskPaths) {
MyCdnProvider::invalidate($diskPaths);
});
Importing back to the filesystem: the theme:copy --import-db command writes asset rows back to the theme directory, streaming bytes from the disk, and applies tombstones by deleting the corresponding on-disk files. This closes the loop for workflows where git remains the durable snapshot: import, commit the diff, deploy, and the next mirror run reflects the merged state. The --purge-db option removes the rows once imported, leaving the disk objects in place since they now match the codebase.
php artisan theme:copy demo --import-db --purge-db
See the database-driven themes documentation for full details.
Database Layer for Language Files & Blueprints
The cms.database_templates layer now extends beyond CMS templates to cover theme language files and Tailor blueprints, using the same database-first read path and tombstone semantics.
Storage: both are stored in the new cms_source_files table, consumed via the October\Rain\Halcyon\SourceFile model and its CMS-scoped subclass Cms\Models\SourceFile. A row represents one file, identified by a (source, path) pair - for example theme.demo.lang with fr.json, or app.blueprint with blog/post.yaml. This is a sibling primitive to the Halcyon model: where Halcyon handles compound template files parsed into sections, SourceFile handles non-compound files where the bytes are opaque. Content is stored inline for text files or by reference to a Storage disk for binaries, which is the mode used by theme assets above.
Language files: when the database layer is active for a theme, language file reads, writes, and deletes in the CMS editor route through the database. At runtime, DB-backed language strings are registered directly with the translator during theme boot, so __() calls in Twig resolve database content without touching the filesystem. A tombstoned locale suppresses the on-disk JSON file entirely.
Blueprints: Tailor blueprints from all three datasources - app (app/blueprints), themes, and plugins - are layered through the database with source identifiers derived from the owning datasource (app.blueprint, theme.{dir}.blueprint, plugin.{author}.{name}.blueprint). Editor file operations (create, save, rename, move, delete, upload) route through the layer, and the BlueprintIndexer consults the database updated_at timestamps alongside filesystem mtimes for its debug-mode cache invalidation, so blueprint changes made on one instance are picked up everywhere.
Round-trip: theme:copy --import-db imports templates, language files, assets, and blueprints in a single pass, and --purge-db clears all of the corresponding rows including tombstones.
Inline Snippets
Snippets can now be inserted inline within a line of text, in addition to the existing block insertion. Where a block snippet occupies its own line, an inline snippet sits within the surrounding text, which suits small pieces of content such as a phone number, a price or a formatted value.
Enabling for a partial: the partial Snippet settings gain an Inline Snippet checkbox alongside the existing AJAX option. It is stored as snippetInline in the partial view bag.
[viewBag]
snippetCode = "inlineLabel"
snippetName = "Inline Label"
snippetInline = 1
Enabling for a component: set snippetInline to true in componentDetails(), in the same way snippetAjax is defined.
public function componentDetails()
{
return [
// ...
'snippetInline' => true
];
}
Markup: an inline snippet is inserted as an inline element rather than a block, so the snippet should render an inline element such as a <span> to sit correctly within the text.
<span class="price">{{ amount }}</span>
Editor behavior: in the rich editor an inline snippet appears as a chip within the line, and can be moved and deleted like a single character in the surrounding text. Block snippets are unchanged, and the option defaults to false, so existing snippets continue to render as blocks.
See the snippets documentation for full details.
Notable Minor Changes
Child themes inherit parent theme blueprints
Child themes now inherit Tailor blueprints from their parent theme. Blueprints in a parent theme's blueprints/ directory (or its database layer) are picked up automatically when the child theme is active - they resolve by handle, appear in the backend navigation, and work with the page finder. When both themes define a blueprint with the same UUID, the child theme version takes priority.
Seed content is also inherited: the Seed Content option now appears for a child theme when its parent contains a seeds/ directory, importing the parent's blueprints, data, and translations. A child theme with its own seeds/ directory uses that instead.
Media Finder copy and paste
The Media Finder form widget gains an optional useCopyPaste property for multiple selection mode. When enabled, the toolbar shows Select All, Copy Selected and Paste buttons.
media_gallery:
label: Gallery
type: mediafinder
mode: image
maxItems: 10
useCopyPaste: true
Copied items are held in browser storage and can be pasted into any Media Finder field that also has the property enabled. Items already present in the target field are skipped, and the maxItems limit is enforced on paste. The property defaults to false, so existing fields are unaffected.
Scaffolding command for themes
A new create:theme command scaffolds a theme directory with a starter layout, home page, and the supporting theme.yaml, version.yaml and composer.json files. The argument is the theme name, which is converted to a directory slug.
php artisan create:theme "My Theme"
Pass the --overwrite option to replace existing files when regenerating a theme.
Editor filesystem functions deprecated
The Editor\Traits\FileSystemFunctions trait is deprecated. Editor CRUD logic has moved to domain-specific operation traits: CMS asset operations live on Cms\Classes\Asset and Tailor blueprint operations live on Tailor\Classes\Blueprint. New code should call these operations through the model classes so cross-cutting concerns, such as the database layer, apply consistently.
Str facade resolves directly to its helper class
The Str global alias now resolves directly to the October\Rain\Support\Str helper class instead of routing through the container, matching how Laravel handles it. Calls such as Str::slug() are now plain static calls with no container round-trip, and the string container binding has been removed. The October\Rain\Support\Facades\Str facade is retained as deprecated for backwards compatibility, so existing code that imports it continues to work; new code should reference the helper class directly.
This is the end of the document, you may read the announcement blog post or visit the changelog for more information.
Version 4.4 introduces user content submissions for Tailor, brings Vue components to the frontend, completes the database-driven themes story for cloud deployments, and adds inline snippets for content editors.
Table of Contents
How to Upgrade to v4.4
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:
In the event that you find some incompatibilities with your plugins due to this release, lock your composer file to the previous version (v4.3) by modifying your composer file below and then run
composer update.User Content Submissions
Tailor gains a new
submissionblueprint type for accepting user generated content from the frontend, such as blog comments, contact form submissions and product reviews. The blueprint defines the fields, a newsubmissionCMS component captures the input, and records arrive in the admin panel as a moderation queue.Frontend component: the
submissioncomponent renders a complete form generated from the blueprint fields, or the theme can supply custom markup posting to theonFormSubmitAJAX handler. Field validation rules from the blueprint are enforced on submission and returned as AJAX field errors. After a successful submission theformSubmittedandformModelvariables become available to the partial.{% component 'commentForm' %}File upload fields are supported by adding the
data-request-filesattribute to the form tag, with uploads validated against the maximum upload size, the fieldfileTypesextension allowlist and themaxFilescount, including SVG sanitisation.Moderation workflow: submissions arrive with a Pending status and stay hidden from the frontend until approved. The submissions list provides Approve and Reject bulk actions, where rejecting is a soft delete that can be restored. The Spam action also rejects other pending submissions received from the same IP address within a configurable window (
spamSweepDays, default 30 days), and approved records are never affected by the sweep. Rejected submissions are deleted forever after a retention period (purgeRejectedDays, default 30 days), cleaned up automatically when viewing the submissions list.Record titles: the
titleTemplateproperty builds the record title from submitted values using Twig, with every field in scope and arecordvariable for accessing relations. Without a template, the title falls back to common fields (name,subject,author_name,full_name,email) before generating a random reference.Spam protection: the component ships with a honeypot field and per-IP rate limiting (6 submissions per minute, override
formGetThrottleRateon the component to change it). Every submission captures the visitor IP address and user agent in thesubmitted_ipandsubmitted_user_agentattributes, available as a list column, filter scope and read-only form fields.Events: the new
cms.form.beforeSubmitevent fires before the record saves and throwing an exception rejects the submission, providing the integration point for spam scoring services, CAPTCHA verification and blocklists. Thecms.form.submitSuccessevent fires after the record saves, useful for sending notifications.See the submission component documentation for full details.
Vue Components in CMS Themes
The October-Vue component pattern that powers the admin panel is now available to CMS themes. A Vue component is a PHP class paired with a template partial and a JavaScript ES module, and a CMS component can register one during its life cycle. October delivers two things to the page, the Vue library and the registered components, and the theme stays in control of mounting the application.
Scaffolding: the new
create:vuecomponentcommand generates the component class, template partial, and asset files, ready for use in the backend panel or a CMS theme.Registering: a CMS component calls
registerVueComponentand the call forwards to the CMS controller, which keeps a single per-page registry with deduplication and automatic resolution of$requiredependencies, exactly like a backend controller.Registering in the
initmethod makes the component available during both page renders and AJAX requests. TheonRunmethod also works when the component is only needed for the initial page render.Twig tags: the feature is delivered by two decoupled tags. The
vueoption on the framework tag loads the Vue 3 library and exposes it as theVueglobal, using the development build when debug mode is on. If the tag is omitted, the theme can include its own Vue 3 build and expose it aswindow.Vue.{% framework vue %}The new
{% vuecomponents %}tag outputs the component templates and registration code, together with theoc.createVueAppandoc.mountVueAppfactory functions. It is placed near the end of the page, before any script that mounts an application.AJAX support: Vue components registered during an AJAX request, for example by a component inside an updated partial, are sent through the AJAX asset pipeline and registered on the client before DOM patching occurs, matching the existing backend behavior. Mounting an application over the updated markup remains the responsibility of the page.
Component classes: frontend component JavaScript modules should read the
Vueglobal (const { ref } = Vue) instead of using bare imports (import { ref } from 'vue'), since bare module specifiers only resolve in the backend panel.As part of this change, the Vue infrastructure moved to the System module:
Backend\Classes\VueComponentBaseis nowSystem\Classes\VueComponentBaseandBackend\Traits\VueMakeris nowSystem\Traits\VueMaker. The Backend classes remain in place as aliases, so existing plugins continue to work unchanged, althoughinstanceofchecks against the Backend class name will not match components extending the System base directly.See the Vue components documentation for full details.
Database-Driven Theme Assets
Theme assets (CSS, JavaScript, images, fonts) can now be published to a shared storage disk with database tracking, completing the database-driven themes story for multi-instance deployments. Where CMS templates already persist to the database, asset edits previously only landed on the local filesystem of the instance that made them. With this feature enabled, an asset edited in the CMS editor is live across every instance immediately.
How it works: asset bytes are stored on a dedicated
assetsfilesystem disk (typically S3 fronted by a CDN) and each change is tracked by a row in thecms_source_filestable. TheASSET_URLenvironment variable points at the same origin as the disk, so URLs generated byasset()resolve to the published location with no changes to October's asset pipeline - no resolver layer, no combiner overrides, no PHP-served asset routes.Enabling:
Three pieces need to be in place. First, define the
assetsdisk inconfig/filesystems.php:Second, point
ASSET_URLat the same origin as the disk. Both read from the same variable so they cannot drift:Third, enable the feature flag. It defaults to off, so a fresh checkout requires no cloud credentials to run:
For local development, swap the disk to a
localdriver pointed at a public path withASSET_URLaligned to its URL. The editor save path and console commands are unchanged, only the disk implementation swaps.Editor behavior: when the layer is enabled, all asset operations in the CMS editor route through the disk and database instead of the local filesystem:
Reads check the database first and fall back to the filesystem, so files that ship with the deploy continue to be served without a row. A tombstone hides the filesystem copy from listings and reads, so deletes propagate across instances even when the on-disk copy cannot be removed. Directory renames and moves re-key every file beneath the prefix, so shipped assets keep working at their new URLs on all instances without touching the local filesystem.
Publishing on deployment: the
october:mirrorcommand gains a--diskoption that uploads all theme, module, plugin, and app asset directories to a filesystem disk. Run it from the deployment pipeline so the disk always reflects the current codebase:The upload is additive only - files are created or overwritten, never deleted, which removes any risk of a mirror run taking down a live asset. Orphaned keys can be cleaned up with object storage lifecycle rules if desired. Unchanged files are skipped using a size comparison against a single remote listing. Supporting options:
--checksum--force--dry-runThe command shares its path inventory and the
system.console.mirror.extendPathsevent with the existing symlink mode, so plugins that extend the mirror paths are published automatically. In disk mode,storage/*paths and root files (index.php,.htaccess) are excluded after the event fires, since these never belong in an asset bucket.CDN cache invalidation: every asset change fires the
cms.asset.invalidateevent with the theme and the changed disk keys. The core stays CDN-agnostic; listen to the event to purge your provider:Importing back to the filesystem: the
theme:copy --import-dbcommand writes asset rows back to the theme directory, streaming bytes from the disk, and applies tombstones by deleting the corresponding on-disk files. This closes the loop for workflows where git remains the durable snapshot: import, commit the diff, deploy, and the next mirror run reflects the merged state. The--purge-dboption removes the rows once imported, leaving the disk objects in place since they now match the codebase.See the database-driven themes documentation for full details.
Database Layer for Language Files & Blueprints
The
cms.database_templateslayer now extends beyond CMS templates to cover theme language files and Tailor blueprints, using the same database-first read path and tombstone semantics.Storage: both are stored in the new
cms_source_filestable, consumed via theOctober\Rain\Halcyon\SourceFilemodel and its CMS-scoped subclassCms\Models\SourceFile. A row represents one file, identified by a(source, path)pair - for exampletheme.demo.langwithfr.json, orapp.blueprintwithblog/post.yaml. This is a sibling primitive to the Halcyon model: where Halcyon handles compound template files parsed into sections,SourceFilehandles non-compound files where the bytes are opaque. Content is stored inline for text files or by reference to a Storage disk for binaries, which is the mode used by theme assets above.Language files: when the database layer is active for a theme, language file reads, writes, and deletes in the CMS editor route through the database. At runtime, DB-backed language strings are registered directly with the translator during theme boot, so
__()calls in Twig resolve database content without touching the filesystem. A tombstoned locale suppresses the on-disk JSON file entirely.Blueprints: Tailor blueprints from all three datasources - app (
app/blueprints), themes, and plugins - are layered through the database with source identifiers derived from the owning datasource (app.blueprint,theme.{dir}.blueprint,plugin.{author}.{name}.blueprint). Editor file operations (create, save, rename, move, delete, upload) route through the layer, and theBlueprintIndexerconsults the databaseupdated_attimestamps alongside filesystem mtimes for its debug-mode cache invalidation, so blueprint changes made on one instance are picked up everywhere.Round-trip:
theme:copy --import-dbimports templates, language files, assets, and blueprints in a single pass, and--purge-dbclears all of the corresponding rows including tombstones.Inline Snippets
Snippets can now be inserted inline within a line of text, in addition to the existing block insertion. Where a block snippet occupies its own line, an inline snippet sits within the surrounding text, which suits small pieces of content such as a phone number, a price or a formatted value.
Enabling for a partial: the partial Snippet settings gain an Inline Snippet checkbox alongside the existing AJAX option. It is stored as
snippetInlinein the partial view bag.Enabling for a component: set
snippetInlinetotrueincomponentDetails(), in the same waysnippetAjaxis defined.Markup: an inline snippet is inserted as an inline element rather than a block, so the snippet should render an inline element such as a
<span>to sit correctly within the text.Editor behavior: in the rich editor an inline snippet appears as a chip within the line, and can be moved and deleted like a single character in the surrounding text. Block snippets are unchanged, and the option defaults to
false, so existing snippets continue to render as blocks.See the snippets documentation for full details.
Notable Minor Changes
Child themes inherit parent theme blueprints
Child themes now inherit Tailor blueprints from their parent theme. Blueprints in a parent theme's
blueprints/directory (or its database layer) are picked up automatically when the child theme is active - they resolve by handle, appear in the backend navigation, and work with the page finder. When both themes define a blueprint with the same UUID, the child theme version takes priority.Seed content is also inherited: the Seed Content option now appears for a child theme when its parent contains a
seeds/directory, importing the parent's blueprints, data, and translations. A child theme with its ownseeds/directory uses that instead.Media Finder copy and paste
The Media Finder form widget gains an optional
useCopyPasteproperty for multiple selection mode. When enabled, the toolbar shows Select All, Copy Selected and Paste buttons.Copied items are held in browser storage and can be pasted into any Media Finder field that also has the property enabled. Items already present in the target field are skipped, and the
maxItemslimit is enforced on paste. The property defaults tofalse, so existing fields are unaffected.Scaffolding command for themes
A new
create:themecommand scaffolds a theme directory with a starter layout, home page, and the supportingtheme.yaml,version.yamlandcomposer.jsonfiles. The argument is the theme name, which is converted to a directory slug.php artisan create:theme "My Theme"Pass the
--overwriteoption to replace existing files when regenerating a theme.Editor filesystem functions deprecated
The
Editor\Traits\FileSystemFunctionstrait is deprecated. Editor CRUD logic has moved to domain-specific operation traits: CMS asset operations live onCms\Classes\Assetand Tailor blueprint operations live onTailor\Classes\Blueprint. New code should call these operations through the model classes so cross-cutting concerns, such as the database layer, apply consistently.Str facade resolves directly to its helper class
The
Strglobal alias now resolves directly to theOctober\Rain\Support\Strhelper class instead of routing through the container, matching how Laravel handles it. Calls such asStr::slug()are now plain static calls with no container round-trip, and thestringcontainer binding has been removed. TheOctober\Rain\Support\Facades\Strfacade is retained as deprecated for backwards compatibility, so existing code that imports it continues to work; new code should reference the helper class directly.This is the end of the document, you may read the announcement blog post or visit the changelog for more information.