Modals and toasts moved to @crudadmin/helpers
Breaking change for every custom admin component that opens a modal or a toast. Calls to the removed APIs throw at runtime.
- Removed
useModalStore (also from window) and the component methods this.openModal(), this.successModal(), this.errorModal(), this.warningModal() and closeModal(). Use Modal and Toast from @crudadmin/helpers/modal, available as window.Modal and window.Toast in custom components.
openModal({ toast: true }) no longer exists. Modals do not open toasts; use Toast.success(), Toast.error(), Toast.warning() or Toast.open().
- The
close callback of a modal is now cancel. It runs from the close button, the X button and Escape. Returning false from success, cancel or an action still keeps the modal opened.
- Modal objects changed shape:
modal.title, modal.message and modal.type are in modal.message.title, modal.message.text and modal.message.type; modal.visible and modal.openedAt are in modal.state; modal.class and modal.key are in modal.options; component props are in modal.props. modal.id stays.
- Components opened in a modal receive the modal in the
modal prop, the separate actions prop is no longer passed; read modal.actions.
- Modal options are read from one place: the options of the opening are merged with the options of the wrapper component, whose values win. Only the
class of both is kept together.
- Renamed modal options:
escClose became dismissible (Escape closes a modal with no closing action) and hiding the footer is footer: false instead of actions: false. The old names keep working.
- The form of a model opened in a modal (a row of the table, a relation opened from a select field) is no longer a Bootstrap modal. It is an entry of the same manager as every other modal, drawn in a
.row-action-modal wrapper; the #modal-inline-… element, its Bootstrap instance and the .modal-backdrop are gone.
- The
RowActionModal component is not registered globally any more and is not placed in a template. The form of a model is opened on the model itself: model.modals.add(), edit(id), view(id), list(), form() and close(). A model which opens its own form in a modal (a relation, a sub model) turns it on with model.modals.enable(); form.modal in the settings still does it on its own.
- Removed the
setBootstrapModalZIndexes() helper. Modals are stacked in the order they were opened.
- The listing button of a relation select is marked with
data-list-relation-row instead of a second data-add-relation-row; update selectors of custom tests.
- Rows requested before their table is drawn now use the default page size instead of asking for no rows. A relation listing opened from a select field used to open empty.
- The page behind an opened modal no longer scrolls. The administration sets the
--modal-open class on <html>; custom styles that scrolled the page while a modal was open have to account for it.
Model settings editor
- UI settings can now be stored in
app/Admin/model-settings.php; commit and deploy this file. Existing $settings, settings() and mutators remain supported, with map overrides applied last.
- Title aliases are normalized in the settings payload:
title.insert becomes title.create and takes precedence over an old title.create; title.edit becomes title.update when no update title is set. Update custom frontend consumers that read the old keys.
- Use column snapshots instead of new
columns.*.before / columns.*.after declarations. Run admin:settings-migrate --dry-run, then admin:settings-migrate to move supported literals and convert legacy ordering. Resolve reported dynamic expressions manually.
- Explicit column visibility overrides can reveal
hidden fields; snapshots retain shared visibility and order, while personal preferences remain above them. Password and inaccessible columns remain excluded.
columns.*.name now takes precedence over a field’s column_name, so editor changes also apply to fields with a custom column label.
- Editing requires full superadmin access,
APP_DEBUG=true and a non-production environment. Production uses the deployed map without exposing the editor.
Removed field types
type:array
- Removed
type:array; replace it with type:json in field definitions.
- Values are now automatically decoded into PHP arrays instead of returned as raw JSON strings. Review manual
json_decode() / json_encode() calls and custom casts.
type:json is hidden from admin table rows by default, which may affect custom table components.
- Invalid JSON strings in admin requests are decoded to
null; review validation if malformed input must be rejected.
- The database column remains JSON; existing data does not need conversion solely for this replacement.
- The
array validation rule and multiple parameter remain supported.
type:boolean
- Removed
type:boolean; replace it with type:checkbox in field definitions, including array notation.
- The database column remains boolean (
tinyint(1) on MySQL); existing 0/1 data does not need conversion solely for this replacement.
- Model and API values are cast to
false/true instead of integers. Review strict comparisons such as === 1 and consumers expecting numeric values.
- Checkbox adds a form input and boolean validation. A missing checkbox in an admin request normally becomes
false; review partial updates.
- The
boolean validation rule remains supported.
Removed commands
admin:queue
- Removed
admin:queue; remove its calls from cron and the Laravel scheduler.
- Run Laravel’s
php artisan queue:work with a process manager such as Supervisor instead. Otherwise jobs may remain unprocessed after the upgrade.
- Move any custom
queue.admin.cli, queue.admin.log_path, queue.admin.timeout, queue.admin.sleep and queue.admin.tries settings to the worker command and process manager configuration; CrudAdmin no longer reads them.
Configuration changes
File browser configuration
- Removed the
ckfinder boolean and boolean filemanager settings; use filemanager.type (none, lfm or ckfinder). CKFinder itself remains supported.
- Replace
'ckfinder' => true with 'filemanager' => ['type' => 'ckfinder']; keep license_name and license_key inside that array.
- Replace
'filemanager' => true with 'filemanager' => ['type' => 'lfm']; use ['type' => 'none'] to disable the browser.
- Old boolean settings no longer enable a file browser. Update project configuration before upgrading.
Legacy file configuration
- Removed the unused
file config section (exists_cache, exists_cache_days, redirect_after_resize). Remove it from project configuration if present.
- These keys already had no effect in v5; file uploads and image resizing are unchanged.
- Use
resizer.storage_cache, resizer.storage_cache_days and resizer.redirect_after_resize to configure the corresponding behavior.
Localized routes
- Removed the unused
routes config key; remove it from project configuration if present. Existing routing behavior is unchanged.
- Register language-prefixed routes with
localizedRoutes() of the crudadmin/website package.
Removed field parameters
component_data
- Removed the unused
component_data field parameter; remove it from field definitions.
- It never populated component props automatically. Read values from the
field or row props instead, and review custom code that accessed field.component_data.
- Leaving it in field definitions may cause validation errors because it is no longer a registered field attribute.
- Button
component() data arguments remain supported.
hasOne
- Removed the
hasOne field parameter. Remove |hasOne (or the hasOne entry in array notation) and keep belongsTo.
- The column and foreign key remain unchanged. The relation now returns Laravel
BelongsTo instead of HasOne; review custom code that calls relation-specific methods or checks its class.
- Leaving
hasOne in field definitions may cause validation errors because it is no longer a registered field attribute.
- Laravel
hasOne() and automatic relations to single child models (maximum = 1) remain supported.
ifExists
- Removed the
ifExists field parameter; replace it with hideOnCreate in field definitions.
- Without the replacement, the field is no longer hidden when creating a record. The replacement keeps the same form visibility behavior; backend validation and saving still apply.
inBackend
- Removed the
inBackend alias; replace it with inAdmin in field definitions, including array notation.
- The replacement keeps the same admin-only behavior. Leaving
inBackend in field definitions may cause validation errors because it is no longer a registered field attribute.
Removed helpers
isActiveController()
- Removed
isActiveController() from resources helpers. Existing calls will fail with an undefined-function error.
- Replace calls with Laravel’s
request()->routeIs() or Route::currentRouteAction().
Admin\Helpers\Helper
- Removed
Admin\Helpers\Helper (isActive(), currentRoute(), controllerName(), link(), error(), priceFormat(), invoiceFormat()). Use request()->routeIs() / Route::currentRouteAction() for the route checks, $errors->first() in Blade and number_format() / str_pad() for the formatting helpers.
laravel/helpers
crudadmin/framework no longer requires laravel/helpers, so global helpers like array_wrap(), array_get(), str_slug(), str_random(), str_singular() or studly_case() are gone unless the project requires the package itself. Replace them with Arr:: / Str:: methods, or run composer require laravel/helpers.
Removed Site builder
- Removed the entire Site builder module:
SiteBuilder, block types, Group::builder(), getSitebuilderBlocksArray(), renderBuilder(), the block selector and its styles. Replace block editing and rendering with your own implementation.
- Remove
sitebuilder / sitebuilder_types config, Admin::isEnabledSitebuilder() calls and the admin:sitebuilder:block command from your project. Custom blocks extending SBType and published Site builder views need replacing too.
- Existing block data is not deleted automatically, but CrudAdmin no longer edits or renders it. Migrate any content you still need before upgrading.
Public website features moved to crudadmin/website
- The frontend editor, gettext scripts of Blade pages, language prefixes in urls, SEO and the site tree moved from
crudadmin/crudadmin to the new crudadmin/website package. Projects using any of them run composer require crudadmin/website. Config keys (frontend_editor, seo, sitetree, localization_remove_default, uploadable_allowed_extensions, gettext_json), route names and urls stay the same; their defaults are provided by the package.
- The
$seo model property is part of the package too. Without it, models with $seo (and eshop products and categories) have no meta fields tab and the slug of sluggable models is not editable in the form. admin:migrate then reports the meta_* and slug_dynamic columns as unknown; keep them when you are going to install the package.
- Moved classes:
Admin\Models\RoutesSeo, SiteTree and StaticContent are AdminWebsite\Models\…; Admin\Eloquent\Modules\SeoModule is AdminWebsite\Eloquent\Modules\SeoModule; Admin\Helpers\SEO, SEOService, FrontendEditor, Admin\Helpers\SiteTree\SiteTree, Admin\Helpers\Localization\EditorMode and LocalizationRedirecter are AdminWebsite\Helpers\…; Admin\Core\Casts\EditorCast is AdminWebsite\Casts\EditorCast; Admin\Middleware\LocalizationMiddleware is AdminWebsite\Middleware\LocalizationMiddleware. The EditorMode, FrontendEditor, SEO and SiteTree facade aliases are unchanged. Update project classes extending them, e.g. an own RoutesSeo model.
- Moved helpers
uploadable(), linkable(), localeUrl(), localizedRoutes(), switchLocale() and encryptText(); Blade directives @translates, @gettext, @editor, @uploadable, @seo, @metatags, @title, @description, @keywords, @image, @author and @seogroup; route macros ->seo() and ->visible(), the router macro addLocalizationAttributes() and the localized middleware. Without the package, calls fail and directives are printed as text.
- Removed
Admin::isEnabledFrontendEditor(), Admin::isSeoEnabled() and Admin::isEnabledSitetree(); use AdminWebsite\Features::frontendEditor(), seo() and sitetree().
- Without the package, website localization no longer reads the language from the first url segment or the session:
Localization::get() returns the default language until the application calls Localization::setLocale(). isValidSegment(), segment(), prefix(), saveIntoSession(), getFromSession(), refreshOnSession() and crossDomainSupport() exist only in the package localization. getLocaleSegmentIdentifier() and gettextJsResourcesMethod() were removed from LocalizationInterface; custom localization classes may drop them.
editor and longeditor values are wrapped in <div data-crudadmin-editor> on non-admin requests only with the package and an enabled frontend editor (frontend_editor.enabled). Otherwise, also in API responses, the value is returned as it is stored.
- Slugs in the language the visitor switched from (
_previous_locale) are found only with the package. Packages set the resolver with AdminModel::resolvePreviousLocaleUsing(); editor field casts are added with AdminModel::addFieldTypeCast().
- Clear compiled Blade views after the upgrade (
php artisan view:clear); views compiled with the old @gettext directive call the removed Gettext::getJSPlugin().
- Frontend scripts are built in the package and published to
public/vendor/crudadmin-website (FrontendEditor.js, Gettextable.js, frontend.css) by php artisan admin:update or the admin_website.assets tag. /vendor/crudadmin/build/assets/FrontendEditor.js, Gettextable.js and frontend.css no longer exist; update custom script tags. The administration no longer installs window.action() and window.decryptText().
JSTranslations::getJavascript() moved to the package, Gettext::getJSPlugin() became JSTranslations::getScriptUrl() of the package. GET /admin/api/bootstrap no longer accepts context=editor; the editor uses GET /admin/api/frontend-editor/bootstrap.
- Models no longer receive
siteTreeColumns(), scopeOnSiteTreeLoad() and getTreeAction(). Models with $sitetree load their key, the $sitetree column and the slug; declare these methods on the model or use the AdminWebsite\Eloquent\Concerns\HasSiteTree trait. parent::siteTreeColumns() calls fail without the trait.
- Removed
AdminHelperServiceProvider::turnOfCacheForAdmin(). It only set admin.cache_time, which nothing read; remove its calls from packages.
Removed admin preview state
- Removed
$publishableState, withTemporaryPublished() and isAdminPublished(). Remove these declarations and calls from project models and code.
- Publishing now toggles directly between hidden and published, including
SiteTree. Former preview rows with published_at = null stay hidden on the website, even for logged administrators; publish them explicitly when ready.
published_state is no longer created, cast or read. Review existing preview rows before dropping this obsolete column during migration.
- For publication checks, use
published_at (set and not in the future). For a custom preview, use withUnpublished() in a controller with your own access checks.
Removed $seoVisible
- Removed
$seoVisible and automatic hiding of SEO attributes on frontend requests; remove the property from your models.
meta_title, meta_keywords, meta_description, meta_canonical_url, meta_image and slug_dynamic now follow normal model serialization. Use $hidden or makeHidden() if these fields must stay out of array and JSON responses.
Removed columns.*.title
- Removed translation support for
columns.*.title; replace it with columns.*.name in model settings (for example, columns.price.name).
title did not change table headers. name sets the column header and remains translatable; a field’s column_name takes precedence.
- Removed
grid.header from the feature reference; remove it from model settings if present.
- The administration never read this key, so removing it does not change the layout or headers.
Removed title.edit
- Removed the unused
title.edit setting from the feature reference; replace it with title.update to customize the edit form heading.
title.edit never changed the heading. Using title.update makes your configured text take effect.
- Removed
header.visiblePermanently; remove it from model settings. Single-record forms can no longer force the header to remain visible through this setting, so review any custom header components or actions.
- The header still appears automatically for localized fields, the gettext editor or an opened record with history.
form.header = false still hides it.
Validation rules are built once
- One
Model::validator() instance builds its rules and mutated data once, until only(), merge() or replace() change it. Repeated get(), getData() and fill() calls no longer run the request mutators again, so a file of a validated field is uploaded once and a request changed in the meantime is not read again.
- Fields are resolved for the validated row only when the model or its modules read it. Models building fields dynamically without the
fields($row) parameter should override hasRowDependentFields() and return true.
- Replace
keepInRequest with allowInput in field definitions, including array notation. The old name is no longer supported.
- Behavior is unchanged:
allowInput accepts submitted values for removeFromForm, invisible and disabled fields on create and update. Validation still applies.
- Without the replacement, these values are ignored; the obsolete parameter may also cause validation errors.
Replaced settings.keyName with $primaryKey
- Remove
keyName from $settings. The administration now reads the model’s actual key through Eloquent getKeyName(); configure Laravel’s $primaryKey property if your database key is not id.
- The old setting only changed frontend behavior. Do not move a display-only column into
$primaryKey: it changes how Eloquent identifies, updates and deletes records.
- The model tree now includes
primaryKey. Update custom frontend code that read settings.keyName; the frontend getKeyName() method remains available.
Removed $bus
- Removed the global
$bus and useEventHub() APIs. Replace custom listeners with window.addEventListener() and emit CustomEvent events; payloads are available as event.detail. Remove listeners when components unmount.
- Sidebar and Gutenberg editor switching now use native browser events. Update resources together with the Gutenberg integration; custom code using the old
closeMenu or disableGutenbergEditors events must be updated.
Replaced settings.xls with TableExportXls
- Remove
xls from model settings and add \Admin\Contracts\Exports\TableExportXls::class to $exports. Install phpoffice/phpspreadsheet if needed.
- The rows endpoint no longer accepts
download to create files. Use the standard export endpoint and its download_url response; replace custom uses of the removed SheetDownloader helper.
- Table formatting, filters and the
setSheet{Column}Attribute() / setExcelSheet() hooks are preserved. Files now use the normal private export storage; empty results produce a valid workbook.
Gettext-only administration translations
- Removed the resources
trans() mixin method, window.trans, useTrans() and layout.localization dictionary. Use literal __('Source message') calls in JavaScript/Vue and _('Source message') in PHP/Blade.
- Custom admin components and extension packages (including eshop and invoices integrations) must not call
trans(), this.trans(), window.trans() or useTrans() in JavaScript/Vue; these APIs no longer exist. Replace keys with literal gettext source messages.
- Laravel’s PHP
trans() helper still exists: calls such as trans('validation.required') use Laravel validation translations and are unaffected by removal of the admin frontend helpers. Use gettext for your own administration UI messages.
- Removed resources
admin::admin.* PHP catalogs. Administration uses the admin gettext catalog. Guest authentication screens use only an allowlist of bundled authentication messages. Move custom private translations into the admin gettext catalog; they are loaded after verification.
- Field
name, title, placeholder and model labels already use gettext. Replace legacy keys such as name:admin::admin.key with literal source text, for example name:Názov. Without migration the old key is displayed as text.
Built-in admin components use script setup
- All built-in administration Vue components now use
<script setup> and are closed by default. Custom components can no longer read their state or call their methods through $parent, $refs or $root (for example $parent.isField(), $parent.getGroupModel(), $root.flattenModelsWithChilds(), opened on sidebar rows or runModalCloser on modals). Pass the data through props or read it from the model and the Pinia stores instead.
- Components rendered through model
components still receive model, row and rows as props, and $parent.model, $parent.row and $parent.rows keep working there.
- The form group component is registered as
FormGroup instead of form-group; update custom code that resolved it by that name.
- License server callbacks no longer run with the component instance as
this. They get an object with the same state and methods, but without $-prefixed instance properties.
- Custom components loaded from
resources/views/admin/components keep using the Options API (export default { ... }); <script setup> is not supported there.
Removed $localization
- Removed the
$localization model property (rows stored per language in language_id), the localization() query scope, isEnabledLanguageForeign(), the --localization option of admin:model and the localization key of the model tree. Remove the property from your models. For translated content of one record use locale fields.
- The administration no longer has the language switch above the table, does not send
language_id with row, export or save requests and does not filter relation select options by language. Rows of all languages are listed together, and new rows are stored without language_id.
language_id is no longer created by migrations nor fillable. Existing columns and data are kept: admin:migrate reports the column as unknown and asks before dropping it, --auto-drop does not drop it. While it exists, its foreign key still blocks deleting a language used by those rows. Keep it through the replacement below, or merge or delete the per-language rows and drop the column yourself.
- Replacement: declare
language_id as a regular field bound to the languages model and add one filter button per language. Existing language_id columns and data keep working, the column type and foreign key match. Replace Model::localization() calls with where('language_id', ...). Models using HasEntryLocales need the field too.
Admin routes
- All admin endpoints moved to one url scheme and got route names (
admin.*). There are no redirects from the old urls; update custom code which calls them directly.
- Internal endpoints of the administration live under
/admin/api/models/{table}/…: rows, rows/{id}, order, fields/{field}/options, buttons/{button}, exports/{export}, settings and history. Creating a row is POST /admin/api/models/{table}, updating PUT /admin/api/models/{table}/rows/{id}. The model and row are taken from the url, _model and _id in the request body are ignored.
- Authentication endpoints called by the login form moved to
/admin/api/auth/… (login, password/email, password/reset, verification). Browser pages such as /admin/login and /admin/logout are unchanged.
- Downloads moved to
/admin/files/…: /admin/download/file is now /admin/files/{table}/{field}/{file}, /admin/user/download/{hash} is /admin/files/secured/{hash} and /admin/download/signed/{hash} is /admin/files/signed/{hash}/{table}/{field}/{file}. Signed links already sent by e-mail stop working; generate them again with $file->download().
- The gettext editor moved to
/admin/api/translations/{language}/{table?}, the stateless editor link to /admin/translations/editor and admin translations to /admin/translations/admin.js. Switching the admin language is PUT /admin/api/user/language/{id}. /vendor/js/ca-translates.js is unchanged.
- Frontend editor endpoints moved from
/frontend-editor/* and /translates/* to /admin/api/frontend-editor/*. FrontendEditor::routes() was removed; the routes are registered automatically.
- Statistics are
POST /admin/api/statistics/{key}, the site tree is saved by PUT /admin/api/sitetree, the development benchmark is /admin/api/dev/benchmark.
- The REST API moved to
/admin/api/v1/…: /admin/api/model/{table} is now /admin/api/v1/models/{table}, rows are updated by PUT or PATCH instead of POST, /admin/api/models_scheme/{table?} is /admin/api/v1/models/{table}/scheme or /admin/api/v1/scheme, and the helpers auth routes (auth/login, user) moved under the same prefix. Swagger moved from /admin/swagger to /admin/api/v1/docs. Update external API clients.
- The
hasAdminRole middleware reads the model from the {model} route parameter, then from the model input. The _model input is no longer read.
Removed window.crudadmin.layout.requests
- Removed the
requests url map from window.crudadmin.layout and the useRequest() helper. Custom Vue components calling useRequest() fail with an undefined function error.
- Use the api composables instead:
useModelApi(table) (rows(), show(), options(), button(), export(), formUrl(), downloadUrl(), …), useAuthApi(), useTranslationsApi(languageId) and usePanelApi(). Requests made with useAxios() are relative to /admin, so useAxios().$get('/api/…') keeps working for custom endpoints.
Removed model slug alias
- Removed the
slug key from the model tree; it was an alias of table with the same value. Replace model.slug, row.slug and models[key].slug with .table in custom Vue components and JavaScript. getModelBuilder() compares with model.table.
- Sidebar items now use the
data-model attribute instead of data-slug, with the same value (table name, or the group key for menu groups). Update custom CSS, scripts and browser tests that select li[data-slug="…"]. The data-slug attribute of the form language switch is unchanged.
Model loading states
- Model loading flags are grouped under one
loading data key: loading.row (row opening in the form), loading.button (running button action) and loading.gettext (translations loading before the gettext editor opens). Replace model.getData('loadingRow') with model.getData('loading').row and model.getData('button_loading') with model.getData('loading').button in custom Vue components. The old keys are removed without aliases.
JSON bootstrap and PHP helpers 2.0
-
The admin now loads initial data from
GET /admin/api/bootstrap. Remove overrides of admin::partials.crudadmin-props; that view no longer exists. Custom admin shells must include an admin-bootstrap meta tag containing route('admin.bootstrap') and initialize window.crudadmin.components.
-
Removed
window.crudadmin.layout, window.crudadmin.filemanager, window.crudadmin.logged and window.crudadmin.verified; there are no compatibility getters. Read private configuration through useSettingStore() and session state through useAuthStore() after bootstrap (boot.ready === true). Replace layout.models with useSettingStore().tree (or .models for the flattened map), layout.user with useAuthStore().user, and layout.auth with useAuthStore().config. Component registration through window.crudadmin.components remains supported.
-
The public-page editor fetches its own restricted bootstrap configuration. Publish matching resources assets together with the PHP changes. Login and successful verification now return bootstrap data and navigate without an admin document reload.
-
PHP
crudadmin/helpers 2.0 requires CrudAdmin 6 and framework 6.0.1 or the matching 6.0-dev branch. Its BootstrapRequest inherits section composition and cache from the framework; existing project subclasses retain their namespace, client and token handling. Replace pinned 1.4.0 constraints with ^2.0 when upgrading.
-
App store startup flags now live in
boot (ready, loading, error, componentsLoaded); replace booted checks with boot.ready === true and isLoaded with boot.componentsLoaded. Read locale and content languages from useLocaleStore() (@crudadmin/helpers/store). Replace admin_languages with useSettingStore().languages, read the selected interface language from the locally extended useLocaleStore().current getter, and read gettext from settingStore.editor.gettext. The temporary appStore.localization group is removed, and model_settings is now settingStore.editor.modelSettings. Update custom components reading the old top-level fields.
-
The admin-specific bootstrap request and controller live in
crudadmin/crudadmin (Admin\Bootstrap\AdminBootstrapRequest, Admin\Controllers\BootstrapController). The reusable Admin\Core\Utilities\BootstrapRequest remains in crudadmin/framework.
-
CKEditor receives filebrowser URLs directly in its instance configuration. Custom editor initializers must supply those options from
useSettingStore().filemanager inside the admin or CAEditor.config.filemanager after public-page editor bootstrap. Republish the matching CKEditor config.js; the window.crudadmin.ckeditorConfig(config) customization hook remains supported.
-
Authentication form configuration is grouped under
useAuthStore().config.form: column, input_type, title, placeholder, password_reset and flashed errors. Update custom login/reset views; provider selection remains under config.
-
app now contains public startup/CSRF state and logo. Move reads of models/tree, versions, environment, author/copyright, paths, filemanager, statistics, dashboard, license and editor settings to useSettingStore(). The setting bootstrap section is forbidden until authentication and 2FA are complete; session loss resets private stores.
-
Pinia now persists private settings, the last verified identity, locale and logo per bootstrap URL, including in development. A complete cache renders before background session verification; stale permissions or a previous session can remain visible until the response arrives. Every request remains authorized by the server. Logout/session loss clears the cache; CSRF tokens and boot flags are never persisted. Custom shells should add
admin-app-hash with Admin::getAppHash() only when (new Admin\Bootstrap\AdminBootstrapRequest)->isAuthorized(); the cache is used only when it was stored for that hash, so a shell without it, or with a changed hash, always waits for a fresh bootstrap.
-
Login and verification responses now contain
store; custom auth flows must await useBootstrap().applyBootstrap(response) before entering private routes. The fresh CSRF token is in store.app.csrf_token. JSON logout returns the public bootstrap. Redirects outside the admin still leave the document.
-
Project scripts/styles and meta/scripts slots now load after private bootstrap. Register components immediately when a script executes instead of relying solely on
window.load; make session initialization idempotent. The guest shell uses a generic title; the login logo remains public through app.logo. Other project assets load only after verification.
-
Admin translations now arrive as
store.locale.translations with bootstrap/login/logout responses. Read useLocaleStore().translations instead of window.CATranslates; remove custom loading of the admin translation script. Guest and pending-2FA catalogs contain only bundled authentication messages. The public-page editor keeps its separate translation script.
-
The admin installs the shared
CrudadminVue translation plugin from @crudadmin/helpers; use matching helpers with reactive catalog replacement. window.CATranslator and window.GettextTranslates are no longer installed inside the admin. Import Translator from @crudadmin/helpers for a separate catalog; use its getTranslator() for gettext methods. Vue translation methods and the global __/n__ helpers remain available.
SmartSMS moved to crudadmin/helpers
Admin\Helpers\SmartSms moved to AdminHelpers\Sms\SmartSms in the crudadmin/helpers package. Update imports in your code. The smartsms config and the SMARTSMS_* environment variables are unchanged.
Admin\Notifications\SmartSMSChannel is removed. crudadmin/helpers registers the notification channel sms (AdminHelpers\Sms\SmartSmsChannel), which sends toSms() of a notification to the phone of the notifiable, or to routeNotificationForSms().
- The SMS login verification sends its code through the
sms channel. Without crudadmin/helpers, register your own sms channel with Notification::extend('sms', ...), otherwise the code is not sent.
Removed MAIL_DEV_WHITELIST
- Removed the
CheckDevEmailWhitelist mail listener and the MAIL_DEV_WHITELIST environment variable. Since Laravel 9 it stopped every e-mail once the variable was set. Use Mail::alwaysTo() in a service provider of your development environment, or a local mail catcher.
admin:encrypt-existing writes encrypted values straight into the table and no longer saves the rows through the model, so model events, rules and history are not triggered and updated_at is kept. New --dry-run, --model and --chunk options.
REST API moved to crudadmin/api
- The REST API (
/admin/api/v1/…), its OpenAPI description and the Swagger UI moved to the optional crudadmin/api package. Projects using them run composer require crudadmin/api. URLs and route names of the token endpoints are unchanged; the api.logging config default comes from the package.
- Moved classes:
Admin\Controllers\Api\ApiController is AdminApi\Controllers\ApiController. The HasAdminApi trait is removed from AdminModel: getAdminApiColumns(), getAdminApiRelations(), getAdminApiFieldType() and getAdminApiFieldName() are AdminApi\Schema\ApiSchema::columns(), relations(), fieldType() and fieldName() with the model as the first argument; the bootAdminApiResponse() scope is app(AdminApi\Formats\Format::class)->query($model, $query, $request); setFullAdminApiResponse() is AdminApi\Response\ApiResponse::row($row). The model hooks scopeWithAdminApiResponse() and setAdminApiResponse() keep working.
- The API now reads and writes through the same code as the administration (
Admin\Crud\RowReader, Admin\Crud\RowWriter). scopeAdminRows() is applied, so rows hidden in the administration are not listed, shown, updated or deleted through the API. Validation, request mutators, uploads, belongsToMany sync, history, onCreate/onUpdate and admin rules run as in the administration; create no longer uses $model->validator(). Update validates and saves only the sent fillable fields, a request without them returns the row unchanged.
- Responses follow Laravel API Resources: lists are
{ data, links, meta } instead of { error, data: { pagination } }, one row is { data } instead of { data: { row } }, create answers 201, delete answers 204 without body, /admin/api/v1/models returns { data: [{ table, name, relations, operations }] }, and login and /user return { data: { user, token } }. Errors are { message } (validation { message, errors }) with 400, 401, 403, 404 or 422 instead of the type, title and error keys of autoAjax.
- Query parameters follow spatie/laravel-query-builder:
_with/with is include=author,comments.author (relation:columns is fields[table]=columns), _columns/columns is fields[table]=columns, _where[column,operator] is filter[column]=>=10, _where[column,in] is filter[column]=a,b, _where[column,notnull] is filter[column]=!null, _scope[name]=value is filter[name]=value, _selector is selector. limit is per_page (up to admin.api.max_per_page, 100); limit=-1 for all rows is removed, iterate the pages. New: sort, filter[column]=~text, include=relationCount. Unknown filters, sorts, includes and fields answer 400 instead of 422.
- Relation names in
include and in ApiSchema::relations() are camelCase (schemaApiComments instead of SchemaApiComments). Included relations are limited by scopeAdminRows() of the related model and need its read permission.
- Only API columns and the existing timestamps are returned. Password fields,
inaccessible fields, $hidden attributes and table columns which are not fields (e.g. remember_token, logout_date, deleted_at) are no longer returned, selected, sorted or filtered.
- Tokens issued by
POST /admin/api/v1/auth/login (also OTP and socialite login) are read only (api, api:read); clients which write rows have to send access=write on login (adds api:write). They are valid only for the REST API; the administration answers 403 to them. Clients which used a REST API token for admin endpoints need a token with the admin (or *) ability. Existing * tokens keep working everywhere.
/admin/api/v1/models lists only models with the read permission. The login credentials are documented in the request body of the OpenAPI description instead of query parameters.
- The stateless frontend editor token (
/admin/translations/editor) has only the frontend-editor ability, it is refused by the administration and the REST API.
- Passwords, tokens and secrets are left out of the REST API request log.
- The session Swagger scheme moved from
/admin/api/v1/openapi.json|yaml to /admin/api/v1/docs/openapi.json|yaml; /admin/api/v1/openapi.json|yaml is now a token route for API clients. The scheme is generated in PHP: /admin/api/v1/scheme returns valid YAML with quoted values, and the description now includes the DELETE operation.
- Removed
Admin\Controllers\Crud\Concerns\CRUDRelations and the validation and insert methods of CRUDController, InsertController and UpdateController; they live in Admin\Crud\RowWriter. UpdateController no longer extends InsertController. getModel() of the CRUD controllers comes from Admin\Crud\Concerns\ResolvesAdminModels.
Login security
- The login verification code (sms / e-mail) is invalidated after 5 wrong attempts (
verificatorMaxAttempts() of the auth model); a new code is sent when the verification state is requested again. Codes are generated with random_int(). GET /admin/verificator is throttled like the json verification endpoint.
- Super passwords (
admin.passwords) no longer sign in accounts without a password hash, and each use is logged into the crudadmin log channel. The admin hasher respects the hashing.bcrypt config.
- Frontend editor writes of the session editor (
/admin/api/frontend-editor/*) require a verified login and respect remote logout, as the admin panel.
Minimum requirements
- CrudAdmin 6 requires Laravel 12 or 13 and MySQL 8.0 or newer. Laravel 11 and older are no longer supported; the Doctrine DBAL fallback and the MySQL 5.7 check of the migrations are removed.
crudadmin/crudadmin requires laravel/sanctum 4.
published_at is always cast to datetime, also on models that still declare the $dates property, which Laravel ignores since version 10.
crudadmin/crudadmin no longer requires laravel/ui; require it in the project if you use its auth scaffolding.
Removed legacy classes and methods
- Removed
Admin\Helpers\Ajax and its Ajax alias (CrudAdmin 3). Use autoAjax()->success(), ->error(), ->message() and ->throw().
- Removed
Admin\Models\Model (CrudAdmin 1). Extend Admin\Eloquent\AdminModel.
- Removed
Admin\Helpers\File and Admin\Core\Helpers\File. Use Admin\Core\Helpers\Storage\AdminFile, which has the same methods.
- Removed
Admin\Eloquent\Casts\DecimalCast. CrudAdmin never applied it to any field; copy it into the project if a model uses it in $casts.
- Removed unused model methods:
filePath() (use getStorageFilePath() and getFieldStorage()->path()), runAdminRule(), scopeFilterByParentField() and checkForChildrenModels() (the HasChildrens trait).
- Removed
hasAccessByTable($table, $permission) of the admin user. Use hasAccess(Admin::getModelByTable($table), $permission).
- Removed
Button::errorToast($message). Use toast($message, 'error').
- Removed
AdminFile::getBase64(). Use 'data:;base64,'.base64_encode($file->get()).
- Role permissions are read only from the JSON cast. Permissions saved as plain strings by CrudAdmin 3 and 4.1 are no longer decoded; resave the roles in the administration.
date values stored in the d.m.Y format are no longer read; the value is null. Convert such columns to Y-m-d. Writing d.m.Y strings from your code still works.
- With a symlinked cache folder, old
/uploads/cache/… image URLs no longer redirect to /cache/…. Update hardcoded image URLs.