diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index dc5c3c379..6f2af7ce1 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -14,7 +14,7 @@ jobs:
       matrix:
         python-version: ['3.7', '3.8', '3.9', '3.10']
         django-version: ['3.2', '4.0']
-        wagtail-version: ['2.15', '2.16']
+        wagtail-version: ['2.15', '2.16', '3.0']
         exclude:
           - python-version: '3.7'
             django-version: '4.0'
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 66d9f9e07..bcd20a96c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,13 @@
 Changelog
 =========
 
+3.1.2 (17.06.2022)
+------------------
+
+* Added support for Wagtail >3.0.
+* Updated travis/tox test settings to test against Wagtail 3.0.
+* Fix: [#421](https://github.com/jazzband/wagtailmenus/issues/421), which prevented creating or editing menus in wagtail 3.0.
+
 3.1.1 (25.04.2022)
 ------------------
 
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 2693b85e0..32526667d 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -27,6 +27,7 @@ This is a [Jazzband](https://jazzband.co/) project. By contributing you agree to
 * Sekani Tembo (Method Softworks)
 * Fernando Cordeiro (MrCordeiro)
   twitter.com/brauhaus
+* Abdulmajeed Isa (amajai)
 
 ## Translators
 
diff --git a/docs/source/releases/index.rst b/docs/source/releases/index.rst
index 8dcc4cf51..c6358e4fb 100644
--- a/docs/source/releases/index.rst
+++ b/docs/source/releases/index.rst
@@ -5,6 +5,7 @@ Release notes
 .. toctree::
     :maxdepth: 1
 
+    3.1.2
     3.1.1
     3.1
     3.0.2
diff --git a/setup.py b/setup.py
index e3ececd0e..1e8fe3e0e 100644
--- a/setup.py
+++ b/setup.py
@@ -69,6 +69,7 @@
         'Framework :: Django :: 3.2',
         'Framework :: Django :: 4.0',
         'Framework :: Wagtail :: 2',
+        'Framework :: Wagtail :: 3',
         'Topic :: Internet :: WWW/HTTP',
         "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
     ],
diff --git a/tox.ini b/tox.ini
index 0606bbcfd..7ac09f0e3 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,14 +13,15 @@ DJANGO =
 WAGTAIL =
     2.15: wt215
     2.16: wt216
+    3.0: wt30
 
 [tox]
 skipsdist = True
 usedevelop = True
 
 envlist =
-    py{37,38,39,310}-dj32-wt{215,216}
-    py{38,39,310}-dj40-wt216
+    py{37,38,39,310}-dj32-wt{215,216,30}
+    py{38,39,310}-dj40-wt{216,30}
 
 [testenv]
 description = Unit tests
@@ -38,3 +39,4 @@ deps =
     dj40: Django>=4.0,<4.1
     wt215: wagtail>=2.15,<2.16
     wt216: wagtail>=2.16,<2.17
+    wt30: wagtail>=3.0,<3.1
diff --git a/wagtailmenus/__init__.py b/wagtailmenus/__init__.py
index f3c82e487..dc1c5e210 100644
--- a/wagtailmenus/__init__.py
+++ b/wagtailmenus/__init__.py
@@ -2,7 +2,7 @@
 
 # major.minor.patch.release.number
 # release must be one of alpha, beta, rc, or final
-VERSION = (3, 1, 1, "final", 0)
+VERSION = (3, 1, 2, "final", 0)
 __version__ = get_version(VERSION)
 stable_branch_name = get_stable_branch_name(VERSION)
 
diff --git a/wagtailmenus/management/commands/autopopulate_main_menus.py b/wagtailmenus/management/commands/autopopulate_main_menus.py
index 4fdcf39d0..171f50b31 100644
--- a/wagtailmenus/management/commands/autopopulate_main_menus.py
+++ b/wagtailmenus/management/commands/autopopulate_main_menus.py
@@ -1,9 +1,11 @@
 import logging
 
 from django.core.management.base import BaseCommand
-from wagtail.core.models import Site
-
 from wagtailmenus.conf import settings
+try:
+    from wagtail.models import Site
+except ImportError:
+    from wagtail.core.models import Site
 
 logger = logging.getLogger(__name__)
 
diff --git a/wagtailmenus/models/menuitems.py b/wagtailmenus/models/menuitems.py
index 40829043d..1891845d2 100644
--- a/wagtailmenus/models/menuitems.py
+++ b/wagtailmenus/models/menuitems.py
@@ -4,11 +4,14 @@
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 from modelcluster.fields import ParentalKey
-from wagtail.admin.edit_handlers import FieldPanel, PageChooserPanel
-from wagtail.core.models import Page, Orderable
-
 from wagtailmenus.conf import settings
 from wagtailmenus.managers import MenuItemManager
+try:
+    from wagtail.admin.panels import FieldPanel, PageChooserPanel
+    from wagtail.models import Page, Orderable
+except ImportError:
+    from wagtail.admin.edit_handlers import FieldPanel, PageChooserPanel
+    from wagtail.core.models import Page, Orderable
 
 
 #########################################################
diff --git a/wagtailmenus/models/menus.py b/wagtailmenus/models/menus.py
index 0526298cd..b700321e8 100644
--- a/wagtailmenus/models/menus.py
+++ b/wagtailmenus/models/menus.py
@@ -10,8 +10,13 @@
 from django.utils.safestring import mark_safe
 from django.utils.translation import gettext_lazy as _
 from modelcluster.models import ClusterableModel
-from wagtail.core import hooks
-from wagtail.core.models import Page, Site
+try:
+    from wagtail import hooks
+    from wagtail.models import Page, Site
+except ImportError:
+    from wagtail.core import hooks
+    from wagtail.core.models import Page, Site
+
 
 from wagtailmenus import forms, panels
 from wagtailmenus.conf import constants, settings
diff --git a/wagtailmenus/models/pages.py b/wagtailmenus/models/pages.py
index 70f0d3522..20d92e516 100644
--- a/wagtailmenus/models/pages.py
+++ b/wagtailmenus/models/pages.py
@@ -7,11 +7,13 @@
 from django.http import HttpResponse
 from django.shortcuts import redirect
 from django.utils.translation import gettext_lazy as _
-from wagtail.core.models import Page
-
 from wagtailmenus.conf import settings
 from wagtailmenus.forms import LinkPageAdminForm
 from wagtailmenus.panels import menupage_settings_panels, linkpage_edit_handler
+try:
+    from wagtail.models import Page
+except ImportError:
+    from wagtail.core.models import Page
 
 
 class MenuPageMixin(models.Model):
diff --git a/wagtailmenus/panels.py b/wagtailmenus/panels.py
index c375ca5a6..d43349e6c 100644
--- a/wagtailmenus/panels.py
+++ b/wagtailmenus/panels.py
@@ -2,12 +2,17 @@
 
 from django.conf import settings as django_settings
 from django.utils.translation import gettext_lazy as _
-from wagtail.admin.edit_handlers import (
-    FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel,
-    PageChooserPanel, ObjectList, TabbedInterface
-)
-
 from wagtailmenus.conf import settings
+try:
+    from wagtail.admin.panels import (
+        FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel,
+        PageChooserPanel, ObjectList, TabbedInterface
+    )
+except ImportError:
+    from wagtail.admin.edit_handlers import (
+        FieldPanel, FieldRowPanel, InlinePanel, MultiFieldPanel,
+        PageChooserPanel, ObjectList, TabbedInterface
+    )
 
 
 # ########################################################
diff --git a/wagtailmenus/templatetags/menu_tags.py b/wagtailmenus/templatetags/menu_tags.py
index d8fb6e339..41ea4da7b 100644
--- a/wagtailmenus/templatetags/menu_tags.py
+++ b/wagtailmenus/templatetags/menu_tags.py
@@ -1,9 +1,11 @@
 from django.template import Library
-from wagtail.core.models import Page
-
 from wagtailmenus.conf import constants, settings
 from wagtailmenus.errors import SubMenuUsageError
 from wagtailmenus.utils.misc import validate_supplied_values
+try:
+    from wagtail.models import Page
+except ImportError:
+    from wagtail.core.models import Page
 
 register = Library()
 
diff --git a/wagtailmenus/tests/models/menus.py b/wagtailmenus/tests/models/menus.py
index 0fb7a3972..7221e92da 100644
--- a/wagtailmenus/tests/models/menus.py
+++ b/wagtailmenus/tests/models/menus.py
@@ -1,13 +1,16 @@
 from django.db import models
 from modelcluster.fields import ParentalKey
-from wagtail.admin.edit_handlers import (
-    FieldPanel, MultiFieldPanel, PageChooserPanel)
-
 from wagtailmenus.models import (
     SectionMenu, ChildrenMenu, AbstractMainMenu,
     AbstractMainMenuItem, AbstractFlatMenu, AbstractFlatMenuItem)
 
 from .utils import TranslatedField
+try:
+    from wagtail.admin.panels import (
+        FieldPanel, MultiFieldPanel, PageChooserPanel)
+except ImportError:
+    from wagtail.admin.edit_handlers import (
+        FieldPanel, MultiFieldPanel, PageChooserPanel)
 
 
 class CustomChildrenMenu(ChildrenMenu):
diff --git a/wagtailmenus/tests/models/pages.py b/wagtailmenus/tests/models/pages.py
index 86987fa27..90374ada3 100644
--- a/wagtailmenus/tests/models/pages.py
+++ b/wagtailmenus/tests/models/pages.py
@@ -3,14 +3,19 @@
 from django.db import models
 from django.http import Http404
 from django.template.response import TemplateResponse
-from wagtail.admin.edit_handlers import (
-    FieldPanel, MultiFieldPanel, PublishingPanel
-)
 from wagtail.contrib.routable_page.models import RoutablePageMixin, route
-from wagtail.core.models import Page
-
 from wagtailmenus.models import MenuPage, AbstractLinkPage
 from .utils import TranslatedField
+try:
+    from wagtail.admin.panels import (
+        FieldPanel, MultiFieldPanel, PublishingPanel
+    )
+    from wagtail.models import Page
+except ImportError:
+    from wagtail.admin.edit_handlers import (
+        FieldPanel, MultiFieldPanel, PublishingPanel
+    )
+    from wagtail.core.models import Page
 
 
 class MultilingualMenuPage(MenuPage):
diff --git a/wagtailmenus/tests/test_backend.py b/wagtailmenus/tests/test_backend.py
index 1b40a095d..0c2865f17 100644
--- a/wagtailmenus/tests/test_backend.py
+++ b/wagtailmenus/tests/test_backend.py
@@ -6,12 +6,15 @@
 from django.test import TestCase, TransactionTestCase, override_settings
 
 from django_webtest import WebTest
-from wagtail.admin.edit_handlers import ObjectList
-from wagtail.core.models import Page, Site
-
 from wagtailmenus import get_flat_menu_model, get_main_menu_model
 
 from wagtailmenus.tests.models import LinkPage
+try:
+    from wagtail.admin.panels import ObjectList
+    from wagtail.models import Page, Site
+except ImportError:
+    from wagtail.admin.edit_handlers import ObjectList
+    from wagtail.core.models import Page, Site
 
 FlatMenu = get_flat_menu_model()
 
diff --git a/wagtailmenus/tests/test_base_menu_classes.py b/wagtailmenus/tests/test_base_menu_classes.py
index daa587a4d..64106fd2f 100644
--- a/wagtailmenus/tests/test_base_menu_classes.py
+++ b/wagtailmenus/tests/test_base_menu_classes.py
@@ -1,9 +1,10 @@
 from django.test import TestCase
-
-from wagtail.core.models import Page, Site
-
 from wagtailmenus.errors import RequestUnavailableError
 from wagtailmenus.models import Menu, MenuWithMenuItems
+try:
+    from wagtail.models import Page, Site
+except ImportError:
+    from wagtail.core.models import Page, Site
 
 
 class TestMenuGetSite(TestCase):
diff --git a/wagtailmenus/tests/test_commands.py b/wagtailmenus/tests/test_commands.py
index 351d4f116..9a907f4d7 100644
--- a/wagtailmenus/tests/test_commands.py
+++ b/wagtailmenus/tests/test_commands.py
@@ -1,7 +1,10 @@
 from django.test import TestCase
 from django.core.management import call_command
-from wagtail.core.models import Site
 from wagtailmenus.conf import settings
+try:
+    from wagtail.models import Site
+except ImportError:
+    from wagtail.core.models import Site
 
 
 class TestAutoPopulateMainMenus(TestCase):
diff --git a/wagtailmenus/tests/test_hooks.py b/wagtailmenus/tests/test_hooks.py
index aec0688df..294bf879e 100644
--- a/wagtailmenus/tests/test_hooks.py
+++ b/wagtailmenus/tests/test_hooks.py
@@ -2,8 +2,10 @@
 
 from bs4 import BeautifulSoup
 from django.test import TestCase
-from wagtail.core import hooks
-
+try:
+    from wagtail import hooks
+except ImportError:
+    from wagtail.core import hooks
 
 class TestHooks(TestCase):
     fixtures = ['test.json']
diff --git a/wagtailmenus/tests/test_menu_items.py b/wagtailmenus/tests/test_menu_items.py
index b0aa4e99a..a8b526c52 100644
--- a/wagtailmenus/tests/test_menu_items.py
+++ b/wagtailmenus/tests/test_menu_items.py
@@ -1,12 +1,14 @@
 from django.core.exceptions import ValidationError
 from django.test import TestCase
 from django.test.client import RequestFactory
-from wagtail.core.models import Page
-
 from wagtailmenus.conf import settings
 from wagtailmenus.models import (
     AbstractMenuItem, MainMenu, MainMenuItem, FlatMenu, FlatMenuItem
 )
+try:
+    from wagtail.models import Page
+except ImportError:
+    from wagtail.core.models import Page
 
 
 class MenuItemModelTestMixin:
diff --git a/wagtailmenus/tests/test_menu_rendering.py b/wagtailmenus/tests/test_menu_rendering.py
index 2105b3840..a703002b1 100644
--- a/wagtailmenus/tests/test_menu_rendering.py
+++ b/wagtailmenus/tests/test_menu_rendering.py
@@ -1,10 +1,12 @@
 from bs4 import BeautifulSoup
 from django.test import TestCase, override_settings
-from wagtail.core.models import Site
-
 from wagtailmenus.errors import SubMenuUsageError
 from wagtailmenus.models import MainMenu, FlatMenu
 from wagtailmenus.templatetags.menu_tags import validate_supplied_values
+try:
+    from wagtail.models import Site
+except ImportError:
+    from wagtail.core.models import Site
 
 
 class TestTemplateTags(TestCase):
diff --git a/wagtailmenus/tests/test_page_models.py b/wagtailmenus/tests/test_page_models.py
index 39071895d..02de7efa9 100644
--- a/wagtailmenus/tests/test_page_models.py
+++ b/wagtailmenus/tests/test_page_models.py
@@ -1,8 +1,10 @@
 from django.test import TestCase
 from django.core.exceptions import ValidationError
-from wagtail.core.models import Site
-
 from wagtailmenus.tests.models import LinkPage
+try:
+    from wagtail.models import Site
+except ImportError:
+    from wagtail.core.models import Site
 
 
 class TestLinkPage(TestCase):
diff --git a/wagtailmenus/tests/urls.py b/wagtailmenus/tests/urls.py
index 4ffe78ef8..a5f81a483 100644
--- a/wagtailmenus/tests/urls.py
+++ b/wagtailmenus/tests/urls.py
@@ -4,7 +4,6 @@
 from wagtail.admin import urls as wagtailadmin_urls
 from wagtail.core import urls as wagtail_urls
 
-
 urlpatterns = [
     re_path(r'^admin/', include(wagtailadmin_urls)),
     re_path(r'^custom-url/$', TemplateView.as_view(template_name='page.html')),
diff --git a/wagtailmenus/tests/utils.py b/wagtailmenus/tests/utils.py
index 83c0bfcdf..0bfc9b1a4 100644
--- a/wagtailmenus/tests/utils.py
+++ b/wagtailmenus/tests/utils.py
@@ -12,12 +12,18 @@
 
 
 def get_page_model():
-    from wagtail.core.models import Page
+    try:
+        from wagtail.models import Page
+    except ImportError:
+        from wagtail.core.models import Page
     return Page
 
 
 def get_site_model():
-    from wagtail.core.models import Site
+    try:
+        from wagtail.models import Site
+    except ImportError:
+        from wagtail.core.models import Site
     return Site
 
 
diff --git a/wagtailmenus/utils/misc.py b/wagtailmenus/utils/misc.py
index 7138f82c7..673ba51a2 100644
--- a/wagtailmenus/utils/misc.py
+++ b/wagtailmenus/utils/misc.py
@@ -1,7 +1,10 @@
 from django.http import Http404, HttpRequest
-from wagtail.core.models import Page, Site
 
 from wagtailmenus.models.menuitems import MenuItem
+try:
+    from wagtail.models import Page, Site
+except ImportError:
+    from wagtail.core.models import Page, Site
 
 
 def get_fake_site():
diff --git a/wagtailmenus/utils/tests/test_misc.py b/wagtailmenus/utils/tests/test_misc.py
index d91b37d6f..b28702b61 100644
--- a/wagtailmenus/utils/tests/test_misc.py
+++ b/wagtailmenus/utils/tests/test_misc.py
@@ -1,8 +1,5 @@
 from django.test import RequestFactory, TestCase, modify_settings
 from distutils.version import LooseVersion
-from wagtail.core import __version__ as wagtail_version
-from wagtail.core.models import Page, Site
-
 from wagtailmenus.conf import defaults
 from wagtailmenus.utils.misc import (
     derive_page, derive_section_root, get_fake_request, get_site_from_request
@@ -10,6 +7,12 @@
 from wagtailmenus.tests.models import (
     ArticleListPage, ArticlePage, LowLevelPage, TopLevelPage
 )
+try:
+    from wagtail import __version__ as wagtail_version
+    from wagtail.models import Page, Site
+except ImportError:
+    from wagtail.core import __version__ as wagtail_version
+    from wagtail.core.models import Page, Site
 
 
 class TestGetFakeRequest(TestCase):
diff --git a/wagtailmenus/views.py b/wagtailmenus/views.py
index ef113b1f9..234bf92e8 100644
--- a/wagtailmenus/views.py
+++ b/wagtailmenus/views.py
@@ -7,13 +7,19 @@
 from django.utils.text import capfirst
 from django.utils.translation import gettext as _
 from wagtail.admin import messages
-from wagtail.admin.edit_handlers import ObjectList, TabbedInterface
 from wagtail.contrib.modeladmin.views import (
     WMABaseView, CreateView, EditView, ModelFormView
 )
-from wagtail.core.models import Site
-
 from wagtailmenus.conf import settings
+from distutils.version import LooseVersion
+try:
+    from wagtail import __version__ as wagtail_version
+    from wagtail.models import Site
+    from wagtail.admin.panels import ObjectList, TabbedInterface
+except ImportError:
+    from wagtail.core import __version__ as wagtail_version
+    from wagtail.core.models import Site
+    from wagtail.admin.edit_handlers import ObjectList, TabbedInterface
 
 
 class SiteSwitchForm(forms.Form):
@@ -54,8 +60,8 @@ def get_edit_handler(self):
                 ObjectList(self.model.settings_panels, heading=_("Settings"),
                            classname="settings"),
             ])
-        if hasattr(edit_handler, 'bind_to'):
-            # For Wagtail>=2.5
+        if LooseVersion(wagtail_version) < LooseVersion('3.0'):
+            # For Wagtail>=2.5,<3.0
             return edit_handler.bind_to(model=self.model)
         return edit_handler.bind_to_model(self.model)
 
diff --git a/wagtailmenus/wagtail_hooks.py b/wagtailmenus/wagtail_hooks.py
index 5f136546c..7457e4df9 100644
--- a/wagtailmenus/wagtail_hooks.py
+++ b/wagtailmenus/wagtail_hooks.py
@@ -1,4 +1,3 @@
-from wagtail.core import hooks
 from wagtail.contrib.modeladmin.options import modeladmin_register
 
 from wagtailmenus.conf import settings
@@ -6,6 +5,10 @@
 from wagtailmenus.modeladmin import ( # noqa
     MainMenuAdmin, FlatMenuAdmin, FlatMenuButtonHelper
 )
+try:
+    from wagtail import hooks
+except ImportError:
+    from wagtail.core import hooks
 
 if settings.MAIN_MENUS_EDITABLE_IN_WAGTAILADMIN:
     modeladmin_register(settings.objects.MAIN_MENUS_MODELADMIN_CLASS)