Upgrade Guide

This will guide you to upgrade from Backpack 4.1 to v5. 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 or 7.3
  • Laravel 9.x or 8.x
  • Backpack\CRUD 4.1.x
  • 10-15 minutes (for most projects)

If you're running Backpack version 3.x or 4.0, please follow ALL other 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.1 to v5. Previous upgrade guides:

Upgrade Steps

The upgrade guide might seem long or intimidating, but really, it's an easy upgrade. Most projects will only be affected by a few of the 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. At the very least, read what's in bold.

Step 0. Upgrade to Laravel 8 if you don't use it yet, then test to confirm your app is working fine. If you also want to upgrade to Laravel 9, we recommend you do that after you've upgraded Backpack to v5. It'll be easier that way.


Step 1. Update your composer.json file to require:

        "backpack/crud": "^5.0.0",
        "backpack/pro": "^1.0.0",

If the spit between these two packages is news to you, please read the open-core section of the release notes, to understand how this affects you.

These two packages together will help you have all the features in v4.1 and more. But since backpack/pro is a closed-source package, to download it, you need to generate your token and password here. If no button is there for you, it means you don't have free access to backpack/pro, so you'll need to purchase it. Follow the 2-step process called "Instructions" in your token, if you haven't already done that on this project.

Step 2. If you have other backpack addons installed (eg. Backpack\PermissionManager), most of them don't need a version bump, but you should take into consideration that some might need (eg: MenuCRUD). However, if you have third-party Backpack add-ons installed, you might want to bump their versions - please check each addon's page.

Step 3. Run composer update in the command line.

If you get any conflicts with backpack first party addons most of the time is just moving one version up, eg: backpack/menucrud: ^2.0 to backpack/menucrud: ^3.0.


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 it does present some small breaking changes. If you've used the repeatable field in your CRUDs

  • please make sure that db column is cast 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; so, when upgrading to v5:
    • 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 v5, 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 allowing you to add an invokable class, acting like a closure, that backpack will run to strip the request.
  • 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 an invokable class. Basically you first create the invokable class and then add it into the config:


namespace App\Http\Requests;

use Illuminate\Http\Request;

class StripBackpackRequest
    public function __invoke(Request $request)
        return $request->except('_token', '_method', '_http_referrer', '_current_tab', '_save_action');
-    'saveAllInputsExcept' => ['_token', '_method', 'http_referrer', 'current_tab', 'save_action'],
+    'strippedRequest' => '\App\Http\Requests\StripBackpackRequest',
+    }),

But you can also do a lot more, because you have the $request in that class. You can see an example here. In addition, please notice that all hidden parameters are now prefixed by an underscore. Starting with v5, 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 no rows when empty (previously it was showing one); 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;
  • if you have subfields that do NOT have their type defined, Backpack will now assume you wanted a text field;
  • we've fixed a bug in the unofficial "subfield type guessing" functionality: previously, if you added a subfield with the name category (for example), and your main entity happened to have a category relationship on it, then the repeatable field would show a relationship field, which is probably not what you wanted;

Step 12. We have removed the simplemde field, since that 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 overridden some of its functionality in your CrudController, 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, after your setupShowOperation() was run, Backpack would try to add more columns. That made it very difficult to delete auto-added columns. You might have used a workaround for this.

Starting with Backpack v5, the operation will no longer do any "automatic setup" inside setupShowOperation()... nor after it. Instead, if you want Backpack to guess column names, you have to manually call a new method, $this->autoSetupShowOperation(). 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:
    • 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 database now. To give you an example, if you've ever 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->setOperationSetting('strippedRequest', function($request) {
        $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 overridden the store() or update() methods), please take into consideration that starting with Backpack v5, 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 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 19. We've updated a lot of CSS & JS dependencies to their latest versions - with one notable exception - Bootstrap. Since not all dependencies support Bootstrap 5 yet, we'll still be using Bootstrap 4 for a while. But as soon as that changes, we'll release a new version for that upgrade alone. This will also make it easier to upgrade - no worries that the interface breaks now, since we haven't upgraded Bootstrap. And thanks to our new business model - you'll most likely have access to the next Backpack version too.

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 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/columns? 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). Your custom fields should still work without this change, but it's such an easy one.

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. If you've overwritten resources/views/crud/form_content.blade.php you may need to update it. JS Fields API is now imported on that file, the easiest way is to add that include directly near the end of the file. In most of the cases you won't be affected by this, but if you have this file in your project source, please make sure it includes the following line.


+    @include('crud::inc.form_fields_script')


Step 23. 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 24. Clear your app's cache:

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

If the table view still looks wonky (search bar out of place, big + instead of ellipsis), then do a hard-reload in your browser (Cmd+Shift+R or Ctrl+Shift+F5) to purge the browser cache too.

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

Like our open-core?

Then you'll love our premium add-ons - productivity tools and tons of new features.