October CMS 4.2 - Modernization Release

Release Note 41

Version 4.2 of October CMS introduces a significant modernization of the frontend architecture. The administration panel has been upgraded to Vue 3 and the JavaScript layer has been migrated to native ES Modules (ESM), eliminating the need for build tools like Webpack or Laravel Mix.

Table of Contents

How to Upgrade to v4.2

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.1) by modifying your composer file below and then run composer update.

"require": {
    "october/all": "4.1.*",
    "october/rain": "4.1.*"
}

Vue 3 Upgrade

The backend administration panel has been upgraded from Vue 2 to Vue 3. This is a complete overhaul of the Vue layer that powers the editor, dashboard, and various interactive components throughout the admin panel.

Key changes for plugin developers:

  • Vue components now use the Vue 3 Composition API and Options API patterns
  • The Vue.extend() pattern has been replaced with standard component definitions
  • The Vue.set() reactive helper is no longer needed (Vue 3 uses Proxy-based reactivity)
  • The $children property has been removed in favor of template refs
  • The event bus has been migrated from a Vue instance to mitt, a tiny event emitter library

If your plugin includes custom Vue components for the backend, you will need to update them for Vue 3 compatibility. The Vue 3 migration guide provides detailed information on the changes:

Event Bus Migration:

// Old way (Vue 2)
this.$bus.$on('eventName', handler);
this.$bus.$emit('eventName', data);

// New way (Vue 3 with mitt)
import mitt from 'mitt';
const emitter = mitt();
emitter.on('eventName', handler);
emitter.emit('eventName', data);

Native ES Modules (ESM)

October CMS v4.2 embraces native browser ES Modules for JavaScript delivery. This means JavaScript files are loaded directly by the browser using import statements without requiring bundling or transpilation.

The backend now includes an import map that defines module aliases for common libraries:

<script type="importmap">
{
    "imports": {
        "larajax": "/modules/system/assets/js/framework.esm.js",
        "bootstrap": "/modules/system/assets/vendor/bootstrap/bootstrap.esm.js",
        "vue": "/modules/system/assets/vendor/vue/vue.esm.js",
        "vue-router": "/modules/system/assets/vendor/vue-router/vue-router.esm.js",
        "js-cookie": "/modules/system/assets/vendor/js-cookie/js.cookie.esm.js",
        "sortablejs": "/modules/system/assets/vendor/sortablejs/sortable.esm.js",
        "dropzone": "/modules/system/assets/vendor/dropzone/dropzone.esm.js",
        "chart.js": "/modules/system/assets/vendor/chartjs/chart.esm.js",
        "p-queue": "/modules/system/assets/vendor/p-queue/p-queue.esm.js"
    }
}
</script>

This allows your JavaScript code to use clean import statements:

import { createApp } from 'vue';
import Cookies from 'js-cookie';
import Sortable from 'sortablejs';

Benefits of ESM:

  • No build step required for development
  • Native browser caching of individual modules
  • Better debugging with source files (no source maps needed)
  • Simplified development workflow

Plugin developers can now write modern JavaScript without a build process. Simply use ES module syntax in your JavaScript files:

// plugins/acme/blog/assets/js/admin.js
export function initBlogAdmin() {
    // Your code here
}

HTTP/2 Recommendation

With the move to native ES Modules, we now recommend using HTTP/2 for your web server. HTTP/2 provides multiplexing, which allows multiple JavaScript module files to be loaded efficiently over a single connection.

While HTTP/1.1 will continue to work, the performance benefits of ES Modules are best realized with HTTP/2, as it eliminates the overhead of multiple connections when loading many small module files.

Most modern hosting environments and web servers (Nginx, Apache 2.4+, Caddy) support HTTP/2 out of the box. Cloud providers like AWS, Cloudflare, and Vercel enable HTTP/2 by default.

Build System Changes

The root-level build configuration has been removed:

  • Removed: package.json, webpack.mix.js, webpack.config.js, webpack.helpers.js
  • Removed: Individual module .mix.js files (backend.mix.js, cms.mix.js, etc.)

A new lightweight build system has been introduced in the modules/system directory for building vendor assets:

cd modules/system
npm install
npm run build

This build script uses esbuild for fast bundling of vendor libraries and sass/less compilers for stylesheets. It is only needed when updating vendor dependencies and is not required for day-to-day development.

For plugin developers: You no longer need to configure Webpack or Laravel Mix for your plugins. Write modern JavaScript using ES modules directly, and they will work in the browser without a build step.

Security Improvements

This release includes security hardening for the backend:

  • Editor Markup Classes: The rich editor and code editor settings now include secure defaults for HTML tag and attribute allowlists, helping prevent XSS attacks through editor content.

  • Event Log Mail Preview: The mail preview functionality in the event logs has been secured to prevent potential content injection.

Data Table Modernization

The Data Table form widget has been rebuilt from the ground up, replacing the legacy custom table implementation with a modern, spreadsheet-like editing experience.

New features:

  • Richer column types — in addition to string, checkbox, and dropdown, the widget now supports numeric, autocomplete, date, and time column types.
  • Column dependencies — columns can depend on other columns using the dependsOn property. When a parent column value changes, the dependent column is automatically cleared and its options are refreshed via AJAX.
  • Inline and AJAX options — dropdown and autocomplete columns support inline options defined in YAML, or dynamic AJAX options loaded from the model using getDataTableOptions or get{FieldName}DataTableOptions methods.
  • Context menu — right-click to insert rows above/below, remove rows, or undo/redo changes.
  • Search — enable a search box above the table with the searching property to filter rows.
  • Manual column resizing — columns can be resized by dragging the column header borders.
  • Row reordering — enable manual row dragging with the sorting property.
  • Undo/Redo — full undo and redo support via keyboard shortcuts (Ctrl+Z / Ctrl+Y) and the context menu.
  • Copy/Paste — native clipboard support for copying and pasting cell data.

Example configuration:

rates:
    type: datatable
    adding: true
    deleting: true
    searching: false
    columns:
        name:
            type: string
            title: Name
        price:
            type: numeric
            title: Price
            width: 100px
        country:
            type: dropdown
            title: Country
        state:
            type: dropdown
            title: State
            dependsOn: country
        is_active:
            type: checkbox
            title: Active

Legacy mode: The previous table implementation is still available by setting useLegacy: true on the field definition. This is intended as a temporary compatibility option and will be removed in a future release. We recommend migrating to the new implementation.

For full documentation, see the Data Table form widget documentation.

Relation Widget Quick Create

The Relation form widget now supports a quickCreate property that adds a "Create New" option to the dropdown for singular relations (belongsTo, hasOne, morphOne). Selecting it opens a popup form to create a new related record on the fly, without leaving the page.

manufacturer:
    label: Manufacturer
    type: relation
    nameFrom: title
    quickCreate: $/acme/shop/models/manufacturer/fields.yaml

The quickCreate property accepts a string path to a form fields definition file for the simplest usage, or an object with extended options for customizing the option text, popup title, form context, and popup size.

manufacturer:
    label: Manufacturer
    type: relation
    nameFrom: title
    quickCreate:
        fields: $/acme/shop/models/manufacturer/fields.yaml
        optionText: Create New Manufacturer
        title: New Manufacturer
        popupSize: large

For full documentation, see the Relation widget documentation.

Quick Translation for Form Fields

Form fields now support quick translation, bringing back a familiar workflow from October CMS v1's Translate plugin. When a form field is marked as translatable, a globe icon appears next to the field label. Clicking it reveals a dropdown listing all other available locales (the current locale is excluded).

Selecting a locale opens a popup where you can view and edit that locale's value on the fly, without leaving the page. This works with every field type — text, textarea, rich editor, repeater, and so on — because each popup renders a full Form widget instance scoped to the selected locale.

This feature is compatible with both Multisite (where each locale has its own database record) and RainLab.Translate (where translations are stored as JSON attributes on the same record). The globe icon only appears for existing records and only shows locales where a translation record already exists.

To make a field translatable, add the translatable property to your field definition:

name:
    label: Name
    type: text
    translatable: true

Core Translatable Trait

A new Translatable trait is now available in October Rain for per-row model translation. This provides built-in translation support without requiring any plugins.

Declaration:

use \October\Rain\Database\Traits\Translatable;

public $translatable = ['name', 'description', 'slug'];

The trait uses a single system_translate_attributes table with one row per model, per attribute, per locale. Locale is resolved automatically from Site/SiteManager.

Key features:

  • Zero overhead when inactive — when only one locale exists (or the default locale is active), getAttribute()/setAttribute() pass through to parent:: with a single string comparison. No extra queries, no relationship loading.
  • Lazy locale resolution — the active and default locales are resolved from Site on first access, not during model construction. This avoids issues during migrations, seeds, or console commands.
  • Per-row storage — each attribute is stored as a separate row, enabling partial updates and direct indexed queries without a separate indexes table.
  • Bulk writes via upsert — all dirty attributes for a locale are written in a single upsert() query, regardless of count.
  • Default value equivalence — for non-default locales, attributes whose value matches the default are not stored. They inherit from the model table via fallback, so changes to the default automatically propagate.
  • JSON/array support — array values are automatically serialized to JSON on write and deserialized on read when the attribute is declared as $jsonable.

Reading and writing translations:

// Implicit — uses current site locale
$product->name; // Returns translated value or falls back to default

// Explicit
$product->getTranslation('name', 'fr');
$product->setTranslation('name', 'fr', 'Produit');
$product->save();

// All locales for one attribute
$product->getTranslations('name');
// Returns: ['en' => 'Product', 'fr' => 'Produit']

Query scopes:

Product::whereTranslation('name', 'fr', 'Produit')->get();
Product::orderByTranslation('name', 'fr', 'asc')->get();

Eager loading:

Product::withTranslation('fr')->get();  // Single locale
Product::withTranslations()->get();     // All locales

Relationship with RainLab.Translate: The core trait and the RainLab.Translate plugin are independent systems. They use separate tables, separate APIs, and don't interfere with each other. Existing plugins that depend on RainLab.Translate continue to work with zero changes. Migration from the plugin to the core trait is opt-in.

Model class: The translation model follows the Revision model pattern — October\Rain\Database\Models\TranslateAttribute is the base class (table: translate_attributes), and System\Models\TranslateAttribute extends it with $table = 'system_translate_attributes'. Models using the trait should override getTranslateAttributeModelClass() to return System\Models\TranslateAttribute::class.

For method signatures, migration guide, and storage details, see the Translatable documentation.

Notable Minor Changes

Form Widgets Migrated to ESM

Several form widgets have been refactored to use ES Modules and the Hot Controls pattern:

  • DatePicker
  • RichEditor
  • MarkdownEditor
  • Repeater
  • PermissionEditor

If you are accessing these controls via JavaScript, ensure you use the modern API:

// Access control instance
const control = oc.fetchControl(element, 'datepicker');
control.someMethod();

Removed Dependencies

The following dependencies have been removed:

  • Bluebird: Promise polyfill no longer needed (native Promises are used)
  • Laravel Mix: Replaced with native ESM and esbuild for vendor builds

Vue Router Upgraded

Vue Router has been upgraded from v3 to v4 to match the Vue 3 upgrade. If your plugins use Vue Router, review the migration guide:

Tailor Migrate Decoupled from October Migrate

The php artisan october:migrate command no longer automatically triggers Tailor blueprint migration. Previously, Tailor's BlueprintIndexer::migrate() was called via the system.updater.migrate event, which added significant overhead to every migration call — including unit test setup. Tailor blueprint migration should now be run explicitly using php artisan tailor:migrate when needed.

Inspector and TreeView Refactored

The Inspector and TreeView components have been refactored from global scripts to ES Modules. If you extend these components, update your code to use ES module imports.


This is the end of the document, you may read the announcement blog post or visit the changelog for more information.

comments powered by Disqus