diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 517cc23cf2..4b95ea22c3 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -163,6 +163,7 @@ Changelog
  * Fix: Layout issues with reports (including form submissions listings) on md device widths (Akash Kumar Sen, LB (Ben) Johnston)
  * Fix: Layout issue with page explorer's inner header item on small device widths (Akash Kumar Sen)
  * Fix: Ensure that `BaseSiteSetting` / `BaseGenericSetting` objects can be pickled (Andy Babic)
+ * Fix: Ensure `DocumentChooserBlock` can be deconstructed for migrations (Matt Westcott)
 
 
 3.0.1 (16.06.2022)
diff --git a/client/src/entrypoints/admin/page-editor.js b/client/src/entrypoints/admin/page-editor.js
index dd050952a9..da30ddbfff 100644
--- a/client/src/entrypoints/admin/page-editor.js
+++ b/client/src/entrypoints/admin/page-editor.js
@@ -94,7 +94,6 @@ function InlinePanel(opts) {
       forms.each(function updateButtonStates(i) {
         const isFirst = i === 0;
         const isLast = i === forms.length - 1;
-        console.log(isFirst, isLast);
         $('[data-inline-panel-child-move-up]', this).prop('disabled', isFirst);
         $('[data-inline-panel-child-move-down]', this).prop('disabled', isLast);
       });
diff --git a/client/src/includes/breadcrumbs.js b/client/src/includes/breadcrumbs.js
index 6322500e8a..eb60b5cd31 100644
--- a/client/src/includes/breadcrumbs.js
+++ b/client/src/includes/breadcrumbs.js
@@ -15,6 +15,8 @@ export default function initCollapsibleBreadcrumbs() {
     '[data-toggle-breadcrumbs]',
   );
 
+  if (!breadcrumbsToggle) return;
+
   const breadcrumbItems = breadcrumbsContainer.querySelectorAll(
     '[data-breadcrumb-item]',
   );
diff --git a/docs/extending/generic_views.md b/docs/extending/generic_views.md
index b003c33ee4..f506ab4164 100644
--- a/docs/extending/generic_views.md
+++ b/docs/extending/generic_views.md
@@ -96,6 +96,10 @@ The viewset also makes a StreamField chooser block class available, as the prope
 from .views import person_chooser_viewset
 
 PersonChooserBlock = person_chooser_viewset.block_class
+
+# When deconstructing a PersonChooserBlock instance for migrations, the module path
+# used in migrations should point back to this module
+PersonChooserBlock.__module__ = "myapp.blocks"
 ```
 
 ## Chooser viewsets for non-model datasources
diff --git a/docs/releases/4.0.md b/docs/releases/4.0.md
index a6d74c9ce5..4411cb18a1 100644
--- a/docs/releases/4.0.md
+++ b/docs/releases/4.0.md
@@ -220,6 +220,7 @@ The bulk of these enhancements have been from Paarth Agarwal, who has been doing
  * Resolve layout issues with reports (including form submissions listings) on md device widths (Akash Kumar Sen, LB (Ben) Johnston)
  * Resolve Layout issue with page explorer's inner header item on small device widths (Akash Kumar Sen)
  * Ensure that `BaseSiteSetting` / `BaseGenericSetting` objects can be pickled (Andy Babic)
+ * Ensure `DocumentChooserBlock` can be deconstructed for migrations (Matt Westcott)
 
 
 ## Upgrade considerations
diff --git a/wagtail/documents/blocks.py b/wagtail/documents/blocks.py
index 291fa803ab..31af414252 100644
--- a/wagtail/documents/blocks.py
+++ b/wagtail/documents/blocks.py
@@ -1,3 +1,7 @@
 from wagtail.documents.views.chooser import viewset as chooser_viewset
 
 DocumentChooserBlock = chooser_viewset.block_class
+
+# When deconstructing a DocumentChooserBlock instance for migrations, the module path
+# used in migrations should point to this module
+DocumentChooserBlock.__module__ = "wagtail.documents.blocks"

diff --git a/wagtail/documents/tests/test_blocks.py b/wagtail/documents/tests/test_blocks.py
new file mode 100644
index 0000000000..031dad9af1
--- /dev/null
+++ b/wagtail/documents/tests/test_blocks.py
@@ -0,0 +1,12 @@
+from django.test import TestCase
+
+from wagtail.documents.blocks import DocumentChooserBlock
+
+
+class TestDocumentChooserBlock(TestCase):
+    def test_deconstruct(self):
+        block = DocumentChooserBlock(required=False)
+        path, args, kwargs = block.deconstruct()
+        self.assertEqual(path, "wagtail.documents.blocks.DocumentChooserBlock")
+        self.assertEqual(args, ())
+        self.assertEqual(kwargs, {"required": False})
diff --git a/wagtail/images/tests/test_blocks.py b/wagtail/images/tests/test_blocks.py
index 904398e34c..bf8d537786 100644
--- a/wagtail/images/tests/test_blocks.py
+++ b/wagtail/images/tests/test_blocks.py
@@ -63,3 +63,10 @@ class TestImageChooserBlock(TestCase):
         )
 
         self.assertHTMLEqual(html, expected_html)
+
+    def test_deconstruct(self):
+        block = ImageChooserBlock(required=False)
+        path, args, kwargs = block.deconstruct()
+        self.assertEqual(path, "wagtail.images.blocks.ImageChooserBlock")
+        self.assertEqual(args, ())
+        self.assertEqual(kwargs, {"required": False})
diff --git a/wagtail/snippets/tests/test_snippets.py b/wagtail/snippets/tests/test_snippets.py
index c9cbcfb156..54c592d4e5 100644
--- a/wagtail/snippets/tests/test_snippets.py
+++ b/wagtail/snippets/tests/test_snippets.py
@@ -3174,6 +3174,13 @@ class TestSnippetChooserBlock(TestCase):
         self.assertEqual(nonrequired_block.clean(test_advert), test_advert)
         self.assertIsNone(nonrequired_block.clean(None))
 
+    def test_deconstruct(self):
+        block = SnippetChooserBlock(Advert, required=False)
+        path, args, kwargs = block.deconstruct()
+        self.assertEqual(path, "wagtail.snippets.blocks.SnippetChooserBlock")
+        self.assertEqual(args, (Advert,))
+        self.assertEqual(kwargs, {"required": False})
+
 
 class TestAdminSnippetChooserWidget(TestCase, WagtailTestUtils):
     def test_adapt(self):

Breadcrumbs JavaScript error on root page listing / index (4.0 RC)
### Issue Summary

The breadcrumbs on the page listing under the root page is triggering a JavaScript error.

### Steps to Reproduce

1. Start a new project with `wagtail start myproject`
2. Load up the Wagtail admin
3. Navigate to the root page
4. Expected: No console errors
5. Actual: Console error triggers `Uncaught TypeError: breadcrumbsToggle is null` (note: error is not as clear in the compressed JS build).

-   I have confirmed that this issue can be reproduced as described on a fresh Wagtail project: no

### Technical details

-   Python version: 3.9
-   Django version: 4.0
-   Wagtail version: 4.0 RC1
-   Browser version: Firefox 103.0 (64-bit) on macOS 12.3

**Full error**

```
Uncaught TypeError: breadcrumbsToggle is null
    initCollapsibleBreadcrumbs breadcrumbs.js:50
    <anonymous> wagtailadmin.js:37
    EventListener.handleEvent* wagtailadmin.js:30
    js wagtailadmin.js:72
    __webpack_require__ wagtailadmin.js:184
    __webpack_exports__ wagtailadmin.js:348
    O wagtailadmin.js:221
    <anonymous> wagtailadmin.js:349
    <anonymous> wagtailadmin.js:351
[breadcrumbs.js:50](webpack://wagtail/client/src/includes/breadcrumbs.js?b04c)
    initCollapsibleBreadcrumbs breadcrumbs.js:50
    <anonymous> wagtailadmin.js:37
    (Async: EventListener.handleEvent)
    <anonymous> wagtailadmin.js:30
    js wagtailadmin.js:72
    __webpack_require__ wagtailadmin.js:184
    __webpack_exports__ wagtailadmin.js:348
    O wagtailadmin.js:221
    <anonymous> wagtailadmin.js:349
    <anonymous> wagtailadmin.js:351
```

### Likely root cause

* `data-breadcrumbs-next` is still being rendered even though the root breadcrumbs are not collapsible
* https://github.com/wagtail/wagtail/blob/5ff6922eb517015651b9012bd6534a912a50b449/wagtail/admin/templates/wagtailadmin/pages/page_listing_header.html#L11
* https://github.com/wagtail/wagtail/blob/5ff6922eb517015651b9012bd6534a912a50b449/wagtail/admin/templates/wagtailadmin/shared/breadcrumbs.html#L11
* https://github.com/wagtail/wagtail/blob/5ff6922eb517015651b9012bd6534a912a50b449/client/src/includes/breadcrumbs.js#L14-L16
* Maybe when content is not collapsible we should not be adding the `data-breadcrumbs-next` so that the JS does not try to init the toggle at all OR maybe we should add extra logic to the JS so that it does not try to init the toggle if it is not present in the DOM. Preference would be to ensure the data attribute is not set when JS logic is not required.

### Screenshot

<img width="1557" alt="Screen Shot 2022-08-14 at 3 15 08 pm" src="https://user-images.githubusercontent.com/1396140/184523614-d44f9a69-a2b5-46cc-b638-eeb5e6435f28.png">


wagtail/documents/tests/test_blocks.py::TestDocumentChooserBlock::test_deconstruct
wagtail/snippets/tests/test_snippets.py::TestSnippetRegistering::test_register_decorator wagtail/snippets/tests/test_snippets.py::TestSnippetRegistering::test_register_function wagtail/snippets/tests/test_snippets.py::TestSnippetOrdering::test_snippets_ordering wagtail/snippets/tests/test_snippets.py::TestSnippetEditHandlers::test_fancy_edit_handler wagtail/snippets/tests/test_snippets.py::TestSnippetEditHandlers::test_get_snippet_edit_handler wagtail/snippets/tests/test_snippets.py::TestSnippetEditHandlers::test_standard_edit_handler wagtail/snippets/tests/test_snippets.py::TestAdminSnippetChooserWidget::test_adapt wagtail/snippets/tests/test_snippets.py::TestPanelConfigurationChecks::test_model_with_single_tabbed_panel_only