Upgrade Guide

This will guide you to upgrade from Backpack 4.1 to 4.2. The steps are color-coded by the probability that you will need it for your application: High, Medium and Low.


Please make sure your project respects the requirements below, before you start the upgrade process. You can check with php artisan backpack:version:

  • PHP 8.x, 7.4.x or 7.3.x
  • Laravel 8.x
  • Backpack\CRUD 4.1.x
  • ~10 minutes (for most projects)

If you're running Backpack version 3.x or 4.0, please follow ALL the minor upgrade guides first, to incrementally get to use Backpack 4.1. Test that your app works well with each version, after each upgrade. Only afterwards can you follow this guide, to upgrade from 4.0 to 4.1. Previous upgrade guides:

Upgrade Steps

The upgrade guide might seem long and intimidating, but really, it's an easy upgrade. But most projects will only be affected by a few of the 22 breaking changes outlined below. And when there are changes needed, they're pretty small.

Please go thorough all steps, to ensure a smooth upgrade process. The steps are color-coded by how likely we think that step is needed for your project: High, Medium and Low.

Step 0. Upgrade to Laravel 8, then test to confirm your app is working fine with Laravel 8 + Backpack 4.1.


Step 1. Update your composer.json file to require "backpack/crud": "4.2.*"

Step 2. If you have a lot of Backpack add-ons installed (and their dependencies), here are their latest versions, that support Backpack 4.2. You can copy-paste the versions of the packages you're using:


        "backpack/logmanager": "^4.0.0",
        "backpack/settings": "^3.0.0",
        "backpack/pagemanager": "^3.0.0",
        "backpack/menucrud": "^2.0.0",
        "backpack/newscrud": "^4.0.0",
        "backpack/permissionmanager": "^6.0.0",
        "backpack/backupmanager": "^2.0.0",
        "spatie/laravel-translatable": "^4.0",
        "backpack/langfilemanager": "^3.0.0",

        /* and in require-dev */

        "backpack/generators": "^3.0",
        "laracasts/generators": "^1.0"

Step 3. Run composer update in the command line.


Step 4. The repeatable field has been completely rewritten, in order to work with values as a nested PHP array, not a JSON. This has HUGE benefits, but presents some small breaking changes. If you've used the repeatable field in your CRUDs

  • please make sure that db column is casted as array or json (eg. add protected $casts = ['testimonials' => 'array']; to your model);
  • a data syntax bug has been fixed - previously, if inside repeatable you used a subfield with multiple values (select_multiple or select2_multiple etc.), it would not store 'categories': [1, 2] like you'd expect, but 'categories[]': [1, 2]; those brackets were not intentional, but we could not fix them without telling you about it; when upgrading to 4.2:
    • if you've coded a workaround to strip those brackets, you can now remove the workaround;
    • if you use the attribute with brackets anywhere, please expect it to be either with or without brackets; Backpack will NOT strip all the brackets automatically, it will only strip them upon saving, when an admin edits that particular entry;

Form Requests

Step 5. For repeatable fields, you no longer have to create custom validation logic. You can now use Laravel's nested array validation. If you've used a repeatable in your CRUDs and validated it in your FormRequest:

  • you can keep your custom validation logic, but make sure you no longer json_decode() the value in the input; if you've used our validation example, that means instead of $fieldGroups = json_decode($value); you should do $fieldGroups = is_array($value) ? $value : [];;
  • you can rewrite that validation in a cleaner and more concise way, using Laravel's nested array input validation rules; for example, you can do 'testimonial.*.name' => 'required|min:5|max:256' to validate all name subfields inside a repeatable testimonial;


No changes needed.


Step 6. Security improvement: Starting with v4.2, Backpack will throttle password request attempts, so that a malicious actor cannot use the "reset password" functionality to send unsolicited emails. We recommend you add this configuration entry to your config/backpack/base.php (before "Authentication"). See #3862 for details.

    | Security

    // Backpack will prevent visitors from requesting password recovery too many times
    // for a certain email, to make sure they cannot be spammed that way.
    // How many seconds should a visitor wait, after they've requested a
    // password reset, before they can try again for the same email?
    'password_recovery_throttle_notifications' => 600, // time in seconds

    // Backpack will prevent an IP from trying to reset the password too many times,
    // so that a malicious actor cannot try too many emails, too see if they have
    // accounts or to increase the AWS/SendGrid/etc bill.
    // How many times in any given time period should the user be allowed to
    // attempt a password reset? Take into account that user might wrongly
    // type an email at first, so at least allow one more try.
    // Defaults to 3,10 - 3 times in 10 minutes.
    'password_recovery_throttle_access' => '3,10',

Step 7. Operation configurations have been moved from config/backpack/crud.php, each to its own file. That way, if an operation config value isn't present, it will fall back to Backpack's default value. To upgrade, please:

  • run php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=config - this will publish a new config file for each operation (eg. config/backpack/operations/create.php, update.php etc.);
  • if you've changed things inside your config/backpack/crud.php, then make the same changes inside the config files you've published above;
  • in your config/backpack/crud.php delete the operations array entirely;

Step 8. Inside config/backpack/crud.php, you were previously able to change what inputs are stripped from the request before saving, by configuring saveAllInputsExcept for the Create and Update operations. We have moved the configuration to config/backpack/operations/create.php & config/backpack/operations/update.php, then:

  • renamed it to strippedRequest;
  • given you the possibility to do whatever you want to the request, by using a closure instead of array;
  • kept the default behaviour; if strippedRequest is undefined or false, Backpack will strip all inputs that don't have fields, which we consider is the safest approach;

If in your config/backpack/operations/create.php or config/backpack/operations/update.php you've copied saveAllInputsExcept as null or false, you don't have to do anything.

However, if you have an array for your saveAllInputsExcept, you can now achieve the same thing by stripping the request yourself in a closure. Basically:

-    'saveAllInputsExcept' => ['_token', '_method', 'http_referrer', 'current_tab', 'save_action'],
+    'strippedRequest' => (function ($request) {
+        return $request->except('_token', '_method', '_http_referrer', '_current_tab', '_save_action');
+    }),

But you can also do a lot more, because you have the $request in that closure. See more info in PR #3987. In addition, please notice that all hidden parameters are now prefixed by an underscore. Starting with 4.2, if it starts with an underscore, you know it's not an actual database column.

Step 9. The Show operation will now show created_at, updated_at columns by default, if they exist. In most cases, this is what you want - show as many things as possible inside the Show operation - and created_at and updated_at provide some useful information about the entry. So it should NOT negatively affect you. But if you don't want to show those columns, you can turn off this new default behavior in your config/backpack/operations/show.php, by defining 'timestamps' => false,. You can also do 'softDeletes' => true, if you want to show that column, by the way.


Step 10. We've improved the guessing of column types, by also taking into consideration your Model casts (PR here). If you have places in your CrudControllers where you have NOT defined a column type (and just assumed it'll probably be text), but that item is cast as date, json, array etc... Backpack will now try to show a more appropriate column type for it, instead of text. This is unlikely to affect you negatively, but... it's not a terrible idea to go through all your ListOperation and ShowOperation views, to make sure you're happy with what's displayed.

Step 11. There have been some changes in how the repeatable field works by default:

  • it now shows 0 rows when empty (previously it was showing 1); we believe that provides a better UX for most projects, but if you don't like it, please define 'min_rows' => 1;
  • we're renamed the fields attribute to subfields for more clarity (but both will work);
  • if you have subfields that do NOT have their type defined, Backpack will now assume you wanted a text field;

Step 12. We have removed the simplemde field, since the JS library hasn't received any updates since 2016. However, there is a drop-in replacement called easymde which is well maintained. If you're using the simplemde field type anywhere in your project, please use easymde instead. A simple find & replace in your Controllers should do.

Step 13. If you're using the Reorder operation and have overriden some of its functionality, please take note that the information is now passed as JSON. Replicate the small changes here in your custom code too.

Step 14. When setting up your Show operation in Backpack 4.1, whatever you did inside setupShowOperation() was called before the opration added all columns. That made it pretty difficult to delete auto-added columns. You might have used a workaround for this.

Starting with Backpack 4.2, the operation will do its "automatic setup" inside an autoSetupShowOperation() method. If you define setupShowOperation(), that method will no longer be called, you have to call it inside you setupShowOperation() if you want what it does. To upgrade:

  • if you do not have a setupShowOperation() in your CrudControllers, you are not affected by this - don't worry;
  • if you do have a setupShowOperation() method in your CrudControllers, just the fact that you have it defined will remove all guessing of columns, so that you have complete control over what columns are shown;
    • to keep the same behaviour as before (guess columns), please run $this->autoSetupShowOperation(); wherever you want inside your setupShowOperation(); add it to the end of your setupShowOperation() to keep the same behaviour as before;
    • if you have any workarounds for the problem mentioned above (difficult to delete columns in setupShowOperation()), call $this->autoSetupShowOperation(); at the start of your setupShowOperation() call and eliminate your workarounds, it should now "just work";

Step 15. The Create and Update operations no longer save the request(), they save your ProductFormRequest (the one that contains the validation). If you have NOT modified the request() or CRUD::getRequest() in any of your CrudControllers, you will not be affected by this, move on.

However, if you have modified the request (most likely to add or remove inputs), those changes will never reach the db. To give you an example, if you've over overridden the store() or update() methods to add an input, it might look something like this:

use \Backpack\CRUD\app\Http\Controllers\Operations\CreateOperation { store as traitStore; }

public function store()
    $this->crud->setOperationSetting('saveAllInputsExcept', ['save_action', 'token', 'http_referrer']);
    $this->crud->getRequest()->request->add(['updated_by' => backpack_user()->id]);     

    return $this->traitStore();

That will no longer work, because Backpack won't save the request() or $this->crud->getRequest() any more, but your ProductRequest. But there are now more ways than ever to achieve the same thing. Please read all solutions below and decide which one is best for you:

Solution (A)

That actually means it's easier to change the request now inside a CrudController, but you need to do it inside the strippedRequest closure:

public function store()
    $this->crud->set('strippedRequest', function($request) {
        $request->add([ 'updated_by' => backpack_user()->id ]);
        return $request->except(['_save_action', '_token', '_http_referrer']);

    return $this->traitStore();

Solution (B)

If you feel overriding the store() or update() methods was messy... you're not alone. We have good news for you, you can now move that logic to your ProductRequest, for example inside the lesser-known prepareForValidation() method:

// app/Http/Requests/ProductRequest.php

    protected function prepareForValidation()
        \CRUD::setOperationSetting('strippedRequest', function ($request) {
            $input = $request->only(\CRUD::getAllFieldNames());
            $input['updated_by'] = backpack_user()->id;

            return $input;

Solution (C)

Alternatively... if you absolutely hate this new behaviour and want your previous code to continue working, you can now easily tell Backpack to save the CRUD::getRequest(), for all CRUDs, in your config/backpack/operations/create.php and config/backpack/operations/update.php. That way, you can keep your old CrudController overrides:

    'strippedRequest' => (function ($request) {
        return CRUD::getRequest()->request->except('_token', '_method', '_http_referrer', '_current_tab', '_save_action');

Step 16. If you've customized the saving process of the Create or Update operations (read: you've overriden the store() or update() methods), please take into consideration that starting with Backpack 4.2, when a select multiple is emptied, it will still be part of the request, as null. Whereas previously (if emptied) it was missing entirely. This applies to all select and select2 fields when used as multiple. You might need to change your saving logic accordingly, instead of expecting them to be missing, to expect them to be null.

Step 17. The page_or_link field has been moved from backpack/crud to backpack/menucrud because it made little sense outside it. If you've used the page_or_link field anywhere in your CrudControllers:

  • if you have MenuCRUD installed:
    • bump the MenuCRUD version in your composer.json ("backpack/menucrud": "^3.0.0")
    • anywhere you've used the page_or_link field, make sure to specify its view_namespace ('view_namespace' => 'menucrud::fields');
  • if you DO NOT have MenuCRUD installed;
    • tell us that this has affected you, in this PR;
    • you can create a new file in your resources/views/vendor/backpack/fields/page_or_link.blade.php and paste the content from here;

CSS & JS Assets

Step 18. We've updated most CSS & JS dependencies to their latest versions. There are two ways to publish the latest styles and scripts for these dependencies:

  • (A) If you have NOT touched you public/packages folder, or placed anything custom inside it:
    • delete the public/packages directory and all its contents;
    • run php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public
    • if you use elFinder, also delete resources/views/vendor/elfinder and run php artisan backpack:filemanager:install
  • (B) Run php artisan vendor:publish --provider="Backpack\CRUD\BackpackServiceProvider" --tag=public --force. Please note this will overwrite anything that's already there. This B solution has a downside: unused files are not removed. A few files Backpack no longer uses will still be in your public/packages folder, even though they're no longer used.

Step 19. We've removed the custom CSS & JS files that Backpack provided for each operation (eg. list.css and create.js - see why here).

  • If you've added any custom code in public/packages/backpack/crud/css and public/packages/backpack/crud/js, copy them to a different location (we suggest public/assets/admin/css and public/assets/admin/js). Then use the brand-new script and style widgets to load them only where you need them. See the updated docs section for more information. This is only needed if YOU have added any custom code there. The Backpack CSS that was there is now included in the bundle.css and bundle.js files.
  • if you haven't modified those at all... it is now safe to delete the public/packages/backpack/crud/css and public/packages/backpack/crud/js directories - those files are no longer loaded.


Step 20. Have you developed any custom fields or columns? Rephrased: do you have anything inside your resources/views/vendor/backpack/crud/fields or resources/views/vendor/backpack/crud/fields? If so, and those fields or columns load any external CSS or JS, we recommended you load them using @loadOnce('path/to/file.css') and @loadOnce('path/to/file.js') instead of <link href="path/to/file.css>" and <script src="path/to/file.js></script>". This will make sure that piece of JS/CSS/code is only loaded once per pageload. You can find more info about it here (and why it's more than @once).

Step 21. If you've overwritten any of the default operations in any way (blade files or PHP classes), take note that we've renamed the system GET/POST parameters (aka hidden inputs) - they're all prefixed by underscore now, to differentiate them from actual database columns. Please replace http_referrer, locale, current_tab with _http_referrer, _locale, _current_tab, respectively. Take a look at the PR to see the affected files. In 99% of all cases you won't be affected by this, there's little reason to overwrite the default operations. This also applies if you've overridden the SaveActions or form_content.


Step 22. By default, all columns now echo using {{ }} instead of {!! !!}. That means they "escape the output", assuming they contain strings, not HTML. This was done to increase default security, to protect the admin from any malicious strings that might have been stored in the database. There are two exceptions to this, two columns that are not escaped by default: custom_html and markdown, where Backpack assumes you store HTML. To upgrade:

  • If you've been showing HTML using the array, array_count, closure, model_function, model_function_attribute, relationship_count or textarea columns, you can use 'escaped' => false on those columns to go back to the previous behaviour. But please read more about this, it might be a good idea to sanitize your input/output if you've forgotten to do so.
  • If you're using the markdown or custom_html columns, please note that they still DO NOT escape the output by default (since they most likely store HTML); make sure you've properly sanitized your input or output - it's super-easy using an HTML Purifier package (you can do that by casting the attribute to CleanHtmlOutput::class in your Model or manually);


Step 23. Clear your app's cache:

php artisan config:clear
php artisan cache:clear
php artisan view:clear

You're done! Good job. Thank you for taking the time to upgrade. Now you can:

Ready to go live?

You don't need a license code on localhost. But in production, you do.