diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt
index 3507a11e5f..f2a8f68c86 100644
--- a/common/CMakeLists.txt
+++ b/common/CMakeLists.txt
@@ -14,6 +14,7 @@ include_directories(
     ${CMAKE_SOURCE_DIR}/resources/bitmaps_png
     ${CMAKE_SOURCE_DIR}/3d-viewer
     ${CMAKE_SOURCE_DIR}/pcbnew
+    ${CMAKE_SOURCE_DIR}/kicad
     ${INC_AFTER}
     )
 
@@ -117,6 +118,11 @@ set( KICOMMON_SRCS
 
     database/database_lib_settings.cpp
 
+    design_block_lib_table.cpp
+    design_block_io.cpp
+    design_block_info.cpp
+    design_block_info_impl.cpp
+
     advanced_config.cpp
     asset_archive.cpp
     array_axis.cpp
@@ -173,6 +179,8 @@ set( KICOMMON_SRCS
     ../scripting/python_scripting.cpp
 
     io/kicad/kicad_io_utils.cpp    # needed by richio
+    io/io_base.cpp
+    io/io_utils.cpp
     )
 
 if( KICAD_IPC_API )
diff --git a/common/advanced_config.cpp b/common/advanced_config.cpp
index 6a67df4c68..bbc6ebea4f 100644
--- a/common/advanced_config.cpp
+++ b/common/advanced_config.cpp
@@ -99,6 +99,7 @@ static const wxChar UpdateUIEventInterval[] = wxT( "UpdateUIEventInterval" );
 static const wxChar V3DRT_BevelHeight_um[] = wxT( "V3DRT_BevelHeight_um" );
 static const wxChar V3DRT_BevelExtentFactor[] = wxT( "V3DRT_BevelExtentFactor" );
 static const wxChar UseClipper2[] = wxT( "UseClipper2" );
+static const wxChar EnableDesignBlocks[] = wxT( "EnableDesignBlocks" );
 static const wxChar EnableGenerators[] = wxT( "EnableGenerators" );
 static const wxChar EnableGit[] = wxT( "EnableGit" );
 static const wxChar EnableLibWithText[] = wxT( "EnableLibWithText" );
@@ -241,6 +242,7 @@ ADVANCED_CFG::ADVANCED_CFG()
     m_CompactSave               = false;
     m_UpdateUIEventInterval     = 0;
     m_ShowRepairSchematic       = false;
+    m_EnableDesignBlocks        = false;
     m_EnableGenerators          = false;
     m_EnableGit                 = false;
     m_EnableLibWithText         = false;
@@ -459,6 +461,9 @@ void ADVANCED_CFG::loadSettings( wxConfigBase& aCfg )
                                                m_DisambiguationMenuDelay,
                                                50, 10000 ) );
 
+    configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableDesignBlocks,
+                                                &m_EnableDesignBlocks, m_EnableDesignBlocks ) );
+
     configParams.push_back( new PARAM_CFG_BOOL( true, AC_KEYS::EnableGenerators,
                                                 &m_EnableGenerators, m_EnableGenerators ) );
 
diff --git a/common/design_block.h b/common/design_block.h
new file mode 100644
index 0000000000..74775ddbf9
--- /dev/null
+++ b/common/design_block.h
@@ -0,0 +1,54 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com>
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+#include <kicommon.h>
+#include <lib_id.h>
+
+
+class DESIGN_BLOCK
+{
+public:
+    void          SetLibId( const LIB_ID& aName ) { m_lib_id = aName; }
+    const LIB_ID& GetLibId() const { return m_lib_id; }
+
+    wxString GetLibIdAsString() const { return m_lib_id.Format(); }
+
+    wxString GetLibDescription() const { return m_libDescription; }
+    void     SetLibDescription( const wxString& aDesc ) { m_libDescription = aDesc; }
+
+    wxString GetKeywords() const { return m_keywords; }
+    void     SetKeywords( const wxString& aKeywords ) { m_keywords = aKeywords; }
+
+    wxString GetDocumentationUrl() const { return m_doc_url; }
+    void     SetDocumentationUrl( const wxString& aDocumentationUrl ) { m_doc_url = aDocumentationUrl; }
+
+    wxString GetSchematicFile() const { return m_schematicFile; }
+    void     SetSchematicFile( const wxString& aFile ) { m_schematicFile = aFile; }
+
+private:
+    LIB_ID m_lib_id;
+    wxString m_schematicFile;  // File name and path for schematic symbol.
+    wxString m_libDescription; // File name and path for documentation file.
+    wxString m_keywords;       // Search keywords to find footprint in library.
+    wxString m_doc_url;        // URL of external documentation
+};
diff --git a/common/design_block_info.cpp b/common/design_block_info.cpp
new file mode 100644
index 0000000000..9d1d397ad1
--- /dev/null
+++ b/common/design_block_info.cpp
@@ -0,0 +1,108 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+/*
+ * Functions to read design block libraries and fill m_design_blocks by available design blocks names
+ * and their documentation (comments and keywords)
+ */
+
+#include <design_block_info.h>
+#include <fp_lib_table.h>
+#include <dialogs/html_message_box.h>
+#include <string_utils.h>
+#include <kiface_ids.h>
+#include <kiway.h>
+#include <lib_id.h>
+#include <thread>
+#include <utility>
+#include <wx/tokenzr.h>
+#include <kiface_base.h>
+
+DESIGN_BLOCK_INFO* DESIGN_BLOCK_LIST::GetDesignBlockInfo( const wxString& aLibNickname,
+                                                          const wxString& aDesignBlockName )
+{
+    if( aDesignBlockName.IsEmpty() )
+        return nullptr;
+
+    for( std::unique_ptr<DESIGN_BLOCK_INFO>& db : m_list )
+    {
+        if( aLibNickname == db->GetLibNickname() && aDesignBlockName == db->GetDesignBlockName() )
+            return db.get();
+    }
+
+    return nullptr;
+}
+
+
+DESIGN_BLOCK_INFO* DESIGN_BLOCK_LIST::GetDesignBlockInfo( const wxString& aDesignBlockName )
+{
+    if( aDesignBlockName.IsEmpty() )
+        return nullptr;
+
+    LIB_ID dbid;
+
+    wxCHECK_MSG( dbid.Parse( aDesignBlockName ) < 0, nullptr,
+                 wxString::Format( wxT( "'%s' is not a valid LIB_ID." ), aDesignBlockName ) );
+
+    return GetDesignBlockInfo( dbid.GetLibNickname(), dbid.GetLibItemName() );
+}
+
+
+std::vector<SEARCH_TERM> DESIGN_BLOCK_INFO::GetSearchTerms()
+{
+    std::vector<SEARCH_TERM> terms;
+
+    terms.emplace_back( SEARCH_TERM( GetName(), 8 ) );
+
+    wxStringTokenizer keywordTokenizer( GetKeywords(), wxS( " " ), wxTOKEN_STRTOK );
+
+    while( keywordTokenizer.HasMoreTokens() )
+        terms.emplace_back( SEARCH_TERM( keywordTokenizer.GetNextToken(), 4 ) );
+
+    // Also include keywords as one long string, just in case
+    terms.emplace_back( SEARCH_TERM( GetKeywords(), 1 ) );
+    terms.emplace_back( SEARCH_TERM( GetDesc(), 1 ) );
+
+    return terms;
+}
+
+
+bool DESIGN_BLOCK_INFO::InLibrary( const wxString& aLibrary ) const
+{
+    return aLibrary == m_nickname;
+}
+
+
+bool operator<( const DESIGN_BLOCK_INFO& lhs, const DESIGN_BLOCK_INFO& rhs )
+{
+    int retv = StrNumCmp( lhs.m_nickname, rhs.m_nickname, false );
+
+    if( retv != 0 )
+        return retv < 0;
+
+    // Technically design block names are not case sensitive because the file name is used
+    // as the design block name.  On windows this would be problematic because windows does
+    // not support case sensitive file names by default.  This should not cause any issues
+    // and allow for a future change to use the name defined in the design block file.
+    return StrNumCmp( lhs.m_dbname, rhs.m_dbname, false ) < 0;
+}
diff --git a/common/design_block_info.h b/common/design_block_info.h
new file mode 100644
index 0000000000..9d8862bce4
--- /dev/null
+++ b/common/design_block_info.h
@@ -0,0 +1,219 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+/*
+ * @file design_block_info.h
+ */
+
+#ifndef DESIGN_BLOCK_INFO_H_
+#define DESIGN_BLOCK_INFO_H_
+
+
+#include <kicommon.h>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <import_export.h>
+#include <ki_exception.h>
+#include <core/sync_queue.h>
+#include <lib_tree_item.h>
+#include <atomic>
+#include <functional>
+#include <memory>
+
+
+class DESIGN_BLOCK_LIB_TABLE;
+class DESIGN_BLOCK_LIST;
+class DESIGN_BLOCK_LIST_IMPL;
+class PROGRESS_REPORTER;
+class wxTopLevelWindow;
+class KIWAY;
+class wxTextFile;
+
+
+/*
+ * Helper class to handle the list of design blocks available in libraries. It stores
+ * design block names, doc and keywords.
+ *
+ * This is a virtual class; its implementation lives in common/design_block_info_impl.cpp.
+ * To get instances of these classes, see DESIGN_BLOCK_LIST::GetInstance().
+ */
+class KICOMMON_API DESIGN_BLOCK_INFO : public LIB_TREE_ITEM
+{
+public:
+    virtual ~DESIGN_BLOCK_INFO() {}
+
+    // These two accessors do not have to call ensure_loaded(), because constructor
+    // fills in these fields:
+
+    const wxString& GetDesignBlockName() const { return m_dbname; }
+
+    wxString GetLibNickname() const override { return m_nickname; }
+
+    wxString GetName() const override { return m_dbname; }
+
+    LIB_ID GetLIB_ID() const override { return LIB_ID( m_nickname, m_dbname ); }
+
+    wxString GetDesc() override
+    {
+        ensure_loaded();
+        return m_doc;
+    }
+
+    wxString GetKeywords()
+    {
+        ensure_loaded();
+        return m_keywords;
+    }
+
+    std::vector<SEARCH_TERM> GetSearchTerms() override;
+
+    int GetOrderNum()
+    {
+        ensure_loaded();
+        return m_num;
+    }
+
+    /**
+     * Test if the #DESIGN_BLOCK_INFO object was loaded from \a aLibrary.
+     *
+     * @param aLibrary is the nickname of the library to test.
+     *
+     * @return true if the #DESIGN_BLOCK_INFO object was loaded from \a aLibrary.  Otherwise
+     *         false.
+     */
+    bool InLibrary( const wxString& aLibrary ) const;
+
+    /**
+     * Less than comparison operator, intended for sorting DESIGN_BLOCK_INFO objects
+     */
+    friend bool operator<( const DESIGN_BLOCK_INFO& lhs, const DESIGN_BLOCK_INFO& rhs );
+
+protected:
+    void ensure_loaded()
+    {
+        if( !m_loaded )
+            load();
+    }
+
+    /// lazily load stuff not filled in by constructor.  This may throw IO_ERRORS.
+    virtual void load(){};
+
+    DESIGN_BLOCK_LIST* m_owner; ///< provides access to DESIGN_BLOCK_LIB_TABLE
+
+    bool m_loaded;
+
+    wxString m_nickname; ///< library as known in DESIGN_BLOCK_LIB_TABLE
+    wxString m_dbname;   ///< Module name.
+    int      m_num;      ///< Order number in the display list.
+    wxString m_doc;      ///< Design block description.
+    wxString m_keywords; ///< Design block keywords.
+};
+
+
+/**
+ * Holds a list of #DESIGN_BLOCK_INFO objects, along with a list of IO_ERRORs or
+ * PARSE_ERRORs that were thrown acquiring the DESIGN_BLOCK_INFOs.
+ *
+ * This is a virtual class; its implementation lives in common/design_block_info_impl.cpp.
+ * To get instances of these classes, see DESIGN_BLOCK_LIST::GetInstance().
+ */
+class KICOMMON_API DESIGN_BLOCK_LIST
+{
+public:
+    typedef std::vector<std::unique_ptr<DESIGN_BLOCK_INFO>> DBILIST;
+    typedef SYNC_QUEUE<std::unique_ptr<IO_ERROR>>           ERRLIST;
+
+    DESIGN_BLOCK_LIST() : m_lib_table( nullptr ) {}
+
+    virtual ~DESIGN_BLOCK_LIST() {}
+
+    virtual void WriteCacheToFile( const wxString& aFilePath ){};
+    virtual void ReadCacheFromFile( const wxString& aFilePath ){};
+
+    /**
+     * @return the number of items stored in list
+     */
+    unsigned GetCount() const { return m_list.size(); }
+
+    /// Was forced to add this by modview_frame.cpp
+    const DBILIST& GetList() const { return m_list; }
+
+    /**
+     * @return Clears the design block info cache
+     */
+    void Clear() { m_list.clear(); }
+
+    /**
+     * Get info for a design block by id.
+     */
+    DESIGN_BLOCK_INFO* GetDesignBlockInfo( const wxString& aDesignBlockName );
+
+    /**
+     * Get info for a design block by libNickname/designBlockName
+     */
+    DESIGN_BLOCK_INFO* GetDesignBlockInfo( const wxString& aLibNickname,
+                                           const wxString& aDesignBlockName );
+
+    /**
+     * Get info for a design block by index.
+     *
+     * @param aIdx index of the given item.
+     * @return the aIdx item in list.
+     */
+    DESIGN_BLOCK_INFO& GetItem( unsigned aIdx ) const { return *m_list[aIdx]; }
+
+    unsigned GetErrorCount() const { return m_errors.size(); }
+
+    std::unique_ptr<IO_ERROR> PopError()
+    {
+        std::unique_ptr<IO_ERROR> error;
+
+        m_errors.pop( error );
+        return error;
+    }
+
+    /**
+     * Read all the design blocks provided by the combination of aTable and aNickname.
+     *
+     * @param aTable defines all the libraries.
+     * @param aNickname is the library to read from, or if NULL means read all design blocks
+     *                  from all known libraries in aTable.
+     * @param aProgressReporter is an optional progress reporter.  ReadDesignBlockFiles() will
+     *                          use 2 phases within the reporter.
+     * @return true if it ran to completion, else false if it aborted after some number of
+     *         errors.  If true, it does not mean there were no errors, check GetErrorCount()
+     *         for that, should be zero to indicate success.
+     */
+    virtual bool ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable,
+                                       const wxString*         aNickname = nullptr,
+                                       PROGRESS_REPORTER*      aProgressReporter = nullptr ) = 0;
+
+    DESIGN_BLOCK_LIB_TABLE* GetTable() const { return m_lib_table; }
+
+protected:
+    DESIGN_BLOCK_LIB_TABLE* m_lib_table = nullptr; ///< no ownership
+
+    DBILIST m_list;
+    ERRLIST m_errors; ///< some can be PARSE_ERRORs also
+};
+
+#endif // DESIGN_BLOCK_INFO_H_
diff --git a/common/design_block_info_impl.cpp b/common/design_block_info_impl.cpp
new file mode 100644
index 0000000000..84eff9a1fb
--- /dev/null
+++ b/common/design_block_info_impl.cpp
@@ -0,0 +1,376 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#include <design_block_info_impl.h>
+
+#include <design_block.h>
+#include <design_block_info.h>
+#include <design_block_lib_table.h>
+#include <kiway.h>
+#include <locale_io.h>
+#include <lib_id.h>
+#include <progress_reporter.h>
+#include <string_utils.h>
+#include <core/thread_pool.h>
+#include <wildcards_and_files_ext.h>
+
+#include <kiplatform/io.h>
+
+#include <wx/textfile.h>
+#include <wx/txtstrm.h>
+#include <wx/wfstream.h>
+
+
+void DESIGN_BLOCK_INFO_IMPL::load()
+{
+    DESIGN_BLOCK_LIB_TABLE* dbtable = m_owner->GetTable();
+
+    wxASSERT( dbtable );
+
+    const DESIGN_BLOCK* design_block = dbtable->GetEnumeratedDesignBlock( m_nickname, m_dbname );
+
+    if( design_block )
+    {
+        m_keywords = design_block->GetKeywords();
+        m_doc = design_block->GetLibDescription();
+    }
+
+    m_loaded = true;
+}
+
+
+bool DESIGN_BLOCK_LIST_IMPL::CatchErrors( const std::function<void()>& aFunc )
+{
+    try
+    {
+        aFunc();
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
+        return false;
+    }
+    catch( const std::exception& se )
+    {
+        // This is a round about way to do this, but who knows what THROW_IO_ERROR()
+        // may be tricked out to do someday, keep it in the game.
+        try
+        {
+            THROW_IO_ERROR( se.what() );
+        }
+        catch( const IO_ERROR& ioe )
+        {
+            m_errors.move_push( std::make_unique<IO_ERROR>( ioe ) );
+        }
+
+        return false;
+    }
+
+    return true;
+}
+
+
+bool DESIGN_BLOCK_LIST_IMPL::ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable,
+                                                   const wxString*         aNickname,
+                                                   PROGRESS_REPORTER*      aProgressReporter )
+{
+    long long int generatedTimestamp = 0;
+
+    if( !CatchErrors(
+                [&]()
+                {
+                    generatedTimestamp = aTable->GenerateTimestamp( aNickname );
+                } ) )
+    {
+        return false;
+    }
+
+    if( generatedTimestamp == m_list_timestamp )
+        return true;
+
+    // Disable KIID generation: not needed for library parts; sometimes very slow
+    KIID_NIL_SET_RESET reset_kiid;
+
+    m_progress_reporter = aProgressReporter;
+
+    if( m_progress_reporter )
+    {
+        m_progress_reporter->SetMaxProgress( m_queue_in.size() );
+        m_progress_reporter->Report( _( "Fetching design_block libraries..." ) );
+    }
+
+    m_cancelled = false;
+    m_lib_table = aTable;
+
+    // Clear data before reading files
+    m_errors.clear();
+    m_list.clear();
+    m_queue_in.clear();
+    m_queue_out.clear();
+
+    if( aNickname )
+    {
+        m_queue_in.push( *aNickname );
+    }
+    else
+    {
+        for( const wxString& nickname : aTable->GetLogicalLibs() )
+            m_queue_in.push( nickname );
+    }
+
+
+    loadLibs();
+
+    if( !m_cancelled )
+    {
+        if( m_progress_reporter )
+        {
+            m_progress_reporter->SetMaxProgress( m_queue_out.size() );
+            m_progress_reporter->AdvancePhase();
+            m_progress_reporter->Report( _( "Loading design_blocks..." ) );
+        }
+
+        loadDesignBlocks();
+
+        if( m_progress_reporter )
+            m_progress_reporter->AdvancePhase();
+    }
+
+    if( m_cancelled )
+        m_list_timestamp = 0; // God knows what we got before we were canceled
+    else
+        m_list_timestamp = generatedTimestamp;
+
+    return m_errors.empty();
+}
+
+
+void DESIGN_BLOCK_LIST_IMPL::loadLibs()
+{
+    thread_pool&                     tp = GetKiCadThreadPool();
+    size_t                           num_returns = m_queue_in.size();
+    std::vector<std::future<size_t>> returns( num_returns );
+
+    auto loader_job =
+            [this]() -> size_t
+            {
+                wxString nickname;
+                size_t retval = 0;
+
+                if( !m_cancelled && m_queue_in.pop( nickname ) )
+                {
+                    if( CatchErrors( [this, &nickname]()
+                                     {
+                                         m_lib_table->PrefetchLib( nickname );
+                                         m_queue_out.push( nickname );
+                                     } ) && m_progress_reporter )
+                    {
+                        m_progress_reporter->AdvanceProgress();
+                    }
+
+                    ++retval;
+                }
+
+                return retval;
+            };
+
+    for( size_t ii = 0; ii < num_returns; ++ii )
+        returns[ii] = tp.submit( loader_job );
+
+    for( const std::future<size_t>& ret : returns )
+    {
+        std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
+
+        while( status != std::future_status::ready )
+        {
+            if( m_progress_reporter && !m_progress_reporter->KeepRefreshing() )
+                m_cancelled = true;
+
+            status = ret.wait_for( std::chrono::milliseconds( 250 ) );
+        }
+    }
+}
+
+
+void DESIGN_BLOCK_LIST_IMPL::loadDesignBlocks()
+{
+    LOCALE_IO toggle_locale;
+
+    // Parse the design_blocks in parallel. WARNING! This requires changing the locale, which is
+    // GLOBAL. It is only thread safe to construct the LOCALE_IO before the threads are created,
+    // destroy it after they finish, and block the main (GUI) thread while they work. Any deviation
+    // from this will cause nasal demons.
+    //
+    // TODO: blast LOCALE_IO into the sun
+
+    SYNC_QUEUE<std::unique_ptr<DESIGN_BLOCK_INFO>> queue_parsed;
+    thread_pool&                                tp = GetKiCadThreadPool();
+    size_t                                      num_elements = m_queue_out.size();
+    std::vector<std::future<size_t>>            returns( num_elements );
+
+    auto db_thread =
+            [ this, &queue_parsed ]() -> size_t
+            {
+                wxString nickname;
+
+                if( m_cancelled || !m_queue_out.pop( nickname ) )
+                    return 0;
+
+                wxArrayString dbnames;
+
+                CatchErrors(
+                        [&]()
+                        {
+                            m_lib_table->DesignBlockEnumerate( dbnames, nickname, false );
+                        } );
+
+                for( wxString dbname : dbnames )
+                {
+                    CatchErrors(
+                            [&]()
+                            {
+                                auto* dbinfo = new DESIGN_BLOCK_INFO_IMPL( this, nickname, dbname );
+                                queue_parsed.move_push( std::unique_ptr<DESIGN_BLOCK_INFO>( dbinfo ) );
+                            } );
+
+                    if( m_cancelled )
+                        return 0;
+                }
+
+                if( m_progress_reporter )
+                    m_progress_reporter->AdvanceProgress();
+
+                return 1;
+            };
+
+    for( size_t ii = 0; ii < num_elements; ++ii )
+        returns[ii] = tp.submit( db_thread );
+
+    for( const std::future<size_t>& ret : returns )
+    {
+        std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
+
+        while( status != std::future_status::ready )
+        {
+            if( m_progress_reporter )
+                m_progress_reporter->KeepRefreshing();
+
+            status = ret.wait_for( std::chrono::milliseconds( 250 ) );
+        }
+    }
+
+    std::unique_ptr<DESIGN_BLOCK_INFO> dbi;
+
+    while( queue_parsed.pop( dbi ) )
+        m_list.push_back( std::move( dbi ) );
+
+    std::sort( m_list.begin(), m_list.end(),
+               []( std::unique_ptr<DESIGN_BLOCK_INFO> const& lhs,
+                   std::unique_ptr<DESIGN_BLOCK_INFO> const& rhs ) -> bool
+               {
+                   return *lhs < *rhs;
+               } );
+}
+
+
+DESIGN_BLOCK_LIST_IMPL::DESIGN_BLOCK_LIST_IMPL() :
+        m_list_timestamp( 0 ), m_progress_reporter( nullptr ), m_cancelled( false )
+{
+}
+
+
+void DESIGN_BLOCK_LIST_IMPL::WriteCacheToFile( const wxString& aFilePath )
+{
+    wxFileName          tmpFileName = wxFileName::CreateTempFileName( aFilePath );
+    wxFFileOutputStream outStream( tmpFileName.GetFullPath() );
+    wxTextOutputStream  txtStream( outStream );
+
+    if( !outStream.IsOk() )
+    {
+        return;
+    }
+
+    txtStream << wxString::Format( wxT( "%lld" ), m_list_timestamp ) << endl;
+
+    for( std::unique_ptr<DESIGN_BLOCK_INFO>& dbinfo : m_list )
+    {
+        txtStream << dbinfo->GetLibNickname() << endl;
+        txtStream << dbinfo->GetName() << endl;
+        txtStream << EscapeString( dbinfo->GetDesc(), CTX_LINE ) << endl;
+        txtStream << EscapeString( dbinfo->GetKeywords(), CTX_LINE ) << endl;
+        txtStream << wxString::Format( wxT( "%d" ), dbinfo->GetOrderNum() ) << endl;
+    }
+
+    txtStream.Flush();
+    outStream.Close();
+
+    // Preserve the permissions of the current file
+    KIPLATFORM::IO::DuplicatePermissions( aFilePath, tmpFileName.GetFullPath() );
+
+    if( !wxRenameFile( tmpFileName.GetFullPath(), aFilePath, true ) )
+    {
+        // cleanup in case rename failed
+        // its also not the end of the world since this is just a cache file
+        wxRemoveFile( tmpFileName.GetFullPath() );
+    }
+}
+
+
+void DESIGN_BLOCK_LIST_IMPL::ReadCacheFromFile( const wxString& aFilePath )
+{
+    wxTextFile cacheFile( aFilePath );
+
+    m_list_timestamp = 0;
+    m_list.clear();
+
+    try
+    {
+        if( cacheFile.Exists() && cacheFile.Open() )
+        {
+            cacheFile.GetFirstLine().ToLongLong( &m_list_timestamp );
+
+            while( cacheFile.GetCurrentLine() + 6 < cacheFile.GetLineCount() )
+            {
+                wxString libNickname = cacheFile.GetNextLine();
+                wxString name = cacheFile.GetNextLine();
+                wxString desc = UnescapeString( cacheFile.GetNextLine() );
+                wxString keywords = UnescapeString( cacheFile.GetNextLine() );
+                int      orderNum = wxAtoi( cacheFile.GetNextLine() );
+
+                DESIGN_BLOCK_INFO_IMPL* dbinfo =
+                        new DESIGN_BLOCK_INFO_IMPL( libNickname, name, desc, keywords, orderNum );
+
+                m_list.emplace_back( std::unique_ptr<DESIGN_BLOCK_INFO>( dbinfo ) );
+            }
+        }
+    }
+    catch( ... )
+    {
+        // whatever went wrong, invalidate the cache
+        m_list_timestamp = 0;
+    }
+
+    // Sanity check: an empty list is very unlikely to be correct.
+    if( m_list.size() == 0 )
+        m_list_timestamp = 0;
+
+    if( cacheFile.IsOpened() )
+        cacheFile.Close();
+}
diff --git a/common/design_block_info_impl.h b/common/design_block_info_impl.h
new file mode 100644
index 0000000000..0f1f6bc4ce
--- /dev/null
+++ b/common/design_block_info_impl.h
@@ -0,0 +1,112 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DESIGN_BLOCK_INFO_IMPL_H
+#define DESIGN_BLOCK_INFO_IMPL_H
+
+#include <atomic>
+#include <functional>
+#include <memory>
+#include <thread>
+#include <vector>
+
+#include <kicommon.h>
+#include <design_block_info.h>
+#include <core/sync_queue.h>
+
+class LOCALE_IO;
+
+class KICOMMON_API DESIGN_BLOCK_INFO_IMPL : public DESIGN_BLOCK_INFO
+{
+public:
+    DESIGN_BLOCK_INFO_IMPL( DESIGN_BLOCK_LIST* aOwner, const wxString& aNickname,
+                            const wxString& aDesignBlockName )
+    {
+        m_nickname = aNickname;
+        m_dbname = aDesignBlockName;
+        m_num = 0;
+
+        m_owner = aOwner;
+        m_loaded = false;
+        load();
+    }
+
+    // A constructor for cached items
+    DESIGN_BLOCK_INFO_IMPL( const wxString& aNickname, const wxString& aDesignBlockName,
+                            const wxString& aDescription, const wxString& aKeywords, int aOrderNum )
+    {
+        m_nickname = aNickname;
+        m_dbname = aDesignBlockName;
+        m_num = aOrderNum;
+        m_doc = aDescription;
+        m_keywords = aKeywords;
+
+        m_owner = nullptr;
+        m_loaded = true;
+    }
+
+
+    // A dummy constructor for use as a target in a binary search
+    DESIGN_BLOCK_INFO_IMPL( const wxString& aNickname, const wxString& aDesignBlockName )
+    {
+        m_nickname = aNickname;
+        m_dbname = aDesignBlockName;
+
+        m_owner = nullptr;
+        m_loaded = true;
+    }
+
+protected:
+    virtual void load() override;
+};
+
+
+class KICOMMON_API DESIGN_BLOCK_LIST_IMPL : public DESIGN_BLOCK_LIST
+{
+public:
+    DESIGN_BLOCK_LIST_IMPL();
+    virtual ~DESIGN_BLOCK_LIST_IMPL(){};
+
+    void WriteCacheToFile( const wxString& aFilePath ) override;
+    void ReadCacheFromFile( const wxString& aFilePath ) override;
+
+    bool ReadDesignBlockFiles( DESIGN_BLOCK_LIB_TABLE* aTable, const wxString* aNickname = nullptr,
+                               PROGRESS_REPORTER* aProgressReporter = nullptr ) override;
+
+protected:
+    void loadLibs();
+    void loadDesignBlocks();
+
+private:
+    /**
+     * Call aFunc, pushing any IO_ERRORs and std::exceptions it throws onto m_errors.
+     *
+     * @return true if no error occurred.
+     */
+    bool CatchErrors( const std::function<void()>& aFunc );
+
+    SYNC_QUEUE<wxString> m_queue_in;
+    SYNC_QUEUE<wxString> m_queue_out;
+    long long            m_list_timestamp;
+    PROGRESS_REPORTER*   m_progress_reporter;
+    std::atomic_bool     m_cancelled;
+    std::mutex           m_join;
+};
+
+#endif // DESIGN_BLOCK_INFO_IMPL_H
diff --git a/common/design_block_io.cpp b/common/design_block_io.cpp
new file mode 100644
index 0000000000..1266dcca20
--- /dev/null
+++ b/common/design_block_io.cpp
@@ -0,0 +1,387 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com>
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <i18n_utility.h>
+#include <wx/dir.h>
+#include <wx/filename.h>
+#include <wx/log.h>
+#include <wx/translation.h>
+#include <wx/string.h>
+#include <wx/arrstr.h>
+#include <wx/datetime.h>
+#include <wildcards_and_files_ext.h>
+#include <kiway_player.h>
+#include <design_block_io.h>
+#include <design_block.h>
+#include <ki_exception.h>
+#include <trace_helpers.h>
+#include <fstream>
+
+const wxString DESIGN_BLOCK_IO_MGR::ShowType( DESIGN_BLOCK_FILE_T aFileType )
+{
+    switch( aFileType )
+    {
+    case KICAD_SEXP: return _( "KiCad" );
+    default: return wxString::Format( _( "UNKNOWN (%d)" ), aFileType );
+    }
+}
+
+
+DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T
+DESIGN_BLOCK_IO_MGR::EnumFromStr( const wxString& aFileType )
+{
+    if( aFileType == _( "KiCad" ) )
+        return DESIGN_BLOCK_FILE_T( KICAD_SEXP );
+
+    return DESIGN_BLOCK_FILE_T( DESIGN_BLOCK_FILE_UNKNOWN );
+}
+
+
+DESIGN_BLOCK_IO* DESIGN_BLOCK_IO_MGR::FindPlugin( DESIGN_BLOCK_FILE_T aFileType )
+{
+    switch( aFileType )
+    {
+    case KICAD_SEXP:          return new DESIGN_BLOCK_IO();
+    default:                  return nullptr;
+    }
+}
+
+
+DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T
+DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl )
+{
+    if( IO_RELEASER<DESIGN_BLOCK_IO>( FindPlugin( KICAD_SEXP ) )->CanReadLibrary( aLibPath ) && aCtl != KICTL_NONKICAD_ONLY )
+        return KICAD_SEXP;
+
+    return DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE;
+}
+
+
+bool DESIGN_BLOCK_IO_MGR::ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps,
+                                          const wxString&              aOldFilePath,
+                                          const wxString&              aNewFilePath )
+{
+    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T oldFileType =
+            DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( aOldFilePath );
+
+    if( oldFileType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
+        return false;
+
+
+    IO_RELEASER<DESIGN_BLOCK_IO> oldFilePI( DESIGN_BLOCK_IO_MGR::FindPlugin( oldFileType ) );
+    IO_RELEASER<DESIGN_BLOCK_IO> kicadPI(
+            DESIGN_BLOCK_IO_MGR::FindPlugin( DESIGN_BLOCK_IO_MGR::KICAD_SEXP ) );
+    wxArrayString dbNames;
+    wxFileName    newFileName( aNewFilePath );
+
+    if( newFileName.HasExt() )
+    {
+        wxString extraDir = newFileName.GetFullName();
+        newFileName.ClearExt();
+        newFileName.SetName( "" );
+        newFileName.AppendDir( extraDir );
+    }
+
+    if( !newFileName.DirExists() && !wxFileName::Mkdir( aNewFilePath, wxS_DIR_DEFAULT ) )
+        return false;
+
+    try
+    {
+        bool bestEfforts = false; // throw on first error
+        oldFilePI->DesignBlockEnumerate( dbNames, aOldFilePath, bestEfforts, aOldFileProps );
+
+        for( const wxString& dbName : dbNames )
+        {
+            std::unique_ptr<const DESIGN_BLOCK> db(
+                    oldFilePI->GetEnumeratedDesignBlock( aOldFilePath, dbName, aOldFileProps ) );
+            kicadPI->DesignBlockSave( aNewFilePath, db.get() );
+        }
+    }
+    catch( ... )
+    {
+        return false;
+    }
+
+    return true;
+}
+
+
+const DESIGN_BLOCK_IO::IO_FILE_DESC DESIGN_BLOCK_IO::GetLibraryDesc() const
+{
+    return IO_BASE::IO_FILE_DESC( _HKI( "KiCad Design Block folders" ), {},
+                                  { FILEEXT::KiCadDesignBlockLibPathExtension }, false );
+}
+
+
+long long DESIGN_BLOCK_IO::GetLibraryTimestamp( const wxString& aLibraryPath ) const
+{
+    wxFileName fn( aLibraryPath );
+
+    if( fn.IsFileReadable() && fn.GetModificationTime().IsValid() )
+        return fn.GetModificationTime().GetValue().GetValue();
+    else
+        return wxDateTime( 0.0 ).GetValue().GetValue();
+}
+
+
+void DESIGN_BLOCK_IO::CreateLibrary( const wxString&                    aLibraryPath,
+                                     const std::map<std::string, UTF8>* aProperties )
+{
+    if( wxDir::Exists( aLibraryPath ) )
+    {
+        THROW_IO_ERROR( wxString::Format( _( "Cannot overwrite library path '%s'." ),
+                                          aLibraryPath.GetData() ) );
+    }
+
+    wxFileName dir;
+    dir.SetPath( aLibraryPath );
+
+    if( !dir.Mkdir() )
+    {
+        THROW_IO_ERROR(
+                wxString::Format( _( "Library path '%s' could not be created.\n\n"
+                                     "Make sure you have write permissions and try again." ),
+                                  dir.GetPath() ) );
+    }
+}
+
+
+bool DESIGN_BLOCK_IO::DeleteLibrary( const wxString&                    aLibraryPath,
+                                     const std::map<std::string, UTF8>* aProperties )
+{
+    wxFileName fn;
+    fn.SetPath( aLibraryPath );
+
+    // Return if there is no library path to delete.
+    if( !fn.DirExists() )
+        return false;
+
+    if( !fn.IsDirWritable() )
+    {
+        THROW_IO_ERROR( wxString::Format( _( "Insufficient permissions to delete folder '%s'." ),
+                                          aLibraryPath.GetData() ) );
+    }
+
+    wxDir dir( aLibraryPath );
+
+    // Design block folders should only contain sub-folders for each design block
+    if( dir.HasFiles() )
+    {
+        THROW_IO_ERROR( wxString::Format( _( "Library folder '%s' has unexpected files." ),
+                                          aLibraryPath.GetData() ) );
+    }
+
+    // Must delete all sub-directories before deleting the library directory
+    if( dir.HasSubDirs() )
+    {
+        wxArrayString dirs;
+
+        // Get all sub-directories in the library path
+        dir.GetAllFiles( aLibraryPath, &dirs, wxEmptyString, wxDIR_DIRS );
+
+        for( size_t i = 0; i < dirs.GetCount(); i++ )
+        {
+            wxFileName tmp = dirs[i];
+
+            if( tmp.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension )
+            {
+                THROW_IO_ERROR( wxString::Format( _( "Unexpected folder '%s' found in library "
+                                                     "path '%s'." ),
+                                                  dirs[i].GetData(), aLibraryPath.GetData() ) );
+            }
+        }
+
+        for( size_t i = 0; i < dirs.GetCount(); i++ )
+            wxRemoveFile( dirs[i] );
+    }
+
+    wxLogTrace( traceDesignBlocks, wxT( "Removing design block library '%s'." ),
+                aLibraryPath.GetData() );
+
+    // Some of the more elaborate wxRemoveFile() crap puts up its own wxLog dialog
+    // we don't want that.  we want bare metal portability with no UI here.
+    if( !wxFileName::Rmdir( aLibraryPath, wxPATH_RMDIR_RECURSIVE ) )
+    {
+        THROW_IO_ERROR( wxString::Format( _( "Design block library '%s' cannot be deleted." ),
+                                          aLibraryPath.GetData() ) );
+    }
+
+    // For some reason removing a directory in Windows is not immediately updated.  This delay
+    // prevents an error when attempting to immediately recreate the same directory when over
+    // writing an existing library.
+#ifdef __WINDOWS__
+    wxMilliSleep( 250L );
+#endif
+
+    return true;
+}
+
+
+void DESIGN_BLOCK_IO::DesignBlockEnumerate( wxArrayString&  aDesignBlockNames,
+                                            const wxString& aLibraryPath, bool aBestEfforts,
+                                            const std::map<std::string, UTF8>* aProperties )
+{
+    // From the starting directory, look for all directories ending in the .block extension
+    wxDir dir( aLibraryPath );
+
+    if( !dir.IsOpened() )
+        return;
+
+    wxString dirname;
+    wxString fileSpec = wxT( "*." ) + wxString( FILEEXT::KiCadDesignBlockPathExtension );
+    bool     cont = dir.GetFirst( &dirname, fileSpec, wxDIR_DIRS );
+
+    while( cont )
+    {
+        aDesignBlockNames.Add( dirname.Before( wxT( '.' ) ) );
+        cont = dir.GetNext( &dirname );
+    }
+}
+
+
+DESIGN_BLOCK* DESIGN_BLOCK_IO::DesignBlockLoad( const wxString& aLibraryPath,
+                                                const wxString& aDesignBlockName, bool aKeepUUID,
+                                                const std::map<std::string, UTF8>* aProperties )
+{
+    DESIGN_BLOCK* newDB = new DESIGN_BLOCK();
+    wxString      dbPath = aLibraryPath + wxFileName::GetPathSeparator() +
+                           aDesignBlockName + wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension + wxFileName::GetPathSeparator();
+    wxString      dbSchPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;
+    wxString      dbMetadataPath = dbPath + aDesignBlockName + wxT( "." ) + FILEEXT::JsonFileExtension;
+
+
+
+    // Library name needs to be empty for when we fill it in with the correct library nickname
+    // one layer above
+    newDB->SetLibId( LIB_ID( wxEmptyString, aDesignBlockName ) );
+    newDB->SetSchematicFile(
+            // Library path
+            aLibraryPath + wxFileName::GetPathSeparator() +
+            // Design block name (project folder)
+            aDesignBlockName + +wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension + wxT( "/" ) +
+            // Schematic file
+            aDesignBlockName + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension );
+
+    // Parse the JSON file if it exists
+    if( wxFileExists( dbMetadataPath ) )
+    {
+        try
+        {
+            nlohmann::json dbMetadata;
+            std::ifstream  dbMetadataFile( dbMetadataPath.fn_str() );
+            dbMetadataFile >> dbMetadata;
+
+            if( dbMetadata.contains( "description" ) )
+                newDB->SetLibDescription( dbMetadata["description"] );
+
+            if( dbMetadata.contains( "keywords" ) )
+                newDB->SetKeywords( dbMetadata["keywords"] );
+
+            if( dbMetadata.contains( "documentation_url" ) )
+                newDB->SetDocumentationUrl( dbMetadata["documentation_url"] );
+        }
+        catch( ... )
+        {
+            THROW_IO_ERROR( wxString::Format(
+                    _( "Design block metadata file '%s' could not be read." ), dbMetadataPath ) );
+        }
+    }
+
+
+    return newDB;
+}
+
+
+void DESIGN_BLOCK_IO::DesignBlockSave( const wxString&                    aLibraryPath,
+                                       const DESIGN_BLOCK*                aDesignBlock,
+                                       const std::map<std::string, UTF8>* aProperties )
+{
+    // Make sure we have a valid LIB_ID or we can't save the design block
+    if( !aDesignBlock->GetLibId().IsValid() )
+    {
+        THROW_IO_ERROR( _( "Design block does not have a valid library ID." ) );
+    }
+
+    if( !wxFileExists( aDesignBlock->GetSchematicFile() ) )
+    {
+        THROW_IO_ERROR( wxString::Format( _( "Schematic source file '%s' does not exist." ),
+                                          aDesignBlock->GetSchematicFile() ) );
+    }
+
+    // Create the design block folder
+    wxFileName dbFolder( aLibraryPath + wxFileName::GetPathSeparator()
+                         + aDesignBlock->GetLibId().GetLibItemName() + wxT( "." )
+                         + FILEEXT::KiCadDesignBlockPathExtension
+                         + wxFileName::GetPathSeparator() );
+
+    if( !dbFolder.DirExists() )
+    {
+        if( !dbFolder.Mkdir() )
+        {
+            THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be created." ),
+                                              dbFolder.GetFullPath().GetData() ) );
+        }
+    }
+
+    // The new schematic file name is based on the design block name, not the source sheet name
+    wxString dbSchematicFile = dbFolder.GetFullPath() + aDesignBlock->GetLibId().GetLibItemName()
+                               + wxT( "." ) + FILEEXT::KiCadSchematicFileExtension;
+
+    // Copy the source sheet file to the design block folder, under the design block name
+    if( !wxCopyFile( aDesignBlock->GetSchematicFile(), dbSchematicFile ) )
+    {
+        THROW_IO_ERROR( wxString::Format(
+                _( "Schematic file '%s' could not be saved as design block at '%s'." ),
+                aDesignBlock->GetSchematicFile().GetData(), dbSchematicFile ) );
+    }
+}
+
+
+void DESIGN_BLOCK_IO::DesignBlockDelete( const wxString& aLibPath, const wxString& aDesignBlockName,
+                                         const std::map<std::string, UTF8>* aProperties )
+{
+    wxFileName dbDir = wxFileName( aLibPath + wxFileName::GetPathSeparator() + aDesignBlockName
+                                   + wxT( "." ) + FILEEXT::KiCadDesignBlockPathExtension );
+
+
+    if( !dbDir.DirExists() )
+    {
+        THROW_IO_ERROR(
+                wxString::Format( _( "Design block '%s' does not exist." ), dbDir.GetFullName() ) );
+    }
+
+    // Delete the whole design block folder
+    if( !wxFileName::Rmdir( dbDir.GetFullPath(), wxPATH_RMDIR_RECURSIVE ) )
+    {
+        THROW_IO_ERROR( wxString::Format( _( "Design block folder '%s' could not be deleted." ),
+                                          dbDir.GetFullPath().GetData() ) );
+    }
+}
+
+
+bool DESIGN_BLOCK_IO::IsLibraryWritable( const wxString& aLibraryPath )
+{
+    wxFileName path( aLibraryPath );
+    return path.IsOk() && path.IsDirWritable();
+}
diff --git a/common/design_block_io.h b/common/design_block_io.h
new file mode 100644
index 0000000000..6b6df633ce
--- /dev/null
+++ b/common/design_block_io.h
@@ -0,0 +1,115 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 Mike Williams <mike@mikebwilliams.com>
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+#ifndef DESIGN_BLOCK_IO_H
+#define DESIGN_BLOCK_IO_H
+
+#include <kicommon.h>
+#include <io/io_base.h>
+#include <io/io_mgr.h>
+
+class DESIGN_BLOCK;
+class DESIGN_BLOCK_IO;
+class wxArrayString;
+
+class KICOMMON_API DESIGN_BLOCK_IO_MGR : public IO_MGR
+{
+public:
+    enum DESIGN_BLOCK_FILE_T
+    {
+        DESIGN_BLOCK_FILE_UNKNOWN = 0, ///< 0 is not a legal menu id on Mac
+        KICAD_SEXP,                    ///< S-expression KiCad file format.
+
+        FILE_TYPE_NONE
+    };
+
+    static const wxString      ShowType( DESIGN_BLOCK_FILE_T aFileType );
+    static DESIGN_BLOCK_IO*    FindPlugin( DESIGN_BLOCK_FILE_T aFileType );
+    static DESIGN_BLOCK_FILE_T EnumFromStr( const wxString& aFileType );
+    static DESIGN_BLOCK_FILE_T GuessPluginTypeFromLibPath( const wxString& aLibPath, int aCtl = 0 );
+    /**
+     * Convert a design block library to the latest KiCad format
+     */
+    static bool ConvertLibrary( std::map<std::string, UTF8>* aOldFileProps,
+                                const wxString& aOldFilePath, const wxString& aNewFilePath );
+};
+
+
+class KICOMMON_API DESIGN_BLOCK_IO : public IO_BASE
+{
+public:
+    DESIGN_BLOCK_IO() : IO_BASE( wxS( "KiCad" ) ) {}
+
+    const IO_BASE::IO_FILE_DESC GetLibraryDesc() const override;
+    long long                   GetLibraryTimestamp( const wxString& aLibraryPath ) const;
+
+    void DesignBlockEnumerate( wxArrayString& aDesignBlockNames, const wxString& aLibraryPath,
+                               bool                               aBestEfforts,
+                               const std::map<std::string, UTF8>* aProperties = nullptr );
+
+    const DESIGN_BLOCK*
+    GetEnumeratedDesignBlock( const wxString& aLibraryPath, const wxString& aDesignBlockName,
+                              const std::map<std::string, UTF8>* aProperties = nullptr )
+    {
+        return DesignBlockLoad( aLibraryPath, aDesignBlockName, false, aProperties );
+    }
+
+    bool DesignBlockExists( const wxString& aLibraryPath, const wxString& aDesignBlockName,
+                            const std::map<std::string, UTF8>* aProperties = nullptr )
+    {
+        return DesignBlockLoad( aLibraryPath, aDesignBlockName, true, aProperties ) != nullptr;
+    }
+
+    DESIGN_BLOCK* ImportDesignBlock( const wxString&                    aDesignBlockPath,
+                                     wxString&                          aDesignBlockNameOut,
+                                     const std::map<std::string, UTF8>* aProperties = nullptr )
+    {
+        return nullptr;
+    }
+
+    void CreateLibrary( const wxString&                    aLibraryPath,
+                        const std::map<std::string, UTF8>* aProperties = nullptr ) override;
+
+    virtual bool DeleteLibrary( const wxString&                    aLibraryPath,
+                                const std::map<std::string, UTF8>* aProperties = nullptr ) override;
+
+
+    bool IsLibraryWritable( const wxString& aLibraryPath ) override;
+
+    DESIGN_BLOCK* DesignBlockLoad( const wxString& aLibraryPath, const wxString& aDesignBlockName,
+                                   bool                               aKeepUUID = false,
+                                   const std::map<std::string, UTF8>* aProperties = nullptr );
+
+    void DesignBlockSave( const wxString& aLibraryPath, const DESIGN_BLOCK* aDesignBlock,
+                          const std::map<std::string, UTF8>* aProperties = nullptr );
+
+    void DesignBlockDelete( const wxString& aLibraryPath, const wxString& aDesignBlockName,
+                            const std::map<std::string, UTF8>* aProperties = nullptr );
+
+    virtual void PrefetchLib( const wxString&                    aLibraryPath,
+                              const std::map<std::string, UTF8>* aProperties = nullptr )
+    {
+    }
+};
+
+#endif
diff --git a/common/design_block_lib_table.cpp b/common/design_block_lib_table.cpp
new file mode 100644
index 0000000000..69ab52f033
--- /dev/null
+++ b/common/design_block_lib_table.cpp
@@ -0,0 +1,683 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+#include <kiface_base.h>
+#include <env_vars.h>
+#include <design_block_info.h>
+#include <lib_id.h>
+#include <lib_table_lexer.h>
+#include <paths.h>
+#include <pgm_base.h>
+#include <search_stack.h>
+#include <settings/kicad_settings.h>
+#include <settings/settings_manager.h>
+#include <systemdirsappend.h>
+#include <design_block_lib_table.h>
+#include <design_block.h>
+
+#include <wx/dir.h>
+#include <wx/hash.h>
+
+#define OPT_SEP '|' ///< options separator character
+
+/// The global design block library table.  This is not dynamically allocated because
+/// in a multiple project environment we must keep its address constant (since it is
+/// the fallback table for multiple projects).
+DESIGN_BLOCK_LIB_TABLE GDesignBlockTable;
+
+/// The global footprint info table.  This is performance-intensive to build so we
+/// keep a hash-stamped global version.  Any deviation from the request vs. stored
+/// hash will result in it being rebuilt.
+DESIGN_BLOCK_LIST_IMPL GDesignBlockList;
+
+
+using namespace LIB_TABLE_T;
+
+
+static const wxChar global_tbl_name[] = wxT( "design-block-lib-table" );
+
+
+bool DESIGN_BLOCK_LIB_TABLE_ROW::operator==( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) const
+{
+    return LIB_TABLE_ROW::operator==( aRow ) && type == aRow.type;
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE_ROW::SetType( const wxString& aType )
+{
+    type = DESIGN_BLOCK_IO_MGR::EnumFromStr( aType );
+
+    if( DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T( -1 ) == type )
+        type = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
+
+    plugin.reset();
+}
+
+
+DESIGN_BLOCK_LIB_TABLE::DESIGN_BLOCK_LIB_TABLE( DESIGN_BLOCK_LIB_TABLE* aFallBackTable ) :
+        LIB_TABLE( aFallBackTable )
+{
+    // not copying fall back, simply search aFallBackTable separately
+    // if "nickName not found".
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE::Parse( LIB_TABLE_LEXER* in )
+{
+    T        tok;
+    wxString errMsg; // to collect error messages
+
+    // This table may be nested within a larger s-expression, or not.
+    // Allow for parser of that optional containing s-epression to have looked ahead.
+    if( in->CurTok() != T_design_block_lib_table )
+    {
+        in->NeedLEFT();
+
+        if( ( tok = in->NextTok() ) != T_design_block_lib_table )
+            in->Expecting( T_design_block_lib_table );
+    }
+
+    while( ( tok = in->NextTok() ) != T_RIGHT )
+    {
+        std::unique_ptr<DESIGN_BLOCK_LIB_TABLE_ROW> row =
+                std::make_unique<DESIGN_BLOCK_LIB_TABLE_ROW>();
+
+        if( tok == T_EOF )
+            in->Expecting( T_RIGHT );
+
+        if( tok != T_LEFT )
+            in->Expecting( T_LEFT );
+
+        // in case there is a "row integrity" error, tell where later.
+        int lineNum = in->CurLineNumber();
+        tok = in->NextTok();
+
+        // Optionally parse the current version number
+        if( tok == T_version )
+        {
+            in->NeedNUMBER( "version" );
+            m_version = std::stoi( in->CurText() );
+            in->NeedRIGHT();
+            continue;
+        }
+
+        if( tok != T_lib )
+            in->Expecting( T_lib );
+
+        // (name NICKNAME)
+        in->NeedLEFT();
+
+        if( ( tok = in->NextTok() ) != T_name )
+            in->Expecting( T_name );
+
+        in->NeedSYMBOLorNUMBER();
+
+        row->SetNickName( in->FromUTF8() );
+
+        in->NeedRIGHT();
+
+        // After (name), remaining (lib) elements are order independent, and in
+        // some cases optional.
+        bool sawType = false;
+        bool sawOpts = false;
+        bool sawDesc = false;
+        bool sawUri = false;
+        bool sawDisabled = false;
+
+        while( ( tok = in->NextTok() ) != T_RIGHT )
+        {
+            if( tok == T_EOF )
+                in->Unexpected( T_EOF );
+
+            if( tok != T_LEFT )
+                in->Expecting( T_LEFT );
+
+            tok = in->NeedSYMBOLorNUMBER();
+
+            switch( tok )
+            {
+            case T_uri:
+                if( sawUri )
+                    in->Duplicate( tok );
+                sawUri = true;
+                in->NeedSYMBOLorNUMBER();
+                row->SetFullURI( in->FromUTF8() );
+                break;
+
+            case T_type:
+                if( sawType )
+                    in->Duplicate( tok );
+                sawType = true;
+                in->NeedSYMBOLorNUMBER();
+                row->SetType( in->FromUTF8() );
+                break;
+
+            case T_options:
+                if( sawOpts )
+                    in->Duplicate( tok );
+                sawOpts = true;
+                in->NeedSYMBOLorNUMBER();
+                row->SetOptions( in->FromUTF8() );
+                break;
+
+            case T_descr:
+                if( sawDesc )
+                    in->Duplicate( tok );
+                sawDesc = true;
+                in->NeedSYMBOLorNUMBER();
+                row->SetDescr( in->FromUTF8() );
+                break;
+
+            case T_disabled:
+                if( sawDisabled )
+                    in->Duplicate( tok );
+                sawDisabled = true;
+                row->SetEnabled( false );
+                break;
+
+            case T_hidden:
+                // Hiding design block libraries is not yet supported.  Unclear what path can set this
+                // attribute, but clear it on load.
+                row->SetVisible();
+                break;
+
+            default: in->Unexpected( tok );
+            }
+
+            in->NeedRIGHT();
+        }
+
+        if( !sawType )
+            in->Expecting( T_type );
+
+        if( !sawUri )
+            in->Expecting( T_uri );
+
+        // All nickNames within this table fragment must be unique, so we do not use doReplace
+        // in doInsertRow().  (However a fallBack table can have a conflicting nickName and ours
+        // will supercede that one since in FindLib() we search this table before any fall back.)
+        wxString nickname = row->GetNickName(); // store it to be able to used it
+                                                // after row deletion if an error occurs
+        bool           doReplace = false;
+        LIB_TABLE_ROW* tmp = row.release();
+
+        if( !doInsertRow( tmp, doReplace ) )
+        {
+            delete tmp; // The table did not take ownership of the row.
+
+            wxString msg = wxString::Format( _( "Duplicate library nickname '%s' found in "
+                                                "design block library table file line %d." ),
+                                             nickname, lineNum );
+
+            if( !errMsg.IsEmpty() )
+                errMsg << '\n';
+
+            errMsg << msg;
+        }
+    }
+
+    if( !errMsg.IsEmpty() )
+        THROW_IO_ERROR( errMsg );
+}
+
+
+bool DESIGN_BLOCK_LIB_TABLE::operator==( const DESIGN_BLOCK_LIB_TABLE& aDesignBlockTable ) const
+{
+    if( m_rows.size() == aDesignBlockTable.m_rows.size() )
+    {
+        for( unsigned i = 0; i < m_rows.size(); ++i )
+        {
+            if( (DESIGN_BLOCK_LIB_TABLE_ROW&) m_rows[i]
+                != (DESIGN_BLOCK_LIB_TABLE_ROW&) aDesignBlockTable.m_rows[i] )
+                return false;
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE::Format( OUTPUTFORMATTER* aOutput, int aIndentLevel ) const
+{
+    aOutput->Print( aIndentLevel, "(design_block_lib_table\n" );
+    aOutput->Print( aIndentLevel + 1, "(version %d)\n", m_version );
+
+    for( LIB_TABLE_ROWS_CITER it = m_rows.begin(); it != m_rows.end(); ++it )
+        it->Format( aOutput, aIndentLevel + 1 );
+
+    aOutput->Print( aIndentLevel, ")\n" );
+}
+
+
+long long DESIGN_BLOCK_LIB_TABLE::GenerateTimestamp( const wxString* aNickname )
+{
+    long long hash = 0;
+
+    if( aNickname )
+    {
+        const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( *aNickname, true );
+
+        wxCHECK( row && row->plugin, hash );
+
+        return row->plugin->GetLibraryTimestamp( row->GetFullURI( true ) )
+               + wxHashTable::MakeKey( *aNickname );
+    }
+
+    for( const wxString& nickname : GetLogicalLibs() )
+    {
+        const DESIGN_BLOCK_LIB_TABLE_ROW* row = nullptr;
+
+        try
+        {
+            row = FindRow( nickname, true );
+        }
+        catch( ... )
+        {
+            // Do nothing if not found: just skip.
+        }
+
+        wxCHECK2( row && row->plugin, continue );
+
+        hash += row->plugin->GetLibraryTimestamp( row->GetFullURI( true ) )
+                + wxHashTable::MakeKey( nickname );
+    }
+
+    return hash;
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE::DesignBlockEnumerate( wxArrayString&  aDesignBlockNames,
+                                                   const wxString& aNickname, bool aBestEfforts )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+    row->plugin->DesignBlockEnumerate( aDesignBlockNames, row->GetFullURI( true ), aBestEfforts,
+                                       row->GetProperties() );
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE::PrefetchLib( const wxString& aNickname )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+    row->plugin->PrefetchLib( row->GetFullURI( true ), row->GetProperties() );
+}
+
+
+const DESIGN_BLOCK_LIB_TABLE_ROW* DESIGN_BLOCK_LIB_TABLE::FindRow( const wxString& aNickname,
+                                                                   bool            aCheckIfEnabled )
+{
+    DESIGN_BLOCK_LIB_TABLE_ROW* row =
+            static_cast<DESIGN_BLOCK_LIB_TABLE_ROW*>( findRow( aNickname, aCheckIfEnabled ) );
+
+    if( !row )
+    {
+        wxString msg = wxString::Format(
+                _( "design-block-lib-table files contain no library named '%s'." ), aNickname );
+        THROW_IO_ERROR( msg );
+    }
+
+    if( !row->plugin )
+        row->setPlugin( DESIGN_BLOCK_IO_MGR::FindPlugin( row->type ) );
+
+    return row;
+}
+
+
+static void setLibNickname( DESIGN_BLOCK* aModule, const wxString& aNickname,
+                            const wxString& aDesignBlockName )
+{
+    // The library cannot know its own name, because it might have been renamed or moved.
+    // Therefore design blocks cannot know their own library nickname when residing in
+    // a design block library.
+    // Only at this API layer can we tell the design block about its actual library nickname.
+    if( aModule )
+    {
+        // remove "const"-ness, I really do want to set nickname without
+        // having to copy the LIB_ID and its two strings, twice each.
+        LIB_ID& dbid = (LIB_ID&) aModule->GetLibId();
+
+        // Catch any misbehaving plugin, which should be setting internal design block name properly:
+        wxASSERT( aDesignBlockName == dbid.GetLibItemName().wx_str() );
+
+        // and clearing nickname
+        wxASSERT( !dbid.GetLibNickname().size() );
+
+        dbid.SetLibNickname( aNickname );
+    }
+}
+
+
+const DESIGN_BLOCK*
+DESIGN_BLOCK_LIB_TABLE::GetEnumeratedDesignBlock( const wxString& aNickname,
+                                                  const wxString& aDesignBlockName )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+
+    return row->plugin->GetEnumeratedDesignBlock( row->GetFullURI( true ), aDesignBlockName,
+                                                  row->GetProperties() );
+}
+
+
+bool DESIGN_BLOCK_LIB_TABLE::DesignBlockExists( const wxString& aNickname,
+                                                const wxString& aDesignBlockName )
+{
+    try
+    {
+        const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+        wxASSERT( row->plugin );
+
+        return row->plugin->DesignBlockExists( row->GetFullURI( true ), aDesignBlockName,
+                                               row->GetProperties() );
+    }
+    catch( ... )
+    {
+        return false;
+    }
+}
+
+
+DESIGN_BLOCK* DESIGN_BLOCK_LIB_TABLE::DesignBlockLoad( const wxString& aNickname,
+                                                       const wxString& aDesignBlockName,
+                                                       bool            aKeepUUID )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+
+    DESIGN_BLOCK* ret = row->plugin->DesignBlockLoad( row->GetFullURI( true ), aDesignBlockName,
+                                                      aKeepUUID, row->GetProperties() );
+
+    setLibNickname( ret, row->GetNickName(), aDesignBlockName );
+
+    return ret;
+}
+
+
+DESIGN_BLOCK_LIB_TABLE::SAVE_T
+DESIGN_BLOCK_LIB_TABLE::DesignBlockSave( const wxString&     aNickname,
+                                         const DESIGN_BLOCK* aDesignBlock, bool aOverwrite )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+
+    if( !aOverwrite )
+    {
+        // Try loading the design block to see if it already exists, caller wants overwrite
+        // protection, which is atypical, not the default.
+
+        wxString DesignBlockname = aDesignBlock->GetLibId().GetLibItemName();
+
+        std::unique_ptr<DESIGN_BLOCK> design_block( row->plugin->DesignBlockLoad(
+                row->GetFullURI( true ), DesignBlockname, row->GetProperties() ) );
+
+        if( design_block.get() )
+            return SAVE_SKIPPED;
+    }
+
+    row->plugin->DesignBlockSave( row->GetFullURI( true ), aDesignBlock, row->GetProperties() );
+
+    return SAVE_OK;
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE::DesignBlockDelete( const wxString& aNickname,
+                                                const wxString& aDesignBlockName )
+
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+    return row->plugin->DesignBlockDelete( row->GetFullURI( true ), aDesignBlockName,
+                                           row->GetProperties() );
+}
+
+
+bool DESIGN_BLOCK_LIB_TABLE::IsDesignBlockLibWritable( const wxString& aNickname )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+    return row->plugin->IsLibraryWritable( row->GetFullURI( true ) );
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE::DesignBlockLibDelete( const wxString& aNickname )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+    row->plugin->DeleteLibrary( row->GetFullURI( true ), row->GetProperties() );
+}
+
+
+void DESIGN_BLOCK_LIB_TABLE::DesignBlockLibCreate( const wxString& aNickname )
+{
+    const DESIGN_BLOCK_LIB_TABLE_ROW* row = FindRow( aNickname, true );
+    wxASSERT( row->plugin );
+    row->plugin->CreateLibrary( row->GetFullURI( true ), row->GetProperties() );
+}
+
+
+DESIGN_BLOCK*
+DESIGN_BLOCK_LIB_TABLE::DesignBlockLoadWithOptionalNickname( const LIB_ID& aDesignBlockId,
+                                                             bool          aKeepUUID )
+{
+    wxString nickname = aDesignBlockId.GetLibNickname();
+    wxString DesignBlockname = aDesignBlockId.GetLibItemName();
+
+    if( nickname.size() )
+    {
+        return DesignBlockLoad( nickname, DesignBlockname, aKeepUUID );
+    }
+
+    // nickname is empty, sequentially search (alphabetically) all libs/nicks for first match:
+    else
+    {
+        std::vector<wxString> nicks = GetLogicalLibs();
+
+        // Search each library going through libraries alphabetically.
+        for( unsigned i = 0; i < nicks.size(); ++i )
+        {
+            // DesignBlockLoad() returns NULL on not found, does not throw exception
+            // unless there's an IO_ERROR.
+            DESIGN_BLOCK* ret = DesignBlockLoad( nicks[i], DesignBlockname, aKeepUUID );
+
+            if( ret )
+                return ret;
+        }
+
+        return nullptr;
+    }
+}
+
+
+const wxString DESIGN_BLOCK_LIB_TABLE::GlobalPathEnvVariableName()
+{
+    return ENV_VAR::GetVersionedEnvVarName( wxS( "DESIGN_BLOCK_DIR" ) );
+}
+
+
+class PCM_DESIGN_BLOCK_LIB_TRAVERSER final : public wxDirTraverser
+{
+public:
+    explicit PCM_DESIGN_BLOCK_LIB_TRAVERSER( const wxString& aPath, DESIGN_BLOCK_LIB_TABLE& aTable,
+                                             const wxString& aPrefix ) :
+            m_lib_table( aTable ),
+            m_path_prefix( aPath ), m_lib_prefix( aPrefix )
+    {
+        wxFileName f( aPath, wxS( "" ) );
+        m_prefix_dir_count = f.GetDirCount();
+    }
+
+    wxDirTraverseResult OnFile( const wxString& aFilePath ) override { return wxDIR_CONTINUE; }
+
+    wxDirTraverseResult OnDir( const wxString& dirPath ) override
+    {
+        wxFileName dir = wxFileName::DirName( dirPath );
+
+        // consider a directory to be a lib if it's name ends with .pretty and
+        // it is under $KICADn_3RD_PARTY/design_blocks/<pkgid>/ i.e. has nested level of at least +3
+        if( dirPath.EndsWith( wxS( ".pretty" ) ) && dir.GetDirCount() >= m_prefix_dir_count + 3 )
+        {
+            wxString versionedPath = wxString::Format(
+                    wxS( "${%s}" ), ENV_VAR::GetVersionedEnvVarName( wxS( "3RD_PARTY" ) ) );
+
+            wxArrayString parts = dir.GetDirs();
+            parts.RemoveAt( 0, m_prefix_dir_count );
+            parts.Insert( versionedPath, 0 );
+
+            wxString libPath = wxJoin( parts, '/' );
+
+            if( !m_lib_table.HasLibraryWithPath( libPath ) )
+            {
+                wxString name = parts.Last().substr( 0, parts.Last().length() - 7 );
+                wxString nickname = wxString::Format( wxS( "%s%s" ), m_lib_prefix, name );
+
+                if( m_lib_table.HasLibrary( nickname ) )
+                {
+                    int increment = 1;
+                    do
+                    {
+                        nickname =
+                                wxString::Format( wxS( "%s%s_%d" ), m_lib_prefix, name, increment );
+                        increment++;
+                    } while( m_lib_table.HasLibrary( nickname ) );
+                }
+
+                m_lib_table.InsertRow( new DESIGN_BLOCK_LIB_TABLE_ROW(
+                        nickname, libPath, wxT( "KiCad" ), wxEmptyString,
+                        _( "Added by Plugin and Content Manager" ) ) );
+            }
+        }
+
+        return wxDIR_CONTINUE;
+    }
+
+private:
+    DESIGN_BLOCK_LIB_TABLE& m_lib_table;
+    wxString                m_path_prefix;
+    wxString                m_lib_prefix;
+    size_t                  m_prefix_dir_count;
+};
+
+
+bool DESIGN_BLOCK_LIB_TABLE::LoadGlobalTable( DESIGN_BLOCK_LIB_TABLE& aTable )
+{
+    bool       tableExists = true;
+    wxFileName fn = GetGlobalTableFileName();
+
+    if( !fn.FileExists() )
+    {
+        tableExists = false;
+
+        if( !fn.DirExists() && !fn.Mkdir( 0x777, wxPATH_MKDIR_FULL ) )
+        {
+            THROW_IO_ERROR( wxString::Format( _( "Cannot create global library table path '%s'." ),
+                                              fn.GetPath() ) );
+        }
+
+        // Attempt to copy the default global file table from the KiCad
+        // template folder to the user's home configuration path.
+        SEARCH_STACK ss;
+
+        SystemDirsAppend( &ss );
+
+        const ENV_VAR_MAP&      envVars = Pgm().GetLocalEnvVariables();
+        std::optional<wxString> v =
+                ENV_VAR::GetVersionedEnvVarValue( envVars, wxT( "TEMPLATE_DIR" ) );
+
+        if( v && !v->IsEmpty() )
+            ss.AddPaths( *v, 0 );
+
+        wxString fileName = ss.FindValidPath( global_tbl_name );
+
+        // The fallback is to create an empty global design block table for the user to populate.
+        if( fileName.IsEmpty() || !::wxCopyFile( fileName, fn.GetFullPath(), false ) )
+        {
+            DESIGN_BLOCK_LIB_TABLE emptyTable;
+
+            emptyTable.Save( fn.GetFullPath() );
+        }
+    }
+
+    aTable.clear();
+    aTable.Load( fn.GetFullPath() );
+
+    SETTINGS_MANAGER& mgr = Pgm().GetSettingsManager();
+    KICAD_SETTINGS*   settings = mgr.GetAppSettings<KICAD_SETTINGS>();
+
+    const ENV_VAR_MAP& env = Pgm().GetLocalEnvVariables();
+    wxString           packagesPath;
+
+    if( std::optional<wxString> v = ENV_VAR::GetVersionedEnvVarValue( env, wxT( "3RD_PARTY" ) ) )
+        packagesPath = *v;
+
+    if( settings->m_PcmLibAutoAdd )
+    {
+        // Scan for libraries in PCM packages directory
+
+        wxFileName d( packagesPath, wxS( "" ) );
+        d.AppendDir( wxS( "design_blocks" ) );
+
+        if( d.DirExists() )
+        {
+            PCM_DESIGN_BLOCK_LIB_TRAVERSER traverser( packagesPath, aTable,
+                                                      settings->m_PcmLibPrefix );
+            wxDir                          dir( d.GetPath() );
+
+            dir.Traverse( traverser );
+        }
+    }
+
+    if( settings->m_PcmLibAutoRemove )
+    {
+        // Remove PCM libraries that no longer exist
+        std::vector<wxString> to_remove;
+
+        for( size_t i = 0; i < aTable.GetCount(); i++ )
+        {
+            LIB_TABLE_ROW& row = aTable.At( i );
+            wxString       path = row.GetFullURI( true );
+
+            if( path.StartsWith( packagesPath ) && !wxDir::Exists( path ) )
+                to_remove.push_back( row.GetNickName() );
+        }
+
+        for( const wxString& nickName : to_remove )
+            aTable.RemoveRow( aTable.FindRow( nickName ) );
+    }
+
+    return tableExists;
+}
+
+
+wxString DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName()
+{
+    wxFileName fn;
+
+    fn.SetPath( PATHS::GetUserSettingsPath() );
+    fn.SetName( global_tbl_name );
+
+    return fn.GetFullPath();
+}
diff --git a/common/lib_table.keywords b/common/lib_table.keywords
index af323dca77..b923e33cf2 100644
--- a/common/lib_table.keywords
+++ b/common/lib_table.keywords
@@ -1,3 +1,4 @@
+design_block_lib_table
 fp_lib_table
 sym_lib_table
 version
@@ -8,4 +9,4 @@ uri
 options
 descr
 disabled
-hidden
\ No newline at end of file
+hidden
diff --git a/common/lib_tree_model.cpp b/common/lib_tree_model.cpp
index 81f18691d6..6a86d7def0 100644
--- a/common/lib_tree_model.cpp
+++ b/common/lib_tree_model.cpp
@@ -359,6 +359,23 @@ LIB_TREE_NODE_LIBRARY& LIB_TREE_NODE_ROOT::AddLib( wxString const& aName, wxStri
 }
 
 
+void LIB_TREE_NODE_ROOT::RemoveLib( wxString const& aName )
+{
+    m_Children.erase( std::remove_if( m_Children.begin(), m_Children.end(),
+                                      [&]( std::unique_ptr<LIB_TREE_NODE>& aNode )
+                                      {
+                                          return aNode->m_Name == aName;
+                                      } ),
+                      m_Children.end() );
+}
+
+
+void LIB_TREE_NODE_ROOT::Clear()
+{
+    m_Children.clear();
+}
+
+
 void LIB_TREE_NODE_ROOT::UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
                                       std::function<bool( LIB_TREE_NODE& aNode )>* aFilter )
 {
diff --git a/common/lib_tree_model_adapter.cpp b/common/lib_tree_model_adapter.cpp
index 81f93f1690..d1eb98b3ee 100644
--- a/common/lib_tree_model_adapter.cpp
+++ b/common/lib_tree_model_adapter.cpp
@@ -247,6 +247,12 @@ void LIB_TREE_MODEL_ADAPTER::DoAddLibrary( const wxString& aNodeName, const wxSt
 }
 
 
+void LIB_TREE_MODEL_ADAPTER::DoRemoveLibrary( const wxString& aNodeName )
+{
+    m_tree.RemoveLib( aNodeName );
+}
+
+
 void LIB_TREE_MODEL_ADAPTER::UpdateSearchString( const wxString& aSearch, bool aState )
 {
     {
@@ -383,7 +389,7 @@ void LIB_TREE_MODEL_ADAPTER::resortTree()
 
 void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode )
 {
-    m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() );
+    m_parent->Prj().PinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
     aTreeNode->m_Pinned = true;
 
     resortTree();
@@ -393,7 +399,7 @@ void LIB_TREE_MODEL_ADAPTER::PinLibrary( LIB_TREE_NODE* aTreeNode )
 
 void LIB_TREE_MODEL_ADAPTER::UnpinLibrary( LIB_TREE_NODE* aTreeNode )
 {
-    m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), isSymbolModel() );
+    m_parent->Prj().UnpinLibrary( aTreeNode->m_LibId.GetLibNickname(), getLibType() );
     aTreeNode->m_Pinned = false;
 
     resortTree();
diff --git a/common/paths.cpp b/common/paths.cpp
index 4b239f0a85..e3ff662752 100644
--- a/common/paths.cpp
+++ b/common/paths.cpp
@@ -106,6 +106,17 @@ wxString PATHS::GetDefaultUserFootprintsPath()
 }
 
 
+wxString PATHS::GetDefaultUserDesignBlocksPath()
+{
+    wxFileName tmp;
+    getUserDocumentPath( tmp );
+
+    tmp.AppendDir( wxT( "blocks" ) );
+
+    return tmp.GetPath();
+}
+
+
 wxString PATHS::GetDefaultUser3DModelsPath()
 {
     wxFileName tmp;
@@ -224,6 +235,16 @@ wxString PATHS::GetStockFootprintsPath()
 }
 
 
+wxString PATHS::GetStockDesignBlocksPath()
+{
+    wxString path;
+
+    path = GetStockEDALibraryPath() + wxT( "/blocks" );
+
+    return path;
+}
+
+
 wxString PATHS::GetStock3dmodelsPath()
 {
     wxString path;
diff --git a/common/project.cpp b/common/project.cpp
index e1cddf821b..6bbd3098df 100644
--- a/common/project.cpp
+++ b/common/project.cpp
@@ -28,6 +28,7 @@
 #include <pgm_base.h>
 #include <confirm.h>
 #include <core/kicad_algo.h>
+#include <design_block_lib_table.h>
 #include <fp_lib_table.h>
 #include <string_utils.h>
 #include <kiface_ids.h>
@@ -167,40 +168,78 @@ const wxString PROJECT::FootprintLibTblName() const
 }
 
 
-void PROJECT::PinLibrary( const wxString& aLibrary, bool isSymbolLibrary )
+const wxString PROJECT::DesignBlockLibTblName() const
+{
+    return libTableName( wxS( "design-block-lib-table" ) );
+}
+
+
+void PROJECT::PinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType )
 {
     COMMON_SETTINGS*       cfg = Pgm().GetCommonSettings();
-    std::vector<wxString>* pinnedLibs = isSymbolLibrary ? &m_projectFile->m_PinnedSymbolLibs
-                                                        : &m_projectFile->m_PinnedFootprintLibs;
+    std::vector<wxString>* pinnedLibsCfg = nullptr;
+    std::vector<wxString>* pinnedLibsFile = nullptr;
 
-    if( !alg::contains( *pinnedLibs, aLibrary ) )
-        pinnedLibs->push_back( aLibrary );
+    switch( aLibType )
+    {
+    case LIB_TYPE_T::SYMBOL_LIB:
+        pinnedLibsFile = &m_projectFile->m_PinnedSymbolLibs;
+        pinnedLibsCfg = &cfg->m_Session.pinned_symbol_libs;
+        break;
+    case LIB_TYPE_T::FOOTPRINT_LIB:
+        pinnedLibsFile = &m_projectFile->m_PinnedFootprintLibs;
+        pinnedLibsCfg = &cfg->m_Session.pinned_fp_libs;
+        break;
+    case LIB_TYPE_T::DESIGN_BLOCK_LIB:
+        pinnedLibsFile = &m_projectFile->m_PinnedDesignBlockLibs;
+        pinnedLibsCfg = &cfg->m_Session.pinned_design_block_libs;
+        break;
+    default:
+        wxFAIL_MSG( "Cannot pin library: invalid library type" );
+        return;
+    }
+
+    if( !alg::contains( *pinnedLibsFile, aLibrary ) )
+        pinnedLibsFile->push_back( aLibrary );
 
     Pgm().GetSettingsManager().SaveProject();
 
-    pinnedLibs = isSymbolLibrary ? &cfg->m_Session.pinned_symbol_libs
-                                 : &cfg->m_Session.pinned_fp_libs;
-
-    if( !alg::contains( *pinnedLibs, aLibrary ) )
-        pinnedLibs->push_back( aLibrary );
+    if( !alg::contains( *pinnedLibsCfg, aLibrary ) )
+        pinnedLibsCfg->push_back( aLibrary );
 
     cfg->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( cfg ) );
 }
 
 
-void PROJECT::UnpinLibrary( const wxString& aLibrary, bool isSymbolLibrary )
+void PROJECT::UnpinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType )
 {
     COMMON_SETTINGS*       cfg = Pgm().GetCommonSettings();
-    std::vector<wxString>* pinnedLibs = isSymbolLibrary ? &m_projectFile->m_PinnedSymbolLibs
-                                                        : &m_projectFile->m_PinnedFootprintLibs;
+    std::vector<wxString>* pinnedLibsCfg = nullptr;
+    std::vector<wxString>* pinnedLibsFile = nullptr;
 
-    alg::delete_matching( *pinnedLibs, aLibrary );
+    switch( aLibType )
+    {
+    case LIB_TYPE_T::SYMBOL_LIB:
+        pinnedLibsFile = &m_projectFile->m_PinnedSymbolLibs;
+        pinnedLibsCfg = &cfg->m_Session.pinned_symbol_libs;
+        break;
+    case LIB_TYPE_T::FOOTPRINT_LIB:
+        pinnedLibsFile = &m_projectFile->m_PinnedFootprintLibs;
+        pinnedLibsCfg = &cfg->m_Session.pinned_fp_libs;
+        break;
+    case LIB_TYPE_T::DESIGN_BLOCK_LIB:
+        pinnedLibsFile = &m_projectFile->m_PinnedDesignBlockLibs;
+        pinnedLibsCfg = &cfg->m_Session.pinned_design_block_libs;
+        break;
+    default:
+        wxFAIL_MSG( "Cannot unpin library: invalid library type" );
+        return;
+    }
+
+    alg::delete_matching( *pinnedLibsFile, aLibrary );
     Pgm().GetSettingsManager().SaveProject();
 
-    pinnedLibs = isSymbolLibrary ? &cfg->m_Session.pinned_symbol_libs
-                                 : &cfg->m_Session.pinned_fp_libs;
-
-    alg::delete_matching( *pinnedLibs, aLibrary );
+    alg::delete_matching( *pinnedLibsCfg, aLibrary );
     cfg->SaveToFile( Pgm().GetSettingsManager().GetPathForSettingsFile( cfg ) );
 }
 
@@ -374,3 +413,39 @@ FP_LIB_TABLE* PROJECT::PcbFootprintLibs( KIWAY& aKiway )
 
     return tbl;
 }
+
+
+DESIGN_BLOCK_LIB_TABLE* PROJECT::DesignBlockLibs()
+{
+    // This is a lazy loading function, it loads the project specific table when
+    // that table is asked for, not before.
+
+    DESIGN_BLOCK_LIB_TABLE* tbl = (DESIGN_BLOCK_LIB_TABLE*) GetElem( ELEM::DESIGN_BLOCK_LIB_TABLE );
+
+    if( tbl )
+    {
+        wxASSERT( tbl->ProjectElementType() == PROJECT::ELEM::DESIGN_BLOCK_LIB_TABLE );
+    }
+    else
+    {
+        try
+        {
+            tbl = new DESIGN_BLOCK_LIB_TABLE( &GDesignBlockTable );
+            tbl->Load( DesignBlockLibTblName() );
+
+            SetElem( ELEM::DESIGN_BLOCK_LIB_TABLE, tbl );
+        }
+        catch( const IO_ERROR& ioe )
+        {
+            DisplayErrorMessage( nullptr, _( "Error loading project design block library table." ),
+                                 ioe.What() );
+        }
+        catch( ... )
+        {
+            DisplayErrorMessage( nullptr,
+                                 _( "Error loading project design block library table." ) );
+        }
+    }
+
+    return tbl;
+}
diff --git a/common/settings/common_settings.cpp b/common/settings/common_settings.cpp
index 34b7e90406..99418b8b88 100644
--- a/common/settings/common_settings.cpp
+++ b/common/settings/common_settings.cpp
@@ -346,6 +346,9 @@ COMMON_SETTINGS::COMMON_SETTINGS() :
     m_params.emplace_back( new PARAM_LIST<wxString>( "session.pinned_fp_libs",
             &m_Session.pinned_fp_libs, {} ) );
 
+    m_params.emplace_back( new PARAM_LIST<wxString>( "session.pinned_design_block_libs",
+            &m_Session.pinned_design_block_libs, {} ) );
+
     m_params.emplace_back( new PARAM<int>( "netclass_panel.sash_pos",
             &m_NetclassPanel.sash_pos, 160 ) );
 
@@ -666,6 +669,10 @@ void COMMON_SETTINGS::InitializeEnvironment()
     path = basePath;
     path.AppendDir( wxT( "symbols" ) );
     addVar( ENV_VAR::GetVersionedEnvVarName( wxS( "SYMBOL_DIR" ) ), path.GetFullPath() );
+
+    path = basePath;
+    path.AppendDir( wxT( "blocks" ) );
+    addVar( ENV_VAR::GetVersionedEnvVarName( wxS( "DESIGN_BLOCK_DIR" ) ), path.GetFullPath() );
 }
 
 
diff --git a/common/settings/kicad_settings.cpp b/common/settings/kicad_settings.cpp
index 67a02bcaad..e7ad5e3d7c 100644
--- a/common/settings/kicad_settings.cpp
+++ b/common/settings/kicad_settings.cpp
@@ -43,6 +43,9 @@ KICAD_SETTINGS::KICAD_SETTINGS() :
     m_params.emplace_back(
             new PARAM_LIST<wxString>( "system.open_projects", &m_OpenProjects, {} ) );
 
+    m_params.emplace_back( new PARAM<wxString>( "system.last_design_block_lib_dir",
+                                                &m_lastDesignBlockLibDir, "" ) );
+
     m_params.emplace_back(
             new PARAM<wxString>( "system.last_update_check_time", &m_lastUpdateCheckTime, "" ) );
 
diff --git a/common/settings/settings_manager.cpp b/common/settings/settings_manager.cpp
index bbc87338c7..7a6f802c35 100644
--- a/common/settings/settings_manager.cpp
+++ b/common/settings/settings_manager.cpp
@@ -1049,7 +1049,11 @@ std::vector<wxString> SETTINGS_MANAGER::GetOpenProjects() const
     std::vector<wxString> ret;
 
     for( const std::pair<const wxString, PROJECT*>& pair : m_projects )
-        ret.emplace_back( pair.first );
+    {
+        // Don't save empty projects (these are the default project settings)
+        if( !pair.first.IsEmpty() )
+            ret.emplace_back( pair.first );
+    }
 
     return ret;
 }
diff --git a/common/tool/actions.cpp b/common/tool/actions.cpp
index 1d34437fc1..754bea5083 100644
--- a/common/tool/actions.cpp
+++ b/common/tool/actions.cpp
@@ -1104,6 +1104,13 @@ TOOL_ACTION ACTIONS::showFootprintLibTable( TOOL_ACTION_ARGS()
         .Tooltip( _( "Edit the global and project footprint library lists" ) )
         .Icon( BITMAPS::library_table ) );
 
+TOOL_ACTION ACTIONS::showDesignBlockLibTable( TOOL_ACTION_ARGS()
+        .Name( "common.SuiteControl.showDesignBLockLibTable" )
+        .Scope( AS_GLOBAL )
+        .FriendlyName( _( "Manage Design Block Libraries..." ) )
+        .Tooltip( _( "Edit the global and project design block library lists" ) )
+        .Icon( BITMAPS::library_table ) );
+
 TOOL_ACTION ACTIONS::gettingStarted( TOOL_ACTION_ARGS()
         .Name( "common.SuiteControl.gettingStarted" )
         .Scope( AS_GLOBAL )
diff --git a/common/tool/library_editor_control.cpp b/common/tool/library_editor_control.cpp
index 9e41123459..fbc86ea9d4 100644
--- a/common/tool/library_editor_control.cpp
+++ b/common/tool/library_editor_control.cpp
@@ -82,7 +82,8 @@ int LIBRARY_EDITOR_CONTROL::PinLibrary( const TOOL_EVENT& aEvent )
 
     if( current && !current->m_Pinned )
     {
-        m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(), false );
+        m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(),
+                                   PROJECT::LIB_TYPE_T::FOOTPRINT_LIB );
         current->m_Pinned = true;
         regenerateLibraryTree();
     }
@@ -98,7 +99,8 @@ int LIBRARY_EDITOR_CONTROL::UnpinLibrary( const TOOL_EVENT& aEvent )
 
     if( current && current->m_Pinned )
     {
-        m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(), false );
+        m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(),
+                                     PROJECT::LIB_TYPE_T::FOOTPRINT_LIB );
         current->m_Pinned = false;
         regenerateLibraryTree();
     }
diff --git a/common/trace_helpers.cpp b/common/trace_helpers.cpp
index 5f97c6be8e..cd3017338e 100644
--- a/common/trace_helpers.cpp
+++ b/common/trace_helpers.cpp
@@ -58,6 +58,7 @@ const wxChar* const traceKiCad2Step = wxT( "KICAD2STEP" );
 const wxChar* const traceUiProfile = wxT( "KICAD_UI_PROFILE" );
 const wxChar* const traceGit = wxT( "KICAD_GIT" );
 const wxChar* const traceEagleIo = wxT( "KICAD_EAGLE_IO" );
+const wxChar* const traceDesignBlocks = wxT( "KICAD_DESIGN_BLOCK" );
 
 
 wxString dump( const wxArrayString& aArray )
diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp
index cc6c1cbe46..c42ab64a91 100644
--- a/common/wildcards_and_files_ext.cpp
+++ b/common/wildcards_and_files_ext.cpp
@@ -186,6 +186,9 @@ const std::string FILEEXT::IpcD356FileExtension( "d356" );
 const std::string FILEEXT::Ipc2581FileExtension( "xml" );
 const std::string FILEEXT::WorkbookFileExtension( "wbk" );
 
+const std::string FILEEXT::KiCadDesignBlockLibPathExtension( "blocks" ); // this is a directory
+const std::string FILEEXT::KiCadDesignBlockPathExtension( "block" );     // this is a directory
+
 const std::string FILEEXT::PngFileExtension( "png" );
 const std::string FILEEXT::JpegFileExtension( "jpg" );
 const std::string FILEEXT::TextFileExtension( "txt" );
@@ -337,6 +340,20 @@ wxString FILEEXT::KiCadFootprintLibPathWildcard()
 }
 
 
+wxString FILEEXT::KiCadDesignBlockPathWildcard()
+{
+    return _( "KiCad design block path" )
+           + AddFileExtListToFilter( { KiCadDesignBlockPathExtension } );
+}
+
+
+wxString FILEEXT::KiCadDesignBlockLibPathWildcard()
+{
+    return _( "KiCad design block library paths" )
+           + AddFileExtListToFilter( { KiCadDesignBlockLibPathExtension } );
+}
+
+
 wxString FILEEXT::DrawingSheetFileWildcard()
 {
     return _( "Drawing sheet files" )
diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt
index ef3cd9be5f..7c32db1b2f 100644
--- a/eeschema/CMakeLists.txt
+++ b/eeschema/CMakeLists.txt
@@ -272,7 +272,10 @@ set( EESCHEMA_SIM_SRCS
     )
 
 set( EESCHEMA_WIDGETS
+    widgets/design_block_pane.cpp
+    widgets/design_block_preview_widget.cpp
     widgets/hierarchy_pane.cpp
+    widgets/panel_design_block_chooser.cpp
     widgets/panel_sch_selection_filter_base.cpp
     widgets/panel_sch_selection_filter.cpp
     widgets/panel_symbol_chooser.cpp
@@ -347,6 +350,8 @@ set( EESCHEMA_SRCS
     bus-wire-junction.cpp
     connection_graph.cpp
     cross-probing.cpp
+    design_block_tree_model_adapter.cpp
+    design_block_utils.cpp
     ee_collectors.cpp
     eeschema_config.cpp
     eeschema_helpers.cpp
@@ -441,6 +446,7 @@ set( EESCHEMA_SRCS
     tools/ee_selection_tool.cpp
     tools/rule_area_create_helper.cpp
     tools/sch_drawing_tools.cpp
+    tools/sch_design_block_control.cpp
     tools/sch_edit_table_tool.cpp
     tools/sch_edit_tool.cpp
     tools/sch_editor_control.cpp
diff --git a/eeschema/cross-probing.cpp b/eeschema/cross-probing.cpp
index 8563476338..3b71d5ffe7 100644
--- a/eeschema/cross-probing.cpp
+++ b/eeschema/cross-probing.cpp
@@ -37,6 +37,7 @@
 #include <tools/ee_actions.h>
 #include <tools/sch_editor_control.h>
 #include <advanced_config.h>
+#include <widgets/design_block_pane.h>
 #include <wx/log.h>
 
 SCH_ITEM* SCH_EDITOR_CONTROL::FindSymbolAndItem( const wxString* aPath, const wxString* aReference,
@@ -998,6 +999,7 @@ void SCH_EDIT_FRAME::KiwayMailIn( KIWAY_EXPRESS& mail )
         break;
 
     case MAIL_RELOAD_LIB:
+        m_designBlocksPane->RefreshLibs();
         SyncView();
         break;
 
diff --git a/eeschema/design_block_tree_model_adapter.cpp b/eeschema/design_block_tree_model_adapter.cpp
new file mode 100644
index 0000000000..1a8763f76f
--- /dev/null
+++ b/eeschema/design_block_tree_model_adapter.cpp
@@ -0,0 +1,199 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <pgm_base.h>
+#include <eda_base_frame.h>
+#include <core/kicad_algo.h>
+#include <settings/common_settings.h>
+#include <project/project_file.h>
+#include <wx/log.h>
+#include <wx/tokenzr.h>
+#include <string_utils.h>
+#include <eda_pattern_match.h>
+#include <design_block.h>
+#include <design_block_lib_table.h>
+#include <design_block_info.h>
+#include <footprint_info.h>
+#include <wx/settings.h>
+
+#include <design_block_tree_model_adapter.h>
+#include <tools/sch_design_block_control.h>
+
+wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>
+DESIGN_BLOCK_TREE_MODEL_ADAPTER::Create( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs )
+{
+    auto* adapter = new DESIGN_BLOCK_TREE_MODEL_ADAPTER( aParent, aLibs );
+    adapter->m_frame = aParent;
+    return wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER>( adapter );
+}
+
+
+DESIGN_BLOCK_TREE_MODEL_ADAPTER::DESIGN_BLOCK_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent,
+                                                                  LIB_TABLE*      aLibs ) :
+        LIB_TREE_MODEL_ADAPTER( aParent, wxT( "pinned_design_block_libs" ) ),
+        m_libs( (DESIGN_BLOCK_LIB_TABLE*) aLibs )
+{
+}
+
+
+void DESIGN_BLOCK_TREE_MODEL_ADAPTER::AddLibraries( EDA_BASE_FRAME* aParent )
+{
+    COMMON_SETTINGS* cfg = Pgm().GetCommonSettings();
+    PROJECT_FILE&    project = aParent->Prj().GetProjectFile();
+
+    for( const wxString& libName : m_libs->GetLogicalLibs() )
+    {
+        const DESIGN_BLOCK_LIB_TABLE_ROW* library = nullptr;
+
+        try
+        {
+            library = m_libs->FindRow( libName, true );
+        }
+        catch( ... )
+        {
+            // Skip loading this library, if not exists/ not found
+            continue;
+        }
+        bool pinned = alg::contains( cfg->m_Session.pinned_design_block_libs, libName )
+                      || alg::contains( project.m_PinnedDesignBlockLibs, libName );
+
+        DoAddLibrary( libName, library->GetDescr(), getDesignBlocks( aParent, libName ), pinned,
+                      true );
+    }
+
+    m_tree.AssignIntrinsicRanks();
+}
+
+
+void DESIGN_BLOCK_TREE_MODEL_ADAPTER::ClearLibraries()
+{
+    m_tree.Clear();
+}
+
+
+std::vector<LIB_TREE_ITEM*>
+DESIGN_BLOCK_TREE_MODEL_ADAPTER::getDesignBlocks( EDA_BASE_FRAME* aParent,
+                                                  const wxString& aLibName )
+{
+    std::vector<LIB_TREE_ITEM*> libList;
+
+
+    auto                               fullListStart = GDesignBlockList.GetList().begin();
+    auto                               fullListEnd = GDesignBlockList.GetList().end();
+    std::unique_ptr<DESIGN_BLOCK_INFO> dummy =
+            std::make_unique<DESIGN_BLOCK_INFO_IMPL>( aLibName, wxEmptyString );
+
+    // List is sorted, so use a binary search to find the range of footnotes for our library
+    auto libBounds = std::equal_range(
+            fullListStart, fullListEnd, dummy,
+            []( const std::unique_ptr<DESIGN_BLOCK_INFO>& a,
+                const std::unique_ptr<DESIGN_BLOCK_INFO>& b )
+            {
+                return StrNumCmp( a->GetLibNickname(), b->GetLibNickname(), false ) < 0;
+            } );
+
+    for( auto i = libBounds.first; i != libBounds.second; ++i )
+        libList.push_back( i->get() );
+
+    return libList;
+}
+
+
+wxString DESIGN_BLOCK_TREE_MODEL_ADAPTER::GenerateInfo( LIB_ID const& aLibId, int aUnit )
+{
+    const wxString DescriptionFormat = wxT(
+            "<b>__NAME__</b>"
+            "<br>__DESC__"
+            "<hr><table border=0>"
+            "__FIELDS__"
+            "</table>" );
+
+    const wxString KeywordsFormat = wxT(
+            "<tr>"
+            "   <td><b>" + _( "Keywords" ) + "</b></td>"
+            "   <td>__KEYWORDS__</td>"
+            "</tr>" );
+
+    const wxString DocFormat = wxT(
+            "<tr>"
+            "   <td><b>" + _( "Documentation" ) + "</b></td>"
+            "   <td><a href=\"__HREF__\">__TEXT__</a></td>"
+            "</tr>" );
+
+
+    if( !aLibId.IsValid() )
+        return wxEmptyString;
+
+    const DESIGN_BLOCK* db = nullptr;
+
+    try
+    {
+        db = m_libs->GetEnumeratedDesignBlock( aLibId.GetLibNickname(), aLibId.GetLibItemName() );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        wxLogError( _( "Error loading design block %s from library '%s'." ) + wxS( "\n%s" ),
+                    aLibId.GetLibItemName().wx_str(), aLibId.GetLibNickname().wx_str(),
+                    ioe.What() );
+
+        return wxEmptyString;
+    }
+
+    wxString html = DescriptionFormat;
+
+    if( db )
+    {
+        wxString name = aLibId.GetLibItemName();
+        wxString desc = db->GetLibDescription();
+        wxString keywords = db->GetKeywords();
+        wxString doc = db->GetDocumentationUrl();
+
+        wxString esc_desc = EscapeHTML( UnescapeString( desc ) );
+
+        // Add line breaks
+        esc_desc.Replace( wxS( "\n" ), wxS( "<br>" ) );
+
+        // Add links
+        esc_desc = LinkifyHTML( esc_desc );
+
+        html.Replace( "__DESC__", esc_desc );
+        html.Replace( "__NAME__", EscapeHTML( name ) );
+
+        wxString keywordsHtml = KeywordsFormat;
+        keywordsHtml.Replace( "__KEYWORDS__", EscapeHTML( keywords ) );
+
+        wxString docHtml = DocFormat;
+        docHtml.Replace( "__HREF__", doc );
+
+        if( doc.Length() > 75 )
+            doc = doc.Left( 72 ) + wxT( "..." );
+
+        docHtml.Replace( "__TEXT__", EscapeHTML( doc ) );
+
+        html.Replace( "__FIELDS__", keywordsHtml + docHtml );
+    }
+
+    return html;
+}
+
+
+TOOL_INTERACTIVE* DESIGN_BLOCK_TREE_MODEL_ADAPTER::GetContextMenuTool()
+{
+    return m_frame->GetToolManager()->GetTool<SCH_DESIGN_BLOCK_CONTROL>();
+}
diff --git a/eeschema/design_block_tree_model_adapter.h b/eeschema/design_block_tree_model_adapter.h
new file mode 100644
index 0000000000..b1fc73aa98
--- /dev/null
+++ b/eeschema/design_block_tree_model_adapter.h
@@ -0,0 +1,62 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2014-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DESIGN_BLOCK_TREE_MODEL_ADAPTER_H
+#define DESIGN_BLOCK_TREE_MODEL_ADAPTER_H
+
+#include <lib_tree_model_adapter.h>
+
+class LIB_TABLE;
+class DESIGN_BLOCK_LIB_TABLE;
+
+class DESIGN_BLOCK_TREE_MODEL_ADAPTER : public LIB_TREE_MODEL_ADAPTER
+{
+public:
+    /**
+     * Factory function: create a model adapter in a reference-counting container.
+     *
+     * @param aLibs library set from which parts will be loaded
+     */
+    static wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER> Create( EDA_BASE_FRAME* aParent,
+                                                           LIB_TABLE*      aLibs );
+
+    void AddLibraries( EDA_BASE_FRAME* aParent );
+    void ClearLibraries();
+
+    wxString GenerateInfo( LIB_ID const& aLibId, int aUnit ) override;
+
+    TOOL_INTERACTIVE* GetContextMenuTool() override;
+
+protected:
+    /**
+     * Constructor; takes a set of libraries to be included in the search.
+     */
+    DESIGN_BLOCK_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs );
+
+    std::vector<LIB_TREE_ITEM*> getDesignBlocks( EDA_BASE_FRAME* aParent,
+                                                 const wxString& aLibName );
+
+    PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::DESIGN_BLOCK_LIB; }
+
+protected:
+    DESIGN_BLOCK_LIB_TABLE* m_libs;
+    EDA_BASE_FRAME*         m_frame;
+};
+
+#endif // DESIGN_BLOCK_TREE_MODEL_ADAPTER_H
diff --git a/eeschema/design_block_utils.cpp b/eeschema/design_block_utils.cpp
new file mode 100644
index 0000000000..a98d70d70a
--- /dev/null
+++ b/eeschema/design_block_utils.cpp
@@ -0,0 +1,522 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 1992-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <pgm_base.h>
+#include <kiway.h>
+#include <design_block.h>
+#include <design_block_lib_table.h>
+#include <design_block_pane.h>
+#include <sch_edit_frame.h>
+#include <wx/choicdlg.h>
+#include <wx/msgdlg.h>
+#include <wx/textdlg.h>
+#include <wildcards_and_files_ext.h>
+#include <paths.h>
+#include <env_paths.h>
+#include <common.h>
+#include <kidialog.h>
+#include <confirm.h>
+#include <tool/tool_manager.h>
+#include <ee_selection_tool.h>
+
+DESIGN_BLOCK_LIB_TABLE* SCH_EDIT_FRAME::selectDesignBlockLibTable( bool aOptional )
+{
+    // If no project is loaded, always work with the global table
+    if( Prj().IsNullProject() )
+    {
+        DESIGN_BLOCK_LIB_TABLE* ret = &GDesignBlockTable;
+
+        if( aOptional )
+        {
+            wxMessageDialog dlg( this, _( "Add the library to the global library table?" ),
+                                 _( "Add To Global Library Table" ), wxYES_NO );
+
+            if( dlg.ShowModal() != wxID_OK )
+                ret = nullptr;
+        }
+
+        return ret;
+    }
+
+    wxArrayString libTableNames;
+    libTableNames.Add( _( "Global" ) );
+    libTableNames.Add( _( "Project" ) );
+
+    wxSingleChoiceDialog dlg( this, _( "Choose the Library Table to add the library to:" ),
+                              _( "Add To Library Table" ), libTableNames );
+
+    if( aOptional )
+    {
+        dlg.FindWindow( wxID_CANCEL )->SetLabel( _( "Skip" ) );
+        dlg.FindWindow( wxID_OK )->SetLabel( _( "Add" ) );
+    }
+
+    if( dlg.ShowModal() != wxID_OK )
+        return nullptr;
+
+    switch( dlg.GetSelection() )
+    {
+    case 0: return &GDesignBlockTable;
+    case 1: return Prj().DesignBlockLibs();
+    default: return nullptr;
+    }
+}
+
+
+wxString SCH_EDIT_FRAME::CreateNewDesignBlockLibrary( const wxString& aLibName,
+                                                      const wxString& aProposedName )
+{
+    return createNewDesignBlockLibrary( aLibName, aProposedName, selectDesignBlockLibTable() );
+}
+
+
+wxString SCH_EDIT_FRAME::createNewDesignBlockLibrary( const wxString&         aLibName,
+                                                      const wxString&         aProposedName,
+                                                      DESIGN_BLOCK_LIB_TABLE* aTable )
+{
+    if( aTable == nullptr )
+        return wxEmptyString;
+
+    wxString   initialPath = aProposedName.IsEmpty() ? Prj().GetProjectPath() : aProposedName;
+    wxFileName fn;
+    bool       doAdd = false;
+    bool       isGlobal = ( aTable == &GDesignBlockTable );
+
+    if( aLibName.IsEmpty() )
+    {
+        fn = initialPath;
+
+        if( !LibraryFileBrowser( false, fn, FILEEXT::KiCadDesignBlockLibPathWildcard(),
+                                 FILEEXT::KiCadDesignBlockLibPathExtension, false, isGlobal,
+                                 PATHS::GetDefaultUserFootprintsPath() ) )
+        {
+            return wxEmptyString;
+        }
+
+        doAdd = true;
+    }
+    else
+    {
+        fn = EnsureFileExtension( aLibName, FILEEXT::KiCadDesignBlockLibPathExtension );
+
+        if( !fn.IsAbsolute() )
+        {
+            fn.SetName( aLibName );
+            fn.MakeAbsolute( initialPath );
+        }
+    }
+
+    // We can save libs only using DESIGN_BLOCK_IO_MGR::KICAD_SEXP format (.pretty libraries)
+    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T piType = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
+    wxString                                 libPath = fn.GetFullPath();
+
+    try
+    {
+        IO_RELEASER<DESIGN_BLOCK_IO> pi( DESIGN_BLOCK_IO_MGR::FindPlugin( piType ) );
+
+        bool writable = false;
+        bool exists   = false;
+
+        try
+        {
+            writable = pi->IsLibraryWritable( libPath );
+            exists   = fn.Exists();
+        }
+        catch( const IO_ERROR& )
+        {
+            // best efforts....
+        }
+
+        if( exists )
+        {
+            if( !writable )
+            {
+                wxString msg = wxString::Format( _( "Library %s is read only." ), libPath );
+                ShowInfoBarError( msg );
+                return wxEmptyString;
+            }
+            else
+            {
+                wxString msg = wxString::Format( _( "Library %s already exists." ), libPath );
+                KIDIALOG dlg( this, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
+                dlg.SetOKLabel( _( "Overwrite" ) );
+                dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
+
+                if( dlg.ShowModal() == wxID_CANCEL )
+                    return wxEmptyString;
+
+                pi->DeleteLibrary( libPath );
+            }
+        }
+
+        pi->CreateLibrary( libPath );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        DisplayError( this, ioe.What() );
+        return wxEmptyString;
+    }
+
+    if( doAdd )
+        AddDesignBlockLibrary( libPath, aTable );
+
+    return libPath;
+}
+
+
+bool SCH_EDIT_FRAME::AddDesignBlockLibrary( const wxString&         aFilename,
+                                            DESIGN_BLOCK_LIB_TABLE* aTable )
+{
+    if( aTable == nullptr )
+        aTable = selectDesignBlockLibTable();
+
+    if( aTable == nullptr )
+        return wxEmptyString;
+
+    bool isGlobal = ( aTable == &GDesignBlockTable );
+
+    wxFileName fn( aFilename );
+
+    if( aFilename.IsEmpty() )
+    {
+        if( !LibraryFileBrowser( true, fn, FILEEXT::KiCadFootprintLibPathWildcard(),
+                                 FILEEXT::KiCadFootprintLibPathExtension, true, isGlobal,
+                                 PATHS::GetDefaultUserFootprintsPath() ) )
+        {
+            return false;
+        }
+    }
+
+    wxString libPath = fn.GetFullPath();
+    wxString libName = fn.GetName();
+
+    if( libName.IsEmpty() )
+        return false;
+
+    // Open a dialog to ask for a description
+    wxString description = wxGetTextFromUser( _( "Enter a description for the library:" ),
+                                              _( "Library Description" ), wxEmptyString, this );
+
+    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T lib_type =
+            DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( libPath );
+
+    if( lib_type == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
+        lib_type = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
+
+    wxString type = DESIGN_BLOCK_IO_MGR::ShowType( lib_type );
+
+    // KiCad lib is our default guess.  So it might not have the .blocks extension
+    // In this case, the extension is part of the library name
+    if( lib_type == DESIGN_BLOCK_IO_MGR::KICAD_SEXP
+        && fn.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension )
+        libName = fn.GetFullName();
+
+    // try to use path normalized to an environmental variable or project path
+    wxString normalizedPath = NormalizePath( libPath, &Pgm().GetLocalEnvVariables(), &Prj() );
+
+    try
+    {
+        DESIGN_BLOCK_LIB_TABLE_ROW* row = new DESIGN_BLOCK_LIB_TABLE_ROW(
+                libName, normalizedPath, type, wxEmptyString, description );
+        aTable->InsertRow( row );
+
+        if( isGlobal )
+            GDesignBlockTable.Save( DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName() );
+        else
+            Prj().DesignBlockLibs()->Save( Prj().DesignBlockLibTblName() );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        DisplayError( this, ioe.What() );
+        return false;
+    }
+
+    LIB_ID libID( libName, wxEmptyString );
+    m_designBlocksPane->RefreshLibs();
+    m_designBlocksPane->SelectLibId( libID );
+
+    return true;
+}
+
+
+void SCH_EDIT_FRAME::SaveSheetAsDesignBlock( const wxString& aLibraryName )
+{
+    // Make sure the user has selected a library to save into
+    if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
+    {
+        DisplayError( this, _( "Please select a library to save the design block to." ) );
+        return;
+    }
+
+    // Just block all attempts to create design blocks with nested sheets at this point
+    std::vector<SCH_ITEM*> sheets;
+    GetScreen()->GetSheets( &sheets );
+
+    if( !sheets.empty() )
+    {
+        DisplayError( this, _( "Design blocks with nested sheets are not supported." ) );
+        return;
+    }
+
+    // Ask the user for the design block name
+    wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() );
+
+    wxString name = wxGetTextFromUser( _( "Enter a name for the design block:" ),
+                                       _( "Design Block Name" ), fn.GetName(), this );
+
+    if( name.IsEmpty() )
+        return;
+
+    // Save a temporary copy of the schematic file, as the plugin is just going to move it
+    wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
+    if( !saveSchematicFile( GetCurrentSheet().Last(), tempFile ) )
+    {
+        DisplayError( this, _( "Error saving temporary schematic file to create design block." ) );
+        wxRemoveFile( tempFile );
+        return;
+    }
+
+
+    DESIGN_BLOCK blk;
+    blk.SetSchematicFile( tempFile );
+    blk.SetLibId( LIB_ID( aLibraryName, name ) );
+
+    try
+    {
+        Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        DisplayError( this, ioe.What() );
+    }
+
+    // Clean up the temporary file
+    wxRemoveFile( tempFile );
+
+    m_designBlocksPane->RefreshLibs();
+    m_designBlocksPane->SelectLibId( blk.GetLibId() );
+}
+
+
+void SCH_EDIT_FRAME::SaveSelectionAsDesignBlock( const wxString& aLibraryName )
+{
+    // Make sure the user has selected a library to save into
+    if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
+    {
+        DisplayError( this, _( "Please select a library to save the design block to." ) );
+        return;
+    }
+
+    // Get all selected items
+    EE_SELECTION selection = m_toolManager->GetTool<EE_SELECTION_TOOL>()->GetSelection();
+
+    if( selection.Empty() )
+    {
+        DisplayError( this, _( "Please select some items to save as a design block." ) );
+        return;
+    }
+
+    // Just block all attempts to create design blocks with nested sheets at this point
+    if( selection.HasType( SCH_SHEET_T ) )
+    {
+        DisplayError( this, _( "Design blocks with nested sheets are not supported." ) );
+        return;
+    }
+
+    // Ask the user for the design block name
+    wxString name = wxGetTextFromUser( _( "Enter a name for the design block:" ),
+                                       _( "Design Block Name" ), wxEmptyString, this );
+
+    if( name.IsEmpty() )
+        return;
+
+    // Create a temperorary screen
+    SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
+
+    // Copy the selected items to the temporary screen
+    for( EDA_ITEM* item : selection )
+    {
+        EDA_ITEM* copy = item->Clone();
+        tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
+    }
+
+    // Create a sheet for the temporary screen
+    SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
+    tempSheet->SetScreen( tempScreen );
+
+    // Save a temporary copy of the schematic file, as the plugin is just going to move it
+    wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
+    if( !saveSchematicFile( tempSheet, tempFile ) )
+    {
+        DisplayError( this, _( "Error saving temporary schematic file to create design block." ) );
+        wxRemoveFile( tempFile );
+        return;
+    }
+
+    // Create a design block
+    DESIGN_BLOCK blk;
+    blk.SetSchematicFile( tempFile );
+    blk.SetLibId( LIB_ID( aLibraryName, name ) );
+
+    try
+    {
+        // Actually save it to disk
+        Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        DisplayError( this, ioe.What() );
+    }
+
+    // Clean up the temporaries
+    wxRemoveFile( tempFile );
+    // This will also delete the screen
+    delete tempSheet;
+
+    m_designBlocksPane->RefreshLibs();
+    m_designBlocksPane->SelectLibId( blk.GetLibId() );
+}
+
+
+bool SCH_EDIT_FRAME::DeleteDesignBlockLibrary( const wxString& aLibName, bool aConfirm )
+{
+    if( aLibName.IsEmpty() )
+    {
+        DisplayError( this, _( "Please select a library to delete." ) );
+        return false;
+    }
+
+    if( !Prj().DesignBlockLibs()->IsDesignBlockLibWritable( aLibName ) )
+    {
+        wxString msg = wxString::Format( _( "Library '%s' is read only." ), aLibName );
+        ShowInfoBarError( msg );
+        return false;
+    }
+
+    // Confirmation
+    wxString msg = wxString::Format( _( "Delete design block library '%s' from disk? This will "
+                                        "delete all design blocks within the library." ),
+                                     aLibName.GetData() );
+
+    if( aConfirm && !IsOK( this, msg ) )
+        return false;
+
+    try
+    {
+        Prj().DesignBlockLibs()->DesignBlockLibDelete( aLibName );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        DisplayError( this, ioe.What() );
+        return false;
+    }
+
+    msg.Printf( _( "Design block library '%s' deleted" ), aLibName.GetData() );
+    SetStatusText( msg );
+
+    m_designBlocksPane->RefreshLibs();
+
+    return true;
+}
+
+
+bool SCH_EDIT_FRAME::DeleteDesignBlockFromLibrary( const LIB_ID& aLibId, bool aConfirm )
+{
+    if( !aLibId.IsValid() )
+        return false;
+
+    wxString libname = aLibId.GetLibNickname();
+    wxString dbname = aLibId.GetLibItemName();
+
+    if( !Prj().DesignBlockLibs()->IsDesignBlockLibWritable( libname ) )
+    {
+        wxString msg = wxString::Format( _( "Library '%s' is read only." ), libname );
+        ShowInfoBarError( msg );
+        return false;
+    }
+
+    // Confirmation
+    wxString msg = wxString::Format( _( "Delete design block '%s' in library '%s' from disk?" ),
+                                     dbname.GetData(), libname.GetData() );
+
+    if( aConfirm && !IsOK( this, msg ) )
+        return false;
+
+    try
+    {
+        Prj().DesignBlockLibs()->DesignBlockDelete( libname, dbname );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        DisplayError( this, ioe.What() );
+        return false;
+    }
+
+    msg.Printf( _( "Design block '%s' deleted from library '%s'" ), dbname.GetData(),
+                libname.GetData() );
+
+    SetStatusText( msg );
+
+    m_designBlocksPane->RefreshLibs();
+
+    return true;
+}
+
+
+DESIGN_BLOCK* SchGetDesignBlock( const LIB_ID& aLibId, DESIGN_BLOCK_LIB_TABLE* aLibTable,
+                                 wxWindow* aParent, bool aShowErrorMsg )
+{
+    wxCHECK_MSG( aLibTable, nullptr, wxS( "Invalid design block library table." ) );
+
+    DESIGN_BLOCK* designBlock = nullptr;
+
+    try
+    {
+        designBlock = aLibTable->DesignBlockLoadWithOptionalNickname( aLibId, true );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        if( aShowErrorMsg )
+        {
+            wxString msg = wxString::Format( _( "Error loading designblock %s from library '%s'." ),
+                                             aLibId.GetLibItemName().wx_str(),
+                                             aLibId.GetLibNickname().wx_str() );
+            DisplayErrorMessage( aParent, msg, ioe.What() );
+        }
+    }
+
+    return designBlock;
+}
+
+
+DESIGN_BLOCK* SCH_EDIT_FRAME::GetDesignBlock( const LIB_ID& aLibId, bool aUseCacheLib,
+                                              bool aShowErrorMsg )
+{
+    return SchGetDesignBlock( aLibId, Prj().DesignBlockLibs(), this, aShowErrorMsg );
+}
+
+
+void SCH_EDIT_FRAME::UpdateDesignBlockOptions()
+{
+    m_designBlocksPane->UpdateCheckboxes();
+}
diff --git a/eeschema/dialogs/dialog_sheet_properties.cpp b/eeschema/dialogs/dialog_sheet_properties.cpp
index 057ecf33c2..5415113d1a 100644
--- a/eeschema/dialogs/dialog_sheet_properties.cpp
+++ b/eeschema/dialogs/dialog_sheet_properties.cpp
@@ -178,26 +178,10 @@ bool DIALOG_SHEET_PROPERTIES::TransferDataToWindow()
     m_borderSwatch->SetSwatchBackground( canvas );
     m_backgroundSwatch->SetSwatchBackground( canvas );
 
-    SCH_SHEET_LIST hierarchy = m_frame->Schematic().GetFullHierarchy();
     SCH_SHEET_PATH instance = m_frame->GetCurrentSheet();
-
     instance.push_back( m_sheet );
 
-    wxString pageNumber;
-
-    if( m_sheet->IsNew() )
-    {
-        // Don't try to be too clever when assigning the next availabe page number.  Just use
-        // the number of sheets plus one.
-        pageNumber.Printf( wxT( "%d" ), static_cast<int>( hierarchy.size() ) + 1 );
-        instance.SetPageNumber( pageNumber );
-    }
-    else
-    {
-        pageNumber = instance.GetPageNumber();
-    }
-
-    m_pageNumberTextCtrl->ChangeValue( pageNumber );
+    m_pageNumberTextCtrl->ChangeValue( instance.GetPageNumber() );
 
     m_cbExcludeFromSim->SetValue( m_sheet->GetExcludedFromSim() );
     m_cbExcludeFromBom->SetValue( m_sheet->GetExcludedFromBOM() );
diff --git a/eeschema/eeschema_config.cpp b/eeschema/eeschema_config.cpp
index d7ac921fd1..386a6f5dc0 100644
--- a/eeschema/eeschema_config.cpp
+++ b/eeschema/eeschema_config.cpp
@@ -34,6 +34,7 @@
 #include <sch_edit_frame.h>
 #include <sch_painter.h>
 #include <schematic.h>
+#include <widgets/design_block_pane.h>
 #include <widgets/hierarchy_pane.h>
 #include <widgets/sch_search_pane.h>
 #include <widgets/panel_sch_selection_filter.h>
@@ -304,6 +305,17 @@ void SCH_EDIT_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg )
             cfg->m_AuiPanels.net_nav_panel_float_pos = netNavigatorPane.floating_pos;
             cfg->m_AuiPanels.net_nav_panel_float_size = netNavigatorPane.floating_size;
         }
+
+        wxAuiPaneInfo& designBlocksPane = m_auimgr.GetPane( DesignBlocksPaneName() );
+        cfg->m_AuiPanels.design_blocks_show = designBlocksPane.IsShown();
+
+        if( designBlocksPane.IsDocked() )
+            cfg->m_AuiPanels.design_blocks_panel_docked_width = m_designBlocksPane->GetSize().x;
+        else
+        {
+            cfg->m_AuiPanels.design_blocks_panel_float_height = designBlocksPane.floating_size.y;
+            cfg->m_AuiPanels.design_blocks_panel_float_width = designBlocksPane.floating_size.x;
+        }
     }
 }
 
diff --git a/eeschema/eeschema_helpers.cpp b/eeschema/eeschema_helpers.cpp
index d99149bb35..c6074baaf5 100644
--- a/eeschema/eeschema_helpers.cpp
+++ b/eeschema/eeschema_helpers.cpp
@@ -60,7 +60,7 @@ SETTINGS_MANAGER* EESCHEMA_HELPERS::GetSettingsManager()
 }
 
 
-PROJECT* EESCHEMA_HELPERS::GetDefaultProject()
+PROJECT* EESCHEMA_HELPERS::GetDefaultProject( bool aSetActive )
 {
     // For some reasons, LoadProject() needs a C locale, so ensure we have the right locale
     // This is mainly when running QA Python tests
@@ -70,7 +70,7 @@ PROJECT* EESCHEMA_HELPERS::GetDefaultProject()
 
     if( !project )
     {
-        GetSettingsManager()->LoadProject( "" );
+        GetSettingsManager()->LoadProject( "", aSetActive );
         project = GetSettingsManager()->GetProject( "" );
     }
 
@@ -78,20 +78,9 @@ PROJECT* EESCHEMA_HELPERS::GetDefaultProject()
 }
 
 
-SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( wxString& aFileName, bool aSetActive )
-{
-    if( aFileName.EndsWith( FILEEXT::KiCadSchematicFileExtension ) )
-        return LoadSchematic( aFileName, SCH_IO_MGR::SCH_KICAD, aSetActive );
-    else if( aFileName.EndsWith( FILEEXT::LegacySchematicFileExtension ) )
-        return LoadSchematic( aFileName, SCH_IO_MGR::SCH_LEGACY, aSetActive );
-
-    // as fall back for any other kind use the legacy format
-    return LoadSchematic( aFileName, SCH_IO_MGR::SCH_LEGACY, aSetActive );
-}
-
-
-SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( wxString& aFileName, SCH_IO_MGR::SCH_FILE_T aFormat,
-                                            bool aSetActive )
+SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( const wxString&        aFileName,
+                                            SCH_IO_MGR::SCH_FILE_T aFormat, bool aSetActive,
+                                            bool aForceDefaultProject )
 {
     wxFileName pro = aFileName;
     pro.SetExt( FILEEXT::ProjectFileExtension );
@@ -104,23 +93,26 @@ SCHEMATIC* EESCHEMA_HELPERS::LoadSchematic( wxString& aFileName, SCH_IO_MGR::SCH
 
     PROJECT* project = GetSettingsManager()->GetProject( projectPath );
 
-    if( !project )
+    if( !aForceDefaultProject )
     {
-        if( wxFileExists( projectPath ) )
+        if( !project )
         {
-            GetSettingsManager()->LoadProject( projectPath, aSetActive );
-            project = GetSettingsManager()->GetProject( projectPath );
+            if( wxFileExists( projectPath ) )
+            {
+                GetSettingsManager()->LoadProject( projectPath, aSetActive );
+                project = GetSettingsManager()->GetProject( projectPath );
+            }
+        }
+        else if( s_SchEditFrame && project == &GetSettingsManager()->Prj() )
+        {
+            // Project is already loaded?  Then so is the board
+            return &s_SchEditFrame->Schematic();
         }
-    }
-    else if( s_SchEditFrame && project == &GetSettingsManager()->Prj() )
-    {
-        // Project is already loaded?  Then so is the board
-        return &s_SchEditFrame->Schematic();
     }
 
     // Board cannot be loaded without a project, so create the default project
-    if( !project )
-        project = GetDefaultProject();
+    if( !project || aForceDefaultProject )
+        project = GetDefaultProject( aSetActive );
 
     IO_RELEASER<SCH_IO> pi( SCH_IO_MGR::FindPlugin( aFormat ) );
 
diff --git a/eeschema/eeschema_helpers.h b/eeschema/eeschema_helpers.h
index 02e8e240b9..ac663879ff 100644
--- a/eeschema/eeschema_helpers.h
+++ b/eeschema/eeschema_helpers.h
@@ -43,15 +43,13 @@ class EESCHEMA_HELPERS
 public:
     static SETTINGS_MANAGER* GetSettingsManager();
     static void              SetSchEditFrame( SCH_EDIT_FRAME* aSchEditFrame );
-    static PROJECT*          GetDefaultProject();
-    static SCHEMATIC*        LoadSchematic( wxString& aFileName, bool aSetActive );
-    static SCHEMATIC*        LoadSchematic( wxString& aFileName, SCH_IO_MGR::SCH_FILE_T aFormat,
-                                            bool aSetActive );
-
+    static PROJECT*          GetDefaultProject( bool aSetActive );
+    static SCHEMATIC*        LoadSchematic( const wxString& aFileName, SCH_IO_MGR::SCH_FILE_T aFormat,
+                                            bool aSetActive, bool aForceDefaultProject );
 
 private:
     static SCH_EDIT_FRAME*   s_SchEditFrame;
     static SETTINGS_MANAGER* s_SettingsManager;
 };
 
-#endif
\ No newline at end of file
+#endif
diff --git a/eeschema/eeschema_jobs_handler.cpp b/eeschema/eeschema_jobs_handler.cpp
index 5176cf8d3b..11da7e1a6a 100644
--- a/eeschema/eeschema_jobs_handler.cpp
+++ b/eeschema/eeschema_jobs_handler.cpp
@@ -149,7 +149,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportPlot( JOB* aJob )
     if( !aPlotJob )
         return CLI::EXIT_CODES::ERR_UNKNOWN;
 
-    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aPlotJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
+    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aPlotJob->m_filename, SCH_IO_MGR::SCH_KICAD,
+                                                      true, false );
 
     if( sch == nullptr )
     {
@@ -247,7 +248,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportNetlist( JOB* aJob )
     if( !aNetJob )
         return CLI::EXIT_CODES::ERR_UNKNOWN;
 
-    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
+    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD,
+                                                      true, false );
 
     if( sch == nullptr )
     {
@@ -358,7 +360,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportBom( JOB* aJob )
     if( !aBomJob )
         return CLI::EXIT_CODES::ERR_UNKNOWN;
 
-    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aBomJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
+    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aBomJob->m_filename, SCH_IO_MGR::SCH_KICAD,
+                                                      true, false );
 
     if( sch == nullptr )
     {
@@ -616,7 +619,8 @@ int EESCHEMA_JOBS_HANDLER::JobExportPythonBom( JOB* aJob )
     if( !aNetJob )
         return CLI::EXIT_CODES::ERR_UNKNOWN;
 
-    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
+    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( aNetJob->m_filename, SCH_IO_MGR::SCH_KICAD,
+                                                      true, false );
 
     if( sch == nullptr )
     {
@@ -963,7 +967,8 @@ int EESCHEMA_JOBS_HANDLER::JobSchErc( JOB* aJob )
     if( !ercJob )
         return CLI::EXIT_CODES::ERR_UNKNOWN;
 
-    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( ercJob->m_filename, SCH_IO_MGR::SCH_KICAD, true );
+    SCHEMATIC* sch = EESCHEMA_HELPERS::LoadSchematic( ercJob->m_filename, SCH_IO_MGR::SCH_KICAD,
+                                                      true, false );
 
     if( sch == nullptr )
     {
diff --git a/eeschema/eeschema_settings.cpp b/eeschema/eeschema_settings.cpp
index df8c373a21..6516a6c3e0 100644
--- a/eeschema/eeschema_settings.cpp
+++ b/eeschema/eeschema_settings.cpp
@@ -126,6 +126,28 @@ const wxAuiPaneInfo& defaultSchSelectionFilterPaneInfo( wxWindow* aWindow )
 }
 
 
+const wxAuiPaneInfo& defaultDesignBlocksPaneInfo( wxWindow* aWindow )
+{
+    static wxAuiPaneInfo paneInfo;
+
+    paneInfo.Name( EDA_DRAW_FRAME::DesignBlocksPaneName() )
+            .Caption( _( "Design Blocks" ) )
+            .CaptionVisible( true )
+            .PaneBorder( true )
+            .Right().Layer( 3 ).Position( 2 )
+            .TopDockable( false )
+            .BottomDockable( false )
+            .CloseButton( false )
+            .MinSize( aWindow->FromDIP( wxSize( 240, 60 ) ) )
+            .BestSize( aWindow->FromDIP( wxSize( 300, 200 ) ) )
+            .FloatingSize( aWindow->FromDIP( wxSize( 800, 600 ) ) )
+            .FloatingPosition( aWindow->FromDIP( wxPoint( 50, 200 ) ) )
+            .Show( true );
+
+    return paneInfo;
+}
+
+
 EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() :
         APP_SETTINGS_BASE( "eeschema", eeschemaSchemaVersion ),
         m_Appearance(),
@@ -240,6 +262,18 @@ EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() :
     m_params.emplace_back( new PARAM<int>( "aui.hierarchy_panel_float_height",
             &m_AuiPanels.hierarchy_panel_float_height, -1 ) );
 
+    m_params.emplace_back( new PARAM<bool>( "aui.design_blocks_show",
+            &m_AuiPanels.design_blocks_show, false ) );
+
+    m_params.emplace_back( new PARAM<int>( "aui.design_blocks_panel_docked_width",
+            &m_AuiPanels.design_blocks_panel_docked_width, -1 ) );
+
+    m_params.emplace_back( new PARAM<int>( "aui.design_blocks_panel_float_width",
+            &m_AuiPanels.design_blocks_panel_float_width, -1 ) );
+
+    m_params.emplace_back( new PARAM<int>( "aui.design_blocks_panel_float_height",
+            &m_AuiPanels.design_blocks_panel_float_height, -1 ) );
+
     m_params.emplace_back( new PARAM<bool>( "aui.schematic_hierarchy_float",
             &m_AuiPanels.schematic_hierarchy_float, false ) );
 
@@ -613,6 +647,24 @@ EESCHEMA_SETTINGS::EESCHEMA_SETTINGS() :
     m_params.emplace_back( new PARAM<bool>( "symbol_chooser.place_all_units",
             &m_SymChooserPanel.place_all_units, true ) );
 
+    m_params.emplace_back( new PARAM<int>( "design_block_chooser.sash_pos_v",
+            &m_DesignBlockChooserPanel.sash_pos_v, -1 ) );
+
+    m_params.emplace_back( new PARAM<int>( "design_block_chooser.width",
+            &m_DesignBlockChooserPanel.width, -1 ) );
+
+    m_params.emplace_back( new PARAM<int>( "design_block_chooser.height",
+            &m_DesignBlockChooserPanel.height, -1 ) );
+
+    m_params.emplace_back( new PARAM<int>( "design_block_chooser.sort_mode",
+            &m_DesignBlockChooserPanel.sort_mode, 0 ) );
+
+    m_params.emplace_back( new PARAM<bool>( "design_block_chooser.repeated_placement",
+            &m_DesignBlockChooserPanel.repeated_placement, false ) );
+
+    m_params.emplace_back( new PARAM<bool>( "design_block_chooser.place_as_sheet",
+            &m_DesignBlockChooserPanel.place_as_sheet, false ) );
+
     m_params.emplace_back( new PARAM<bool>( "import_graphics.interactive_placement",
             &m_ImportGraphics.interactive_placement, true ) );
 
diff --git a/eeschema/eeschema_settings.h b/eeschema/eeschema_settings.h
index c685216eea..163f155caa 100644
--- a/eeschema/eeschema_settings.h
+++ b/eeschema/eeschema_settings.h
@@ -35,6 +35,7 @@ using KIGFX::COLOR4D;
 extern const wxAuiPaneInfo& defaultNetNavigatorPaneInfo();
 extern const wxAuiPaneInfo& defaultPropertiesPaneInfo( wxWindow* aWindow );
 extern const wxAuiPaneInfo& defaultSchSelectionFilterPaneInfo( wxWindow* aWindow );
+extern const wxAuiPaneInfo& defaultDesignBlocksPaneInfo( wxWindow* aWindow );
 
 
 
@@ -101,6 +102,10 @@ public:
         int  properties_panel_width;
         float properties_splitter;
         bool show_properties;
+        bool design_blocks_show;
+        int  design_blocks_panel_docked_width;
+        int  design_blocks_panel_float_width;
+        int  design_blocks_panel_float_height;
     };
 
     struct AUTOPLACE_FIELDS
@@ -269,6 +274,18 @@ public:
         bool place_all_units;
     };
 
+    struct PANEL_DESIGN_BLOCK_CHOOSER
+    {
+        int  sash_pos_h;
+        int  sash_pos_v;
+        int  width;
+        int  height;
+        int  sort_mode;
+        bool repeated_placement;
+        bool place_as_sheet;
+        bool keep_annotations;
+    };
+
     struct DIALOG_IMPORT_GRAPHICS
     {
         bool     interactive_placement;
@@ -359,6 +376,8 @@ public:
 
     PANEL_SYM_CHOOSER m_SymChooserPanel;
 
+    PANEL_DESIGN_BLOCK_CHOOSER m_DesignBlockChooserPanel;
+
     DIALOG_IMPORT_GRAPHICS m_ImportGraphics;
 
     SELECTION m_Selection;
diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp
index fc20503741..03d1a2534f 100644
--- a/eeschema/files-io.cpp
+++ b/eeschema/files-io.cpp
@@ -614,93 +614,6 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 }
 
 
-bool SCH_EDIT_FRAME::AppendSchematic()
-{
-    SCH_SCREEN* screen = GetScreen();
-
-    wxCHECK( screen, false );
-
-    // open file chooser dialog
-    wxString path = wxPathOnly( Prj().GetProjectFullName() );
-
-    wxFileDialog dlg( this, _( "Insert Schematic" ), path, wxEmptyString,
-                      FILEEXT::KiCadSchematicFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
-
-    if( dlg.ShowModal() == wxID_CANCEL )
-        return false;
-
-    return AddSheetAndUpdateDisplay( dlg.GetPath() );
-}
-
-
-bool SCH_EDIT_FRAME::AddSheetAndUpdateDisplay( const wxString aFullFileName )
-{
-    SCH_COMMIT         commit( m_toolManager );
-    EE_SELECTION_TOOL* selectionTool = m_toolManager->GetTool<EE_SELECTION_TOOL>();
-
-    selectionTool->ClearSelection();
-
-    // Mark all existing items on the screen so we don't select them after appending
-    for( EDA_ITEM* item : GetScreen()->Items() )
-        item->SetFlags( SKIP_STRUCT );
-
-    if( !LoadSheetFromFile( GetCurrentSheet().Last(), &GetCurrentSheet(), aFullFileName ) )
-        return false;
-
-    initScreenZoom();
-    SetSheetNumberAndCount();
-
-    SyncView();
-    OnModify();
-    HardRedraw();   // Full reinit of the current screen and the display.
-    bool selected = false;
-
-    // Select all new items
-    for( EDA_ITEM* item : GetScreen()->Items() )
-    {
-        if( !item->HasFlag( SKIP_STRUCT ) )
-        {
-            commit.Added( item, GetScreen() );
-            selectionTool->AddItemToSel( item, true );
-            selected = true;
-
-            if( item->Type() == SCH_LINE_T )
-                item->SetFlags( STARTPOINT | ENDPOINT );
-        }
-        else
-            item->ClearFlags( SKIP_STRUCT );
-    }
-
-    if( selected )
-        m_toolManager->ProcessEvent( EVENTS::SelectedEvent );
-
-    // Start moving selection, cancel undoes the insertion
-    if( !m_toolManager->RunSynchronousAction( EE_ACTIONS::move, &commit ) )
-        commit.Revert();
-    else
-        commit.Push( _( "Import Schematic Sheet Content..." ) );
-
-    UpdateHierarchyNavigator();
-
-    return true;
-}
-
-
-void SCH_EDIT_FRAME::OnAppendProject( wxCommandEvent& event )
-{
-    if( GetScreen() && GetScreen()->IsModified() )
-    {
-        wxString msg = _( "This operation cannot be undone.\n\n"
-                          "Do you want to save the current document before proceeding?" );
-
-        if( IsOK( this, msg ) )
-            SaveProject();
-    }
-
-    AppendSchematic();
-}
-
-
 void SCH_EDIT_FRAME::OnImportProject( wxCommandEvent& aEvent )
 {
     if( Schematic().RootScreen() && !Schematic().RootScreen()->Items().empty() )
@@ -840,6 +753,19 @@ bool SCH_EDIT_FRAME::saveSchematicFile( SCH_SHEET* aSheet, const wxString& aSave
     // Write through symlinks, don't replace them
     WX_FILENAME::ResolvePossibleSymlinks( schematicFileName );
 
+    if( !schematicFileName.DirExists() )
+    {
+        if( !wxMkdir( schematicFileName.GetPath() ) )
+        {
+            msg.Printf( _( "Error saving schematic file '%s'.\n%s" ),
+                        schematicFileName.GetFullPath(),
+                        "Could not create directory: %s" + schematicFileName.GetPath() );
+            DisplayError( this, msg );
+
+            return false;
+        }
+    }
+
     if( !IsWritable( schematicFileName ) )
         return false;
 
diff --git a/eeschema/menubar.cpp b/eeschema/menubar.cpp
index dbf7f98277..f86e7beb51 100644
--- a/eeschema/menubar.cpp
+++ b/eeschema/menubar.cpp
@@ -94,13 +94,6 @@ void SCH_EDIT_FRAME::doReCreateMenuBar()
 
     fileMenu->AppendSeparator();
 
-    fileMenu->Add( _( "Insert Schematic Sheet Content..." ),
-                   _( "Append schematic sheet content from another project to the current sheet" ),
-                   ID_APPEND_PROJECT,
-                   BITMAPS::add_document );
-
-    fileMenu->AppendSeparator();
-
     // Import submenu
     ACTION_MENU* submenuImport = new ACTION_MENU( false, selTool );
     submenuImport->SetTitle( _( "Import" ) );
@@ -207,6 +200,9 @@ void SCH_EDIT_FRAME::doReCreateMenuBar()
     if( ADVANCED_CFG::GetCfg().m_IncrementalConnectivity )
         viewMenu->Add( EE_ACTIONS::showNetNavigator, ACTION_MENU::CHECK );
 
+    if( ADVANCED_CFG::GetCfg().m_EnableDesignBlocks )
+        viewMenu->Add( ACTIONS::showLibraryTree, ACTION_MENU::CHECK, _( "Show Design Blocks" ) );
+
     viewMenu->AppendSeparator();
     viewMenu->Add( EE_ACTIONS::navigateBack );
     viewMenu->Add( EE_ACTIONS::navigateUp );
@@ -258,6 +254,7 @@ void SCH_EDIT_FRAME::doReCreateMenuBar()
     placeMenu->Add( EE_ACTIONS::drawSheet );
     placeMenu->Add( EE_ACTIONS::placeSheetPin );
     placeMenu->Add( EE_ACTIONS::syncAllSheetsPins );
+    placeMenu->Add( EE_ACTIONS::importSheet );
 
     placeMenu->AppendSeparator();
     placeMenu->Add( EE_ACTIONS::placeSchematicText );
diff --git a/eeschema/sch_base_frame.cpp b/eeschema/sch_base_frame.cpp
index 4481411f15..f56164045b 100644
--- a/eeschema/sch_base_frame.cpp
+++ b/eeschema/sch_base_frame.cpp
@@ -40,8 +40,10 @@
 #include <preview_items/selection_area.h>
 #include <project_sch.h>
 #include <symbol_library.h>
-#include <sch_base_frame.h>
 #include <symbol_lib_table.h>
+#include <sch_base_frame.h>
+#include <design_block.h>
+#include <design_block_lib_table.h>
 #include <tool/action_toolbar.h>
 #include <tool/tool_manager.h>
 #include <tool/tool_dispatcher.h>
@@ -55,6 +57,7 @@
 
 #include <navlib/nl_schematic_plugin.h>
 
+
 LIB_SYMBOL* SchGetLibSymbol( const LIB_ID& aLibId, SYMBOL_LIB_TABLE* aLibTable,
                              SYMBOL_LIB* aCacheLib, wxWindow* aParent, bool aShowErrorMsg )
 {
diff --git a/eeschema/sch_base_frame.h b/eeschema/sch_base_frame.h
index 5fc4a3f6e0..28f10d0dfb 100644
--- a/eeschema/sch_base_frame.h
+++ b/eeschema/sch_base_frame.h
@@ -146,6 +146,7 @@ public:
 
     void UpdateStatusBar() override;
 
+
     /**
      * Call the library viewer to select symbol to import into schematic.
      * If the library viewer is currently running, it is closed and reopened in modal mode.
diff --git a/eeschema/sch_edit_frame.cpp b/eeschema/sch_edit_frame.cpp
index 395894abf6..c635caae1f 100644
--- a/eeschema/sch_edit_frame.cpp
+++ b/eeschema/sch_edit_frame.cpp
@@ -34,6 +34,7 @@
 #include <dialogs/dialog_schematic_find.h>
 #include <dialogs/dialog_book_reporter.h>
 #include <dialogs/dialog_symbol_fields_table.h>
+#include <widgets/design_block_pane.h>
 #include <eeschema_id.h>
 #include <executable_names.h>
 #include <gal/graphics_abstraction_layer.h>
@@ -77,6 +78,7 @@
 #include <tools/ee_grid_helper.h>
 #include <tools/ee_inspection_tool.h>
 #include <tools/ee_point_editor.h>
+#include <tools/sch_design_block_control.h>
 #include <tools/sch_drawing_tools.h>
 #include <tools/sch_edit_tool.h>
 #include <tools/sch_edit_table_tool.h>
@@ -119,7 +121,6 @@ BEGIN_EVENT_TABLE( SCH_EDIT_FRAME, SCH_BASE_FRAME )
     EVT_MENU_RANGE( ID_FILE1, ID_FILEMAX, SCH_EDIT_FRAME::OnLoadFile )
     EVT_MENU( ID_FILE_LIST_CLEAR, SCH_EDIT_FRAME::OnClearFileHistory )
 
-    EVT_MENU( ID_APPEND_PROJECT, SCH_EDIT_FRAME::OnAppendProject )
     EVT_MENU( ID_IMPORT_NON_KICAD_SCH, SCH_EDIT_FRAME::OnImportProject )
 
     EVT_MENU( wxID_EXIT, SCH_EDIT_FRAME::OnExit )
@@ -141,7 +142,8 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
     m_diffSymbolDialog( nullptr ),
     m_symbolFieldsTableDialog( nullptr ),
     m_netNavigator( nullptr ),
-    m_highlightedConnChanged( false )
+    m_highlightedConnChanged( false ),
+    m_designBlocksPane( nullptr )
 {
     m_maximizeByDefault = true;
     m_schematic = new SCHEMATIC( nullptr );
@@ -208,6 +210,7 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
     m_propertiesPanel->SetSplitterProportion( eeconfig()->m_AuiPanels.properties_splitter );
 
     m_selectionFilterPanel = new PANEL_SCH_SELECTION_FILTER( this );
+    m_designBlocksPane = new DESIGN_BLOCK_PANE( this, nullptr, m_designBlockHistoryList );
 
     m_auimgr.SetManagedWindow( this );
 
@@ -236,6 +239,8 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
     m_auimgr.AddPane( m_propertiesPanel, defaultPropertiesPaneInfo( this ) );
     m_auimgr.AddPane( m_selectionFilterPanel, defaultSchSelectionFilterPaneInfo( this ) );
 
+    m_auimgr.AddPane( m_designBlocksPane, defaultDesignBlocksPaneInfo( this ) );
+
     m_auimgr.AddPane( createHighlightedNetNavigator(), defaultNetNavigatorPaneInfo() );
 
     m_auimgr.AddPane( m_optionsToolBar, EDA_PANE().VToolbar().Name( wxS( "OptToolbar" ) )
@@ -274,11 +279,13 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
     wxAuiPaneInfo& netNavigatorPane = m_auimgr.GetPane( NetNavigatorPaneName() );
     wxAuiPaneInfo& propertiesPane = m_auimgr.GetPane( PropertiesPaneName() );
     wxAuiPaneInfo& selectionFilterPane = m_auimgr.GetPane( wxS( "SelectionFilter" ) );
+    wxAuiPaneInfo& designBlocksPane = m_auimgr.GetPane( DesignBlocksPaneName() );
     EESCHEMA_SETTINGS* cfg = eeconfig();
 
     hierarchy_pane.Show( cfg->m_AuiPanels.show_schematic_hierarchy );
     netNavigatorPane.Show( cfg->m_AuiPanels.show_net_nav_panel );
     propertiesPane.Show( cfg->m_AuiPanels.show_properties );
+    designBlocksPane.Show( cfg->m_AuiPanels.design_blocks_show );
     updateSelectionFilterVisbility();
 
     // The selection filter doesn't need to grow in the vertical direction when docked
@@ -326,6 +333,10 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) :
     if( cfg->m_AuiPanels.float_net_nav_panel )
         netNavigatorPane.Float();
 
+    if( cfg->m_AuiPanels.design_blocks_show )
+        SetAuiPaneSize( m_auimgr, designBlocksPane,
+                        cfg->m_AuiPanels.design_blocks_panel_docked_width, -1 );
+
     if( cfg->m_AuiPanels.hierarchy_panel_docked_width > 0 )
     {
         // If the net navigator is not show, let the hierarchy navigator take all of the vertical
@@ -522,6 +533,7 @@ void SCH_EDIT_FRAME::setupTools()
     m_toolManager->RegisterTool( new SCH_EDIT_TOOL );
     m_toolManager->RegisterTool( new SCH_EDIT_TABLE_TOOL );
     m_toolManager->RegisterTool( new EE_INSPECTION_TOOL );
+    m_toolManager->RegisterTool( new SCH_DESIGN_BLOCK_CONTROL );
     m_toolManager->RegisterTool( new SCH_EDITOR_CONTROL );
     m_toolManager->RegisterTool( new SCH_FIND_REPLACE_TOOL );
     m_toolManager->RegisterTool( new EE_POINT_EDITOR );
@@ -577,6 +589,12 @@ void SCH_EDIT_FRAME::setupUIConditions()
                 return m_auimgr.GetPane( NetNavigatorPaneName() ).IsShown();
             };
 
+    auto designBlockCond =
+            [ this ] (const SELECTION& aSel )
+            {
+                return m_auimgr.GetPane( DesignBlocksPaneName() ).IsShown();
+            };
+
     auto undoCond =
             [ this ] (const SELECTION& aSel )
             {
@@ -597,6 +615,7 @@ void SCH_EDIT_FRAME::setupUIConditions()
     mgr->SetConditions( EE_ACTIONS::showHierarchy,    CHECK( hierarchyNavigatorCond ) );
     mgr->SetConditions( EE_ACTIONS::showNetNavigator, CHECK( netNavigatorCond ) );
     mgr->SetConditions( ACTIONS::showProperties,      CHECK( propertiesCond ) );
+    mgr->SetConditions( ACTIONS::showLibraryTree,     CHECK( designBlockCond ) );
     mgr->SetConditions( ACTIONS::toggleGrid,          CHECK( cond.GridVisible() ) );
     mgr->SetConditions( ACTIONS::toggleGridOverrides, CHECK( cond.GridOverrides() ) );
     mgr->SetConditions( ACTIONS::toggleCursorStyle,   CHECK( cond.FullscreenCursor() ) );
@@ -745,12 +764,16 @@ void SCH_EDIT_FRAME::setupUIConditions()
     mgr->SetConditions( EE_ACTIONS::toggleAnnotateAuto,    CHECK( showAnnotateAutomaticallyCond ) );
     mgr->SetConditions( ACTIONS::toggleBoundingBoxes,      CHECK( cond.BoundingBoxes() ) );
 
+    mgr->SetConditions( EE_ACTIONS::saveSheetAsDesignBlock,     ENABLE( hasElements ) );
+    mgr->SetConditions( EE_ACTIONS::saveSelectionAsDesignBlock, ENABLE( SELECTION_CONDITIONS::NotEmpty ) );
+
 #define CURRENT_TOOL( action ) mgr->SetConditions( action, CHECK( cond.CurrentTool( action ) ) )
 
     CURRENT_TOOL( ACTIONS::deleteTool );
     CURRENT_TOOL( EE_ACTIONS::highlightNetTool );
     CURRENT_TOOL( EE_ACTIONS::placeSymbol );
     CURRENT_TOOL( EE_ACTIONS::placePower );
+    CURRENT_TOOL( EE_ACTIONS::placeDesignBlock );
     CURRENT_TOOL( EE_ACTIONS::drawWire );
     CURRENT_TOOL( EE_ACTIONS::drawBus );
     CURRENT_TOOL( EE_ACTIONS::placeBusWireEntry );
@@ -764,6 +787,7 @@ void SCH_EDIT_FRAME::setupUIConditions()
     CURRENT_TOOL( EE_ACTIONS::drawSheet );
     CURRENT_TOOL( EE_ACTIONS::placeSheetPin );
     CURRENT_TOOL( EE_ACTIONS::syncSheetPins );
+    CURRENT_TOOL( EE_ACTIONS::drawSheetCopy );
     CURRENT_TOOL( EE_ACTIONS::drawRectangle );
     CURRENT_TOOL( EE_ACTIONS::drawCircle );
     CURRENT_TOOL( EE_ACTIONS::drawArc );
diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h
index a08204bb70..a139b3d3c8 100644
--- a/eeschema/sch_edit_frame.h
+++ b/eeschema/sch_edit_frame.h
@@ -56,6 +56,8 @@ class SCH_FIELD;
 class SCH_JUNCTION;
 class SCHEMATIC;
 class SCH_COMMIT;
+class DESIGN_BLOCK;
+class DESIGN_BLOCK_PANE;
 class DIALOG_BOOK_REPORTER;
 class DIALOG_ERC;
 class DIALOG_SYMBOL_FIELDS_TABLE;
@@ -262,6 +264,11 @@ public:
      */
     void OnFindDialogClose();
 
+    /**
+     * Design block panel options have changed and the panel needs to be refreshed.
+     */
+    void UpdateDesignBlockOptions();
+
     /**
      * Break a single segment into two at the specified point.
      * @param aCommit Transaction container used to record changes for undo/redo
@@ -484,23 +491,6 @@ public:
 
     wxString GetCurrentFileName() const override;
 
-    /**
-     * Import a KiCad schematic into the current sheet.
-     *
-     * @return True if the schematic was imported properly.
-     */
-    bool AppendSchematic();
-
-    /**
-     * Add a sheet file into the current sheet and updates display
-     *
-     * @note Used in AppendSchematic() and SCH_EDIT_TOOL::ddAppendFile() (so it is public)
-     *
-     * @param aFullFileName Path and name of sheet
-     * @return True if the sheet was properly added
-     */
-    bool AddSheetAndUpdateDisplay( const wxString aFullFileName );
-
     /**
      * Check if any of the screens has unsaved changes and asks the user whether to save or
      * drop them.
@@ -636,11 +626,16 @@ public:
      *                      possible file recursion issues.
      * @param aFileName is the file name to load.  The file name is expected to have an absolute
      *                  path.
-     *
+     * @param aSkipRecursionCheck is true to skip the recursion check. This is used when loading
+     *                  a schematic sheet that is not part of the current project. If we are placing
+     *                  sheet contents instead of a sheet, then we do not need to check for recursion.
+     * @param aSkipLibCheck is true to skip the new/duplicate lib check. This is always triggered when
+     *                  placing design blocks so it is not necessary to check for new/duplicate libs.
      * @return True if the schematic was imported properly.
      */
     bool LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurrentSheet,
-                            const wxString& aFileName );
+                            const wxString& aFileName, bool aSkipRecursionCheck = false,
+                            bool aSkipLibCheck = false );
 
     /**
      * Removes a given junction and heals any wire segments under the junction
@@ -747,6 +742,51 @@ public:
      */
     bool CreateArchiveLibrary( const wxString& aFileName );
 
+    /**
+     * If a library name is given, creates a new design block library in the project folder
+     * with the given name. If no library name is given it prompts user for a library path,
+     * then creates a new design block library at that location.
+     * If library exists, user is warned about that, and is given a chance
+     * to abort the new creation, and in that case existing library is first deleted.
+     *
+     * @param aProposedName is the initial path and filename shown in the file chooser dialog.
+     * @return The newly created library path if library was successfully created, else
+     *         wxEmptyString because user aborted or error.
+     */
+    wxString CreateNewDesignBlockLibrary( const wxString& aLibName = wxEmptyString,
+                                          const wxString& aProposedName = wxEmptyString );
+
+    /**
+     * Add an existing library to either the global or project library table.
+     *
+     * @param aFileName the library to add; a file open dialog will be displayed if empty.
+     * @return true if successfully added.
+     */
+    bool AddDesignBlockLibrary( const wxString& aFilename, DESIGN_BLOCK_LIB_TABLE* aTable );
+
+    void SaveSheetAsDesignBlock( const wxString& aLibraryName );
+
+    void SaveSelectionAsDesignBlock( const wxString& aLibraryName );
+
+    bool DeleteDesignBlockLibrary( const wxString& aLibName, bool aConfirm );
+
+    bool DeleteDesignBlockFromLibrary( const LIB_ID& aLibId, bool aConfirm );
+
+
+    /**
+     * Load design block from design block library table.
+     *
+     * @param aLibId is the design block library identifier to load.
+     * @param aUseCacheLib set to true to fall back to cache library if design block is not found in
+     *                     design block library table.
+     * @param aShowErrorMessage set to true to show any error messages.
+     * @return The design block found in the library or NULL if the design block was not found.
+     */
+    DESIGN_BLOCK* GetDesignBlock( const LIB_ID& aLibId, bool aUseCacheLib = false,
+                                  bool aShowErrorMsg = false );
+
+    DESIGN_BLOCK_PANE* GetDesignBlockPane() const { return m_designBlocksPane; }
+
     /**
      * Plot or print the current sheet to the clipboard.
      */
@@ -840,6 +880,8 @@ public:
 
     void ToggleProperties() override;
 
+    void ToggleLibraryTree() override;
+
     DIALOG_BOOK_REPORTER* GetSymbolDiffDialog();
 
     DIALOG_ERC* GetErcDialog();
@@ -934,6 +976,20 @@ protected:
     void onPluginAvailabilityChanged( wxCommandEvent& aEvt );
 #endif
 
+    /**
+     * Prompts a user to select global or project library tables
+     *
+     * @return Pointer to library table selected or nullptr if none selected/canceled
+     */
+    DESIGN_BLOCK_LIB_TABLE* selectDesignBlockLibTable( bool aOptional = false );
+
+    /**
+     * Create a new library in the given table (presumed to be either the global or project
+     * library table).
+     */
+    wxString createNewDesignBlockLibrary( const wxString& aLibName, const wxString& aProposedName,
+                                          DESIGN_BLOCK_LIB_TABLE* aTable );
+
 private:
     // Called when resizing the Hierarchy Navigator panel
     void OnResizeHierarchyNavigator( wxSizeEvent& aEvent );
@@ -946,7 +1002,6 @@ private:
     void OnExit( wxCommandEvent& event );
 
     void OnLoadFile( wxCommandEvent& event );
-    void OnAppendProject( wxCommandEvent& event );
     void OnImportProject( wxCommandEvent& event );
 
     void OnClearFileHistory( wxCommandEvent& aEvent );
@@ -1048,6 +1103,10 @@ private:
 
     std::vector<wxEvtHandler*> m_schematicChangeListeners;
 
+    std::vector<LIB_ID> m_designBlockHistoryList;
+
+    DESIGN_BLOCK_PANE* m_designBlocksPane;
+
 #ifdef KICAD_IPC_API
     std::unique_ptr<API_HANDLER_SCH> m_apiHandler;
 #endif
diff --git a/eeschema/sch_painter.cpp b/eeschema/sch_painter.cpp
index e3e3aa6f88..6577eabcda 100644
--- a/eeschema/sch_painter.cpp
+++ b/eeschema/sch_painter.cpp
@@ -2212,6 +2212,9 @@ wxString SCH_PAINTER::expandLibItemTextVars( const wxString& aSourceText,
     std::function<bool( wxString* )> symbolResolver =
             [&]( wxString* token ) -> bool
             {
+                if( !m_schematic )
+                    return false;
+
                 return aSymbolContext->ResolveTextVar( &m_schematic->CurrentSheet(), token );
             };
 
@@ -2244,7 +2247,7 @@ void SCH_PAINTER::draw( const SCH_SYMBOL* aSymbol, int aLayer )
         // return;
     }
 
-    int unit = aSymbol->GetUnitSelection( &m_schematic->CurrentSheet() );
+    int unit = m_schematic ? aSymbol->GetUnitSelection( &m_schematic->CurrentSheet() ) : 1;
     int bodyStyle = aSymbol->GetBodyStyle();
 
     // Use dummy symbol if the actual couldn't be found (or couldn't be locked).
diff --git a/eeschema/sheet.cpp b/eeschema/sheet.cpp
index 233509d9b7..3caf1a9a90 100644
--- a/eeschema/sheet.cpp
+++ b/eeschema/sheet.cpp
@@ -164,7 +164,8 @@ void SCH_EDIT_FRAME::InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename
 
 
 bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurrentSheet,
-                                        const wxString& aFileName )
+                                        const wxString& aFileName, bool aSkipRecursionCheck,
+                                        bool aSkipLibCheck )
 {
     wxASSERT( aSheet && aCurrentSheet );
 
@@ -235,20 +236,18 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr
 
     // If the loaded schematic is in a different folder from the current project and
     // it contains hierarchical sheets, the hierarchical sheet paths need to be updated.
-    if( fileName.GetPathWithSep() != Prj().GetProjectPath() && tmpSheet->CountSheets() )
+    //
+    // Additionally, we need to make all backing screens absolute paths be in the current project
+    // path not the source path.
+    if( fileName.GetPathWithSep() != Prj().GetProjectPath() )
     {
         SCH_SHEET_LIST loadedSheets( tmpSheet.get() );
 
         for( const SCH_SHEET_PATH& sheetPath : loadedSheets )
         {
-            // Skip the loaded sheet since the user already determined if the file path should
-            // be relative or absolute.
-            if( sheetPath.size() == 1 )
-                continue;
+            wxString lastSheetPath = Prj().GetProjectPath();
 
-            wxString lastSheetPath = fileName.GetPathWithSep();
-
-            for( unsigned i = 1; i < sheetPath.size(); i++ )
+            for( unsigned i = 0; i < sheetPath.size(); i++ )
             {
                 SCH_SHEET* sheet = sheetPath.at( i );
                 wxCHECK2( sheet, continue );
@@ -256,6 +255,14 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr
                 SCH_SCREEN* screen = sheet->GetScreen();
                 wxCHECK2( screen, continue );
 
+                // Fix screen path to be based on the current project path.
+                // Basically, make an absolute screen path relative to the schematic file
+                // we started with, then make it absolute again using the current project path.
+                wxFileName screenFileName = screen->GetFileName();
+                screenFileName.MakeRelativeTo( fileName.GetPath() );
+                screenFileName.MakeAbsolute( Prj().GetProjectPath() );
+                screen->SetFileName( screenFileName.GetFullPath() );
+
                 // Use the screen file name which should always be absolute.
                 wxFileName loadedSheetFileName = screen->GetFileName();
                 wxCHECK2( loadedSheetFileName.IsAbsolute(), continue );
@@ -268,7 +275,6 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr
                 else
                     sheetFileName = loadedSheetFileName.GetFullPath();
 
-                sheetFileName.Replace( wxT( "\\" ), wxT( "/" ) );
                 sheet->SetFileName( sheetFileName );
                 lastSheetPath = loadedSheetFileName.GetPath();
             }
@@ -279,11 +285,11 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr
     SCH_SHEET_LIST schematicSheets = Schematic().BuildUnorderedSheetList();
 
     // Make sure any new sheet changes do not cause any recursion issues.
-    if( CheckSheetForRecursion( tmpSheet.get(), aCurrentSheet )
-          || checkForNoFullyDefinedLibIds( tmpSheet.get() ) )
-    {
+    if( !aSkipRecursionCheck && CheckSheetForRecursion( tmpSheet.get(), aCurrentSheet ) )
+        return false;
+
+    if( checkForNoFullyDefinedLibIds( tmpSheet.get() ) )
         return false;
-    }
 
     // Make a valiant attempt to warn the user of all possible scenarios where there could
     // be broken symbol library links.
@@ -372,7 +378,7 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr
 
             // If there are any new or duplicate libraries, check to see if it's possible that
             // there could be any missing libraries that would cause broken symbol library links.
-            if( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() )
+            if( !aSkipLibCheck && ( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() ) )
             {
                 if( !symLibTableFn.Exists() || !symLibTableFn.IsFileReadable() )
                 {
@@ -381,8 +387,8 @@ bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aCurr
                              "incorrect symbol library references.\n\n"
                              "Do you wish to continue?" );
                     wxMessageDialog msgDlg4( this, msg, _( "Continue Load Schematic" ),
-                                             wxOK | wxCANCEL | wxCANCEL_DEFAULT |
-                                             wxCENTER | wxICON_QUESTION );
+                                             wxOK | wxCANCEL | wxCANCEL_DEFAULT | wxCENTER
+                                                     | wxICON_QUESTION );
                     msgDlg4.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
 
                     if( msgDlg4.ShowModal() == wxID_CANCEL )
diff --git a/eeschema/symbol_tree_model_adapter.h b/eeschema/symbol_tree_model_adapter.h
index 92f63bd1e5..037f60ce47 100644
--- a/eeschema/symbol_tree_model_adapter.h
+++ b/eeschema/symbol_tree_model_adapter.h
@@ -64,7 +64,7 @@ protected:
      */
     SYMBOL_TREE_MODEL_ADAPTER( EDA_BASE_FRAME* aParent, LIB_TABLE* aLibs );
 
-    bool isSymbolModel() override { return true; }
+    PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::SYMBOL_LIB; }
 
 private:
     friend class SYMBOL_ASYNC_LOADER;
diff --git a/eeschema/symbol_tree_synchronizing_adapter.h b/eeschema/symbol_tree_synchronizing_adapter.h
index 72313d574e..3884fe8aac 100644
--- a/eeschema/symbol_tree_synchronizing_adapter.h
+++ b/eeschema/symbol_tree_synchronizing_adapter.h
@@ -66,7 +66,7 @@ protected:
     SYMBOL_TREE_SYNCHRONIZING_ADAPTER( SYMBOL_EDIT_FRAME* aParent,
                                        SYMBOL_LIBRARY_MANAGER* aLibMgr );
 
-    bool isSymbolModel() override { return true; }
+    PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::SYMBOL_LIB; }
 
 protected:
     SYMBOL_EDIT_FRAME*      m_frame;
diff --git a/eeschema/toolbars_sch_editor.cpp b/eeschema/toolbars_sch_editor.cpp
index 9220424bb6..e83919f161 100644
--- a/eeschema/toolbars_sch_editor.cpp
+++ b/eeschema/toolbars_sch_editor.cpp
@@ -37,6 +37,7 @@
 #include <tool/action_toolbar.h>
 #include <tools/ee_actions.h>
 #include <tools/ee_selection_tool.h>
+#include <widgets/design_block_pane.h>
 #include <widgets/hierarchy_pane.h>
 #include <widgets/wx_aui_utils.h>
 #include <widgets/sch_properties_panel.h>
@@ -355,3 +356,45 @@ void SCH_EDIT_FRAME::ToggleSchematicHierarchy()
         m_auimgr.Update();
     }
 }
+
+
+void SCH_EDIT_FRAME::ToggleLibraryTree()
+{
+    EESCHEMA_SETTINGS* cfg = eeconfig();
+
+    wxCHECK( cfg, /* void */ );
+
+    wxAuiPaneInfo& db_library_pane = m_auimgr.GetPane( DesignBlocksPaneName() );
+
+    db_library_pane.Show( !db_library_pane.IsShown() );
+
+    if( db_library_pane.IsShown() )
+    {
+        if( db_library_pane.IsFloating() )
+        {
+            db_library_pane.FloatingSize( cfg->m_AuiPanels.design_blocks_panel_float_width,
+                                          cfg->m_AuiPanels.design_blocks_panel_float_height );
+            m_auimgr.Update();
+        }
+        else if( cfg->m_AuiPanels.design_blocks_panel_docked_width > 0 )
+        {
+            // SetAuiPaneSize also updates m_auimgr
+            SetAuiPaneSize( m_auimgr, db_library_pane,
+                            cfg->m_AuiPanels.design_blocks_panel_docked_width, -1 );
+        }
+    }
+    else
+    {
+        if( db_library_pane.IsFloating() )
+        {
+            cfg->m_AuiPanels.design_blocks_panel_float_width  = db_library_pane.floating_size.x;
+            cfg->m_AuiPanels.design_blocks_panel_float_height = db_library_pane.floating_size.y;
+        }
+        else
+        {
+            cfg->m_AuiPanels.design_blocks_panel_docked_width = m_designBlocksPane->GetSize().x;
+        }
+
+        m_auimgr.Update();
+    }
+}
diff --git a/eeschema/tools/ee_actions.cpp b/eeschema/tools/ee_actions.cpp
index d3adc6828f..b2e50bb01f 100644
--- a/eeschema/tools/ee_actions.cpp
+++ b/eeschema/tools/ee_actions.cpp
@@ -149,6 +149,28 @@ TOOL_ACTION EE_ACTIONS::syncSelection( TOOL_ACTION_ARGS()
         .Name( "eeschema.InteractiveSelection.SyncSelection" )
         .Scope( AS_GLOBAL ) );
 
+// SCH_DESIGN_BLOCK_CONTROL
+TOOL_ACTION EE_ACTIONS::saveSheetAsDesignBlock( TOOL_ACTION_ARGS()
+        .Name( "eeschema.SchDesignBlockControl.saveSheetAsDesignBlock" )
+        .Scope( AS_GLOBAL )
+        .FriendlyName( _( "Save Sheet as Design Block..." ) )
+        .Tooltip( _( "Create a new design block from the current sheet" ) )
+        .Icon( BITMAPS::new_component ) );
+
+TOOL_ACTION EE_ACTIONS::saveSelectionAsDesignBlock( TOOL_ACTION_ARGS()
+        .Name( "eeschema.SchDesignBlockControl.saveSelectionAsDesignBlock" )
+        .Scope( AS_GLOBAL )
+        .FriendlyName( _( "Save Selection as Design Block..." ) )
+        .Tooltip( _( "Create a new design block from the current selection" ) )
+        .Icon( BITMAPS::new_component ) );
+
+TOOL_ACTION EE_ACTIONS::deleteDesignBlock( TOOL_ACTION_ARGS()
+        .Name( "eeschema.SchDesignBlockControl.saveDeleteDesignBlock" )
+        .Scope( AS_GLOBAL )
+        .FriendlyName( _( "Delete Design Block" ) )
+        .Tooltip( _( "Remove the selected design block from its library" ) )
+        .Icon( BITMAPS::trash ) );
+
 // SYMBOL_EDITOR_CONTROL
 //
 TOOL_ACTION EE_ACTIONS::saveLibraryAs( TOOL_ACTION_ARGS()
@@ -399,6 +421,17 @@ TOOL_ACTION EE_ACTIONS::placePower( TOOL_ACTION_ARGS()
         .Flags( AF_ACTIVATE )
         .Parameter<SCH_SYMBOL*>( nullptr ) );
 
+TOOL_ACTION EE_ACTIONS::placeDesignBlock( TOOL_ACTION_ARGS()
+        .Name( "eeschema.InteractiveDrawing.placeDesignBlock" )
+        .Scope( AS_GLOBAL )
+        .DefaultHotkey( MD_SHIFT + 'B' )
+        .FriendlyName( _( "Place Design Block" ) )
+        .Tooltip( _( "Add selected design block to current sheet" ) )
+        .Icon( BITMAPS::add_component )
+        .Flags( AF_ACTIVATE )
+        .Parameter<DESIGN_BLOCK*>( nullptr ) );
+
+
 TOOL_ACTION EE_ACTIONS::placeNoConnect( TOOL_ACTION_ARGS()
         .Name( "eeschema.InteractiveDrawing.placeNoConnect" )
         .Scope( AS_GLOBAL )
@@ -464,6 +497,15 @@ TOOL_ACTION EE_ACTIONS::drawSheet( TOOL_ACTION_ARGS()
         .Flags( AF_ACTIVATE )
         .Parameter( SCH_SHEET_T ) );
 
+TOOL_ACTION EE_ACTIONS::drawSheetCopy( TOOL_ACTION_ARGS()
+        .Name( "eeschema.InteractiveDrawing.drawSheetCopy" )
+        .Scope( AS_GLOBAL )
+        .FriendlyName( _( "Draw Sheet Copy" ) )
+        .Tooltip( _( "Copy sheet into project and draw on current sheet" ) )
+        .Icon( BITMAPS::add_hierarchical_subsheet )
+        .Flags( AF_ACTIVATE )
+        .Parameter<wxString*> ( nullptr ) );
+
 TOOL_ACTION EE_ACTIONS::placeSheetPin( TOOL_ACTION_ARGS()
         .Name( "eeschema.InteractiveDrawing.placeSheetPin" )
         .Scope( AS_GLOBAL )
@@ -487,6 +529,15 @@ TOOL_ACTION EE_ACTIONS::syncAllSheetsPins( TOOL_ACTION_ARGS()
         .Icon( BITMAPS::import_hierarchical_label )
         .Flags( AF_ACTIVATE ) );
 
+TOOL_ACTION EE_ACTIONS::importSheet( TOOL_ACTION_ARGS()
+        .Name( "eeschema.InteractiveDrawing.importSheet" )
+        .Scope( AS_GLOBAL )
+        .FriendlyName( _( "Import Sheet" ) )
+        .Tooltip( _( "Import sheet into project" ) )
+        .Icon( BITMAPS::add_hierarchical_subsheet )
+        .Flags( AF_ACTIVATE )
+        .Parameter<wxString*> ( nullptr ) );
+
 TOOL_ACTION EE_ACTIONS::placeGlobalLabel( TOOL_ACTION_ARGS()
         .Name( "eeschema.InteractiveDrawing.placeGlobalLabel" )
         .Scope( AS_GLOBAL )
diff --git a/eeschema/tools/ee_actions.h b/eeschema/tools/ee_actions.h
index 0ba2d4a6e1..3a256cc716 100644
--- a/eeschema/tools/ee_actions.h
+++ b/eeschema/tools/ee_actions.h
@@ -78,6 +78,7 @@ public:
     static TOOL_ACTION pickerTool;
     static TOOL_ACTION placeSymbol;
     static TOOL_ACTION placePower;
+    static TOOL_ACTION placeDesignBlock;
     static TOOL_ACTION drawWire;
     static TOOL_ACTION drawBus;
     static TOOL_ACTION unfoldBus;
@@ -89,7 +90,9 @@ public:
     static TOOL_ACTION placeGlobalLabel;
     static TOOL_ACTION placeHierLabel;
     static TOOL_ACTION drawSheet;
+    static TOOL_ACTION drawSheetCopy;
     static TOOL_ACTION placeSheetPin;
+    static TOOL_ACTION importSheet;
     // Sync sheet pins for selected sheet symbol
     static TOOL_ACTION syncSheetPins;
     // Sync sheet pins for all sheet symbols
@@ -197,6 +200,11 @@ public:
     static TOOL_ACTION unsetDNP;
     static TOOL_ACTION toggleDNP;
 
+    // Design Block management
+    static TOOL_ACTION saveSheetAsDesignBlock;
+    static TOOL_ACTION saveSelectionAsDesignBlock;
+    static TOOL_ACTION deleteDesignBlock;
+
     // Library management
     static TOOL_ACTION saveLibraryAs;
     static TOOL_ACTION saveSymbolCopyAs;
diff --git a/eeschema/tools/sch_design_block_control.cpp b/eeschema/tools/sch_design_block_control.cpp
new file mode 100644
index 0000000000..af02729c93
--- /dev/null
+++ b/eeschema/tools/sch_design_block_control.cpp
@@ -0,0 +1,204 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2019-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+#include <tool/library_editor_control.h>
+#include <wildcards_and_files_ext.h>
+#include <bitmaps/bitmap_types.h>
+#include <confirm.h>
+#include <kidialog.h>
+#include <gestfich.h> // To open with a text editor
+#include <wx/filedlg.h>
+#include <sch_design_block_control.h>
+#include <design_block_pane.h>
+#include <panel_design_block_chooser.h>
+#include <ee_actions.h>
+
+bool SCH_DESIGN_BLOCK_CONTROL::Init()
+{
+    m_editFrame     = getEditFrame<SCH_EDIT_FRAME>();
+    m_frame         = m_editFrame;
+    m_selectionTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
+
+    auto pinnedLib =
+            [this]( const SELECTION& aSel )
+            {
+                //
+                LIB_TREE_NODE* current = getCurrentTreeNode();
+                return current && current->m_Type == LIB_TREE_NODE::TYPE::LIBRARY
+                       && current->m_Pinned;
+            };
+    auto unpinnedLib =
+            [this](const SELECTION& aSel )
+            {
+                LIB_TREE_NODE* current = getCurrentTreeNode();
+                return current && current->m_Type == LIB_TREE_NODE::TYPE::LIBRARY
+                       && !current->m_Pinned;
+            };
+
+    auto isInLibrary =
+            [this](const SELECTION& aSel )
+            {
+                LIB_TREE_NODE* current = getCurrentTreeNode();
+                return current
+                       && ( current->m_Type == LIB_TREE_NODE::TYPE::LIBRARY
+                            || current->m_Type == LIB_TREE_NODE::TYPE::ITEM );
+            };
+
+    auto isDesignBlock =
+            [this](const SELECTION& aSel )
+            {
+                LIB_TREE_NODE* current = getCurrentTreeNode();
+                return current && current->m_Type == LIB_TREE_NODE::TYPE::ITEM;
+            };
+
+    CONDITIONAL_MENU& ctxMenu = m_menu->GetMenu();
+
+    ctxMenu.AddItem( EE_ACTIONS::placeDesignBlock, isDesignBlock, 1);
+    ctxMenu.AddItem( ACTIONS::pinLibrary, unpinnedLib, 1 );
+    ctxMenu.AddItem( ACTIONS::unpinLibrary, pinnedLib, 1 );
+    ctxMenu.AddSeparator( 1 );
+
+    ctxMenu.AddItem( ACTIONS::newLibrary,                       SELECTION_CONDITIONS::ShowAlways, 10 );
+    ctxMenu.AddItem( EE_ACTIONS::saveSheetAsDesignBlock,        isInLibrary, 10 );
+    ctxMenu.AddItem( EE_ACTIONS::saveSelectionAsDesignBlock,    isInLibrary, 10 );
+    ctxMenu.AddItem( EE_ACTIONS::deleteDesignBlock,             isDesignBlock, 10 );
+
+    ctxMenu.AddSeparator( 400 );
+    ctxMenu.AddItem( ACTIONS::hideLibraryTree, SELECTION_CONDITIONS::ShowAlways, 400 );
+
+    return true;
+}
+
+
+int SCH_DESIGN_BLOCK_CONTROL::PinLibrary( const TOOL_EVENT& aEvent )
+{
+    LIB_TREE_NODE* current = getCurrentTreeNode();
+
+    if( current && !current->m_Pinned )
+    {
+        m_frame->Prj().PinLibrary( current->m_LibId.GetLibNickname(),
+                                   PROJECT::LIB_TYPE_T::DESIGN_BLOCK_LIB );
+        current->m_Pinned = true;
+        getDesignBlockPane()->RefreshLibs();
+    }
+
+    return 0;
+}
+
+
+int SCH_DESIGN_BLOCK_CONTROL::UnpinLibrary( const TOOL_EVENT& aEvent )
+{
+    LIB_TREE_NODE* current = getCurrentTreeNode();
+
+    if( current && current->m_Pinned )
+    {
+        m_frame->Prj().UnpinLibrary( current->m_LibId.GetLibNickname(),
+                                     PROJECT::LIB_TYPE_T::DESIGN_BLOCK_LIB );
+        current->m_Pinned = false;
+        getDesignBlockPane()->RefreshLibs();
+    }
+
+    return 0;
+}
+
+
+int SCH_DESIGN_BLOCK_CONTROL::NewLibrary( const TOOL_EVENT& aEvent )
+{
+    m_editFrame->CreateNewDesignBlockLibrary();
+    return 0;
+}
+
+
+int SCH_DESIGN_BLOCK_CONTROL::SaveSheetAsDesignBlock( const TOOL_EVENT& aEvent )
+{
+    LIB_TREE_NODE* current = getCurrentTreeNode();
+
+    if( current )
+        m_editFrame->SaveSheetAsDesignBlock( current->m_LibId.GetLibNickname() );
+
+    return 0;
+}
+
+
+int SCH_DESIGN_BLOCK_CONTROL::SaveSelectionAsDesignBlock( const TOOL_EVENT& aEvent )
+{
+    LIB_TREE_NODE* current = getCurrentTreeNode();
+
+    if( current )
+        m_editFrame->SaveSelectionAsDesignBlock( current->m_LibId.GetLibNickname() );
+
+    return 0;
+}
+
+
+int SCH_DESIGN_BLOCK_CONTROL::DeleteDesignBlock( const TOOL_EVENT& aEvent )
+{
+    LIB_TREE_NODE* current = getCurrentTreeNode();
+
+    if( current )
+        m_editFrame->DeleteDesignBlockFromLibrary( current->m_LibId, true );
+
+    return 0;
+}
+
+
+int SCH_DESIGN_BLOCK_CONTROL::HideLibraryTree( const TOOL_EVENT& aEvent )
+{
+    m_editFrame->ToggleLibraryTree();
+    return 0;
+}
+
+
+void SCH_DESIGN_BLOCK_CONTROL::setTransitions()
+{
+    Go( &SCH_DESIGN_BLOCK_CONTROL::PinLibrary,                  ACTIONS::pinLibrary.MakeEvent() );
+    Go( &SCH_DESIGN_BLOCK_CONTROL::UnpinLibrary,                ACTIONS::unpinLibrary.MakeEvent() );
+
+    Go( &SCH_DESIGN_BLOCK_CONTROL::NewLibrary,                  ACTIONS::newLibrary.MakeEvent() );
+
+    Go( &SCH_DESIGN_BLOCK_CONTROL::SaveSheetAsDesignBlock,      EE_ACTIONS::saveSheetAsDesignBlock.MakeEvent() );
+    Go( &SCH_DESIGN_BLOCK_CONTROL::SaveSelectionAsDesignBlock,  EE_ACTIONS::saveSelectionAsDesignBlock.MakeEvent() );
+    Go( &SCH_DESIGN_BLOCK_CONTROL::DeleteDesignBlock,           EE_ACTIONS::deleteDesignBlock.MakeEvent() );
+
+    Go( &SCH_DESIGN_BLOCK_CONTROL::HideLibraryTree,             ACTIONS::hideLibraryTree.MakeEvent() );
+}
+
+
+LIB_ID SCH_DESIGN_BLOCK_CONTROL::getSelectedLibId()
+{
+    m_editFrame->GetDesignBlockPane()->GetSelectedLibId();
+
+    return LIB_ID();
+}
+
+
+DESIGN_BLOCK_PANE* SCH_DESIGN_BLOCK_CONTROL::getDesignBlockPane()
+{
+    return m_editFrame->GetDesignBlockPane();
+}
+
+
+LIB_TREE_NODE* SCH_DESIGN_BLOCK_CONTROL::getCurrentTreeNode()
+{
+    LIB_TREE* libTree = getDesignBlockPane()->GetDesignBlockPanel()->GetLibTree();
+    return libTree ? libTree->GetCurrentTreeNode() : nullptr;
+}
diff --git a/eeschema/tools/sch_design_block_control.h b/eeschema/tools/sch_design_block_control.h
new file mode 100644
index 0000000000..1191b253ca
--- /dev/null
+++ b/eeschema/tools/sch_design_block_control.h
@@ -0,0 +1,66 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2019-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+#ifndef SCH_DESIGN_BLOCK_CONTROL_H
+#define SCH_DESIGN_BLOCK_CONTROL_H
+
+#include <sch_base_frame.h>
+#include <tools/ee_tool_base.h>
+
+/**
+ * Handle schematic design block actions in the schematic editor.
+ */
+class SCH_DESIGN_BLOCK_CONTROL : public wxEvtHandler, public EE_TOOL_BASE<SCH_BASE_FRAME>
+{
+public:
+    SCH_DESIGN_BLOCK_CONTROL() : EE_TOOL_BASE<SCH_BASE_FRAME>( "eeschema.SchDesignBlockControl" ) {}
+
+    /// @copydoc TOOL_INTERACTIVE::Init()
+    bool Init() override;
+
+    int PinLibrary( const TOOL_EVENT& aEvent );
+    int UnpinLibrary( const TOOL_EVENT& aEvent );
+
+    int NewLibrary( const TOOL_EVENT& aEvent );
+    int DeleteLibrary( const TOOL_EVENT& aEvent );
+
+    int SaveSheetAsDesignBlock( const TOOL_EVENT& aEvent );
+    int SaveSelectionAsDesignBlock( const TOOL_EVENT& aEvent );
+    int DeleteDesignBlock( const TOOL_EVENT& aEvent );
+
+    int HideLibraryTree( const TOOL_EVENT& aEvent );
+
+private:
+    LIB_ID getSelectedLibId();
+    ///< Set up handlers for various events.
+    void setTransitions() override;
+
+    DESIGN_BLOCK_PANE* getDesignBlockPane();
+    LIB_TREE_NODE*     getCurrentTreeNode();
+
+    SCH_EDIT_FRAME*    m_editFrame = nullptr;
+};
+
+
+#endif
diff --git a/eeschema/tools/sch_drawing_tools.cpp b/eeschema/tools/sch_drawing_tools.cpp
index d6756c8a3f..6725b3fba9 100644
--- a/eeschema/tools/sch_drawing_tools.cpp
+++ b/eeschema/tools/sch_drawing_tools.cpp
@@ -34,9 +34,12 @@
 #include <tools/ee_grid_helper.h>
 #include <tools/rule_area_create_helper.h>
 #include <gal/graphics_abstraction_layer.h>
+#include <design_block_lib_table.h>
 #include <ee_actions.h>
 #include <sch_edit_frame.h>
 #include <pgm_base.h>
+#include <design_block.h>
+#include <design_block_pane.h>
 #include <eeschema_id.h>
 #include <confirm.h>
 #include <view/view_controls.h>
@@ -541,6 +544,268 @@ int SCH_DRAWING_TOOLS::PlaceSymbol( const TOOL_EVENT& aEvent )
 }
 
 
+int SCH_DRAWING_TOOLS::ImportSheet( const TOOL_EVENT& aEvent )
+{
+    bool          placingDesignBlock = aEvent.IsAction( &EE_ACTIONS::placeDesignBlock );
+    DESIGN_BLOCK* designBlock =
+            placingDesignBlock && m_frame->GetDesignBlockPane()->GetSelectedLibId().IsValid()
+                    ? m_frame->GetDesignBlock( m_frame->GetDesignBlockPane()->GetSelectedLibId() )
+                    : nullptr;
+    wxString*     importSourceFile = !placingDesignBlock ? aEvent.Parameter<wxString*>() : nullptr;
+    wxString      sheetFileName = wxEmptyString;
+
+    if( !placingDesignBlock )
+    {
+        if( importSourceFile != nullptr )
+            sheetFileName = *importSourceFile;
+    }
+    else if( designBlock )
+        sheetFileName = designBlock->GetSchematicFile();
+
+    COMMON_SETTINGS*            common_settings = Pgm().GetCommonSettings();
+    EESCHEMA_SETTINGS*          cfg = m_frame->eeconfig();
+    SCHEMATIC_SETTINGS&         schSettings = m_frame->Schematic().Settings();
+    SCH_SCREEN*                 screen = m_frame->GetScreen();
+    SCH_SHEET_PATH&             sheetPath = m_frame->GetCurrentSheet();
+
+    KIGFX::VIEW_CONTROLS* controls = getViewControls();
+    EE_GRID_HELPER        grid( m_toolMgr );
+    VECTOR2I              cursorPos;
+
+    if( !cfg || !common_settings )
+        return 0;
+
+    if( m_inDrawingTool )
+        return 0;
+
+    auto setCursor =
+            [&]()
+            {
+                m_frame->GetCanvas()->SetCurrentCursor( designBlock ? KICURSOR::MOVING
+                                                               : KICURSOR::COMPONENT );
+            };
+
+    auto placeSheetContents =
+            [&]()
+            {
+                SCH_COMMIT         commit( m_toolMgr );
+                EE_SELECTION_TOOL* selectionTool = m_toolMgr->GetTool<EE_SELECTION_TOOL>();
+                EDA_ITEMS          newItems;
+                bool               keepAnnotations = cfg->m_DesignBlockChooserPanel.keep_annotations;
+
+                selectionTool->ClearSelection();
+
+                // Mark all existing items on the screen so we don't select them after appending
+                for( EDA_ITEM* item : screen->Items() )
+                    item->SetFlags( SKIP_STRUCT );
+
+                if( !m_frame->LoadSheetFromFile( sheetPath.Last(), &sheetPath, sheetFileName, true,
+                                                 placingDesignBlock ) )
+                    return false;
+
+                m_frame->SetSheetNumberAndCount();
+
+                m_frame->SyncView();
+                m_frame->OnModify();
+                m_frame->HardRedraw(); // Full reinit of the current screen and the display.
+
+                // Select all new items
+                for( EDA_ITEM* item : screen->Items() )
+                {
+                    if( !item->HasFlag( SKIP_STRUCT ) )
+                    {
+                        if( item->Type() == SCH_SYMBOL_T && !keepAnnotations )
+                            static_cast<SCH_SYMBOL*>( item )->ClearAnnotation( &sheetPath, false );
+
+                        if( item->Type() == SCH_LINE_T )
+                            item->SetFlags( STARTPOINT | ENDPOINT );
+
+                        commit.Added( item, screen );
+                        newItems.emplace_back( item );
+                    }
+                    else
+                        item->ClearFlags( SKIP_STRUCT );
+                }
+
+                selectionTool->AddItemsToSel( &newItems, true );
+
+                cursorPos = grid.Align( controls->GetMousePosition(),
+                                        grid.GetSelectionGrid( selectionTool->GetSelection() ) );
+                controls->ForceCursorPosition( true, cursorPos );
+
+                // Move everything to our current mouse position now
+                // that we have a selection to get a reference point
+                VECTOR2I anchorPos = selectionTool->GetSelection().GetReferencePoint();
+                VECTOR2I delta = cursorPos - anchorPos;
+
+                // Will all be SCH_ITEMs as these were pulled from the screen->Items()
+                for( EDA_ITEM* item : newItems )
+                    static_cast<SCH_ITEM*>( item )->Move( delta );
+
+                if( !keepAnnotations )
+                {
+                    EESCHEMA_SETTINGS::PANEL_ANNOTATE& annotate = cfg->m_AnnotatePanel;
+
+                    if( annotate.automatic )
+                    {
+                        NULL_REPORTER reporter;
+                        m_frame->AnnotateSymbols( &commit, ANNOTATE_SELECTION,
+                                                  (ANNOTATE_ORDER_T) annotate.sort_order,
+                                                  (ANNOTATE_ALGO_T) annotate.method, true /* recursive */,
+                                                  schSettings.m_AnnotateStartNum, false, false, reporter );
+                    }
+
+                    // Annotation will clear selection, so we need to restore it
+                    for( EDA_ITEM* item : newItems )
+                    {
+                        if( item->Type() == SCH_LINE_T )
+                            item->SetFlags( STARTPOINT | ENDPOINT );
+                    }
+
+                    selectionTool->AddItemsToSel( &newItems, true );
+                }
+
+                // Start moving selection, cancel undoes the insertion
+                bool placed = m_toolMgr->RunSynchronousAction( EE_ACTIONS::move, &commit );
+
+                // Update our cursor position to the new location in case we're placing repeated copies
+                cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE );
+
+                if( placed )
+                    commit.Push( placingDesignBlock ? _( "Add design block" )
+                                                    : _( "Import Schematic Sheet Content..." ) );
+                else
+                    commit.Revert();
+
+                m_frame->UpdateHierarchyNavigator();
+
+                return placed;
+            };
+
+    // Whether we are placing the sheet as a sheet, or as its contents, we need to get a filename
+    // if we weren't provided one
+    if( sheetFileName.IsEmpty() )
+    {
+            wxString path;
+            wxString file;
+
+            if (!placingDesignBlock) {
+
+                if( sheetFileName.IsEmpty() )
+                {
+                    path = wxPathOnly( m_frame->Prj().GetProjectFullName() );
+                    file = wxEmptyString;
+                }
+                else
+                {
+                    path = wxPathOnly( sheetFileName );
+                    file = wxFileName( sheetFileName ).GetFullName();
+                }
+
+                // Open file chooser dialog even if we have been provided a file so the user
+                // can select the options they want
+                wxFileDialog dlg( m_frame, _( "Choose Schematic" ), path, file,
+                                  FILEEXT::KiCadSchematicFileWildcard(),
+                                  wxFD_OPEN | wxFD_FILE_MUST_EXIST );
+
+                FILEDLG_IMPORT_SHEET_CONTENTS dlgHook( cfg );
+                dlg.SetCustomizeHook( dlgHook );
+
+                if( dlg.ShowModal() == wxID_CANCEL )
+                    return 0;
+
+                sheetFileName = dlg.GetPath();
+
+                m_frame->UpdateDesignBlockOptions();
+            }
+
+            if( sheetFileName.IsEmpty() )
+                return 0;
+    }
+
+    // If we're placing sheet contents, we don't even want to run our tool loop, just add the items
+    // to the canvas and run the move tool
+    if( !cfg->m_DesignBlockChooserPanel.place_as_sheet )
+    {
+            while( placeSheetContents() && cfg->m_DesignBlockChooserPanel.repeated_placement )
+                ;
+
+            m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
+            m_view->ClearPreview();
+            delete designBlock;
+            designBlock = nullptr;
+
+            return 0;
+    }
+
+    // We're placing a sheet as a sheet, we need to run a small tool loop to get the starting
+    // coordinate of the sheet drawing
+    m_frame->PushTool( aEvent );
+
+    Activate();
+
+    // Must be done after Activate() so that it gets set into the correct context
+    getViewControls()->ShowCursor( true );
+
+    // Set initial cursor
+    setCursor();
+
+    if( common_settings->m_Input.immediate_actions && !aEvent.IsReactivate() )
+    {
+        m_toolMgr->PrimeTool( { 0, 0 } );
+    }
+
+    // Main loop: keep receiving events
+    while( TOOL_EVENT* evt = Wait() )
+    {
+        setCursor();
+        grid.SetSnap( !evt->Modifier( MD_SHIFT ) );
+        grid.SetUseGrid( getView()->GetGAL()->GetGridSnapping() && !evt->DisableGridSnapping() );
+
+        cursorPos = grid.Align( controls->GetMousePosition(), GRID_HELPER_GRIDS::GRID_CONNECTABLE );
+        controls->ForceCursorPosition( true, cursorPos );
+
+        // The tool hotkey is interpreted as a click when drawing
+        bool isSyntheticClick = designBlock && evt->IsActivate() && evt->HasPosition()
+                                && evt->Matches( aEvent );
+
+        if( evt->IsCancelInteractive() || ( designBlock && evt->IsAction( &ACTIONS::undo ) ) )
+        {
+            m_frame->GetInfoBar()->Dismiss();
+            break;
+        }
+        else if( evt->IsClick( BUT_LEFT ) || evt->IsDblClick( BUT_LEFT ) || isSyntheticClick )
+        {
+            wxString* tempFileName = new wxString( sheetFileName );
+            m_toolMgr->PostAction( EE_ACTIONS::drawSheetCopy, tempFileName );
+            break;
+        }
+        else if( evt->IsClick( BUT_RIGHT ) )
+        {
+            // Warp after context menu only if dragging...
+            if( !designBlock )
+                m_toolMgr->VetoContextMenuMouseWarp();
+
+            m_menu->ShowContextMenu( m_selectionTool->GetSelection() );
+        }
+        else if( evt->IsAction( &ACTIONS::duplicate )
+                 || evt->IsAction( &EE_ACTIONS::repeatDrawItem ) )
+        {
+            wxBell();
+        }
+        else
+        {
+            evt->SetPassEvent();
+        }
+    }
+
+    m_frame->PopTool( aEvent );
+    m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
+
+    return 0;
+}
+
+
 int SCH_DRAWING_TOOLS::PlaceImage( const TOOL_EVENT& aEvent )
 {
     SCH_BITMAP*      image = aEvent.Parameter<SCH_BITMAP*>();
@@ -707,7 +972,7 @@ int SCH_DRAWING_TOOLS::PlaceImage( const TOOL_EVENT& aEvent )
 
                 if( !image || !image->ReadImageFile( fullFilename ) )
                 {
-                    wxMessageBox( _( "Could not load image from '%s'." ), fullFilename );
+                    wxMessageBox( wxString::Format( _( "Could not load image from '%s'." ), fullFilename ) );
                     delete image;
                     image = nullptr;
                     continue;
@@ -2452,7 +2717,12 @@ int SCH_DRAWING_TOOLS::DrawTable( const TOOL_EVENT& aEvent )
 
 int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent )
 {
-    SCH_SHEET* sheet = nullptr;
+    bool       isDrawSheetCopy   = aEvent.IsAction( &EE_ACTIONS::drawSheetCopy );
+    wxString*  filename          = isDrawSheetCopy ? aEvent.Parameter<wxString*>() : nullptr;
+    SCH_SHEET* sheet             = nullptr;
+
+    // Make sure we've been passed a filename if we're importing a sheet
+    wxCHECK( !isDrawSheetCopy || filename, 0 );
 
     if( m_inDrawingTool )
         return 0;
@@ -2490,7 +2760,7 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent )
     // Set initial cursor
     setCursor();
 
-    if( aEvent.HasPosition() )
+    if( aEvent.HasPosition() && !isDrawSheetCopy )
         m_toolMgr->PrimeTool( aEvent.Position() );
 
     // Main loop: keep receiving events
@@ -2575,16 +2845,57 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent )
 
             m_toolMgr->RunAction( EE_ACTIONS::clearSelection );
 
-            sheet = new SCH_SHEET( m_frame->GetCurrentSheet().Last(), cursorPos );
+            if( isDrawSheetCopy )
+            {
+                if( !wxFileExists( *filename ) )
+                {
+                    wxMessageBox( wxString::Format( _( "File '%s' does not exist." ), *filename ) );
+                    m_frame->PopTool( aEvent );
+                    break;
+                }
+
+                sheet = new SCH_SHEET( m_frame->GetCurrentSheet().Last(), cursorPos );
+
+                if( !m_frame->LoadSheetFromFile( sheet, &m_frame->GetCurrentSheet(), *filename ) )
+                {
+                    wxMessageBox( wxString::Format( _( "Could not import sheet from '%s'." ), *filename ) );
+                    delete sheet;
+                    sheet = nullptr;
+                    m_frame->PopTool( aEvent );
+                    break;
+                }
+
+                wxFileName fn( *filename );
+
+                sheet->GetFields()[SHEETNAME].SetText( wxT( "Imported Sheet" ) );
+                sheet->GetFields()[SHEETFILENAME].SetText( fn.GetName() + wxT( "." )
+                                                           + FILEEXT::KiCadSchematicFileExtension );
+            }
+            else
+            {
+                sheet = new SCH_SHEET( m_frame->GetCurrentSheet().Last(), cursorPos );
+                sheet->SetScreen( nullptr );
+                sheet->GetFields()[SHEETNAME].SetText( wxT( "Untitled Sheet" ) );
+                sheet->GetFields()[SHEETFILENAME].SetText( wxT( "untitled." )
+                                                           + FILEEXT::KiCadSchematicFileExtension );
+            }
+
             sheet->SetFlags( IS_NEW | IS_MOVING );
-            sheet->SetScreen( nullptr );
             sheet->SetBorderWidth( schIUScale.MilsToIU( cfg->m_Drawing.default_line_thickness ) );
             sheet->SetBorderColor( cfg->m_Drawing.default_sheet_border_color );
             sheet->SetBackgroundColor( cfg->m_Drawing.default_sheet_background_color );
-            sheet->GetFields()[ SHEETNAME ].SetText( "Untitled Sheet" );
-            sheet->GetFields()[ SHEETFILENAME ].SetText( "untitled." + FILEEXT::KiCadSchematicFileExtension );
             sizeSheet( sheet, cursorPos );
 
+            SCH_SHEET_LIST hierarchy = m_frame->Schematic().GetFullHierarchy();
+            SCH_SHEET_PATH instance = m_frame->GetCurrentSheet();
+            instance.push_back( sheet );
+            wxString pageNumber;
+
+            // Don't try to be too clever when assigning the next availabe page number.  Just use
+            // the number of sheets plus one.
+            pageNumber.Printf( wxT( "%d" ), static_cast<int>( hierarchy.size() ) + 1 );
+            instance.SetPageNumber( pageNumber );
+
             m_view->ClearPreview();
             m_view->AddToPreview( sheet->Clone() );
         }
@@ -2603,9 +2914,12 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent )
 
                 sheet->AutoplaceFields( /* aScreen */ nullptr, /* aManual */ false );
 
-                SCH_COMMIT commit( m_toolMgr );
-                commit.Add( sheet, m_frame->GetScreen() );
-                commit.Push( "Draw Sheet" );
+                // Use the commit we were provided or make our own
+                SCH_COMMIT  tempCommit = SCH_COMMIT( m_toolMgr );
+                SCH_COMMIT& c = evt->Commit() ? *( (SCH_COMMIT*) evt->Commit() ) : tempCommit;
+
+                c.Add( sheet, m_frame->GetScreen() );
+                c.Push( isDrawSheetCopy ? "Import Sheet Copy" : "Draw Sheet" );
 
                 SCH_SHEET_PATH newPath = m_frame->GetCurrentSheet();
                 newPath.push_back( sheet );
@@ -2669,6 +2983,9 @@ int SCH_DRAWING_TOOLS::DrawSheet( const TOOL_EVENT& aEvent )
     getViewControls()->CaptureCursor( false );
     m_frame->GetCanvas()->SetCurrentCursor( KICURSOR::ARROW );
 
+    // We own the string if we're importing a sheet
+    delete filename;
+
     return 0;
 }
 
@@ -2834,7 +3151,10 @@ void SCH_DRAWING_TOOLS::setTransitions()
     Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       EE_ACTIONS::placeHierLabel.MakeEvent() );
     Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       EE_ACTIONS::placeGlobalLabel.MakeEvent() );
     Go( &SCH_DRAWING_TOOLS::DrawSheet,           EE_ACTIONS::drawSheet.MakeEvent() );
+    Go( &SCH_DRAWING_TOOLS::DrawSheet,           EE_ACTIONS::drawSheetCopy.MakeEvent() );
     Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       EE_ACTIONS::placeSheetPin.MakeEvent() );
+    Go( &SCH_DRAWING_TOOLS::ImportSheet,         EE_ACTIONS::placeDesignBlock.MakeEvent() );
+    Go( &SCH_DRAWING_TOOLS::ImportSheet,         EE_ACTIONS::importSheet.MakeEvent() );
     Go( &SCH_DRAWING_TOOLS::TwoClickPlace,       EE_ACTIONS::placeSchematicText.MakeEvent() );
     Go( &SCH_DRAWING_TOOLS::DrawShape,           EE_ACTIONS::drawRectangle.MakeEvent() );
     Go( &SCH_DRAWING_TOOLS::DrawShape,           EE_ACTIONS::drawCircle.MakeEvent() );
diff --git a/eeschema/tools/sch_drawing_tools.h b/eeschema/tools/sch_drawing_tools.h
index 3b516361d4..5e099c3964 100644
--- a/eeschema/tools/sch_drawing_tools.h
+++ b/eeschema/tools/sch_drawing_tools.h
@@ -55,6 +55,7 @@ public:
     int PlaceSymbol( const TOOL_EVENT& aEvent );
     int SingleClickPlace( const TOOL_EVENT& aEvent );
     int TwoClickPlace( const TOOL_EVENT& aEvent );
+    int ImportSheet( const TOOL_EVENT& aEvent );
     int DrawShape( const TOOL_EVENT& aEvent );
     int DrawRuleArea( const TOOL_EVENT& aEvent );
     int DrawTable( const TOOL_EVENT& aEvent );
@@ -83,6 +84,7 @@ private:
 
     std::vector<PICKED_SYMBOL> m_symbolHistoryList;
     std::vector<PICKED_SYMBOL> m_powerHistoryList;
+    std::vector<LIB_ID>        m_designBlockHistoryList;
 
     LABEL_FLAG_SHAPE           m_lastSheetPinType;
     LABEL_FLAG_SHAPE           m_lastGlobalLabelShape;
diff --git a/eeschema/tools/sch_edit_tool.cpp b/eeschema/tools/sch_edit_tool.cpp
index cbad1666f4..2830183eff 100644
--- a/eeschema/tools/sch_edit_tool.cpp
+++ b/eeschema/tools/sch_edit_tool.cpp
@@ -2924,8 +2924,7 @@ int SCH_EDIT_TOOL::EditPageNumber( const TOOL_EVENT& aEvent )
 
 int SCH_EDIT_TOOL::DdAppendFile( const TOOL_EVENT& aEvent )
 {
-    wxString aFileName = *aEvent.Parameter<wxString*>();
-    return ( m_frame->AddSheetAndUpdateDisplay( aFileName ) ? 0 : 1 );
+    return m_toolMgr->RunAction( EE_ACTIONS::importSheet, aEvent.Parameter<wxString*>() );
 }
 
 
diff --git a/eeschema/tools/sch_editor_control.cpp b/eeschema/tools/sch_editor_control.cpp
index 914600e59f..6152563e83 100644
--- a/eeschema/tools/sch_editor_control.cpp
+++ b/eeschema/tools/sch_editor_control.cpp
@@ -2434,6 +2434,13 @@ int SCH_EDITOR_CONTROL::ToggleProperties( const TOOL_EVENT& aEvent )
 }
 
 
+int SCH_EDITOR_CONTROL::ToggleLibraryTree( const TOOL_EVENT& aEvent )
+{
+    getEditFrame<SCH_EDIT_FRAME>()->ToggleLibraryTree();
+    return 0;
+}
+
+
 int SCH_EDITOR_CONTROL::ToggleHiddenPins( const TOOL_EVENT& aEvent )
 {
     EESCHEMA_SETTINGS* cfg = m_frame->eeconfig();
@@ -2808,6 +2815,8 @@ void SCH_EDITOR_CONTROL::setTransitions()
     Go( &SCH_EDITOR_CONTROL::ShowHierarchy,         EE_ACTIONS::showHierarchy.MakeEvent() );
     Go( &SCH_EDITOR_CONTROL::ShowNetNavigator,      EE_ACTIONS::showNetNavigator.MakeEvent() );
     Go( &SCH_EDITOR_CONTROL::ToggleProperties,      ACTIONS::showProperties.MakeEvent() );
+    Go( &SCH_EDITOR_CONTROL::ToggleLibraryTree,     ACTIONS::hideLibraryTree.MakeEvent() );
+    Go( &SCH_EDITOR_CONTROL::ToggleLibraryTree,     ACTIONS::showLibraryTree.MakeEvent() );
 
     Go( &SCH_EDITOR_CONTROL::ToggleHiddenPins,      EE_ACTIONS::toggleHiddenPins.MakeEvent() );
     Go( &SCH_EDITOR_CONTROL::ToggleHiddenFields,    EE_ACTIONS::toggleHiddenFields.MakeEvent() );
diff --git a/eeschema/tools/sch_editor_control.h b/eeschema/tools/sch_editor_control.h
index eb1d698890..d0a04cd0cb 100644
--- a/eeschema/tools/sch_editor_control.h
+++ b/eeschema/tools/sch_editor_control.h
@@ -130,6 +130,7 @@ public:
     int ShowHierarchy( const TOOL_EVENT& aEvent );
     int ShowNetNavigator( const TOOL_EVENT& aEvent );
     int ToggleProperties( const TOOL_EVENT& aEvent );
+    int ToggleLibraryTree( const TOOL_EVENT& aEvent );
 
     int ToggleHiddenPins( const TOOL_EVENT& aEvent );
     int ToggleHiddenFields( const TOOL_EVENT& aEvent );
diff --git a/eeschema/widgets/design_block_pane.cpp b/eeschema/widgets/design_block_pane.cpp
new file mode 100644
index 0000000000..962c8d50af
--- /dev/null
+++ b/eeschema/widgets/design_block_pane.cpp
@@ -0,0 +1,157 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <design_block.h>
+#include <widgets/design_block_pane.h>
+#include <widgets/panel_design_block_chooser.h>
+#include <eeschema_settings.h>
+#include <kiface_base.h>
+#include <sch_edit_frame.h>
+#include <core/kicad_algo.h>
+#include <template_fieldnames.h>
+#include <wx/button.h>
+#include <wx/checkbox.h>
+#include <wx/sizer.h>
+#include <confirm.h>
+#include <wildcards_and_files_ext.h>
+#include <ee_actions.h>
+#include <tool/tool_manager.h>
+
+static const wxString REPEATED_PLACEMENT = _( "Place repeated copies" );
+static const wxString PLACE_AS_SHEET = _( "Place as sheet" );
+static const wxString KEEP_ANNOTATIONS = _( "Keep annotations" );
+
+DESIGN_BLOCK_PANE::DESIGN_BLOCK_PANE( SCH_EDIT_FRAME* aParent, const LIB_ID* aPreselect,
+                                      std::vector<LIB_ID>& aHistoryList ) :
+        WX_PANEL( aParent ), m_frame( aParent )
+{
+    wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
+    m_chooserPanel = new PANEL_DESIGN_BLOCK_CHOOSER( aParent, this, aHistoryList,
+                                                     [aParent]()
+                                                     {
+                                                         aParent->GetToolManager()->RunAction(
+                                                                 EE_ACTIONS::placeDesignBlock );
+                                                     } );
+    sizer->Add( m_chooserPanel, 1, wxEXPAND, 5 );
+
+    if( aPreselect && aPreselect->IsValid() )
+        m_chooserPanel->SetPreselect( *aPreselect );
+
+    SetName( wxT( "Design Blocks" ) );
+
+    wxBoxSizer* cbSizer = new wxBoxSizer( wxVERTICAL );
+
+    m_repeatedPlacement = new wxCheckBox( this, wxID_ANY, REPEATED_PLACEMENT );
+    m_repeatedPlacement->SetToolTip( _( "Place copies of the design block on subsequent clicks." ) );
+
+    m_placeAsSheet = new wxCheckBox( this, wxID_ANY, PLACE_AS_SHEET );
+    m_placeAsSheet->SetToolTip( _( "Place the design block as a new sheet." ) );
+
+    m_keepAnnotations = new wxCheckBox( this, wxID_ANY, KEEP_ANNOTATIONS );
+    m_keepAnnotations->SetToolTip( _( "Preserve reference designators in the source schematic. "
+                                      "Otherwise, clear then reannotate according to settings." ) );
+    UpdateCheckboxes();
+
+    // Set all checkbox handlers to the same function
+    m_repeatedPlacement->Bind( wxEVT_CHECKBOX, &DESIGN_BLOCK_PANE::OnCheckBox, this );
+    m_placeAsSheet->Bind( wxEVT_CHECKBOX, &DESIGN_BLOCK_PANE::OnCheckBox, this );
+    m_keepAnnotations->Bind( wxEVT_CHECKBOX, &DESIGN_BLOCK_PANE::OnCheckBox, this );
+
+    cbSizer->Add( m_repeatedPlacement, 0, wxLEFT, 5 );
+    cbSizer->Add( m_placeAsSheet, 0, wxLEFT, 5 );
+    cbSizer->Add( m_keepAnnotations, 0, wxLEFT, 5 );
+
+    sizer->Add( cbSizer, 0, wxEXPAND | wxLEFT, 5 );
+    SetSizer( sizer );
+
+    m_chooserPanel->FinishSetup();
+    Layout();
+
+    Bind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, m_chooserPanel );
+}
+
+
+void DESIGN_BLOCK_PANE::OnCheckBox( wxCommandEvent& aEvent )
+{
+    if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
+    {
+        cfg->m_DesignBlockChooserPanel.repeated_placement = m_repeatedPlacement->GetValue();
+        cfg->m_DesignBlockChooserPanel.place_as_sheet = m_placeAsSheet->GetValue();
+        cfg->m_DesignBlockChooserPanel.keep_annotations = m_keepAnnotations->GetValue();
+    }
+}
+
+
+void DESIGN_BLOCK_PANE::UpdateCheckboxes()
+{
+    if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
+    {
+        m_repeatedPlacement->SetValue( cfg->m_DesignBlockChooserPanel.repeated_placement );
+        m_placeAsSheet->SetValue( cfg->m_DesignBlockChooserPanel.place_as_sheet );
+        m_keepAnnotations->SetValue( cfg->m_DesignBlockChooserPanel.keep_annotations );
+    }
+}
+
+
+LIB_ID DESIGN_BLOCK_PANE::GetSelectedLibId( int* aUnit ) const
+{
+    return m_chooserPanel->GetSelectedLibId( aUnit );
+}
+
+
+void DESIGN_BLOCK_PANE::SelectLibId( const LIB_ID& aLibId )
+{
+    m_chooserPanel->SelectLibId( aLibId );
+}
+
+
+void DESIGN_BLOCK_PANE::RefreshLibs()
+{
+    m_chooserPanel->RefreshLibs();
+}
+
+
+FILEDLG_IMPORT_SHEET_CONTENTS::FILEDLG_IMPORT_SHEET_CONTENTS( EESCHEMA_SETTINGS* aSettings )
+{
+    wxASSERT( aSettings );
+    m_settings = aSettings;
+};
+
+
+void FILEDLG_IMPORT_SHEET_CONTENTS::TransferDataFromCustomControls()
+{
+    m_settings->m_DesignBlockChooserPanel.repeated_placement = m_cbRepeatedPlacement->GetValue();
+    m_settings->m_DesignBlockChooserPanel.place_as_sheet = m_cbPlaceAsSheet->GetValue();
+    m_settings->m_DesignBlockChooserPanel.keep_annotations = m_cbKeepAnnotations->GetValue();
+}
+
+
+void FILEDLG_IMPORT_SHEET_CONTENTS::AddCustomControls( wxFileDialogCustomize& customizer )
+{
+    m_cbRepeatedPlacement = customizer.AddCheckBox( REPEATED_PLACEMENT );
+    m_cbRepeatedPlacement->SetValue( m_settings->m_DesignBlockChooserPanel.repeated_placement );
+    m_cbPlaceAsSheet = customizer.AddCheckBox( PLACE_AS_SHEET );
+    m_cbPlaceAsSheet->SetValue( m_settings->m_DesignBlockChooserPanel.place_as_sheet );
+    m_cbKeepAnnotations = customizer.AddCheckBox( KEEP_ANNOTATIONS );
+    m_cbKeepAnnotations->SetValue( m_settings->m_DesignBlockChooserPanel.keep_annotations );
+}
diff --git a/eeschema/widgets/design_block_pane.h b/eeschema/widgets/design_block_pane.h
new file mode 100644
index 0000000000..9df5b81b57
--- /dev/null
+++ b/eeschema/widgets/design_block_pane.h
@@ -0,0 +1,113 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+#ifndef DESIGN_BLOCK_PANE_H
+#define DESIGN_BLOCK_PANE_H
+
+#include <design_block_tree_model_adapter.h>
+#include <widgets/html_window.h>
+#include <widgets/wx_panel.h>
+#include <wx/checkbox.h>
+#include <wx/filedlgcustomize.h>
+#include <eeschema_settings.h>
+
+
+class SCH_EDIT_FRAME;
+class PANEL_DESIGN_BLOCK_CHOOSER;
+
+
+class DESIGN_BLOCK_PANE : public WX_PANEL
+{
+public:
+    /**
+     * Create dialog to choose design_block.
+     *
+     * @param aParent   a SCH_BASE_FRAME parent window.
+     * @param aAllowFieldEdits  if false, all functions that allow the user to edit fields
+     *                          (currently just footprint selection) will not be available.
+     * @param aShowFootprints   if false, all footprint preview and selection features are
+     *                          disabled. This forces aAllowFieldEdits false too.
+     */
+    DESIGN_BLOCK_PANE( SCH_EDIT_FRAME* aParent, const LIB_ID* aPreselect,
+                       std::vector<LIB_ID>& aHistoryList );
+
+    /**
+     * To be called after this dialog returns from ShowModal().
+     *
+     * For multi-unit design_blocks, if the user selects the design_block itself rather than picking
+     * an individual unit, 0 will be returned in aUnit.
+     * Beware that this is an invalid unit number - this should be replaced with whatever
+     * default is desired (usually 1).
+     *
+     * @param aUnit if not NULL, the selected unit is filled in here.
+     * @return the #LIB_ID of the design_block that has been selected.
+     */
+    LIB_ID GetSelectedLibId( int* aUnit = nullptr ) const;
+    void   SelectLibId( const LIB_ID& aLibId );
+
+    void RefreshLibs();
+
+    /* Handler for checkbox events */
+    void OnCheckBox( wxCommandEvent& aEvent );
+    void UpdateCheckboxes();
+
+    void OnSaveSheetAsDesignBlock( wxCommandEvent& aEvent );
+    void OnSaveSelectionAsDesignBlock( wxCommandEvent& aEvent );
+
+    void OnDeleteLibrary( wxCommandEvent& aEvent );
+    void OnDeleteDesignBlock( wxCommandEvent& aEvent );
+
+    PANEL_DESIGN_BLOCK_CHOOSER* GetDesignBlockPanel() const { return m_chooserPanel; }
+
+protected:
+    PANEL_DESIGN_BLOCK_CHOOSER* m_chooserPanel;
+
+    wxCheckBox*                 m_repeatedPlacement;
+    wxCheckBox*                 m_placeAsSheet;
+    wxCheckBox*                 m_keepAnnotations;
+
+    SCH_EDIT_FRAME*             m_frame;
+};
+
+
+// This is a helper class for the file dialog to allow the user to choose similar options
+// as the design block chooser when importing a sheet.
+class FILEDLG_IMPORT_SHEET_CONTENTS : public wxFileDialogCustomizeHook
+{
+public:
+    FILEDLG_IMPORT_SHEET_CONTENTS( EESCHEMA_SETTINGS* aSettings );
+
+    virtual void AddCustomControls( wxFileDialogCustomize& customizer ) override;
+
+    virtual void TransferDataFromCustomControls() override;
+
+private:
+    EESCHEMA_SETTINGS* m_settings;
+
+    wxFileDialogCheckBox* m_cbRepeatedPlacement;
+    wxFileDialogCheckBox* m_cbPlaceAsSheet;
+    wxFileDialogCheckBox* m_cbKeepAnnotations;
+
+    wxDECLARE_NO_COPY_CLASS( FILEDLG_IMPORT_SHEET_CONTENTS );
+};
+
+#endif
diff --git a/eeschema/widgets/design_block_preview_widget.cpp b/eeschema/widgets/design_block_preview_widget.cpp
new file mode 100644
index 0000000000..603228aad6
--- /dev/null
+++ b/eeschema/widgets/design_block_preview_widget.cpp
@@ -0,0 +1,241 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "design_block_preview_widget.h"
+#include <schematic.h>
+#include <sch_sheet.h>
+#include <sch_view.h>
+#include <sch_screen.h>
+#include <gal/gal_display_options.h>
+#include <gal/graphics_abstraction_layer.h>
+#include <math/vector2wx.h>
+#include <design_block_lib_table.h>
+#include <design_block.h>
+#include <sch_preview_panel.h>
+#include <pgm_base.h>
+#include <sch_painter.h>
+#include <eda_draw_frame.h>
+#include <project_sch.h>
+#include <eeschema_settings.h>
+#include <eeschema_helpers.h>
+#include <settings/settings_manager.h>
+#include <wx/log.h>
+#include <wx/stattext.h>
+
+
+DESIGN_BLOCK_PREVIEW_WIDGET::DESIGN_BLOCK_PREVIEW_WIDGET( wxWindow* aParent, bool aIncludeStatus,
+                                              EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvasType ) :
+        wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 ),
+        m_preview( nullptr ),
+        m_status( nullptr ),
+        m_statusPanel( nullptr ),
+        m_statusSizer( nullptr ),
+        m_previewItem( nullptr )
+{
+    auto common_settings = Pgm().GetCommonSettings();
+    auto app_settings = Pgm().GetSettingsManager().GetAppSettings<EESCHEMA_SETTINGS>();
+
+    m_galDisplayOptions.ReadConfig( *common_settings, app_settings->m_Window, this );
+    m_galDisplayOptions.m_forceDisplayCursor = false;
+
+    EDA_DRAW_PANEL_GAL::GAL_TYPE canvasType = aCanvasType;
+
+    // Allows only a CAIRO or OPENGL canvas:
+    if( canvasType != EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL
+        && canvasType != EDA_DRAW_PANEL_GAL::GAL_FALLBACK )
+    {
+        canvasType = EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL;
+    }
+
+    m_preview = new SCH_PREVIEW_PANEL( this, wxID_ANY, wxDefaultPosition, wxSize( -1, -1 ),
+                                       m_galDisplayOptions, canvasType );
+    m_preview->SetStealsFocus( false );
+    m_preview->ShowScrollbars( wxSHOW_SB_NEVER, wxSHOW_SB_NEVER );
+    m_preview->GetGAL()->SetAxesEnabled( false );
+
+    // Do not display the grid: the look is not good for a small canvas area.
+    // But mainly, due to some strange bug I (JPC) was unable to fix, the grid creates
+    // strange artifacts on Windows when Eeschema is run from KiCad manager (but not in
+    // stand alone...).
+    m_preview->GetGAL()->SetGridVisibility( true );
+
+    // Early initialization of the canvas background color,
+    // before any OnPaint event is fired for the canvas using a wrong bg color
+    KIGFX::VIEW* view = m_preview->GetView();
+    auto         settings = static_cast<SCH_RENDER_SETTINGS*>( view->GetPainter()->GetSettings() );
+
+    if( auto* theme = Pgm().GetSettingsManager().GetColorSettings( app_settings->m_ColorTheme ) )
+        settings->LoadColors( theme );
+
+    const COLOR4D& backgroundColor = settings->GetBackgroundColor();
+    const COLOR4D& foregroundColor = settings->GetCursorColor();
+
+    m_preview->GetGAL()->SetClearColor( backgroundColor );
+
+    settings->m_ShowPinsElectricalType = false;
+    settings->m_ShowPinNumbers = false;
+    settings->m_ShowHiddenPins = false;
+    settings->m_ShowHiddenFields = false;
+
+    m_outerSizer = new wxBoxSizer( wxVERTICAL );
+
+    if( aIncludeStatus )
+    {
+        m_statusPanel = new wxPanel( this );
+        m_statusPanel->SetBackgroundColour( backgroundColor.ToColour() );
+        m_status = new wxStaticText( m_statusPanel, wxID_ANY, wxEmptyString );
+        m_status->SetForegroundColour( settings->GetLayerColor( LAYER_REFERENCEPART ).ToColour() );
+        m_statusSizer = new wxBoxSizer( wxVERTICAL );
+        m_statusSizer->Add( 0, 0, 1 ); // add a spacer
+        m_statusSizer->Add( m_status, 0, wxALIGN_CENTER );
+        m_statusSizer->Add( 0, 0, 1 ); // add a spacer
+        m_statusPanel->SetSizer( m_statusSizer );
+
+        // Give the status panel the same color scheme as the canvas so it isn't jarring when
+        // switched to.
+        m_statusPanel->SetBackgroundColour( backgroundColor.ToColour() );
+        m_statusPanel->SetForegroundColour( foregroundColor.ToColour() );
+
+        // Give the preview panel a small top border to align its top with the status panel,
+        // and give the status panel a small bottom border to align its bottom with the preview
+        // panel.
+        m_outerSizer->Add( m_preview, 1, wxTOP | wxEXPAND, 5 );
+        m_outerSizer->Add( m_statusPanel, 1, wxBOTTOM | wxEXPAND, 5 );
+
+        // Hide the status panel to start
+        m_statusPanel->Hide();
+    }
+    else
+    {
+        m_outerSizer->Add( m_preview, 1, wxEXPAND, 0 );
+    }
+
+    SetSizer( m_outerSizer );
+    Layout();
+
+    Connect( wxEVT_SIZE, wxSizeEventHandler( DESIGN_BLOCK_PREVIEW_WIDGET::onSize ), nullptr, this );
+}
+
+
+DESIGN_BLOCK_PREVIEW_WIDGET::~DESIGN_BLOCK_PREVIEW_WIDGET()
+{
+    delete m_preview;
+    delete m_previewItem;
+}
+
+
+void DESIGN_BLOCK_PREVIEW_WIDGET::SetStatusText( wxString const& aText )
+{
+    wxCHECK( m_statusPanel, /* void */ );
+
+    m_status->SetLabel( aText );
+    m_preview->Hide();
+    m_statusPanel->Show();
+    Layout();
+}
+
+
+void DESIGN_BLOCK_PREVIEW_WIDGET::onSize( wxSizeEvent& aEvent )
+{
+    if( m_previewItem )
+    {
+        fitOnDrawArea();
+        m_preview->ForceRefresh();
+    }
+
+    aEvent.Skip();
+}
+
+
+void DESIGN_BLOCK_PREVIEW_WIDGET::fitOnDrawArea()
+{
+    if( !m_previewItem )
+        return;
+
+    // set the view scale to fit the item on screen
+    KIGFX::VIEW* view = m_preview->GetView();
+
+    // Calculate the drawing area size, in internal units, for a scaling factor = 1.0
+    view->SetScale( 1.0 );
+    VECTOR2D clientSize = view->ToWorld( ToVECTOR2D( m_preview->GetClientSize() ), false );
+    // Calculate the draw scale to fit the drawing area
+    double scale = std::min( fabs( clientSize.x / m_itemBBox.GetWidth() ),
+                             fabs( clientSize.y / m_itemBBox.GetHeight() ) );
+
+    // Above calculation will yield an exact fit; add a bit of whitespace around block
+    scale /= 1.2;
+
+    // Now fix the best scale
+    view->SetScale( scale );
+    view->SetCenter( m_itemBBox.Centre() );
+}
+
+
+void DESIGN_BLOCK_PREVIEW_WIDGET::DisplayDesignBlock( DESIGN_BLOCK* aDesignBlock )
+{
+    KIGFX::VIEW* view = m_preview->GetView();
+
+    if( m_previewItem )
+    {
+        view->Clear();
+        delete m_previewItem;
+        m_previewItem = nullptr;
+    }
+
+    if( aDesignBlock )
+    {
+        m_previewItem = EESCHEMA_HELPERS::LoadSchematic( aDesignBlock->GetSchematicFile(),
+                                                         SCH_IO_MGR::SCH_KICAD, false, true );
+        BOX2I bBox;
+
+        if( m_previewItem )
+        {
+            for( EDA_ITEM* item : m_previewItem->CurrentSheet().LastScreen()->Items() )
+            {
+                view->Add( item );
+
+                if( item->Type() == SCH_FIELD_T )
+                {
+                    if( !static_cast<const SCH_FIELD*>( item )->IsVisible() )
+                        continue;
+                }
+
+                bBox.Merge( item->GetBoundingBox() );
+            }
+        }
+
+        m_itemBBox = bBox;
+
+        if( !m_preview->IsShownOnScreen() )
+        {
+            m_preview->Show();
+
+            if( m_statusPanel )
+                m_statusPanel->Hide();
+
+            Layout(); // Ensure panel size is up to date.
+        }
+
+        // Calculate the draw scale to fit the drawing area
+        fitOnDrawArea();
+    }
+
+    m_preview->ForceRefresh();
+    m_preview->Show();
+}
diff --git a/eeschema/widgets/design_block_preview_widget.h b/eeschema/widgets/design_block_preview_widget.h
new file mode 100644
index 0000000000..e0b72599b4
--- /dev/null
+++ b/eeschema/widgets/design_block_preview_widget.h
@@ -0,0 +1,81 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef DESIGN_BLOCK_PREVIEW_WIDGET_H
+#define DESIGN_BLOCK_PREVIEW_WIDGET_H
+
+#include <wx/panel.h>
+#include <kiway.h>
+#include <gal_display_options_common.h>
+#include <class_draw_panel_gal.h>
+
+
+class LIB_ID;
+class DESIGN_BLOCK;
+class SCHEMATIC;
+class SCH_SHEET;
+class wxStaticText;
+class wxSizer;
+
+
+class DESIGN_BLOCK_PREVIEW_WIDGET : public wxPanel
+{
+public:
+    /**
+     * Construct a symbol preview widget.
+     *
+     * @param aParent - parent window
+     * @param aCanvasType = the type of canvas (GAL_TYPE_OPENGL or GAL_TYPE_CAIRO only)
+     */
+    DESIGN_BLOCK_PREVIEW_WIDGET( wxWindow* aParent, bool aIncludeStatus,
+                                 EDA_DRAW_PANEL_GAL::GAL_TYPE aCanvasType );
+
+    ~DESIGN_BLOCK_PREVIEW_WIDGET() override;
+
+    /**
+     * Set the contents of the status label and display it.
+     */
+    void SetStatusText( const wxString& aText );
+
+    /**
+     * Set the currently displayed symbol.
+     */
+    void DisplayDesignBlock( DESIGN_BLOCK* aDesignBlock );
+
+protected:
+    void onSize( wxSizeEvent& aEvent );
+
+    void fitOnDrawArea();    // set the view scale to fit the item on screen and center
+
+    GAL_DISPLAY_OPTIONS_IMPL   m_galDisplayOptions;
+    EDA_DRAW_PANEL_GAL*        m_preview;
+
+    wxStaticText*              m_status;
+    wxPanel*                   m_statusPanel;
+    wxSizer*                   m_statusSizer;
+    wxSizer*                   m_outerSizer;
+
+    SCHEMATIC*                 m_previewItem;
+
+    /// The bounding box of the current item
+    BOX2I                      m_itemBBox;
+};
+
+
+#endif // DESIGN_BLOCK_PREVIEW_WIDGET_H
diff --git a/eeschema/widgets/panel_design_block_chooser.cpp b/eeschema/widgets/panel_design_block_chooser.cpp
new file mode 100644
index 0000000000..941fe8ecd1
--- /dev/null
+++ b/eeschema/widgets/panel_design_block_chooser.cpp
@@ -0,0 +1,431 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2016-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#include <pgm_base.h>
+#include <design_block.h>
+#include <design_block_lib_table.h>
+#include <panel_design_block_chooser.h>
+#include <design_block_preview_widget.h>
+#include <kiface_base.h>
+#include <sch_edit_frame.h>
+#include <project_sch.h>
+#include <widgets/lib_tree.h>
+#include <settings/settings_manager.h>
+#include <project/project_file.h>
+#include <eeschema_settings.h>
+#include <dialogs/html_message_box.h>
+#include <string_utils.h>
+#include <wx/button.h>
+#include <wx/clipbrd.h>
+#include <wx/log.h>
+#include <wx/panel.h>
+#include <wx/sizer.h>
+#include <wx/splitter.h>
+#include <wx/timer.h>
+#include <wx/wxhtml.h>
+#include <wx/msgdlg.h>
+#include <widgets/wx_progress_reporters.h>
+
+
+wxString PANEL_DESIGN_BLOCK_CHOOSER::g_designBlockSearchString;
+
+
+PANEL_DESIGN_BLOCK_CHOOSER::PANEL_DESIGN_BLOCK_CHOOSER( SCH_EDIT_FRAME* aFrame, wxWindow* aParent,
+                                                        std::vector<LIB_ID>&  aHistoryList,
+                                                        std::function<void()> aSelectHandler ) :
+        wxPanel( aParent, wxID_ANY, wxDefaultPosition, wxDefaultSize ),
+        m_vsplitter( nullptr ),
+        m_tree( nullptr ),
+        m_preview( nullptr ),
+        m_frame( aFrame ),
+        m_selectHandler( std::move( aSelectHandler ) ),
+        m_historyList( aHistoryList )
+{
+    DESIGN_BLOCK_LIB_TABLE* libs = m_frame->Prj().DesignBlockLibs();
+
+    // Make sure settings are loaded before we start running multi-threaded design block loaders
+    Pgm().GetSettingsManager().GetAppSettings<EESCHEMA_SETTINGS>();
+
+    // Load design block files:
+    WX_PROGRESS_REPORTER* progressReporter =
+            new WX_PROGRESS_REPORTER( aParent, _( "Loading Design Block Libraries" ), 3 );
+    GDesignBlockList.ReadDesignBlockFiles( libs, nullptr, progressReporter );
+
+    // Force immediate deletion of the WX_PROGRESS_REPORTER.  Do not use Destroy(), or use
+    // Destroy() followed by wxSafeYield() because on Windows, APP_PROGRESS_DIALOG and
+    // WX_PROGRESS_REPORTER have some side effects on the event loop manager.  For instance, a
+    // subsequent call to ShowModal() or ShowQuasiModal() for a dialog following the use of a
+    // WX_PROGRESS_REPORTER results in incorrect modal or quasi modal behavior.
+    delete progressReporter;
+
+    if( GDesignBlockList.GetErrorCount() )
+        displayErrors( aFrame );
+
+    m_adapter = DESIGN_BLOCK_TREE_MODEL_ADAPTER::Create( m_frame, libs );
+
+    // -------------------------------------------------------------------------------------
+    // Construct the actual panel
+    //
+
+    wxBoxSizer* sizer = new wxBoxSizer( wxVERTICAL );
+
+    m_vsplitter = new wxSplitterWindow( this, wxID_ANY, wxDefaultPosition, wxDefaultSize,
+                                        wxSP_LIVE_UPDATE | wxSP_NOBORDER | wxSP_3DSASH );
+
+
+    // Avoid the splitter window being assigned as the parent to additional windows.
+    m_vsplitter->SetExtraStyle( wxWS_EX_TRANSIENT );
+
+    wxPanel*    treePanel = new wxPanel( m_vsplitter );
+    wxBoxSizer* treeSizer = new wxBoxSizer( wxVERTICAL );
+    treePanel->SetSizer( treeSizer );
+
+    wxPanel*    detailsPanel = new wxPanel( m_vsplitter );
+    wxBoxSizer* detailsSizer = new wxBoxSizer( wxVERTICAL );
+    detailsPanel->SetSizer( detailsSizer );
+
+    m_preview = new DESIGN_BLOCK_PREVIEW_WIDGET( detailsPanel, false,
+                                                 EDA_DRAW_PANEL_GAL::GAL_TYPE_OPENGL );
+    detailsSizer->Add( m_preview, 1, wxEXPAND, 5 );
+    detailsPanel->Layout();
+    detailsSizer->Fit( detailsPanel );
+
+    m_vsplitter->SetSashGravity( 0.5 );
+    m_vsplitter->SetMinimumPaneSize( 20 );
+    m_vsplitter->SplitHorizontally( treePanel, detailsPanel );
+
+    sizer->Add( m_vsplitter, 1, wxEXPAND, 5 );
+
+
+    m_tree = new LIB_TREE( treePanel, wxT( "design_blocks" ), libs, m_adapter,
+                           LIB_TREE::FLAGS::ALL_WIDGETS, nullptr );
+
+    treeSizer->Add( m_tree, 1, wxEXPAND, 5 );
+    treePanel->Layout();
+    treeSizer->Fit( treePanel );
+
+    RefreshLibs();
+    m_adapter->FinishTreeInitialization();
+
+    m_tree->SetSearchString( g_designBlockSearchString );
+
+    m_dbl_click_timer = new wxTimer( this );
+    m_open_libs_timer = new wxTimer( this );
+
+    SetSizer( sizer );
+
+    Layout();
+
+    Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this,
+          m_dbl_click_timer->GetId() );
+    Bind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onOpenLibsTimer, this,
+          m_open_libs_timer->GetId() );
+    Bind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this );
+    Bind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this );
+
+    // Open the user's previously opened libraries on timer expiration.
+    // This is done on a timer because we need a gross hack to keep GTK from garbling the
+    // display. Must be longer than the search debounce timer.
+    m_open_libs_timer->StartOnce( 300 );
+}
+
+
+PANEL_DESIGN_BLOCK_CHOOSER::~PANEL_DESIGN_BLOCK_CHOOSER()
+{
+    Unbind( wxEVT_TIMER, &PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer, this );
+    Unbind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this );
+    Unbind( EVT_LIBITEM_CHOSEN, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen, this );
+    Unbind( wxEVT_CHAR_HOOK, &PANEL_DESIGN_BLOCK_CHOOSER::OnChar, this );
+
+    // Stop the timer during destruction early to avoid potential race conditions (that do happen)
+    m_dbl_click_timer->Stop();
+    m_open_libs_timer->Stop();
+    delete m_dbl_click_timer;
+    delete m_open_libs_timer;
+
+    g_designBlockSearchString = m_tree->GetSearchString();
+
+    if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
+    {
+        // Save any changes to column widths, etc.
+        m_adapter->SaveSettings();
+
+        cfg->m_DesignBlockChooserPanel.width = GetParent()->GetSize().x;
+        cfg->m_DesignBlockChooserPanel.height = GetParent()->GetSize().y;
+
+        if( m_vsplitter )
+            cfg->m_DesignBlockChooserPanel.sash_pos_v = m_vsplitter->GetSashPosition();
+
+        cfg->m_DesignBlockChooserPanel.sort_mode = m_tree->GetSortMode();
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::OnChar( wxKeyEvent& aEvent )
+{
+    if( aEvent.GetKeyCode() == WXK_ESCAPE )
+    {
+        wxObject* eventSource = aEvent.GetEventObject();
+
+        if( wxTextCtrl* textCtrl = dynamic_cast<wxTextCtrl*>( eventSource ) )
+        {
+            // First escape cancels search string value
+            if( textCtrl->GetValue() == m_tree->GetSearchString()
+                && !m_tree->GetSearchString().IsEmpty() )
+            {
+                m_tree->SetSearchString( wxEmptyString );
+                return;
+            }
+        }
+    }
+    else
+    {
+        aEvent.Skip();
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::FinishSetup()
+{
+    if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
+    {
+        auto horizPixelsFromDU =
+                [&]( int x ) -> int
+                {
+                    wxSize sz( x, 0 );
+                    return GetParent()->ConvertDialogToPixels( sz ).x;
+                };
+
+        EESCHEMA_SETTINGS::PANEL_DESIGN_BLOCK_CHOOSER& panelCfg = cfg->m_DesignBlockChooserPanel;
+
+        int w = panelCfg.width > 40 ? panelCfg.width : horizPixelsFromDU( 440 );
+        int h = panelCfg.height > 40 ? panelCfg.height : horizPixelsFromDU( 340 );
+
+        GetParent()->SetSize( wxSize( w, h ) );
+        GetParent()->Layout();
+
+        // We specify the width of the right window (m_design_block_view_panel), because specify
+        // the width of the left window does not work as expected when SetSashGravity() is called
+
+        if( panelCfg.sash_pos_h < 0 )
+            panelCfg.sash_pos_h = horizPixelsFromDU( 220 );
+
+        if( panelCfg.sash_pos_v < 0 )
+            panelCfg.sash_pos_v = horizPixelsFromDU( 230 );
+
+        if( m_vsplitter )
+            m_vsplitter->SetSashPosition( panelCfg.sash_pos_v );
+
+        m_adapter->SetSortMode( (LIB_TREE_MODEL_ADAPTER::SORT_MODE) panelCfg.sort_mode );
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::RefreshLibs( bool aProgress )
+{
+    // Unselect before syncing to avoid null reference in the adapter
+    // if a selected item is removed during the sync
+    m_tree->Unselect();
+
+    DESIGN_BLOCK_TREE_MODEL_ADAPTER* adapter =
+            static_cast<DESIGN_BLOCK_TREE_MODEL_ADAPTER*>( m_adapter.get() );
+
+    // Clear all existing libraries then re-add
+    adapter->ClearLibraries();
+
+    // Read the libraries from disk if they've changed
+    DESIGN_BLOCK_LIB_TABLE* fpTable = m_frame->Prj().DesignBlockLibs();
+
+    // Sync FOOTPRINT_INFO list to the libraries on disk
+    if( aProgress )
+    {
+        WX_PROGRESS_REPORTER progressReporter( this, _( "Updating Design Block Libraries" ), 2 );
+        GDesignBlockList.ReadDesignBlockFiles( fpTable, nullptr, &progressReporter );
+        progressReporter.Show( false );
+    }
+    else
+    {
+        GDesignBlockList.ReadDesignBlockFiles( fpTable, nullptr, nullptr );
+    }
+
+    rebuildHistoryNode();
+
+    if( !m_historyList.empty() )
+        adapter->SetPreselectNode( m_historyList[0], 0 );
+
+    adapter->AddLibraries( m_frame );
+
+    m_tree->Regenerate( true );
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::SetPreselect( const LIB_ID& aPreselect )
+{
+    m_adapter->SetPreselectNode( aPreselect, 0 );
+}
+
+
+LIB_ID PANEL_DESIGN_BLOCK_CHOOSER::GetSelectedLibId( int* aUnit ) const
+{
+    return m_tree->GetSelectedLibId( aUnit );
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::SelectLibId( const LIB_ID& aLibId )
+{
+    m_tree->CenterLibId( aLibId );
+    m_tree->SelectLibId( aLibId );
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer( wxTimerEvent& aEvent )
+{
+    // Hack because of eaten MouseUp event. See PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen
+    // for the beginning of this spaghetti noodle.
+
+    wxMouseState state = wxGetMouseState();
+
+    if( state.LeftIsDown() )
+    {
+        // Mouse hasn't been raised yet, so fire the timer again. Otherwise the
+        // purpose of this timer is defeated.
+        m_dbl_click_timer->StartOnce( PANEL_DESIGN_BLOCK_CHOOSER::DBLCLICK_DELAY );
+    }
+    else
+    {
+        m_selectHandler();
+        addDesignBlockToHistory( m_tree->GetSelectedLibId() );
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::onOpenLibsTimer( wxTimerEvent& aEvent )
+{
+    if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
+        m_adapter->OpenLibs( cfg->m_LibTree.open_libs );
+
+    // Bind this now se we don't spam the event queue with EVT_LIBITEM_SELECTED events during
+    // the initial load.
+    Bind( EVT_LIBITEM_SELECTED, &PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected, this );
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockSelected( wxCommandEvent& aEvent )
+{
+    if( GetSelectedLibId().IsValid() )
+        m_preview->DisplayDesignBlock( m_frame->GetDesignBlock( GetSelectedLibId() ) );
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::onDesignBlockChosen( wxCommandEvent& aEvent )
+{
+    if( m_tree->GetSelectedLibId().IsValid() )
+    {
+        // Got a selection. We can't just end the modal dialog here, because wx leaks some events
+        // back to the parent window (in particular, the MouseUp following a double click).
+        //
+        // NOW, here's where it gets really fun. wxTreeListCtrl eats MouseUp.  This isn't really
+        // feasible to bypass without a fully custom wxDataViewCtrl implementation, and even then
+        // might not be fully possible (docs are vague). To get around this, we use a one-shot
+        // timer to schedule the dialog close.
+        //
+        // See PANEL_DESIGN_BLOCK_CHOOSER::onCloseTimer for the other end of this spaghetti noodle.
+        m_dbl_click_timer->StartOnce( PANEL_DESIGN_BLOCK_CHOOSER::DBLCLICK_DELAY );
+    }
+}
+
+void PANEL_DESIGN_BLOCK_CHOOSER::addDesignBlockToHistory( const LIB_ID& aLibId )
+{
+    LIB_ID savedId = GetSelectedLibId();
+
+    m_tree->Unselect();
+
+    // Remove duplicates
+    for( int i = (int) m_historyList.size() - 1; i >= 0; --i )
+    {
+        if( m_historyList[i] == aLibId )
+            m_historyList.erase( m_historyList.begin() + i );
+    }
+
+    // Add the new name at the beginning of the history list
+    m_historyList.insert( m_historyList.begin(), aLibId );
+
+    // Remove extra names
+    while( m_historyList.size() >= 8 )
+        m_historyList.pop_back();
+
+    rebuildHistoryNode();
+    m_tree->Regenerate( true );
+
+    SelectLibId( savedId );
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::rebuildHistoryNode()
+{
+    wxString history = wxT( "-- " ) + _( "Recently Used" ) + wxT( " --" );
+
+    m_adapter->DoRemoveLibrary( history );
+
+    // Build the history list
+    std::vector<LIB_TREE_ITEM*> historyInfos;
+
+    for( const LIB_ID& lib : m_historyList )
+    {
+        LIB_TREE_ITEM* fp_info =
+                GDesignBlockList.GetDesignBlockInfo( lib.GetLibNickname(), lib.GetLibItemName() );
+
+        // this can be null, for example, if the design block has been deleted from a library.
+        if( fp_info != nullptr )
+            historyInfos.push_back( fp_info );
+    }
+
+    m_adapter->DoAddLibrary( history, wxEmptyString, historyInfos, false, true );
+}
+
+
+void PANEL_DESIGN_BLOCK_CHOOSER::displayErrors( wxTopLevelWindow* aWindow )
+{
+    // @todo: go to a more HTML !<table>! ? centric output, possibly with recommendations
+    // for remedy of errors.  Add numeric error codes to PARSE_ERROR, and switch on them for
+    // remedies, etc.  Full access is provided to everything in every exception!
+
+    HTML_MESSAGE_BOX dlg( aWindow, _( "Load Error" ) );
+
+    dlg.MessageSet( _( "Errors were encountered loading footprints:" ) );
+
+    wxString msg;
+
+    while( std::unique_ptr<IO_ERROR> error = GDesignBlockList.PopError() )
+    {
+        wxString tmp = EscapeHTML( error->Problem() );
+
+        // Preserve new lines in error messages so queued errors don't run together.
+        tmp.Replace( wxS( "\n" ), wxS( "<BR>" ) );
+        msg += wxT( "<p>" ) + tmp + wxT( "</p>" );
+    }
+
+    dlg.AddHTML_Text( msg );
+
+    dlg.ShowModal();
+}
diff --git a/eeschema/widgets/panel_design_block_chooser.h b/eeschema/widgets/panel_design_block_chooser.h
new file mode 100644
index 0000000000..127879a40a
--- /dev/null
+++ b/eeschema/widgets/panel_design_block_chooser.h
@@ -0,0 +1,118 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2014-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+#ifndef PANEL_DESIGN_BLOCK_CHOOSER_H
+#define PANEL_DESIGN_BLOCK_CHOOSER_H
+
+#include <widgets/lib_tree.h>
+#include <design_block_tree_model_adapter.h>
+#include <widgets/html_window.h>
+
+class wxPanel;
+class wxTimer;
+class wxSplitterWindow;
+
+class SCH_EDIT_FRAME;
+class DESIGN_BLOCK_PREVIEW_WIDGET;
+
+
+class PANEL_DESIGN_BLOCK_CHOOSER : public wxPanel
+{
+public:
+/**
+ * Create dialog to choose design_block.
+ *
+ * @param aFrame  the parent frame (usually a SCH_EDIT_FRAME or DESIGN_BLOCK_CHOOSER_FRAME)
+ * @param aParent the parent window (usually a DIALOG_SHIM or DESIGN_BLOCK_CHOOSER_FRAME)
+ * @param aAcceptHandler a handler to be called on double-click of a footprint
+ * @param aEscapeHandler a handler to be called on <ESC>
+ */
+    PANEL_DESIGN_BLOCK_CHOOSER( SCH_EDIT_FRAME* aFrame, wxWindow* aParent,
+                                std::vector<LIB_ID>&  aHistoryList,
+                                std::function<void()> aSelectHandler );
+
+    ~PANEL_DESIGN_BLOCK_CHOOSER();
+
+    void OnChar( wxKeyEvent& aEvent );
+
+    void FinishSetup();
+
+    void SetPreselect( const LIB_ID& aPreselect );
+
+    void RefreshLibs( bool aProgress = false );
+
+    /**
+     * To be called after this dialog returns from ShowModal().
+     *
+     * For multi-unit design_blocks, if the user selects the design_block itself rather than picking
+     * an individual unit, 0 will be returned in aUnit.
+     * Beware that this is an invalid unit number - this should be replaced with whatever
+     * default is desired (usually 1).
+     *
+     * @param aUnit if not NULL, the selected unit is filled in here.
+     * @return the #LIB_ID of the design_block that has been selected.
+     */
+    LIB_ID GetSelectedLibId( int* aUnit = nullptr ) const;
+    void   SelectLibId( const LIB_ID& aLibId );
+
+    LIB_TREE* GetLibTree() { return m_tree; }
+
+protected:
+    static constexpr int DBLCLICK_DELAY = 100; // milliseconds
+
+    void OnDetailsCharHook( wxKeyEvent& aEvt );
+    void onCloseTimer( wxTimerEvent& aEvent );
+    void onOpenLibsTimer( wxTimerEvent& aEvent );
+
+    void onDesignBlockSelected( wxCommandEvent& aEvent );
+
+    /**
+     * Handle the selection of an item. This is called when either the search box or the tree
+     * receive an Enter, or the tree receives a double click.
+     * If the item selected is a category, it is expanded or collapsed; if it is a design_block, the
+     * design_block is picked.
+     */
+    void onDesignBlockChosen( wxCommandEvent& aEvent );
+
+    void addDesignBlockToHistory( const LIB_ID& aLibId );
+    void rebuildHistoryNode();
+
+    void displayErrors( wxTopLevelWindow* aWindow );
+
+    static wxString g_designBlockSearchString;
+
+    wxTimer*          m_dbl_click_timer;
+    wxTimer*          m_open_libs_timer;
+    wxSplitterWindow* m_vsplitter;
+
+    wxObjectDataPtr<LIB_TREE_MODEL_ADAPTER> m_adapter;
+
+    LIB_TREE*    m_tree;
+    DESIGN_BLOCK_PREVIEW_WIDGET* m_preview;
+
+    SCH_EDIT_FRAME*       m_frame;
+    std::function<void()> m_selectHandler;
+
+    std::vector<LIB_ID> m_historyList;
+};
+
+#endif /* PANEL_DESIGN_BLOCK_CHOOSER_H */
diff --git a/include/advanced_config.h b/include/advanced_config.h
index 90d765ca29..c6af868c14 100644
--- a/include/advanced_config.h
+++ b/include/advanced_config.h
@@ -466,6 +466,15 @@ public:
      */
     int m_DisambiguationMenuDelay;
 
+    /**
+     * Enable the new Design Blocks feature
+     *
+     * Setting name: "EnableDesignBlocks"
+     * Valid values: true or false
+     * Default value: false
+     */
+    bool m_EnableDesignBlocks;
+
     /**
      * Enable support for generators.
      *
diff --git a/include/core/typeinfo.h b/include/core/typeinfo.h
index 20d663cb75..4ad06c1ede 100644
--- a/include/core/typeinfo.h
+++ b/include/core/typeinfo.h
@@ -228,6 +228,7 @@ enum KICAD_T
      */
     SYMBOL_LIB_TABLE_T,
     FP_LIB_TABLE_T,
+    DESIGN_BLOCK_LIB_TABLE_T,
     SYMBOL_LIBS_T,
     SEARCH_STACK_T,
     S3D_CACHE_T,
@@ -516,6 +517,7 @@ constexpr bool IsMiscType( const KICAD_T aType )
 
     case SYMBOL_LIB_TABLE_T:
     case FP_LIB_TABLE_T:
+    case DESIGN_BLOCK_LIB_TABLE_T:
     case SYMBOL_LIBS_T:
     case SEARCH_STACK_T:
     case S3D_CACHE_T:
diff --git a/include/design_block_lib_table.h b/include/design_block_lib_table.h
new file mode 100644
index 0000000000..60c6868988
--- /dev/null
+++ b/include/design_block_lib_table.h
@@ -0,0 +1,291 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+#ifndef DESIGN_BLOCK_LIB_TABLE_H_
+#define DESIGN_BLOCK_LIB_TABLE_H_
+
+#include <kicommon.h>
+#include <lib_table_base.h>
+#include <design_block_io.h>
+#include <design_block_info_impl.h>
+
+class DESIGN_BLOCK;
+class DESIGN_BLOCK_LIB_TABLE_GRID;
+
+/**
+ * Hold a record identifying a library accessed by the appropriate design block library #PLUGIN
+ * object in the #DESIGN_BLOCK_LIB_TABLE.
+ */
+class KICOMMON_API DESIGN_BLOCK_LIB_TABLE_ROW : public LIB_TABLE_ROW
+{
+public:
+    DESIGN_BLOCK_LIB_TABLE_ROW( const wxString& aNick, const wxString& aURI, const wxString& aType,
+                                const wxString& aOptions, const wxString& aDescr = wxEmptyString ) :
+            LIB_TABLE_ROW( aNick, aURI, aOptions, aDescr )
+    {
+        SetType( aType );
+    }
+
+    DESIGN_BLOCK_LIB_TABLE_ROW() : type( DESIGN_BLOCK_IO_MGR::KICAD_SEXP ) {}
+
+    bool operator==( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) const;
+
+    bool operator!=( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) const { return !( *this == aRow ); }
+
+    /**
+     * return the type of design block library table represented by this row.
+     */
+    const wxString GetType() const override { return DESIGN_BLOCK_IO_MGR::ShowType( type ); }
+
+    /**
+     * Change the type represented by this row.
+     */
+    void SetType( const wxString& aType ) override;
+
+    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T GetFileType() { return type; }
+
+protected:
+    DESIGN_BLOCK_LIB_TABLE_ROW( const DESIGN_BLOCK_LIB_TABLE_ROW& aRow ) :
+            LIB_TABLE_ROW( aRow ), type( aRow.type )
+    {
+    }
+
+private:
+    virtual LIB_TABLE_ROW* do_clone() const override
+    {
+        return new DESIGN_BLOCK_LIB_TABLE_ROW( *this );
+    }
+
+    void setPlugin( DESIGN_BLOCK_IO* aPlugin ) { plugin.reset( aPlugin ); }
+
+    friend class DESIGN_BLOCK_LIB_TABLE;
+
+private:
+    IO_RELEASER<DESIGN_BLOCK_IO>             plugin;
+    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T type;
+};
+
+
+class KICOMMON_API DESIGN_BLOCK_LIB_TABLE : public LIB_TABLE
+{
+public:
+    PROJECT::ELEM ProjectElementType() override { return PROJECT::ELEM::DESIGN_BLOCK_LIB_TABLE; }
+
+    virtual void Parse( LIB_TABLE_LEXER* aLexer ) override;
+
+    virtual void Format( OUTPUTFORMATTER* aOutput, int aIndentLevel ) const override;
+
+    /**
+     * Build a design block library table by pre-pending this table fragment in front of
+     * @a aFallBackTable.  Loading of this table fragment is done by using Parse().
+     *
+     * @param aFallBackTable is another DESIGN_BLOCK_LIB_TABLE which is searched only when a row
+     *                       is not found in this table.  No ownership is taken of
+     *                       \a aFallBackTable.
+     */
+    DESIGN_BLOCK_LIB_TABLE( DESIGN_BLOCK_LIB_TABLE* aFallBackTable = nullptr );
+
+    bool operator==( const DESIGN_BLOCK_LIB_TABLE& aFpTable ) const;
+
+    bool operator!=( const DESIGN_BLOCK_LIB_TABLE& r ) const { return !( *this == r ); }
+
+    /**
+     * Return an #DESIGN_BLOCK_LIB_TABLE_ROW if \a aNickName is found in this table or in any chained
+     * fall back table fragment.
+     *
+     * If \a aCheckIfEnabled is true, the library will be ignored even if it is disabled.
+     * Otherwise, the row found will be returned even if entry is disabled.
+     *
+     * The #PLUGIN is loaded and attached to the "plugin" field of the #DESIGN_BLOCK_LIB_TABLE_ROW if
+     * not already loaded.
+     *
+     * @param aNickName is the name of library nickname to find.
+     * @param aCheckIfEnabled is the flag to check if the library found is enabled.
+     * @return the library \a NickName if found.
+     * @throw IO_ERROR if \a aNickName cannot be found.
+     */
+    const DESIGN_BLOCK_LIB_TABLE_ROW* FindRow( const wxString& aNickName,
+                                               bool            aCheckIfEnabled = false );
+
+    /**
+     * Return a list of design block names contained within the library given by @a aNickname.
+     *
+     * @param aDesignBlockNames is the list to fill with the design block names found in \a aNickname
+     * @param aNickname is a locator for the "library", it is a "name" in LIB_TABLE_ROW.
+     * @param aBestEfforts if true, don't throw on errors.
+     *
+     * @throw IO_ERROR if the library cannot be found, or design block cannot be loaded.
+     */
+    void DesignBlockEnumerate( wxArrayString& aDesignBlockNames, const wxString& aNickname,
+                               bool aBestEfforts );
+
+    /**
+     * Generate a hashed timestamp representing the last-mod-times of the library indicated
+     * by \a aNickname, or all libraries if \a aNickname is NULL.
+     */
+    long long GenerateTimestamp( const wxString* aNickname );
+
+    /**
+     * If possible, prefetches the specified library (e.g. performing downloads). Does not parse.
+     * Threadsafe.
+     *
+     * This is a no-op for libraries that cannot be prefetched.
+     *
+     * @param aNickname is a locator for the library; it is a name in LIB_TABLE_ROW.
+     *
+     * @throw IO_ERROR if there is an error prefetching the library.
+     */
+    void PrefetchLib( const wxString& aNickname );
+
+    /**
+     * Load a design block having @a aDesignBlockName from the library given by @a aNickname.
+     *
+     * @param aNickname is a locator for the "library", it is a "name" in #LIB_TABLE_ROW.
+     * @param aDesignBlockName is the name of the design block to load.
+     * @param aKeepUUID = true to keep initial items UUID, false to set new UUID
+     *                   normally true if loaded in the design block editor, false
+     *                   if loaded in the board editor. Used only in kicad_plugin
+     * @return  the design block if found caller owns it, else NULL if not found.
+     *
+     * @throw   IO_ERROR if the library cannot be found or read.  No exception
+     *          is thrown in the case where aDesignBlockName cannot be found.
+     */
+    DESIGN_BLOCK* DesignBlockLoad( const wxString& aNickname, const wxString& aDesignBlockName,
+                                   bool aKeepUUID = false );
+
+    /**
+     * Indicates whether or not the given design block already exists in the given library.
+     */
+    bool DesignBlockExists( const wxString& aNickname, const wxString& aDesignBlockName );
+
+    /**
+     * A version of #DesignBlockLoad() for use after #DesignBlockEnumerate() for more efficient
+     * cache management.
+     *
+     * The return value is const to allow it to return a reference to a cached item.
+     */
+    const DESIGN_BLOCK* GetEnumeratedDesignBlock( const wxString& aNickname,
+                                                  const wxString& aDesignBlockName );
+    /**
+     * The set of return values from DesignBlockSave() below.
+     */
+    enum SAVE_T
+    {
+        SAVE_OK,
+        SAVE_SKIPPED,
+    };
+
+    /**
+     * Write @a aDesignBlock to an existing library given by @a aNickname.
+     *
+     * If a design block by the same name already exists, it is replaced.
+     *
+     * @param aNickname is a locator for the "library", it is a "name" in #LIB_TABLE_ROW.
+     * @param aDesignBlock is what to store in the library. The caller continues to own the
+     *                   design block after this call.
+     * @param aOverwrite when true means overwrite any existing design block by the same name,
+     *                   else if false means skip the write and return SAVE_SKIPPED.
+     * @return #SAVE_OK or #SAVE_SKIPPED.  If error saving, then #IO_ERROR is thrown.
+     *
+     * @throw IO_ERROR if there is a problem saving.
+     */
+    SAVE_T DesignBlockSave( const wxString& aNickname, const DESIGN_BLOCK* aDesignBlock,
+                            bool aOverwrite = true );
+
+    /**
+     * Delete the @a aDesignBlockName from the library given by @a aNickname.
+     *
+     * @param aNickname is a locator for the "library", it is a "name" in #LIB_TABLE_ROW.
+     * @param aDesignBlockName is the name of a design block to delete from the specified library.
+     *
+     * @throw IO_ERROR if there is a problem finding the design block or the library, or deleting it.
+     */
+    void DesignBlockDelete( const wxString& aNickname, const wxString& aDesignBlockName );
+
+    /**
+     * Return true if the library given by @a aNickname is writable.
+     *
+     * Often system libraries are read only because of where they are installed.
+     *
+     * @throw IO_ERROR if no library at aLibraryPath exists.
+     */
+    bool IsDesignBlockLibWritable( const wxString& aNickname );
+
+    void DesignBlockLibDelete( const wxString& aNickname );
+
+    void DesignBlockLibCreate( const wxString& aNickname );
+
+    /**
+     * Load a design block having @a aDesignBlockId with possibly an empty nickname.
+     *
+     * @param aDesignBlockId the [nickname] and name of the design block to load.
+     * @param aKeepUUID = true to keep initial items UUID, false to set new UUID
+     *                   normally true if loaded in the design block editor, false
+     *                   if loaded in the board editor
+     *                   used only in kicad_plugin
+     * @return  the #DESIGN_BLOCK if found caller owns it, else NULL if not found.
+     *
+     * @throw   IO_ERROR if the library cannot be found or read.  No exception is
+     *                   thrown in the case where \a aDesignBlockName cannot be found.
+     * @throw   PARSE_ERROR if @a aDesignBlockId is not parsed OK.
+     */
+    DESIGN_BLOCK* DesignBlockLoadWithOptionalNickname( const LIB_ID& aDesignBlockId,
+                                                       bool          aKeepUUID = false );
+
+    /**
+     * Load the global design block library table into \a aTable.
+     *
+     * This probably should be move into the application object when KiCad is changed
+     * to a single process application.  This is the least painful solution for the
+     * time being.
+     *
+     * @param aTable the #DESIGN_BLOCK_LIB_TABLE object to load.
+     * @return true if the global library table exists and is loaded properly.
+     * @throw IO_ERROR if an error occurs attempting to load the design block library
+     *                 table.
+     */
+    static bool LoadGlobalTable( DESIGN_BLOCK_LIB_TABLE& aTable );
+
+    /**
+     * @return the platform specific global design block library path and file name.
+     */
+    static wxString GetGlobalTableFileName();
+
+    /**
+     * Return the name of the environment variable used to hold the directory of
+     * locally installed "KiCad sponsored" system design block libraries.
+     *
+     *These can be either legacy or pretty format.  The only thing special about this
+     * particular environment variable is that it is set automatically by KiCad on
+     * program start up, <b>if</b> it is not set already in the environment.
+     */
+    static const wxString GlobalPathEnvVariableName();
+
+private:
+    friend class DESIGN_BLOCK_LIB_TABLE_GRID;
+};
+
+KICOMMON_API extern DESIGN_BLOCK_LIB_TABLE GDesignBlockTable;
+KICOMMON_API extern DESIGN_BLOCK_LIST_IMPL GDesignBlockList;
+
+#endif // DESIGN_BLOCK_LIB_TABLE_H_
diff --git a/include/eda_draw_frame.h b/include/eda_draw_frame.h
index e387f8dfff..e0ca6f2064 100644
--- a/include/eda_draw_frame.h
+++ b/include/eda_draw_frame.h
@@ -417,6 +417,8 @@ public:
 
     static const wxString NetInspectorPanelName() { return wxS( "NetInspector" ); }
 
+    static const wxString DesignBlocksPaneName() { return wxS( "DesignBlocks" ); }
+
     /**
      * Fetch an item by KIID.  Frame-type-specific implementation.
      */
diff --git a/include/frame_type.h b/include/frame_type.h
index 77be547e80..a3fa800942 100644
--- a/include/frame_type.h
+++ b/include/frame_type.h
@@ -62,7 +62,7 @@ enum FRAME_T
 
     FRAME_CALC,
 
-    KIWAY_PLAYER_COUNT,         // counts subset of FRAME_T's which are KIWAY_PLAYER derivatives
+    KIWAY_PLAYER_COUNT, // counts subset of FRAME_T's which are KIWAY_PLAYER derivatives
 
     // C++ project manager is not a KIWAY_PLAYER
     KICAD_MAIN_FRAME_T = KIWAY_PLAYER_COUNT,
@@ -112,11 +112,12 @@ enum FRAME_T
 
     // Library table dialogs are transient and are never returned
     DIALOG_CONFIGUREPATHS,
+    DIALOG_DESIGN_BLOCK_LIBRARY_TABLE,
     DIALOG_SCH_LIBRARY_TABLE,
     DIALOG_PCB_LIBRARY_TABLE
 };
 
-    //TEXT_EDITOR_FRAME_T,
+//TEXT_EDITOR_FRAME_T,
 
 
 #endif  // FRAME_T_H_
diff --git a/include/id.h b/include/id.h
index 332493cd26..67e5a59d62 100644
--- a/include/id.h
+++ b/include/id.h
@@ -71,8 +71,7 @@
 
 enum main_id
 {
-    ID_APPEND_PROJECT         = wxID_HIGHEST,
-    ID_LOAD_FILE,
+    ID_LOAD_FILE = wxID_HIGHEST,
     ID_NEW_BOARD,
     ID_SAVE_BOARD,
     ID_SAVE_BOARD_AS,
diff --git a/include/lib_tree_model.h b/include/lib_tree_model.h
index a97a551014..c2ea06a45c 100644
--- a/include/lib_tree_model.h
+++ b/include/lib_tree_model.h
@@ -285,6 +285,16 @@ public:
      */
     LIB_TREE_NODE_LIBRARY& AddLib( wxString const& aName, wxString const& aDesc );
 
+    /**
+     * Remove a library node from the root.
+     */
+    void RemoveLib( wxString const& aName );
+
+    /**
+     * Clear the tree
+     */
+    void Clear();
+
     void UpdateScore( EDA_COMBINED_MATCHER* aMatcher, const wxString& aLib,
                       std::function<bool( LIB_TREE_NODE& aNode )>* aFilter ) override;
 };
diff --git a/include/lib_tree_model_adapter.h b/include/lib_tree_model_adapter.h
index 5ed660f0b4..6b57667816 100644
--- a/include/lib_tree_model_adapter.h
+++ b/include/lib_tree_model_adapter.h
@@ -92,6 +92,8 @@
  * - `HasDefaultCompare()` - whether sorted by default
  */
 
+#include <project.h>
+
 class APP_SETTINGS_BASE;
 class TOOL_INTERACTIVE;
 class EDA_BASE_FRAME;
@@ -182,6 +184,13 @@ public:
                        const std::vector<LIB_TREE_ITEM*>& aItemList,
                        bool pinned, bool presorted );
 
+    /**
+     * Remove the library by name.
+     *
+     * @param aNodeName    the name of the library to remove
+     */
+    void DoRemoveLibrary( const wxString& aNodeName );
+
     std::vector<wxString> GetAvailableColumns() const { return m_availableColumns; }
 
     std::vector<wxString> GetShownColumns() const { return m_shownColumns; }
@@ -390,7 +399,7 @@ protected:
                   unsigned int            aCol,
                   wxDataViewItemAttr&     aAttr ) const override;
 
-    virtual bool isSymbolModel() = 0;
+    virtual PROJECT::LIB_TYPE_T getLibType() = 0;
 
     void resortTree();
 
diff --git a/include/paths.h b/include/paths.h
index 84052eb7c3..3cdafd9614 100644
--- a/include/paths.h
+++ b/include/paths.h
@@ -67,6 +67,11 @@ public:
      */
     static wxString GetDefaultUserFootprintsPath();
 
+    /**
+     * Gets the default path we point users to create projects
+     */
+    static wxString GetDefaultUserDesignBlocksPath();
+
     /**
      * Gets the default path we point users to create projects
      */
@@ -98,6 +103,11 @@ public:
      */
     static wxString GetStockFootprintsPath();
 
+    /**
+     * Gets the stock (install) footprints path
+     */
+    static wxString GetStockDesignBlocksPath();
+
     /**
      * Gets the stock (install) 3dmodels path
      */
diff --git a/include/project.h b/include/project.h
index bf40950736..9afcb150b6 100644
--- a/include/project.h
+++ b/include/project.h
@@ -42,6 +42,7 @@
 /// default name for nameless projects
 #define NAMELESS_PROJECT _( "untitled" )
 
+class DESIGN_BLOCK_LIB_TABLE;
 class FP_LIB_TABLE;
 class SYMBOL_LIBS;
 class SEARCH_STACK;
@@ -75,6 +76,8 @@ public:
         SYMBOL_LIB_TABLE,
         SEARCH_STACK,
 
+        DESIGN_BLOCK_LIB_TABLE,
+
         COUNT
     };
 
@@ -177,8 +180,22 @@ public:
      */
     virtual const wxString SymbolLibTableName() const;
 
-    void PinLibrary( const wxString& aLibrary, bool isSymbolLibrary );
-    void UnpinLibrary( const wxString& aLibrary, bool isSymbolLibrary );
+    /**
+     * Return the path and file name of this projects design block library table.
+     */
+    virtual const wxString DesignBlockLibTblName() const;
+
+    enum LIB_TYPE_T
+    {
+        SYMBOL_LIB,
+        FOOTPRINT_LIB,
+        DESIGN_BLOCK_LIB,
+
+        LIB_TYPE_COUNT
+    };
+
+    void PinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType );
+    void UnpinLibrary( const wxString& aLibrary, enum LIB_TYPE_T aLibType );
 
     virtual PROJECT_FILE& GetProjectFile() const
     {
@@ -277,6 +294,11 @@ public:
      */
     virtual FP_LIB_TABLE* PcbFootprintLibs( KIWAY& aKiway );
 
+    /**
+     * Return the table of design block libraries.
+     */
+    virtual DESIGN_BLOCK_LIB_TABLE* DesignBlockLibs();
+
 private:
     friend class SETTINGS_MANAGER; // so that SM can set project path
     friend class TEST_NETLISTS_FIXTURE; // TODO(JE) make this not required
diff --git a/include/project/project_file.h b/include/project/project_file.h
index 7b5371ef31..e4500dbcbe 100644
--- a/include/project/project_file.h
+++ b/include/project/project_file.h
@@ -123,6 +123,9 @@ public:
     /// The list of pinned footprint libraries
     std::vector<wxString> m_PinnedFootprintLibs;
 
+    /// The list of pinned design block libraries
+    std::vector<wxString> m_PinnedDesignBlockLibs;
+
     std::map<wxString, wxString> m_TextVars;
 
     /**
diff --git a/include/settings/common_settings.h b/include/settings/common_settings.h
index c32f6865c3..1fd4d8df61 100644
--- a/include/settings/common_settings.h
+++ b/include/settings/common_settings.h
@@ -114,6 +114,7 @@ public:
         bool remember_open_files;
         std::vector<wxString> pinned_symbol_libs;
         std::vector<wxString> pinned_fp_libs;
+        std::vector<wxString> pinned_design_block_libs;
     };
 
     struct SYSTEM
diff --git a/include/settings/kicad_settings.h b/include/settings/kicad_settings.h
index 20e6905841..08123c6a2c 100644
--- a/include/settings/kicad_settings.h
+++ b/include/settings/kicad_settings.h
@@ -39,6 +39,8 @@ public:
 
     std::vector<wxString> m_OpenProjects;
 
+    wxString m_lastDesignBlockLibDir;
+
     std::vector<std::pair<wxString, wxString>> m_PcmRepositories;
     wxString                                   m_PcmLastDownloadDir;
 
diff --git a/include/tool/actions.h b/include/tool/actions.h
index 1d110842a1..e3209c4282 100644
--- a/include/tool/actions.h
+++ b/include/tool/actions.h
@@ -217,6 +217,7 @@ public:
     static TOOL_ACTION configurePaths;
     static TOOL_ACTION showSymbolLibTable;
     static TOOL_ACTION showFootprintLibTable;
+    static TOOL_ACTION showDesignBlockLibTable;
     static TOOL_ACTION gettingStarted;
     static TOOL_ACTION help;
     static TOOL_ACTION about;
diff --git a/include/trace_helpers.h b/include/trace_helpers.h
index 3eae51bf89..4eb4080662 100644
--- a/include/trace_helpers.h
+++ b/include/trace_helpers.h
@@ -241,6 +241,13 @@ extern KICOMMON_API const wxChar* const traceGit;
  */
 extern KICOMMON_API const wxChar* const traceEagleIo;
 
+/*
+ * Flag to enable Design Block O debug tracing.
+ *
+ * Use "KICAD_EAGLE_IO" to enable.
+ */
+extern KICOMMON_API const wxChar* const traceDesignBlocks;
+
 ///@}
 
 /**
diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h
index e7df1c4cc1..796d1be2bf 100644
--- a/include/wildcards_and_files_ext.h
+++ b/include/wildcards_and_files_ext.h
@@ -176,6 +176,9 @@ public:
     static const std::string Ipc2581FileExtension;
     static const std::string WorkbookFileExtension;
 
+    static const std::string KiCadDesignBlockLibPathExtension;
+    static const std::string KiCadDesignBlockPathExtension;
+
     static const std::string PngFileExtension;
     static const std::string JpegFileExtension;
     static const std::string TextFileExtension;
@@ -246,6 +249,8 @@ public:
     static wxString DocModulesFileName();
     static wxString KiCadFootprintLibFileWildcard();
     static wxString KiCadFootprintLibPathWildcard();
+    static wxString KiCadDesignBlockLibPathWildcard();
+    static wxString KiCadDesignBlockPathWildcard();
     static wxString TextFileWildcard();
     static wxString ModLegacyExportFileWildcard();
     static wxString ErcFileWildcard();
diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt
index e34422c9a2..9833eed72f 100644
--- a/kicad/CMakeLists.txt
+++ b/kicad/CMakeLists.txt
@@ -9,6 +9,7 @@ add_subdirectory( pcm )
 
 include_directories( BEFORE ${INC_BEFORE} )
 include_directories(
+    ${CMAKE_SOURCE_DIR}/common/dialogs
     ${CMAKE_SOURCE_DIR}/pcbnew
     ${CMAKE_SOURCE_DIR}/eeschema
     ${CMAKE_SOURCE_DIR}/kicad/pcm
@@ -24,6 +25,8 @@ set( KICAD_SRCS
     dialogs/dialog_template_selector.cpp
     dialogs/panel_kicad_launcher_base.cpp
     dialogs/panel_kicad_launcher.cpp
+    dialogs/panel_design_block_lib_table_base.cpp
+    dialogs/panel_design_block_lib_table.cpp
     files-io.cpp
     import_proj.cpp
     import_project.cpp
diff --git a/kicad/dialogs/panel_design_block_lib_table.cpp b/kicad/dialogs/panel_design_block_lib_table.cpp
new file mode 100644
index 0000000000..e149d365e3
--- /dev/null
+++ b/kicad/dialogs/panel_design_block_lib_table.cpp
@@ -0,0 +1,1155 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2012-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, you may find one here:
+ * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
+ * or you may search the http://www.gnu.org website for the version 2 license,
+ * or you may write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
+ */
+
+
+/*  TODO:
+
+*)  After any change to uri, reparse the environment variables.
+
+*/
+
+
+#include <set>
+#include <wx/dir.h>
+#include <wx/log.h>
+#include <wx/regex.h>
+#include <wx/grid.h>
+#include <wx/dirdlg.h>
+#include <wx/filedlg.h>
+#include <wx/msgdlg.h>
+
+#include <common.h>
+#include <project.h>
+#include <env_vars.h>
+#include <lib_id.h>
+#include <design_block_lib_table.h>
+#include <lib_table_lexer.h>
+#include <bitmaps.h>
+#include <lib_table_grid_tricks.h>
+#include <widgets/wx_grid.h>
+#include <widgets/std_bitmap_button.h>
+#include <confirm.h>
+#include <lib_table_grid.h>
+#include <wildcards_and_files_ext.h>
+#include <pgm_base.h>
+#include <env_paths.h>
+#include <dialogs/panel_design_block_lib_table.h>
+#include <dialogs/dialog_edit_library_tables.h>
+#include <dialogs/dialog_plugin_options.h>
+#include <kiway.h>
+#include <kiway_express.h>
+#include <widgets/grid_readonly_text_helpers.h>
+#include <widgets/grid_text_button_helpers.h>
+#include <settings/settings_manager.h>
+#include <settings/kicad_settings.h>
+#include <paths.h>
+#include <macros.h>
+
+// clang-format off
+
+/**
+ * Container that describes file type info for the add a library options
+ */
+struct SUPPORTED_FILE_TYPE
+{
+    wxString m_Description;            ///< Description shown in the file picker dialog
+    wxString m_FileFilter;             ///< Filter used for file pickers if m_IsFile is true
+    wxString m_FolderSearchExtension;  ///< In case of folders it stands for extensions of files stored inside
+    bool     m_IsFile;                 ///< Whether the library is a folder or a file
+    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T m_Plugin;
+};
+
+// clang-format on
+
+/**
+ * Traverser implementation that looks to find any and all "folder" libraries by looking for files
+ * with a specific extension inside folders
+ */
+class LIBRARY_TRAVERSER : public wxDirTraverser
+{
+public:
+    LIBRARY_TRAVERSER( std::vector<std::string> aSearchExtensions, wxString aInitialDir ) :
+            m_searchExtensions( aSearchExtensions ), m_currentDir( aInitialDir )
+    {
+    }
+
+    virtual wxDirTraverseResult OnFile( const wxString& aFileName ) override
+    {
+        wxFileName file( aFileName );
+
+        for( const std::string& ext : m_searchExtensions )
+        {
+            if( file.GetExt().IsSameAs( ext, false ) )
+                m_foundDirs.insert( { m_currentDir, 1 } );
+        }
+
+        return wxDIR_CONTINUE;
+    }
+
+    virtual wxDirTraverseResult OnOpenError( const wxString& aOpenErrorName ) override
+    {
+        m_failedDirs.insert( { aOpenErrorName, 1 } );
+        return wxDIR_IGNORE;
+    }
+
+    bool HasDirectoryOpenFailures() { return m_failedDirs.size() > 0; }
+
+    virtual wxDirTraverseResult OnDir( const wxString& aDirName ) override
+    {
+        m_currentDir = aDirName;
+        return wxDIR_CONTINUE;
+    }
+
+    void GetPaths( wxArrayString& aPathArray )
+    {
+        for( std::pair<const wxString, int>& foundDirsPair : m_foundDirs )
+            aPathArray.Add( foundDirsPair.first );
+    }
+
+    void GetFailedPaths( wxArrayString& aPathArray )
+    {
+        for( std::pair<const wxString, int>& failedDirsPair : m_failedDirs )
+            aPathArray.Add( failedDirsPair.first );
+    }
+
+private:
+    std::vector<std::string>          m_searchExtensions;
+    wxString                          m_currentDir;
+    std::unordered_map<wxString, int> m_foundDirs;
+    std::unordered_map<wxString, int> m_failedDirs;
+};
+
+
+/**
+ * This class builds a wxGridTableBase by wrapping an #DESIGN_BLOCK_LIB_TABLE object.
+ */
+class DESIGN_BLOCK_LIB_TABLE_GRID : public LIB_TABLE_GRID, public DESIGN_BLOCK_LIB_TABLE
+{
+    friend class PANEL_DESIGN_BLOCK_LIB_TABLE;
+    friend class DESIGN_BLOCK_GRID_TRICKS;
+
+protected:
+    LIB_TABLE_ROW* at( size_t aIndex ) override { return &m_rows.at( aIndex ); }
+
+    size_t size() const override { return m_rows.size(); }
+
+    LIB_TABLE_ROW* makeNewRow() override
+    {
+        return dynamic_cast<LIB_TABLE_ROW*>( new DESIGN_BLOCK_LIB_TABLE_ROW );
+    }
+
+    LIB_TABLE_ROWS_ITER begin() override { return m_rows.begin(); }
+
+    LIB_TABLE_ROWS_ITER insert( LIB_TABLE_ROWS_ITER aIterator, LIB_TABLE_ROW* aRow ) override
+    {
+        return m_rows.insert( aIterator, aRow );
+    }
+
+    void push_back( LIB_TABLE_ROW* aRow ) override { m_rows.push_back( aRow ); }
+
+    LIB_TABLE_ROWS_ITER erase( LIB_TABLE_ROWS_ITER aFirst, LIB_TABLE_ROWS_ITER aLast ) override
+    {
+        return m_rows.erase( aFirst, aLast );
+    }
+
+public:
+    DESIGN_BLOCK_LIB_TABLE_GRID( const DESIGN_BLOCK_LIB_TABLE& aTableToEdit )
+    {
+        m_rows = aTableToEdit.m_rows;
+    }
+
+    void SetValue( int aRow, int aCol, const wxString& aValue ) override
+    {
+        wxCHECK( aRow < (int) size(), /* void */ );
+
+        LIB_TABLE_GRID::SetValue( aRow, aCol, aValue );
+
+        // If setting a filepath, attempt to auto-detect the format
+        if( aCol == COL_URI )
+        {
+            LIB_TABLE_ROW* row = at( (size_t) aRow );
+            wxString       fullURI = row->GetFullURI( true );
+
+            DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T pluginType =
+                    DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( fullURI );
+
+            if( pluginType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
+                pluginType = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
+
+            SetValue( aRow, COL_TYPE, DESIGN_BLOCK_IO_MGR::ShowType( pluginType ) );
+        }
+    }
+};
+
+
+class DESIGN_BLOCK_GRID_TRICKS : public LIB_TABLE_GRID_TRICKS
+{
+public:
+    DESIGN_BLOCK_GRID_TRICKS( DIALOG_EDIT_LIBRARY_TABLES* aParent, WX_GRID* aGrid ) :
+            LIB_TABLE_GRID_TRICKS( aGrid ), m_dialog( aParent )
+    {
+    }
+
+protected:
+    DIALOG_EDIT_LIBRARY_TABLES* m_dialog;
+
+    void optionsEditor( int aRow ) override
+    {
+        DESIGN_BLOCK_LIB_TABLE_GRID* tbl = (DESIGN_BLOCK_LIB_TABLE_GRID*) m_grid->GetTable();
+
+        if( tbl->GetNumberRows() > aRow )
+        {
+            LIB_TABLE_ROW*  row = tbl->at( (size_t) aRow );
+            const wxString& options = row->GetOptions();
+            wxString        result = options;
+            std::map<std::string, UTF8> choices;
+
+            DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T pi_type =
+                    DESIGN_BLOCK_IO_MGR::EnumFromStr( row->GetType() );
+            IO_RELEASER<DESIGN_BLOCK_IO> pi( DESIGN_BLOCK_IO_MGR::FindPlugin( pi_type ) );
+            pi->GetLibraryOptions( &choices );
+
+            DIALOG_PLUGIN_OPTIONS dlg( m_dialog, row->GetNickName(), choices, options, &result );
+            dlg.ShowModal();
+
+            if( options != result )
+            {
+                row->SetOptions( result );
+                m_grid->Refresh();
+            }
+        }
+    }
+
+    /// handle specialized clipboard text, with leading "(design_block_lib_table", OR
+    /// spreadsheet formatted text.
+    void paste_text( const wxString& cb_text ) override
+    {
+        DESIGN_BLOCK_LIB_TABLE_GRID* tbl = (DESIGN_BLOCK_LIB_TABLE_GRID*) m_grid->GetTable();
+        size_t                       ndx = cb_text.find( "(design_block_lib_table" );
+
+        if( ndx != std::string::npos )
+        {
+            // paste the DESIGN_BLOCK_LIB_TABLE_ROWs of s-expression (design_block_lib_table), starting
+            // at column 0 regardless of current cursor column.
+
+            STRING_LINE_READER     slr( TO_UTF8( cb_text ), wxT( "Clipboard" ) );
+            LIB_TABLE_LEXER        lexer( &slr );
+            DESIGN_BLOCK_LIB_TABLE tmp_tbl;
+            bool                   parsed = true;
+
+            try
+            {
+                tmp_tbl.Parse( &lexer );
+            }
+            catch( PARSE_ERROR& pe )
+            {
+                DisplayError( m_dialog, pe.What() );
+                parsed = false;
+            }
+
+            if( parsed )
+            {
+                // make sure the table is big enough...
+                if( tmp_tbl.GetCount() > (unsigned) tbl->GetNumberRows() )
+                    tbl->AppendRows( tmp_tbl.GetCount() - tbl->GetNumberRows() );
+
+                for( unsigned i = 0; i < tmp_tbl.GetCount(); ++i )
+                    tbl->m_rows.replace( i, tmp_tbl.At( i ).clone() );
+            }
+
+            m_grid->AutoSizeColumns( false );
+        }
+        else
+        {
+            // paste spreadsheet formatted text.
+            GRID_TRICKS::paste_text( cb_text );
+
+            m_grid->AutoSizeColumns( false );
+        }
+    }
+};
+
+
+PANEL_DESIGN_BLOCK_LIB_TABLE::PANEL_DESIGN_BLOCK_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent,
+                                                            PROJECT*                    aProject,
+                                                            DESIGN_BLOCK_LIB_TABLE* aGlobalTable,
+                                                            const wxString&         aGlobalTblPath,
+                                                            DESIGN_BLOCK_LIB_TABLE* aProjectTable,
+                                                            const wxString&         aProjectTblPath,
+                                                            const wxString& aProjectBasePath ) :
+        PANEL_DESIGN_BLOCK_LIB_TABLE_BASE( aParent ),
+        m_globalTable( aGlobalTable ), m_projectTable( aProjectTable ), m_project( aProject ),
+        m_projectBasePath( aProjectBasePath ), m_parent( aParent )
+{
+    m_global_grid->SetTable( new DESIGN_BLOCK_LIB_TABLE_GRID( *aGlobalTable ), true );
+
+    // add Cut, Copy, and Paste to wxGrids
+    m_path_subs_grid->PushEventHandler( new GRID_TRICKS( m_path_subs_grid ) );
+
+    populatePluginList();
+
+    wxArrayString choices;
+
+    for( auto& [fileType, desc] : m_supportedDesignBlockFiles )
+        choices.Add( DESIGN_BLOCK_IO_MGR::ShowType( fileType ) );
+
+
+    KICAD_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<KICAD_SETTINGS>();
+
+    if( cfg->m_lastDesignBlockLibDir.IsEmpty() )
+        cfg->m_lastDesignBlockLibDir = PATHS::GetDefaultUserDesignBlocksPath();
+
+    m_lastProjectLibDir = m_projectBasePath;
+
+    auto autoSizeCol = [&]( WX_GRID* aGrid, int aCol )
+    {
+        int prevWidth = aGrid->GetColSize( aCol );
+
+        aGrid->AutoSizeColumn( aCol, false );
+        aGrid->SetColSize( aCol, std::max( prevWidth, aGrid->GetColSize( aCol ) ) );
+    };
+
+    auto setupGrid = [&]( WX_GRID* aGrid )
+    {
+        // Give a bit more room for wxChoice editors
+        aGrid->SetDefaultRowSize( aGrid->GetDefaultRowSize() + 4 );
+
+        // add Cut, Copy, and Paste to wxGrids
+        aGrid->PushEventHandler( new DESIGN_BLOCK_GRID_TRICKS( m_parent, aGrid ) );
+
+        aGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
+
+        wxGridCellAttr* attr;
+
+        attr = new wxGridCellAttr;
+        attr->SetEditor( new GRID_CELL_PATH_EDITOR(
+                m_parent, aGrid, &cfg->m_lastDesignBlockLibDir, true, m_projectBasePath,
+                [this]( WX_GRID* grid, int row ) -> wxString
+                {
+                    auto* libTable = static_cast<DESIGN_BLOCK_LIB_TABLE_GRID*>( grid->GetTable() );
+                    auto* tableRow =
+                            static_cast<DESIGN_BLOCK_LIB_TABLE_ROW*>( libTable->at( row ) );
+                    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T fileType = tableRow->GetFileType();
+                    const IO_BASE::IO_FILE_DESC&             pluginDesc =
+                            m_supportedDesignBlockFiles.at( fileType );
+
+                    if( pluginDesc.m_IsFile )
+                        return pluginDesc.FileFilter();
+                    else
+                        return wxEmptyString;
+                } ) );
+        aGrid->SetColAttr( COL_URI, attr );
+
+        attr = new wxGridCellAttr;
+        attr->SetEditor( new wxGridCellChoiceEditor( choices ) );
+        aGrid->SetColAttr( COL_TYPE, attr );
+
+        attr = new wxGridCellAttr;
+        attr->SetRenderer( new wxGridCellBoolRenderer() );
+        attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
+        aGrid->SetColAttr( COL_ENABLED, attr );
+
+        // No visibility control for design block libraries yet; this feature is primarily
+        // useful for database libraries and it's only implemented for schematic symbols
+        // at the moment.
+        aGrid->HideCol( COL_VISIBLE );
+
+        // all but COL_OPTIONS, which is edited with Option Editor anyways.
+        autoSizeCol( aGrid, COL_NICKNAME );
+        autoSizeCol( aGrid, COL_TYPE );
+        autoSizeCol( aGrid, COL_URI );
+        autoSizeCol( aGrid, COL_DESCR );
+
+        // Gives a selection to each grid, mainly for delete button. wxGrid's wake up with
+        // a currentCell which is sometimes not highlighted.
+        if( aGrid->GetNumberRows() > 0 )
+            aGrid->SelectRow( 0 );
+    };
+
+    setupGrid( m_global_grid );
+
+    populateEnvironReadOnlyTable();
+
+    if( aProjectTable )
+    {
+        m_project_grid->SetTable( new DESIGN_BLOCK_LIB_TABLE_GRID( *aProjectTable ), true );
+        setupGrid( m_project_grid );
+    }
+    else
+    {
+        m_pageNdx = 0;
+        m_notebook->DeletePage( 1 );
+        m_project_grid = nullptr;
+    }
+
+    m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) );
+    m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) );
+
+    // select the last selected page
+    m_notebook->SetSelection( m_pageNdx );
+    m_cur_grid = ( m_pageNdx == 0 ) ? m_global_grid : m_project_grid;
+
+    // for ALT+A handling, we want the initial focus to be on the first selected grid.
+    m_parent->SetInitialFocus( m_cur_grid );
+
+    // Configure button logos
+    m_append_button->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
+    m_delete_button->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
+    m_move_up_button->SetBitmap( KiBitmapBundle( BITMAPS::small_up ) );
+    m_move_down_button->SetBitmap( KiBitmapBundle( BITMAPS::small_down ) );
+    m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
+
+    // For aesthetic reasons, we must set the size of m_browseButton to match the other bitmaps
+    // manually (for instance m_append_button)
+    Layout(); // Needed at least on MSW to compute the actual buttons sizes, after initializing
+              // their bitmaps
+    wxSize buttonSize = m_append_button->GetSize();
+
+    m_browseButton->SetWidthPadding( 4 );
+    m_browseButton->SetMinSize( buttonSize );
+
+    // Populate the browse library options
+    wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
+
+    auto joinExts = []( const std::vector<std::string>& aExts )
+    {
+        wxString joined;
+        for( const std::string& ext : aExts )
+        {
+            if( !joined.empty() )
+                joined << wxS( ", " );
+
+            joined << wxS( "*." ) << ext;
+        }
+
+        return joined;
+    };
+
+    for( auto& [type, desc] : m_supportedDesignBlockFiles )
+    {
+        wxString entryStr = DESIGN_BLOCK_IO_MGR::ShowType( type );
+
+        if( desc.m_IsFile && !desc.m_FileExtensions.empty() )
+        {
+            entryStr << wxString::Format( wxS( " (%s)" ), joinExts( desc.m_FileExtensions ) );
+        }
+        else if( !desc.m_IsFile && !desc.m_ExtensionsInDir.empty() )
+        {
+            wxString midPart = wxString::Format( _( "folder with %s files" ),
+                                                 joinExts( desc.m_ExtensionsInDir ) );
+
+            entryStr << wxString::Format( wxS( " (%s)" ), midPart );
+        }
+
+        browseMenu->Append( type, entryStr );
+
+        browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED,
+                          &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler, this, type );
+    }
+
+    Layout();
+
+    // This is the button only press for the browse button instead of the menu
+    m_browseButton->Bind( wxEVT_BUTTON, &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler,
+                          this );
+}
+
+
+PANEL_DESIGN_BLOCK_LIB_TABLE::~PANEL_DESIGN_BLOCK_LIB_TABLE()
+{
+    wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
+    for( auto& [type, desc] : m_supportedDesignBlockFiles )
+    {
+        browseMenu->Unbind( wxEVT_COMMAND_MENU_SELECTED,
+                            &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler, this, type );
+    }
+    m_browseButton->Unbind( wxEVT_BUTTON, &PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler,
+                            this );
+
+    // Delete the GRID_TRICKS.
+    // Any additional event handlers should be popped before the window is deleted.
+    m_global_grid->PopEventHandler( true );
+
+    if( m_project_grid )
+        m_project_grid->PopEventHandler( true );
+
+    m_path_subs_grid->PopEventHandler( true );
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::populatePluginList()
+{
+    for( const DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T& type :
+         { DESIGN_BLOCK_IO_MGR::KICAD_SEXP } )
+    {
+        IO_RELEASER<DESIGN_BLOCK_IO> pi( DESIGN_BLOCK_IO_MGR::FindPlugin( type ) );
+
+        if( !pi )
+            continue;
+
+        if( const IO_BASE::IO_FILE_DESC& desc = pi->GetLibraryDesc() )
+            m_supportedDesignBlockFiles.emplace( type, desc );
+    }
+}
+
+
+bool PANEL_DESIGN_BLOCK_LIB_TABLE::verifyTables()
+{
+    wxString msg;
+
+    for( DESIGN_BLOCK_LIB_TABLE_GRID* model : { global_model(), project_model() } )
+    {
+        if( !model )
+            continue;
+
+        for( int r = 0; r < model->GetNumberRows(); )
+        {
+            wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
+            wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
+            unsigned illegalCh = 0;
+
+            if( !nick || !uri )
+            {
+                if( !nick && !uri )
+                    msg = _( "A library table row nickname and path cells are empty." );
+                else if( !nick )
+                    msg = _( "A library table row nickname cell is empty." );
+                else
+                    msg = _( "A library table row path cell is empty." );
+
+                wxWindow* topLevelParent = wxGetTopLevelParent( this );
+
+                wxMessageDialog badCellDlg( topLevelParent, msg, _( "Invalid Row Definition" ),
+                                            wxYES_NO | wxCENTER | wxICON_QUESTION | wxYES_DEFAULT );
+                badCellDlg.SetExtendedMessage( _( "Empty cells will result in all rows that are "
+                                                  "invalid to be removed from the table." ) );
+                badCellDlg.SetYesNoLabels(
+                        wxMessageDialog::ButtonLabel( _( "Remove Invalid Cells" ) ),
+                        wxMessageDialog::ButtonLabel( _( "Cancel Table Update" ) ) );
+
+                if( badCellDlg.ShowModal() == wxID_NO )
+                    return false;
+
+                // Delete the "empty" row, where empty means missing nick or uri.
+                // This also updates the UI which could be slow, but there should only be a few
+                // rows to delete, unless the user fell asleep on the Add Row
+                // button.
+                model->DeleteRows( r, 1 );
+            }
+            else if( ( illegalCh = LIB_ID::FindIllegalLibraryNameChar( nick ) ) )
+            {
+                msg = wxString::Format( _( "Illegal character '%c' in nickname '%s'." ), illegalCh,
+                                        nick );
+
+                // show the tabbed panel holding the grid we have flunked:
+                if( model != cur_model() )
+                    m_notebook->SetSelection( model == global_model() ? 0 : 1 );
+
+                m_cur_grid->MakeCellVisible( r, 0 );
+                m_cur_grid->SetGridCursor( r, 1 );
+
+                wxWindow* topLevelParent = wxGetTopLevelParent( this );
+
+                wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
+                errdlg.ShowModal();
+                return false;
+            }
+            else
+            {
+                // set the trimmed values back into the table so they get saved to disk.
+                model->SetValue( r, COL_NICKNAME, nick );
+                model->SetValue( r, COL_URI, uri );
+
+                // Make sure to not save a hidden flag
+                model->SetValue( r, COL_VISIBLE, wxS( "1" ) );
+
+                ++r; // this row was OK.
+            }
+        }
+    }
+
+    // check for duplicate nickNames, separately in each table.
+    for( DESIGN_BLOCK_LIB_TABLE_GRID* model : { global_model(), project_model() } )
+    {
+        if( !model )
+            continue;
+
+        for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
+        {
+            wxString nick1 = model->GetValue( r1, COL_NICKNAME );
+
+            for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 )
+            {
+                wxString nick2 = model->GetValue( r2, COL_NICKNAME );
+
+                if( nick1 == nick2 )
+                {
+                    msg = wxString::Format( _( "Multiple libraries cannot share the same "
+                                               "nickname ('%s')." ),
+                                            nick1 );
+
+                    // show the tabbed panel holding the grid we have flunked:
+                    if( model != cur_model() )
+                        m_notebook->SetSelection( model == global_model() ? 0 : 1 );
+
+                    // go to the lower of the two rows, it is technically the duplicate:
+                    m_cur_grid->MakeCellVisible( r2, 0 );
+                    m_cur_grid->SetGridCursor( r2, 1 );
+
+                    wxWindow* topLevelParent = wxGetTopLevelParent( this );
+
+                    wxMessageDialog errdlg( topLevelParent, msg, _( "Library Nickname Error" ) );
+                    errdlg.ShowModal();
+                    return false;
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::OnUpdateUI( wxUpdateUIEvent& event )
+{
+    m_pageNdx = (unsigned) std::max( 0, m_notebook->GetSelection() );
+    m_cur_grid = m_pageNdx == 0 ? m_global_grid : m_project_grid;
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::appendRowHandler( wxCommandEvent& event )
+{
+    if( !m_cur_grid->CommitPendingChanges() )
+        return;
+
+    if( m_cur_grid->AppendRows( 1 ) )
+    {
+        int last_row = m_cur_grid->GetNumberRows() - 1;
+
+        // wx documentation is wrong, SetGridCursor does not make visible.
+        m_cur_grid->MakeCellVisible( last_row, COL_ENABLED );
+        m_cur_grid->SetGridCursor( last_row, COL_NICKNAME );
+        m_cur_grid->EnableCellEditControl( true );
+        m_cur_grid->ShowCellEditControl();
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::deleteRowHandler( wxCommandEvent& event )
+{
+    if( !m_cur_grid->CommitPendingChanges() )
+        return;
+
+    int curRow = m_cur_grid->GetGridCursorRow();
+    int curCol = m_cur_grid->GetGridCursorCol();
+
+    // In a wxGrid, collect rows that have a selected cell, or are selected
+    // It is not so easy: it depends on the way the selection was made.
+    // Here, we collect rows selected by clicking on a row label, and rows that contain any
+    // previously-selected cells.
+    // If no candidate, just delete the row with the grid cursor.
+    wxArrayInt            selectedRows = m_cur_grid->GetSelectedRows();
+    wxGridCellCoordsArray cells = m_cur_grid->GetSelectedCells();
+    wxGridCellCoordsArray blockTopLeft = m_cur_grid->GetSelectionBlockTopLeft();
+    wxGridCellCoordsArray blockBotRight = m_cur_grid->GetSelectionBlockBottomRight();
+
+    // Add all row having cell selected to list:
+    for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
+        selectedRows.Add( cells[ii].GetRow() );
+
+    // Handle block selection
+    if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
+    {
+        for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
+            selectedRows.Add( i );
+    }
+
+    // Use the row having the grid cursor only if we have no candidate:
+    if( selectedRows.size() == 0 && m_cur_grid->GetGridCursorRow() >= 0 )
+        selectedRows.Add( m_cur_grid->GetGridCursorRow() );
+
+    if( selectedRows.size() == 0 )
+    {
+        wxBell();
+        return;
+    }
+
+    std::sort( selectedRows.begin(), selectedRows.end() );
+
+    // Remove selected rows (note: a row can be stored more than once in list)
+    int last_row = -1;
+
+    // Needed to avoid a wxWidgets alert if the row to delete is the last row
+    // at least on wxMSW 3.2
+    m_cur_grid->ClearSelection();
+
+    for( int ii = selectedRows.GetCount() - 1; ii >= 0; ii-- )
+    {
+        int row = selectedRows[ii];
+
+        if( row != last_row )
+        {
+            last_row = row;
+            m_cur_grid->DeleteRows( row, 1 );
+        }
+    }
+
+    if( m_cur_grid->GetNumberRows() > 0 && curRow >= 0 )
+        m_cur_grid->SetGridCursor( std::min( curRow, m_cur_grid->GetNumberRows() - 1 ), curCol );
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::moveUpHandler( wxCommandEvent& event )
+{
+    if( !m_cur_grid->CommitPendingChanges() )
+        return;
+
+    DESIGN_BLOCK_LIB_TABLE_GRID* tbl = cur_model();
+    int                          curRow = m_cur_grid->GetGridCursorRow();
+
+    // @todo: add multiple selection moves.
+    if( curRow >= 1 )
+    {
+        boost::ptr_vector<LIB_TABLE_ROW>::auto_type move_me =
+                tbl->m_rows.release( tbl->m_rows.begin() + curRow );
+
+        --curRow;
+        tbl->m_rows.insert( tbl->m_rows.begin() + curRow, move_me.release() );
+
+        if( tbl->GetView() )
+        {
+            // Update the wxGrid
+            wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow, 0 );
+            tbl->GetView()->ProcessTableMessage( msg );
+        }
+
+        m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
+        m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::moveDownHandler( wxCommandEvent& event )
+{
+    if( !m_cur_grid->CommitPendingChanges() )
+        return;
+
+    DESIGN_BLOCK_LIB_TABLE_GRID* tbl = cur_model();
+    int                          curRow = m_cur_grid->GetGridCursorRow();
+
+    // @todo: add multiple selection moves.
+    if( unsigned( curRow + 1 ) < tbl->m_rows.size() )
+    {
+        boost::ptr_vector<LIB_TABLE_ROW>::auto_type move_me =
+                tbl->m_rows.release( tbl->m_rows.begin() + curRow );
+
+        ++curRow;
+        tbl->m_rows.insert( tbl->m_rows.begin() + curRow, move_me.release() );
+
+        if( tbl->GetView() )
+        {
+            // Update the wxGrid
+            wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow - 1, 0 );
+            tbl->GetView()->ProcessTableMessage( msg );
+        }
+
+        m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
+        m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
+    }
+}
+
+
+// @todo refactor this function into single location shared with PANEL_SYM_LIB_TABLE
+void PANEL_DESIGN_BLOCK_LIB_TABLE::onMigrateLibraries( wxCommandEvent& event )
+{
+    if( !m_cur_grid->CommitPendingChanges() )
+        return;
+
+    wxArrayInt selectedRows = m_cur_grid->GetSelectedRows();
+
+    if( selectedRows.empty() && m_cur_grid->GetGridCursorRow() >= 0 )
+        selectedRows.push_back( m_cur_grid->GetGridCursorRow() );
+
+    wxArrayInt rowsToMigrate;
+    wxString   kicadType = DESIGN_BLOCK_IO_MGR::ShowType( DESIGN_BLOCK_IO_MGR::KICAD_SEXP );
+    wxString   msg;
+
+    for( int row : selectedRows )
+    {
+        if( m_cur_grid->GetCellValue( row, COL_TYPE ) != kicadType )
+            rowsToMigrate.push_back( row );
+    }
+
+    if( rowsToMigrate.size() <= 0 )
+    {
+        wxMessageBox( wxString::Format( _( "Select one or more rows containing libraries "
+                                           "to save as current KiCad format." ) ) );
+        return;
+    }
+    else
+    {
+        if( rowsToMigrate.size() == 1 )
+        {
+            msg.Printf( _( "Save '%s' as current KiCad format "
+                           "and replace entry in table?" ),
+                        m_cur_grid->GetCellValue( rowsToMigrate[0], COL_NICKNAME ) );
+        }
+        else
+        {
+            msg.Printf( _( "Save %d libraries as current KiCad format "
+                           "and replace entries in table?" ),
+                        (int) rowsToMigrate.size() );
+        }
+
+        if( !IsOK( m_parent, msg ) )
+            return;
+    }
+
+    for( int row : rowsToMigrate )
+    {
+        wxString   libName = m_cur_grid->GetCellValue( row, COL_NICKNAME );
+        wxString   relPath = m_cur_grid->GetCellValue( row, COL_URI );
+        wxString   resolvedPath = ExpandEnvVarSubstitutions( relPath, m_project );
+        wxFileName legacyLib( resolvedPath );
+
+        if( !legacyLib.Exists() )
+        {
+            msg.Printf( _( "Library '%s' not found." ), relPath );
+            DisplayErrorMessage( wxGetTopLevelParent( this ), msg );
+            continue;
+        }
+
+        wxFileName newLib( resolvedPath );
+        newLib.AppendDir( newLib.GetName() + "." + FILEEXT::KiCadDesignBlockLibPathExtension );
+        newLib.SetName( "" );
+        newLib.ClearExt();
+
+        if( newLib.DirExists() )
+        {
+            msg.Printf( _( "Folder '%s' already exists. Do you want overwrite any existing design "
+                           "blocks?" ),
+                        newLib.GetFullPath() );
+
+            switch( wxMessageBox( msg, _( "Migrate Library" ),
+                                  wxYES_NO | wxCANCEL | wxICON_QUESTION, m_parent ) )
+            {
+            case wxYES: break;
+            case wxNO: continue;
+            case wxCANCEL: return;
+            }
+        }
+
+        wxString                         options = m_cur_grid->GetCellValue( row, COL_OPTIONS );
+        std::unique_ptr<std::map<std::string, UTF8>> props(
+                LIB_TABLE::ParseOptions( options.ToStdString() ) );
+
+        if( DESIGN_BLOCK_IO_MGR::ConvertLibrary( props.get(), legacyLib.GetFullPath(),
+                                                 newLib.GetFullPath() ) )
+        {
+            relPath =
+                    NormalizePath( newLib.GetFullPath(), &Pgm().GetLocalEnvVariables(), m_project );
+
+            // Do not use the project path in the global library table.  This will almost
+            // assuredly be wrong for a different project.
+            if( m_cur_grid == m_global_grid && relPath.Contains( "${KIPRJMOD}" ) )
+                relPath = newLib.GetFullPath();
+
+            m_cur_grid->SetCellValue( row, COL_URI, relPath );
+            m_cur_grid->SetCellValue( row, COL_TYPE, kicadType );
+        }
+        else
+        {
+            msg.Printf( _( "Failed to save design block library file '%s'." ),
+                        newLib.GetFullPath() );
+            DisplayErrorMessage( wxGetTopLevelParent( this ), msg );
+        }
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event )
+{
+    if( !m_cur_grid->CommitPendingChanges() )
+        return;
+
+    DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T fileType = DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE;
+
+    // We are bound both to the menu and button with this one handler
+    // So we must set the file type based on it
+    if( event.GetEventType() == wxEVT_BUTTON )
+    {
+        // Let's default to adding a kicad design block file for just the design block
+        fileType = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
+    }
+    else
+    {
+        fileType = static_cast<DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T>( event.GetId() );
+    }
+
+    if( fileType == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
+    {
+        wxLogWarning( wxT( "File type selection event received but could not find the file type "
+                           "in the table" ) );
+        return;
+    }
+
+    const IO_BASE::IO_FILE_DESC& fileDesc = m_supportedDesignBlockFiles.at( fileType );
+    KICAD_SETTINGS*              cfg = Pgm().GetSettingsManager().GetAppSettings<KICAD_SETTINGS>();
+
+    wxString title =
+            wxString::Format( _( "Select %s Library" ), DESIGN_BLOCK_IO_MGR::ShowType( fileType ) );
+    wxString openDir = cfg->m_lastDesignBlockLibDir;
+
+    if( m_cur_grid == m_project_grid )
+        openDir = m_lastProjectLibDir;
+
+    wxArrayString files;
+
+    wxWindow* topLevelParent = wxGetTopLevelParent( this );
+
+    if( fileDesc.m_IsFile )
+    {
+        wxFileDialog dlg( topLevelParent, title, openDir, wxEmptyString, fileDesc.FileFilter(),
+                          wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
+
+        int result = dlg.ShowModal();
+
+        if( result == wxID_CANCEL )
+            return;
+
+        dlg.GetPaths( files );
+
+        if( m_cur_grid == m_global_grid )
+            cfg->m_lastDesignBlockLibDir = dlg.GetDirectory();
+        else
+            m_lastProjectLibDir = dlg.GetDirectory();
+    }
+    else
+    {
+        wxDirDialog dlg( topLevelParent, title, openDir,
+                         wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST | wxDD_MULTIPLE );
+
+        int result = dlg.ShowModal();
+
+        if( result == wxID_CANCEL )
+            return;
+
+        dlg.GetPaths( files );
+
+        if( !files.IsEmpty() )
+        {
+            wxFileName first( files.front() );
+
+            if( m_cur_grid == m_global_grid )
+                cfg->m_lastDesignBlockLibDir = first.GetPath();
+            else
+                m_lastProjectLibDir = first.GetPath();
+        }
+    }
+
+    // Drop the last directory if the path is a .pretty folder
+    if( cfg->m_lastDesignBlockLibDir.EndsWith( FILEEXT::KiCadDesignBlockLibPathExtension ) )
+        cfg->m_lastDesignBlockLibDir =
+                cfg->m_lastDesignBlockLibDir.BeforeLast( wxFileName::GetPathSeparator() );
+
+    const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
+    bool               addDuplicates = false;
+    bool               applyToAll = false;
+    wxString           warning = _( "Warning: Duplicate Nicknames" );
+    wxString           msg = _( "A library nicknamed '%s' already exists." );
+    wxString           detailedMsg = _( "One of the nicknames will need to be changed after "
+                                                  "adding this library." );
+
+    for( const wxString& filePath : files )
+    {
+        wxFileName fn( filePath );
+        wxString   nickname = LIB_ID::FixIllegalChars( fn.GetName(), true );
+        bool       doAdd = true;
+
+        if( fileType == DESIGN_BLOCK_IO_MGR::KICAD_SEXP
+            && fn.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension )
+            nickname = LIB_ID::FixIllegalChars( fn.GetFullName(), true ).wx_str();
+
+        if( cur_model()->ContainsNickname( nickname ) )
+        {
+            if( !applyToAll )
+            {
+                // The cancel button adds the library to the table anyway
+                addDuplicates = OKOrCancelDialog( wxGetTopLevelParent( this ), warning,
+                                                  wxString::Format( msg, nickname ), detailedMsg,
+                                                  _( "Skip" ), _( "Add Anyway" ), &applyToAll )
+                                == wxID_CANCEL;
+            }
+
+            doAdd = addDuplicates;
+        }
+
+        if( doAdd && m_cur_grid->AppendRows( 1 ) )
+        {
+            int last_row = m_cur_grid->GetNumberRows() - 1;
+
+            m_cur_grid->SetCellValue( last_row, COL_NICKNAME, nickname );
+
+            m_cur_grid->SetCellValue( last_row, COL_TYPE,
+                                      DESIGN_BLOCK_IO_MGR::ShowType( fileType ) );
+
+            // try to use path normalized to an environmental variable or project path
+            wxString path = NormalizePath( filePath, &envVars, m_projectBasePath );
+
+            // Do not use the project path in the global library table.  This will almost
+            // assuredly be wrong for a different project.
+            if( m_pageNdx == 0 && path.Contains( wxT( "${KIPRJMOD}" ) ) )
+                path = fn.GetFullPath();
+
+            m_cur_grid->SetCellValue( last_row, COL_URI, path );
+        }
+    }
+
+    if( !files.IsEmpty() )
+    {
+        int new_row = m_cur_grid->GetNumberRows() - 1;
+        m_cur_grid->MakeCellVisible( new_row, m_cur_grid->GetGridCursorCol() );
+        m_cur_grid->SetGridCursor( new_row, m_cur_grid->GetGridCursorCol() );
+    }
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::adjustPathSubsGridColumns( int aWidth )
+{
+    // Account for scroll bars
+    aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x );
+
+    m_path_subs_grid->AutoSizeColumn( 0 );
+    m_path_subs_grid->SetColSize( 0, std::max( 72, m_path_subs_grid->GetColSize( 0 ) ) );
+    m_path_subs_grid->SetColSize( 1, std::max( 120, aWidth - m_path_subs_grid->GetColSize( 0 ) ) );
+}
+
+
+void PANEL_DESIGN_BLOCK_LIB_TABLE::onSizeGrid( wxSizeEvent& event )
+{
+    adjustPathSubsGridColumns( event.GetSize().GetX() );
+
+    event.Skip();
+}
+
+
+bool PANEL_DESIGN_BLOCK_LIB_TABLE::TransferDataFromWindow()
+{
+    if( !m_cur_grid->CommitPendingChanges() )
+        return false;
+
+    if( verifyTables() )
+    {
+        if( *global_model() != *m_globalTable )
+        {
+            m_parent->m_GlobalTableChanged = true;
+
+            m_globalTable->TransferRows( global_model()->m_rows );
+        }
+
+        if( project_model() && *project_model() != *m_projectTable )
+        {
+            m_parent->m_ProjectTableChanged = true;
+
+            m_projectTable->TransferRows( project_model()->m_rows );
+        }
+
+        return true;
+    }
+
+    return false;
+}
+
+
+/// Populate the readonly environment variable table with names and values
+/// by examining all the full_uri columns.
+void PANEL_DESIGN_BLOCK_LIB_TABLE::populateEnvironReadOnlyTable()
+{
+    wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED );
+    wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required.
+
+    std::set<wxString> unique;
+
+    // clear the table
+    m_path_subs_grid->ClearRows();
+
+    for( DESIGN_BLOCK_LIB_TABLE_GRID* tbl : { global_model(), project_model() } )
+    {
+        if( !tbl )
+            continue;
+
+        for( int row = 0; row < tbl->GetNumberRows(); ++row )
+        {
+            wxString uri = tbl->GetValue( row, COL_URI );
+
+            while( re.Matches( uri ) )
+            {
+                wxString envvar = re.GetMatch( uri, 2 );
+
+                // if not ${...} form then must be $(...)
+                if( envvar.IsEmpty() )
+                    envvar = re.GetMatch( uri, 4 );
+
+                // ignore duplicates
+                unique.insert( envvar );
+
+                // delete the last match and search again
+                uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
+            }
+        }
+    }
+
+    // Make sure this special environment variable shows up even if it was
+    // not used yet.  It is automatically set by KiCad to the directory holding
+    // the current project.
+    unique.insert( PROJECT_VAR_NAME );
+    unique.insert( DESIGN_BLOCK_LIB_TABLE::GlobalPathEnvVariableName() );
+
+    // This special environment variable is used to locate 3d shapes
+    unique.insert( ENV_VAR::GetVersionedEnvVarName( wxS( "3DMODEL_DIR" ) ) );
+
+    for( const wxString& evName : unique )
+    {
+        int row = m_path_subs_grid->GetNumberRows();
+        m_path_subs_grid->AppendRows( 1 );
+
+        m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) );
+        m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() );
+
+        wxString evValue;
+        wxGetEnv( evName, &evValue );
+        m_path_subs_grid->SetCellValue( row, 1, evValue );
+        m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() );
+    }
+
+    // No combobox editors here, but it looks better if its consistent with the other
+    // grids in the dialog.
+    m_path_subs_grid->SetDefaultRowSize( m_path_subs_grid->GetDefaultRowSize() + 2 );
+
+    adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() );
+}
+
+//-----</event handlers>---------------------------------
+
+
+size_t PANEL_DESIGN_BLOCK_LIB_TABLE::m_pageNdx = 0;
diff --git a/kicad/dialogs/panel_design_block_lib_table.h b/kicad/dialogs/panel_design_block_lib_table.h
new file mode 100644
index 0000000000..ded14ccea8
--- /dev/null
+++ b/kicad/dialogs/panel_design_block_lib_table.h
@@ -0,0 +1,106 @@
+/*
+ * This program source code file is part of KiCad, a free EDA CAD application.
+ *
+ * Copyright (C) 2018-2024 KiCad Developers, see AUTHORS.txt for contributors.
+ *
+ * This program is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation, either version 3 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef PANEL_DESIGN_BLOCK_LIB_TABLE_H
+#define PANEL_DESIGN_BLOCK_LIB_TABLE_H
+
+#include <dialogs/dialog_edit_library_tables.h>
+#include <dialogs/panel_design_block_lib_table_base.h>
+#include <widgets/wx_grid.h>
+#include <design_block_io.h>
+
+class DESIGN_BLOCK_LIB_TABLE;
+class DESIGN_BLOCK_LIB_TABLE_GRID;
+class PROJECT;
+
+
+/**
+ * Dialog to show and edit symbol library tables.
+ */
+class PANEL_DESIGN_BLOCK_LIB_TABLE : public PANEL_DESIGN_BLOCK_LIB_TABLE_BASE
+{
+public:
+    PANEL_DESIGN_BLOCK_LIB_TABLE( DIALOG_EDIT_LIBRARY_TABLES* aParent, PROJECT* aProject,
+                                  DESIGN_BLOCK_LIB_TABLE* aGlobalTable,
+                                  const wxString&         aGlobalTblPath,
+                                  DESIGN_BLOCK_LIB_TABLE* aProjectTable,
+                                  const wxString&         aProjectTblPath,
+                                  const wxString&         aProjectBasePath );
+    ~PANEL_DESIGN_BLOCK_LIB_TABLE() override;
+
+private:
+    bool TransferDataFromWindow() override;
+
+    /**
+     * Trim important fields, removes blank row entries, and checks for duplicates.
+     *
+     * @return bool - true if tables are OK, else false.
+     */
+    bool verifyTables();
+
+    void OnUpdateUI( wxUpdateUIEvent& event ) override;
+    void appendRowHandler( wxCommandEvent& event ) override;
+    void browseLibrariesHandler( wxCommandEvent& event );
+    void deleteRowHandler( wxCommandEvent& event ) override;
+    void moveUpHandler( wxCommandEvent& event ) override;
+    void moveDownHandler( wxCommandEvent& event ) override;
+    void onMigrateLibraries( wxCommandEvent& event ) override;
+    void onSizeGrid( wxSizeEvent& event ) override;
+
+    void adjustPathSubsGridColumns( int aWidth );
+
+    /// Populate the readonly environment variable table with names and values
+    /// by examining all the full_uri columns.
+    void populateEnvironReadOnlyTable();
+    void populatePluginList();
+
+    DESIGN_BLOCK_LIB_TABLE_GRID* global_model() const
+    {
+        return (DESIGN_BLOCK_LIB_TABLE_GRID*) m_global_grid->GetTable();
+    }
+
+    DESIGN_BLOCK_LIB_TABLE_GRID* project_model() const
+    {
+        return m_project_grid ? (DESIGN_BLOCK_LIB_TABLE_GRID*) m_project_grid->GetTable() : nullptr;
+    }
+
+    DESIGN_BLOCK_LIB_TABLE_GRID* cur_model() const
+    {
+        return (DESIGN_BLOCK_LIB_TABLE_GRID*) m_cur_grid->GetTable();
+    }
+
+    // caller's tables are modified only on OK button and successful verification.
+    DESIGN_BLOCK_LIB_TABLE* m_globalTable;
+    DESIGN_BLOCK_LIB_TABLE* m_projectTable;
+    PROJECT*                m_project;
+    wxString                m_projectBasePath;
+
+    DIALOG_EDIT_LIBRARY_TABLES* m_parent;
+
+    WX_GRID*      m_cur_grid; // changed based on tab choice
+    static size_t m_pageNdx;  // Remember last notebook page selected during a session
+
+    //< Transient (unsaved) last browsed folder when adding a project level library.
+    wxString m_lastProjectLibDir;
+
+    std::map<DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T, IO_BASE::IO_FILE_DESC>
+            m_supportedDesignBlockFiles;
+};
+
+#endif // PANEL_DESIGN_BLOCK_LIB_TABLE_H
diff --git a/kicad/dialogs/panel_design_block_lib_table_base.cpp b/kicad/dialogs/panel_design_block_lib_table_base.cpp
new file mode 100644
index 0000000000..3f55abd419
--- /dev/null
+++ b/kicad/dialogs/panel_design_block_lib_table_base.cpp
@@ -0,0 +1,227 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#include "widgets/std_bitmap_button.h"
+#include "widgets/wx_grid.h"
+
+#include "panel_design_block_lib_table_base.h"
+
+///////////////////////////////////////////////////////////////////////////
+
+PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::PANEL_DESIGN_BLOCK_LIB_TABLE_BASE( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name ) : wxPanel( parent, id, pos, size, style, name )
+{
+	wxBoxSizer* bMainSizer;
+	bMainSizer = new wxBoxSizer( wxVERTICAL );
+
+	m_notebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
+	m_global_panel = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+	wxBoxSizer* m_global_sizer;
+	m_global_sizer = new wxBoxSizer( wxVERTICAL );
+
+	m_global_grid = new WX_GRID( m_global_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
+
+	// Grid
+	m_global_grid->CreateGrid( 1, 7 );
+	m_global_grid->EnableEditing( true );
+	m_global_grid->EnableGridLines( true );
+	m_global_grid->EnableDragGridSize( false );
+	m_global_grid->SetMargins( 0, 0 );
+
+	// Columns
+	m_global_grid->SetColSize( 0, 48 );
+	m_global_grid->SetColSize( 1, 48 );
+	m_global_grid->SetColSize( 2, 100 );
+	m_global_grid->SetColSize( 3, 240 );
+	m_global_grid->SetColSize( 4, 100 );
+	m_global_grid->SetColSize( 5, 80 );
+	m_global_grid->SetColSize( 6, 240 );
+	m_global_grid->EnableDragColMove( false );
+	m_global_grid->EnableDragColSize( true );
+	m_global_grid->SetColLabelValue( 0, _("Active") );
+	m_global_grid->SetColLabelValue( 1, _("Visible") );
+	m_global_grid->SetColLabelValue( 2, _("Nickname") );
+	m_global_grid->SetColLabelValue( 3, _("Library Path") );
+	m_global_grid->SetColLabelValue( 4, _("Library Format") );
+	m_global_grid->SetColLabelValue( 5, _("Options") );
+	m_global_grid->SetColLabelValue( 6, _("Description") );
+	m_global_grid->SetColLabelSize( 22 );
+	m_global_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Rows
+	m_global_grid->EnableDragRowSize( false );
+	m_global_grid->SetRowLabelSize( 0 );
+	m_global_grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Label Appearance
+
+	// Cell Defaults
+	m_global_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER );
+	m_global_sizer->Add( m_global_grid, 1, wxALL|wxEXPAND, 5 );
+
+
+	m_global_panel->SetSizer( m_global_sizer );
+	m_global_panel->Layout();
+	m_global_sizer->Fit( m_global_panel );
+	m_notebook->AddPage( m_global_panel, _("Global Libraries"), true );
+	m_project_panel = new wxPanel( m_notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
+	wxBoxSizer* m_project_sizer;
+	m_project_sizer = new wxBoxSizer( wxVERTICAL );
+
+	m_project_grid = new WX_GRID( m_project_panel, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
+
+	// Grid
+	m_project_grid->CreateGrid( 1, 7 );
+	m_project_grid->EnableEditing( true );
+	m_project_grid->EnableGridLines( true );
+	m_project_grid->EnableDragGridSize( false );
+	m_project_grid->SetMargins( 0, 0 );
+
+	// Columns
+	m_project_grid->SetColSize( 0, 48 );
+	m_project_grid->SetColSize( 1, 48 );
+	m_project_grid->SetColSize( 2, 100 );
+	m_project_grid->SetColSize( 3, 240 );
+	m_project_grid->SetColSize( 4, 100 );
+	m_project_grid->SetColSize( 5, 80 );
+	m_project_grid->SetColSize( 6, 240 );
+	m_project_grid->EnableDragColMove( false );
+	m_project_grid->EnableDragColSize( true );
+	m_project_grid->SetColLabelValue( 0, _("Active") );
+	m_project_grid->SetColLabelValue( 1, _("Visible") );
+	m_project_grid->SetColLabelValue( 2, _("Nickname") );
+	m_project_grid->SetColLabelValue( 3, _("Library Path") );
+	m_project_grid->SetColLabelValue( 4, _("LIbrary Format") );
+	m_project_grid->SetColLabelValue( 5, _("Options") );
+	m_project_grid->SetColLabelValue( 6, _("Description") );
+	m_project_grid->SetColLabelSize( 22 );
+	m_project_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Rows
+	m_project_grid->EnableDragRowSize( false );
+	m_project_grid->SetRowLabelSize( 0 );
+	m_project_grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Label Appearance
+
+	// Cell Defaults
+	m_project_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER );
+	m_project_sizer->Add( m_project_grid, 1, wxALL|wxEXPAND, 5 );
+
+
+	m_project_panel->SetSizer( m_project_sizer );
+	m_project_panel->Layout();
+	m_project_sizer->Fit( m_project_panel );
+	m_notebook->AddPage( m_project_panel, _("Project Specific Libraries"), false );
+
+	bMainSizer->Add( m_notebook, 1, wxEXPAND|wxRIGHT|wxLEFT, 5 );
+
+	wxBoxSizer* bButtonsSizer;
+	bButtonsSizer = new wxBoxSizer( wxHORIZONTAL );
+
+	m_append_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+	m_append_button->SetToolTip( _("Add empty row to table") );
+
+	bButtonsSizer->Add( m_append_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
+
+	m_browseButton = new SPLIT_BUTTON( this, wxID_ANY, _( "Add Existing" ), wxDefaultPosition );
+	m_browseButton->SetToolTip( _("Add Existing") );
+
+	bButtonsSizer->Add( m_browseButton, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
+
+	m_move_up_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+	m_move_up_button->SetToolTip( _("Move up") );
+
+	bButtonsSizer->Add( m_move_up_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
+
+	m_move_down_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+	m_move_down_button->SetToolTip( _("Move down") );
+
+	bButtonsSizer->Add( m_move_down_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
+
+
+	bButtonsSizer->Add( 20, 0, 0, wxEXPAND, 5 );
+
+	m_delete_button = new STD_BITMAP_BUTTON( this, wxID_ANY, wxNullBitmap, wxDefaultPosition, wxSize( -1,-1 ), wxBU_AUTODRAW|0 );
+	m_delete_button->SetToolTip( _("Remove library from table") );
+
+	bButtonsSizer->Add( m_delete_button, 0, wxTOP|wxBOTTOM|wxRIGHT, 5 );
+
+
+	bButtonsSizer->Add( 20, 0, 1, wxEXPAND, 5 );
+
+	m_migrate_libs_button = new wxButton( this, wxID_ANY, _("Migrate Libraries"), wxDefaultPosition, wxDefaultSize, 0 );
+	bButtonsSizer->Add( m_migrate_libs_button, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5 );
+
+
+	bMainSizer->Add( bButtonsSizer, 0, wxEXPAND|wxALL, 8 );
+
+	wxStaticText* stPathsLabel;
+	stPathsLabel = new wxStaticText( this, wxID_ANY, _("Available path substitutions:"), wxDefaultPosition, wxDefaultSize, 0 );
+	stPathsLabel->Wrap( -1 );
+	bMainSizer->Add( stPathsLabel, 0, wxTOP|wxRIGHT|wxLEFT|wxEXPAND, 8 );
+
+
+	bMainSizer->Add( 0, 2, 0, wxEXPAND, 5 );
+
+	m_path_subs_grid = new WX_GRID( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
+
+	// Grid
+	m_path_subs_grid->CreateGrid( 1, 2 );
+	m_path_subs_grid->EnableEditing( false );
+	m_path_subs_grid->EnableGridLines( true );
+	m_path_subs_grid->EnableDragGridSize( false );
+	m_path_subs_grid->SetMargins( 0, 0 );
+
+	// Columns
+	m_path_subs_grid->SetColSize( 0, 150 );
+	m_path_subs_grid->SetColSize( 1, 500 );
+	m_path_subs_grid->AutoSizeColumns();
+	m_path_subs_grid->EnableDragColMove( false );
+	m_path_subs_grid->EnableDragColSize( true );
+	m_path_subs_grid->SetColLabelSize( 0 );
+	m_path_subs_grid->SetColLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Rows
+	m_path_subs_grid->EnableDragRowSize( true );
+	m_path_subs_grid->SetRowLabelSize( 0 );
+	m_path_subs_grid->SetRowLabelAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
+
+	// Label Appearance
+
+	// Cell Defaults
+	m_path_subs_grid->SetDefaultCellAlignment( wxALIGN_LEFT, wxALIGN_CENTER );
+	m_path_subs_grid->SetToolTip( _("This is a read-only table which shows pertinent environment variables.") );
+
+	bMainSizer->Add( m_path_subs_grid, 0, wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT, 5 );
+
+
+	this->SetSizer( bMainSizer );
+	this->Layout();
+	bMainSizer->Fit( this );
+
+	// Connect Events
+	this->Connect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::OnUpdateUI ) );
+	m_append_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::appendRowHandler ), NULL, this );
+	m_move_up_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveUpHandler ), NULL, this );
+	m_move_down_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveDownHandler ), NULL, this );
+	m_delete_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::deleteRowHandler ), NULL, this );
+	m_migrate_libs_button->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onMigrateLibraries ), NULL, this );
+	m_path_subs_grid->Connect( wxEVT_SIZE, wxSizeEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onSizeGrid ), NULL, this );
+}
+
+PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::~PANEL_DESIGN_BLOCK_LIB_TABLE_BASE()
+{
+	// Disconnect Events
+	this->Disconnect( wxEVT_UPDATE_UI, wxUpdateUIEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::OnUpdateUI ) );
+	m_append_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::appendRowHandler ), NULL, this );
+	m_move_up_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveUpHandler ), NULL, this );
+	m_move_down_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::moveDownHandler ), NULL, this );
+	m_delete_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::deleteRowHandler ), NULL, this );
+	m_migrate_libs_button->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onMigrateLibraries ), NULL, this );
+	m_path_subs_grid->Disconnect( wxEVT_SIZE, wxSizeEventHandler( PANEL_DESIGN_BLOCK_LIB_TABLE_BASE::onSizeGrid ), NULL, this );
+
+}
diff --git a/kicad/dialogs/panel_design_block_lib_table_base.fbp b/kicad/dialogs/panel_design_block_lib_table_base.fbp
new file mode 100644
index 0000000000..748097e474
--- /dev/null
+++ b/kicad/dialogs/panel_design_block_lib_table_base.fbp
@@ -0,0 +1,1048 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
+<wxFormBuilder_Project>
+    <FileVersion major="1" minor="16" />
+    <object class="Project" expanded="1">
+        <property name="class_decoration"></property>
+        <property name="code_generation">C++</property>
+        <property name="disconnect_events">1</property>
+        <property name="disconnect_mode">source_name</property>
+        <property name="disconnect_php_events">0</property>
+        <property name="disconnect_python_events">0</property>
+        <property name="embedded_files_path">res</property>
+        <property name="encoding">UTF-8</property>
+        <property name="event_generation">connect</property>
+        <property name="file">panel_design_block_lib_table_base</property>
+        <property name="first_id">1000</property>
+        <property name="help_provider">none</property>
+        <property name="image_path_wrapper_function_name"></property>
+        <property name="indent_with_spaces"></property>
+        <property name="internationalize">1</property>
+        <property name="name">panel_design_block_lib_table</property>
+        <property name="namespace"></property>
+        <property name="path">.</property>
+        <property name="precompiled_header"></property>
+        <property name="relative_path">1</property>
+        <property name="skip_lua_events">1</property>
+        <property name="skip_php_events">1</property>
+        <property name="skip_python_events">1</property>
+        <property name="ui_table">UI</property>
+        <property name="use_array_enum">0</property>
+        <property name="use_enum">0</property>
+        <property name="use_microsoft_bom">0</property>
+        <object class="Panel" expanded="1">
+            <property name="aui_managed">0</property>
+            <property name="aui_manager_style">wxAUI_MGR_DEFAULT</property>
+            <property name="bg"></property>
+            <property name="context_help"></property>
+            <property name="context_menu">1</property>
+            <property name="enabled">1</property>
+            <property name="event_handler">impl_virtual</property>
+            <property name="fg"></property>
+            <property name="font"></property>
+            <property name="hidden">0</property>
+            <property name="id">wxID_ANY</property>
+            <property name="maximum_size"></property>
+            <property name="minimum_size"></property>
+            <property name="name">PANEL_DESIGN_BLOCK_LIB_TABLE_BASE</property>
+            <property name="pos"></property>
+            <property name="size">-1,-1</property>
+            <property name="subclass">; forward_declare</property>
+            <property name="tooltip"></property>
+            <property name="two_step_creation">0</property>
+            <property name="window_extra_style"></property>
+            <property name="window_name"></property>
+            <property name="window_style">wxTAB_TRAVERSAL</property>
+            <event name="OnUpdateUI">OnUpdateUI</event>
+            <object class="wxBoxSizer" expanded="1">
+                <property name="minimum_size"></property>
+                <property name="name">bMainSizer</property>
+                <property name="orient">wxVERTICAL</property>
+                <property name="permission">none</property>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND|wxRIGHT|wxLEFT</property>
+                    <property name="proportion">1</property>
+                    <object class="wxNotebook" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="bitmapsize"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_notebook</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style"></property>
+                        <property name="subclass">; ; forward_declare</property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                        <object class="notebookpage" expanded="1">
+                            <property name="bitmap"></property>
+                            <property name="label">Global Libraries</property>
+                            <property name="select">1</property>
+                            <object class="wxPanel" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">0</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_global_panel</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">0</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style">wxTAB_TRAVERSAL</property>
+                                <object class="wxBoxSizer" expanded="1">
+                                    <property name="minimum_size"></property>
+                                    <property name="name">m_global_sizer</property>
+                                    <property name="orient">wxVERTICAL</property>
+                                    <property name="permission">none</property>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxALL|wxEXPAND</property>
+                                        <property name="proportion">1</property>
+                                        <object class="wxGrid" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="autosize_cols">0</property>
+                                            <property name="autosize_rows">0</property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="cell_bg"></property>
+                                            <property name="cell_font"></property>
+                                            <property name="cell_horiz_alignment">wxALIGN_LEFT</property>
+                                            <property name="cell_text"></property>
+                                            <property name="cell_vert_alignment">wxALIGN_CENTER</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="col_label_horiz_alignment">wxALIGN_CENTER</property>
+                                            <property name="col_label_size">22</property>
+                                            <property name="col_label_values">&quot;Active&quot; &quot;Visible&quot; &quot;Nickname&quot; &quot;Library Path&quot; &quot;Library Format&quot; &quot;Options&quot; &quot;Description&quot;</property>
+                                            <property name="col_label_vert_alignment">wxALIGN_CENTER</property>
+                                            <property name="cols">7</property>
+                                            <property name="column_sizes">48,48,100,240,100,80,240</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">1</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_col_move">0</property>
+                                            <property name="drag_col_size">1</property>
+                                            <property name="drag_grid_size">0</property>
+                                            <property name="drag_row_size">0</property>
+                                            <property name="editing">1</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">0</property>
+                                            <property name="font"></property>
+                                            <property name="grid_line_color"></property>
+                                            <property name="grid_lines">1</property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="label_bg"></property>
+                                            <property name="label_font"></property>
+                                            <property name="label_text"></property>
+                                            <property name="margin_height">0</property>
+                                            <property name="margin_width">0</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">0</property>
+                                            <property name="name">m_global_grid</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Fixed</property>
+                                            <property name="row_label_horiz_alignment">wxALIGN_CENTER</property>
+                                            <property name="row_label_size">0</property>
+                                            <property name="row_label_values"></property>
+                                            <property name="row_label_vert_alignment">wxALIGN_CENTER</property>
+                                            <property name="row_sizes"></property>
+                                            <property name="rows">1</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="subclass">WX_GRID; widgets/wx_grid.h; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                        <object class="notebookpage" expanded="1">
+                            <property name="bitmap"></property>
+                            <property name="label">Project Specific Libraries</property>
+                            <property name="select">0</property>
+                            <object class="wxPanel" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_project_panel</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="subclass"></property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style">wxTAB_TRAVERSAL</property>
+                                <object class="wxBoxSizer" expanded="1">
+                                    <property name="minimum_size"></property>
+                                    <property name="name">m_project_sizer</property>
+                                    <property name="orient">wxVERTICAL</property>
+                                    <property name="permission">none</property>
+                                    <object class="sizeritem" expanded="0">
+                                        <property name="border">5</property>
+                                        <property name="flag">wxALL|wxEXPAND</property>
+                                        <property name="proportion">1</property>
+                                        <object class="wxGrid" expanded="0">
+                                            <property name="BottomDockable">1</property>
+                                            <property name="LeftDockable">1</property>
+                                            <property name="RightDockable">1</property>
+                                            <property name="TopDockable">1</property>
+                                            <property name="aui_layer"></property>
+                                            <property name="aui_name"></property>
+                                            <property name="aui_position"></property>
+                                            <property name="aui_row"></property>
+                                            <property name="autosize_cols">0</property>
+                                            <property name="autosize_rows">0</property>
+                                            <property name="best_size"></property>
+                                            <property name="bg"></property>
+                                            <property name="caption"></property>
+                                            <property name="caption_visible">1</property>
+                                            <property name="cell_bg"></property>
+                                            <property name="cell_font"></property>
+                                            <property name="cell_horiz_alignment">wxALIGN_LEFT</property>
+                                            <property name="cell_text"></property>
+                                            <property name="cell_vert_alignment">wxALIGN_CENTER</property>
+                                            <property name="center_pane">0</property>
+                                            <property name="close_button">1</property>
+                                            <property name="col_label_horiz_alignment">wxALIGN_CENTER</property>
+                                            <property name="col_label_size">22</property>
+                                            <property name="col_label_values">&quot;Active&quot; &quot;Visible&quot; &quot;Nickname&quot; &quot;Library Path&quot; &quot;LIbrary Format&quot; &quot;Options&quot; &quot;Description&quot;</property>
+                                            <property name="col_label_vert_alignment">wxALIGN_CENTER</property>
+                                            <property name="cols">7</property>
+                                            <property name="column_sizes">48,48,100,240,100,80,240</property>
+                                            <property name="context_help"></property>
+                                            <property name="context_menu">1</property>
+                                            <property name="default_pane">0</property>
+                                            <property name="dock">Dock</property>
+                                            <property name="dock_fixed">1</property>
+                                            <property name="docking">Left</property>
+                                            <property name="drag_col_move">0</property>
+                                            <property name="drag_col_size">1</property>
+                                            <property name="drag_grid_size">0</property>
+                                            <property name="drag_row_size">0</property>
+                                            <property name="editing">1</property>
+                                            <property name="enabled">1</property>
+                                            <property name="fg"></property>
+                                            <property name="floatable">0</property>
+                                            <property name="font"></property>
+                                            <property name="grid_line_color"></property>
+                                            <property name="grid_lines">1</property>
+                                            <property name="gripper">0</property>
+                                            <property name="hidden">0</property>
+                                            <property name="id">wxID_ANY</property>
+                                            <property name="label_bg"></property>
+                                            <property name="label_font"></property>
+                                            <property name="label_text"></property>
+                                            <property name="margin_height">0</property>
+                                            <property name="margin_width">0</property>
+                                            <property name="max_size"></property>
+                                            <property name="maximize_button">0</property>
+                                            <property name="maximum_size"></property>
+                                            <property name="min_size"></property>
+                                            <property name="minimize_button">0</property>
+                                            <property name="minimum_size"></property>
+                                            <property name="moveable">0</property>
+                                            <property name="name">m_project_grid</property>
+                                            <property name="pane_border">1</property>
+                                            <property name="pane_position"></property>
+                                            <property name="pane_size"></property>
+                                            <property name="permission">protected</property>
+                                            <property name="pin_button">1</property>
+                                            <property name="pos"></property>
+                                            <property name="resize">Fixed</property>
+                                            <property name="row_label_horiz_alignment">wxALIGN_CENTER</property>
+                                            <property name="row_label_size">0</property>
+                                            <property name="row_label_values"></property>
+                                            <property name="row_label_vert_alignment">wxALIGN_CENTER</property>
+                                            <property name="row_sizes"></property>
+                                            <property name="rows">1</property>
+                                            <property name="show">1</property>
+                                            <property name="size"></property>
+                                            <property name="subclass">WX_GRID; widgets/wx_grid.h; forward_declare</property>
+                                            <property name="toolbar_pane">0</property>
+                                            <property name="tooltip"></property>
+                                            <property name="window_extra_style"></property>
+                                            <property name="window_name"></property>
+                                            <property name="window_style"></property>
+                                        </object>
+                                    </object>
+                                </object>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">8</property>
+                    <property name="flag">wxEXPAND|wxALL</property>
+                    <property name="proportion">0</property>
+                    <object class="wxBoxSizer" expanded="1">
+                        <property name="minimum_size"></property>
+                        <property name="name">bButtonsSizer</property>
+                        <property name="orient">wxHORIZONTAL</property>
+                        <property name="permission">none</property>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBitmapButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="auth_needed">0</property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Add Library</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_append_button</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size">-1,-1</property>
+                                <property name="style"></property>
+                                <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip">Add empty row to table</property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">appendRowHandler</event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property>
+                            <property name="proportion">0</property>
+                            <object class="CustomControl" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="class">SPLIT_BUTTON</property>
+                                <property name="close_button">1</property>
+                                <property name="construction">m_browseButton = new SPLIT_BUTTON( this, wxID_ANY, _( &quot;Add Existing&quot; ), wxDefaultPosition );</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="declaration"></property>
+                                <property name="default_pane">0</property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="include">#include &lt;widgets/split_button.h&gt;</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size">-1,-1</property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_browseButton</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="settings"></property>
+                                <property name="show">1</property>
+                                <property name="size">-1,-1</property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip">Add Existing</property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBitmapButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="auth_needed">0</property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Move Up</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_move_up_button</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size">-1,-1</property>
+                                <property name="style"></property>
+                                <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip">Move up</property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">moveUpHandler</event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBitmapButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="auth_needed">0</property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Move Down</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_move_down_button</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size">-1,-1</property>
+                                <property name="style"></property>
+                                <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip">Move down</property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">moveDownHandler</event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">0</property>
+                            <object class="spacer" expanded="1">
+                                <property name="height">0</property>
+                                <property name="permission">protected</property>
+                                <property name="width">20</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxTOP|wxBOTTOM|wxRIGHT</property>
+                            <property name="proportion">0</property>
+                            <object class="wxBitmapButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="auth_needed">0</property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Remove Library</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_delete_button</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size">-1,-1</property>
+                                <property name="style"></property>
+                                <property name="subclass">STD_BITMAP_BUTTON; widgets/std_bitmap_button.h; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip">Remove library from table</property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">deleteRowHandler</event>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxEXPAND</property>
+                            <property name="proportion">1</property>
+                            <object class="spacer" expanded="1">
+                                <property name="height">0</property>
+                                <property name="permission">protected</property>
+                                <property name="width">20</property>
+                            </object>
+                        </object>
+                        <object class="sizeritem" expanded="1">
+                            <property name="border">5</property>
+                            <property name="flag">wxALIGN_CENTER_VERTICAL|wxALL</property>
+                            <property name="proportion">0</property>
+                            <object class="wxButton" expanded="1">
+                                <property name="BottomDockable">1</property>
+                                <property name="LeftDockable">1</property>
+                                <property name="RightDockable">1</property>
+                                <property name="TopDockable">1</property>
+                                <property name="aui_layer"></property>
+                                <property name="aui_name"></property>
+                                <property name="aui_position"></property>
+                                <property name="aui_row"></property>
+                                <property name="auth_needed">0</property>
+                                <property name="best_size"></property>
+                                <property name="bg"></property>
+                                <property name="bitmap"></property>
+                                <property name="caption"></property>
+                                <property name="caption_visible">1</property>
+                                <property name="center_pane">0</property>
+                                <property name="close_button">1</property>
+                                <property name="context_help"></property>
+                                <property name="context_menu">1</property>
+                                <property name="current"></property>
+                                <property name="default">0</property>
+                                <property name="default_pane">0</property>
+                                <property name="disabled"></property>
+                                <property name="dock">Dock</property>
+                                <property name="dock_fixed">0</property>
+                                <property name="docking">Left</property>
+                                <property name="enabled">1</property>
+                                <property name="fg"></property>
+                                <property name="floatable">1</property>
+                                <property name="focus"></property>
+                                <property name="font"></property>
+                                <property name="gripper">0</property>
+                                <property name="hidden">0</property>
+                                <property name="id">wxID_ANY</property>
+                                <property name="label">Migrate Libraries</property>
+                                <property name="margins"></property>
+                                <property name="markup">0</property>
+                                <property name="max_size"></property>
+                                <property name="maximize_button">0</property>
+                                <property name="maximum_size"></property>
+                                <property name="min_size"></property>
+                                <property name="minimize_button">0</property>
+                                <property name="minimum_size"></property>
+                                <property name="moveable">1</property>
+                                <property name="name">m_migrate_libs_button</property>
+                                <property name="pane_border">1</property>
+                                <property name="pane_position"></property>
+                                <property name="pane_size"></property>
+                                <property name="permission">protected</property>
+                                <property name="pin_button">1</property>
+                                <property name="pos"></property>
+                                <property name="position"></property>
+                                <property name="pressed"></property>
+                                <property name="resize">Resizable</property>
+                                <property name="show">1</property>
+                                <property name="size"></property>
+                                <property name="style"></property>
+                                <property name="subclass">; ; forward_declare</property>
+                                <property name="toolbar_pane">0</property>
+                                <property name="tooltip"></property>
+                                <property name="validator_data_type"></property>
+                                <property name="validator_style">wxFILTER_NONE</property>
+                                <property name="validator_type">wxDefaultValidator</property>
+                                <property name="validator_variable"></property>
+                                <property name="window_extra_style"></property>
+                                <property name="window_name"></property>
+                                <property name="window_style"></property>
+                                <event name="OnButtonClick">onMigrateLibraries</event>
+                            </object>
+                        </object>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">8</property>
+                    <property name="flag">wxTOP|wxRIGHT|wxLEFT|wxEXPAND</property>
+                    <property name="proportion">0</property>
+                    <object class="wxStaticText" expanded="1">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="label">Available path substitutions:</property>
+                        <property name="markup">0</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">stPathsLabel</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">none</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="style"></property>
+                        <property name="subclass">; ; forward_declare</property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip"></property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                        <property name="wrap">-1</property>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="1">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND</property>
+                    <property name="proportion">0</property>
+                    <object class="spacer" expanded="1">
+                        <property name="height">2</property>
+                        <property name="permission">protected</property>
+                        <property name="width">0</property>
+                    </object>
+                </object>
+                <object class="sizeritem" expanded="0">
+                    <property name="border">5</property>
+                    <property name="flag">wxEXPAND|wxBOTTOM|wxRIGHT|wxLEFT</property>
+                    <property name="proportion">0</property>
+                    <object class="wxGrid" expanded="0">
+                        <property name="BottomDockable">1</property>
+                        <property name="LeftDockable">1</property>
+                        <property name="RightDockable">1</property>
+                        <property name="TopDockable">1</property>
+                        <property name="aui_layer"></property>
+                        <property name="aui_name"></property>
+                        <property name="aui_position"></property>
+                        <property name="aui_row"></property>
+                        <property name="autosize_cols">1</property>
+                        <property name="autosize_rows">0</property>
+                        <property name="best_size"></property>
+                        <property name="bg"></property>
+                        <property name="caption"></property>
+                        <property name="caption_visible">1</property>
+                        <property name="cell_bg"></property>
+                        <property name="cell_font"></property>
+                        <property name="cell_horiz_alignment">wxALIGN_LEFT</property>
+                        <property name="cell_text"></property>
+                        <property name="cell_vert_alignment">wxALIGN_CENTER</property>
+                        <property name="center_pane">0</property>
+                        <property name="close_button">1</property>
+                        <property name="col_label_horiz_alignment">wxALIGN_CENTER</property>
+                        <property name="col_label_size">0</property>
+                        <property name="col_label_values"></property>
+                        <property name="col_label_vert_alignment">wxALIGN_CENTER</property>
+                        <property name="cols">2</property>
+                        <property name="column_sizes">150,500</property>
+                        <property name="context_help"></property>
+                        <property name="context_menu">1</property>
+                        <property name="default_pane">0</property>
+                        <property name="dock">Dock</property>
+                        <property name="dock_fixed">0</property>
+                        <property name="docking">Left</property>
+                        <property name="drag_col_move">0</property>
+                        <property name="drag_col_size">1</property>
+                        <property name="drag_grid_size">0</property>
+                        <property name="drag_row_size">1</property>
+                        <property name="editing">0</property>
+                        <property name="enabled">1</property>
+                        <property name="fg"></property>
+                        <property name="floatable">1</property>
+                        <property name="font"></property>
+                        <property name="grid_line_color"></property>
+                        <property name="grid_lines">1</property>
+                        <property name="gripper">0</property>
+                        <property name="hidden">0</property>
+                        <property name="id">wxID_ANY</property>
+                        <property name="label_bg"></property>
+                        <property name="label_font"></property>
+                        <property name="label_text"></property>
+                        <property name="margin_height">0</property>
+                        <property name="margin_width">0</property>
+                        <property name="max_size"></property>
+                        <property name="maximize_button">0</property>
+                        <property name="maximum_size"></property>
+                        <property name="min_size"></property>
+                        <property name="minimize_button">0</property>
+                        <property name="minimum_size"></property>
+                        <property name="moveable">1</property>
+                        <property name="name">m_path_subs_grid</property>
+                        <property name="pane_border">1</property>
+                        <property name="pane_position"></property>
+                        <property name="pane_size"></property>
+                        <property name="permission">protected</property>
+                        <property name="pin_button">1</property>
+                        <property name="pos"></property>
+                        <property name="resize">Resizable</property>
+                        <property name="row_label_horiz_alignment">wxALIGN_CENTER</property>
+                        <property name="row_label_size">0</property>
+                        <property name="row_label_values"></property>
+                        <property name="row_label_vert_alignment">wxALIGN_CENTER</property>
+                        <property name="row_sizes"></property>
+                        <property name="rows">1</property>
+                        <property name="show">1</property>
+                        <property name="size"></property>
+                        <property name="subclass">WX_GRID; widgets/wx_grid.h; forward_declare</property>
+                        <property name="toolbar_pane">0</property>
+                        <property name="tooltip">This is a read-only table which shows pertinent environment variables.</property>
+                        <property name="window_extra_style"></property>
+                        <property name="window_name"></property>
+                        <property name="window_style"></property>
+                        <event name="OnSize">onSizeGrid</event>
+                    </object>
+                </object>
+            </object>
+        </object>
+    </object>
+</wxFormBuilder_Project>
diff --git a/kicad/dialogs/panel_design_block_lib_table_base.h b/kicad/dialogs/panel_design_block_lib_table_base.h
new file mode 100644
index 0000000000..c815fd136d
--- /dev/null
+++ b/kicad/dialogs/panel_design_block_lib_table_base.h
@@ -0,0 +1,74 @@
+///////////////////////////////////////////////////////////////////////////
+// C++ code generated with wxFormBuilder (version 3.10.1-0-g8feb16b)
+// http://www.wxformbuilder.org/
+//
+// PLEASE DO *NOT* EDIT THIS FILE!
+///////////////////////////////////////////////////////////////////////////
+
+#pragma once
+
+#include <wx/artprov.h>
+#include <wx/xrc/xmlres.h>
+#include <wx/intl.h>
+class STD_BITMAP_BUTTON;
+class WX_GRID;
+
+#include <wx/colour.h>
+#include <wx/settings.h>
+#include <wx/string.h>
+#include <wx/font.h>
+#include <wx/grid.h>
+#include <wx/gdicmn.h>
+#include <wx/sizer.h>
+#include <wx/panel.h>
+#include <wx/bitmap.h>
+#include <wx/image.h>
+#include <wx/icon.h>
+#include <wx/notebook.h>
+#include <wx/bmpbuttn.h>
+#include <wx/button.h>
+#include <widgets/split_button.h>
+#include <wx/stattext.h>
+
+///////////////////////////////////////////////////////////////////////////
+
+
+///////////////////////////////////////////////////////////////////////////////
+/// Class PANEL_DESIGN_BLOCK_LIB_TABLE_BASE
+///////////////////////////////////////////////////////////////////////////////
+class PANEL_DESIGN_BLOCK_LIB_TABLE_BASE : public wxPanel
+{
+	private:
+
+	protected:
+		wxNotebook* m_notebook;
+		wxPanel* m_global_panel;
+		WX_GRID* m_global_grid;
+		wxPanel* m_project_panel;
+		WX_GRID* m_project_grid;
+		STD_BITMAP_BUTTON* m_append_button;
+		SPLIT_BUTTON* m_browseButton;
+		STD_BITMAP_BUTTON* m_move_up_button;
+		STD_BITMAP_BUTTON* m_move_down_button;
+		STD_BITMAP_BUTTON* m_delete_button;
+		wxButton* m_migrate_libs_button;
+		WX_GRID* m_path_subs_grid;
+
+		// Virtual event handlers, override them in your derived class
+		virtual void OnUpdateUI( wxUpdateUIEvent& event ) { event.Skip(); }
+		virtual void appendRowHandler( wxCommandEvent& event ) { event.Skip(); }
+		virtual void moveUpHandler( wxCommandEvent& event ) { event.Skip(); }
+		virtual void moveDownHandler( wxCommandEvent& event ) { event.Skip(); }
+		virtual void deleteRowHandler( wxCommandEvent& event ) { event.Skip(); }
+		virtual void onMigrateLibraries( wxCommandEvent& event ) { event.Skip(); }
+		virtual void onSizeGrid( wxSizeEvent& event ) { event.Skip(); }
+
+
+	public:
+
+		PANEL_DESIGN_BLOCK_LIB_TABLE_BASE( wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxSize( -1,-1 ), long style = wxTAB_TRAVERSAL, const wxString& name = wxEmptyString );
+
+		~PANEL_DESIGN_BLOCK_LIB_TABLE_BASE();
+
+};
+
diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp
index 3d99f13634..631cfcccf3 100644
--- a/kicad/kicad.cpp
+++ b/kicad/kicad.cpp
@@ -47,6 +47,7 @@
 #include <systemdirsappend.h>
 #include <trace_helpers.h>
 #include <wildcards_and_files_ext.h>
+#include <confirm.h>
 
 #include <git2.h>
 #include <stdexcept>
@@ -61,6 +62,7 @@
 #include <api/api_server.h>
 #endif
 
+#include <design_block_lib_table.h>
 
 // a dummy to quiet linking with EDA_BASE_FRAME::config();
 #include <kiface_base.h>
@@ -353,6 +355,27 @@ bool PGM_KICAD::OnPgmInit()
         }
     }
 
+    try
+    {
+        DESIGN_BLOCK_LIB_TABLE::LoadGlobalTable( GDesignBlockTable );
+    }
+    catch( const IO_ERROR& ioe )
+    {
+        // if we are here, a incorrect global design block library table was found.
+        // Incorrect global design block library table is not a fatal error:
+        // the user just has to edit the (partially) loaded table.
+        wxString msg = _( "An error occurred attempting to load the global design block library "
+                          "table.\n"
+                          "Please edit this global design block library table in Preferences "
+                          "menu." );
+
+        DisplayErrorMessage( nullptr, msg, ioe.What() );
+    }
+
+    if( managerFrame->IsProjectActive() && GDesignBlockList.GetCount() == 0 )
+        GDesignBlockList.ReadCacheFromFile( Prj().GetProjectPath()
+                                            + wxT( "design-block-info-cache" ) );
+
     frame->Show( true );
     frame->Raise();
 
diff --git a/kicad/menubar.cpp b/kicad/menubar.cpp
index d2c3246c82..e12b6778d8 100644
--- a/kicad/menubar.cpp
+++ b/kicad/menubar.cpp
@@ -210,6 +210,8 @@ void KICAD_MANAGER_FRAME::doReCreateMenuBar()
     prefsMenu->Add( ACTIONS::configurePaths );
     prefsMenu->Add( ACTIONS::showSymbolLibTable );
     prefsMenu->Add( ACTIONS::showFootprintLibTable );
+    if( ADVANCED_CFG::GetCfg().m_EnableDesignBlocks )
+        prefsMenu->Add( ACTIONS::showDesignBlockLibTable );
     prefsMenu->Add( ACTIONS::openPreferences );
 
     prefsMenu->AppendSeparator();
diff --git a/kicad/pgm_kicad.h b/kicad/pgm_kicad.h
index 12e98c7451..d131e0b4c9 100644
--- a/kicad/pgm_kicad.h
+++ b/kicad/pgm_kicad.h
@@ -28,6 +28,9 @@
 #include <pgm_base.h>
 #include <bin_mod.h>
 
+class DESIGN_BLOCK_LIB_TABLE;
+class DESIGN_BLOCK_LIST_IMPL;
+
 /**
  * PGM_KICAD
  * extends PGM_BASE to bring in FileHistory() and PdfBrowser() which were moved from EDA_APP
diff --git a/kicad/tools/kicad_manager_control.cpp b/kicad/tools/kicad_manager_control.cpp
index 3a532fc03a..3e96f2aacb 100644
--- a/kicad/tools/kicad_manager_control.cpp
+++ b/kicad/tools/kicad_manager_control.cpp
@@ -22,6 +22,7 @@
 #include <env_vars.h>
 #include <executable_names.h>
 #include <pgm_base.h>
+#include <pgm_kicad.h>
 #include <policy_keys.h>
 #include <kiway.h>
 #include <kicad_manager_frame.h>
@@ -36,6 +37,7 @@
 #include <tool/tool_event.h>
 #include <tools/kicad_manager_actions.h>
 #include <tools/kicad_manager_control.h>
+#include <dialogs/panel_design_block_lib_table.h>
 #include <dialogs/dialog_template_selector.h>
 #include <dialogs/git/dialog_git_repository.h>
 #include <git/git_clone_handler.h>
@@ -43,6 +45,7 @@
 #include <paths.h>
 #include <wx/dir.h>
 #include <wx/filedlg.h>
+#include <design_block_lib_table.h>
 #include "dialog_pcm.h"
 
 #include "widgets/filedlg_new_project.h"
@@ -887,6 +890,65 @@ int KICAD_MANAGER_CONTROL::Execute( const TOOL_EVENT& aEvent )
 }
 
 
+int KICAD_MANAGER_CONTROL::ShowDesignBlockLibTable( const TOOL_EVENT& aEvent )
+{
+    // For some reason, after a click or a double click the bitmap button calling
+    // PCM keeps the focus althougt the focus was not set to this button.
+    // This hack force removing the focus from this button
+    m_frame->SetFocus();
+    wxSafeYield();
+
+    DESIGN_BLOCK_LIB_TABLE* globalTable = &GDesignBlockTable;
+    wxString                globalTablePath = DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName();
+    DESIGN_BLOCK_LIB_TABLE* projectTable = Prj().DesignBlockLibs();
+    wxString                projectTablePath = Prj().DesignBlockLibTblName();
+    wxString                msg;
+
+    DIALOG_EDIT_LIBRARY_TABLES dlg( m_frame, _( "Design Block Libraries" ) );
+
+    if( Prj().IsNullProject() )
+        projectTable = nullptr;
+
+    dlg.InstallPanel( new PANEL_DESIGN_BLOCK_LIB_TABLE( &dlg, &Prj(), globalTable, globalTablePath,
+                                                        projectTable, projectTablePath,
+                                                        Prj().GetProjectPath() ) );
+
+    if( dlg.ShowModal() == wxID_CANCEL )
+        return 0;
+
+    if( dlg.m_GlobalTableChanged )
+    {
+        try
+        {
+            globalTable->Save( globalTablePath );
+        }
+        catch( const IO_ERROR& ioe )
+        {
+            msg.Printf( _( "Error saving global library table:\n\n%s" ), ioe.What() );
+            wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
+        }
+    }
+
+    if( projectTable && dlg.m_ProjectTableChanged )
+    {
+        try
+        {
+            projectTable->Save( projectTablePath );
+        }
+        catch( const IO_ERROR& ioe )
+        {
+            msg.Printf( _( "Error saving project-specific library table:\n\n%s" ), ioe.What() );
+            wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
+        }
+    }
+
+    std::string payload = "";
+    Kiway.ExpressMail( FRAME_SCH, MAIL_RELOAD_LIB, payload );
+    Kiway.ExpressMail( FRAME_SCH_VIEWER, MAIL_RELOAD_LIB, payload );
+
+    return 0;
+}
+
 int KICAD_MANAGER_CONTROL::ShowPluginManager( const TOOL_EVENT& aEvent )
 {
     if( KIPLATFORM::POLICY::GetPolicyBool( POLICY_KEY_PCM )
@@ -924,6 +986,7 @@ int KICAD_MANAGER_CONTROL::ShowPluginManager( const TOOL_EVENT& aEvent )
         // Reset project tables
         Prj().SetElem( PROJECT::ELEM::SYMBOL_LIB_TABLE, nullptr );
         Prj().SetElem( PROJECT::ELEM::FPTBL, nullptr );
+        Prj().SetElem( PROJECT::ELEM::DESIGN_BLOCK_LIB_TABLE, nullptr );
 
         KIWAY& kiway = m_frame->Kiway();
 
@@ -979,5 +1042,7 @@ void KICAD_MANAGER_CONTROL::setTransitions()
     Go( &KICAD_MANAGER_CONTROL::Execute,         KICAD_MANAGER_ACTIONS::editOtherSch.MakeEvent() );
     Go( &KICAD_MANAGER_CONTROL::Execute,         KICAD_MANAGER_ACTIONS::editOtherPCB.MakeEvent() );
 
+    Go( &KICAD_MANAGER_CONTROL::ShowDesignBlockLibTable,
+                                                   ACTIONS::showDesignBlockLibTable.MakeEvent() );
     Go( &KICAD_MANAGER_CONTROL::ShowPluginManager, KICAD_MANAGER_ACTIONS::showPluginManager.MakeEvent() );
 }
diff --git a/kicad/tools/kicad_manager_control.h b/kicad/tools/kicad_manager_control.h
index f95466f41c..bf1c2dd5b0 100644
--- a/kicad/tools/kicad_manager_control.h
+++ b/kicad/tools/kicad_manager_control.h
@@ -65,6 +65,7 @@ public:
     int ShowPlayer( const TOOL_EVENT& aEvent );
     int Execute( const TOOL_EVENT& aEvent );
 
+    int ShowDesignBlockLibTable( const TOOL_EVENT& aEvent );
     int ShowPluginManager( const TOOL_EVENT& aEvent );
 
     ///< Set up handlers for various events.
diff --git a/pcbnew/fp_tree_model_adapter.h b/pcbnew/fp_tree_model_adapter.h
index 1b4d3a246e..f620c992d0 100644
--- a/pcbnew/fp_tree_model_adapter.h
+++ b/pcbnew/fp_tree_model_adapter.h
@@ -51,7 +51,7 @@ protected:
 
     std::vector<LIB_TREE_ITEM*> getFootprints( const wxString& aLibName );
 
-    bool isSymbolModel() override { return false; }
+    PROJECT::LIB_TYPE_T getLibType() override { return PROJECT::LIB_TYPE_T::FOOTPRINT_LIB; }
 
 protected:
     FP_LIB_TABLE*   m_libs;
diff --git a/pcbnew/tools/drawing_tool.cpp b/pcbnew/tools/drawing_tool.cpp
index 13932a61f5..e3820b88a5 100644
--- a/pcbnew/tools/drawing_tool.cpp
+++ b/pcbnew/tools/drawing_tool.cpp
@@ -708,7 +708,7 @@ int DRAWING_TOOL::PlaceReferenceImage( const TOOL_EVENT& aEvent )
 
                 if( !image || !image->ReadImageFile( fullFilename ) )
                 {
-                    wxMessageBox( _( "Could not load image from '%s'." ), fullFilename );
+                    wxMessageBox( wxString::Format(_( "Could not load image from '%s'." ), fullFilename ) );
                     delete image;
                     image = nullptr;
                     continue;