diff --git a/examples/network-manager-app/linux/main.cpp b/examples/network-manager-app/linux/main.cpp
index ee6763a6d3d47d..6274be9a093268 100644
--- a/examples/network-manager-app/linux/main.cpp
+++ b/examples/network-manager-app/linux/main.cpp
@@ -16,10 +16,14 @@
  */
 
 #include <AppMain.h>
+#include <app/clusters/thread-network-directory-server/thread-network-directory-server.h>
 #include <app/clusters/wifi-network-management-server/wifi-network-management-server.h>
 #include <lib/core/CHIPSafeCasts.h>
+#include <lib/support/CodeUtils.h>
 #include <lib/support/Span.h>
 
+#include <optional>
+
 using namespace chip;
 using namespace chip::app;
 using namespace chip::app::Clusters;
@@ -32,6 +36,13 @@ ByteSpan ByteSpanFromCharSpan(CharSpan span)
     return ByteSpan(Uint8::from_const_char(span.data()), span.size());
 }
 
+std::optional<DefaultThreadNetworkDirectoryServer> gThreadNetworkDirectoryServer;
+void emberAfThreadNetworkDirectoryClusterInitCallback(chip::EndpointId endpoint)
+{
+    VerifyOrDie(!gThreadNetworkDirectoryServer);
+    gThreadNetworkDirectoryServer.emplace(endpoint).Init();
+}
+
 int main(int argc, char * argv[])
 {
     if (ChipLinuxAppInit(argc, argv) != 0)
diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.matter b/examples/network-manager-app/network-manager-common/network-manager-app.matter
index bee7b9d2df4c61..22fee8337e549e 100644
--- a/examples/network-manager-app/network-manager-common/network-manager-app.matter
+++ b/examples/network-manager-app/network-manager-common/network-manager-app.matter
@@ -1195,6 +1195,51 @@ cluster WiFiNetworkManagement = 1105 {
   command access(invoke: administer) NetworkPassphraseRequest(): NetworkPassphraseResponse = 0;
 }
 
+/** Manages the names and credentials of Thread networks visible to the user. */
+cluster ThreadNetworkDirectory = 1107 {
+  revision 1;
+
+  struct ThreadNetworkStruct {
+    octet_string<8> extendedPanID = 0;
+    char_string<16> networkName = 1;
+    int16u channel = 2;
+    int64u activeTimestamp = 3;
+  }
+
+  attribute access(read: manage, write: manage) nullable octet_string<8> preferredExtendedPanID = 0;
+  readonly attribute access(read: operate) ThreadNetworkStruct threadNetworks[] = 1;
+  readonly attribute int8u threadNetworkTableSize = 2;
+  readonly attribute command_id generatedCommandList[] = 65528;
+  readonly attribute command_id acceptedCommandList[] = 65529;
+  readonly attribute event_id eventList[] = 65530;
+  readonly attribute attrib_id attributeList[] = 65531;
+  readonly attribute bitmap32 featureMap = 65532;
+  readonly attribute int16u clusterRevision = 65533;
+
+  request struct AddNetworkRequest {
+    octet_string<254> operationalDataset = 0;
+  }
+
+  request struct RemoveNetworkRequest {
+    octet_string<8> extendedPanID = 0;
+  }
+
+  request struct GetOperationalDatasetRequest {
+    octet_string<8> extendedPanID = 0;
+  }
+
+  response struct OperationalDatasetResponse = 3 {
+    octet_string<254> operationalDataset = 0;
+  }
+
+  /** Adds an entry to the ThreadNetworks list. */
+  timed command access(invoke: manage) AddNetwork(AddNetworkRequest): DefaultSuccess = 0;
+  /** Removes an entry from the ThreadNetworks list. */
+  timed command access(invoke: manage) RemoveNetwork(RemoveNetworkRequest): DefaultSuccess = 1;
+  /** Retrieves a Thread Operational Dataset from the ThreadNetworks list. */
+  timed command GetOperationalDataset(GetOperationalDatasetRequest): OperationalDatasetResponse = 2;
+}
+
 endpoint 0 {
   device type ma_rootdevice = 22, version 1;
 
@@ -1473,6 +1518,23 @@ endpoint 1 {
     handle command NetworkPassphraseRequest;
     handle command NetworkPassphraseResponse;
   }
+
+  server cluster ThreadNetworkDirectory {
+    callback attribute preferredExtendedPanID;
+    callback attribute threadNetworks;
+    callback attribute threadNetworkTableSize;
+    callback attribute generatedCommandList;
+    callback attribute acceptedCommandList;
+    callback attribute eventList;
+    callback attribute attributeList;
+    ram      attribute featureMap default = 0;
+    ram      attribute clusterRevision default = 1;
+
+    handle command AddNetwork;
+    handle command RemoveNetwork;
+    handle command GetOperationalDataset;
+    handle command OperationalDatasetResponse;
+  }
 }
 
 
diff --git a/examples/network-manager-app/network-manager-common/network-manager-app.zap b/examples/network-manager-app/network-manager-common/network-manager-app.zap
index 2e0180ebc27357..ec8316ce2ad3c0 100644
--- a/examples/network-manager-app/network-manager-common/network-manager-app.zap
+++ b/examples/network-manager-app/network-manager-common/network-manager-app.zap
@@ -3349,6 +3349,194 @@
               "reportableChange": 0
             }
           ]
+        },
+        {
+          "name": "Thread Network Directory",
+          "code": 1107,
+          "mfgCode": null,
+          "define": "THREAD_NETWORK_DIRECTORY_CLUSTER",
+          "side": "server",
+          "enabled": 1,
+          "commands": [
+            {
+              "name": "AddNetwork",
+              "code": 0,
+              "mfgCode": null,
+              "source": "client",
+              "isIncoming": 1,
+              "isEnabled": 1
+            },
+            {
+              "name": "RemoveNetwork",
+              "code": 1,
+              "mfgCode": null,
+              "source": "client",
+              "isIncoming": 1,
+              "isEnabled": 1
+            },
+            {
+              "name": "GetOperationalDataset",
+              "code": 2,
+              "mfgCode": null,
+              "source": "client",
+              "isIncoming": 1,
+              "isEnabled": 1
+            },
+            {
+              "name": "OperationalDatasetResponse",
+              "code": 3,
+              "mfgCode": null,
+              "source": "server",
+              "isIncoming": 0,
+              "isEnabled": 1
+            }
+          ],
+          "attributes": [
+            {
+              "name": "PreferredExtendedPanID",
+              "code": 0,
+              "mfgCode": null,
+              "side": "server",
+              "type": "octet_string",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "ThreadNetworks",
+              "code": 1,
+              "mfgCode": null,
+              "side": "server",
+              "type": "array",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "ThreadNetworkTableSize",
+              "code": 2,
+              "mfgCode": null,
+              "side": "server",
+              "type": "int8u",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "GeneratedCommandList",
+              "code": 65528,
+              "mfgCode": null,
+              "side": "server",
+              "type": "array",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "AcceptedCommandList",
+              "code": 65529,
+              "mfgCode": null,
+              "side": "server",
+              "type": "array",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "EventList",
+              "code": 65530,
+              "mfgCode": null,
+              "side": "server",
+              "type": "array",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "AttributeList",
+              "code": 65531,
+              "mfgCode": null,
+              "side": "server",
+              "type": "array",
+              "included": 1,
+              "storageOption": "External",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": null,
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "FeatureMap",
+              "code": 65532,
+              "mfgCode": null,
+              "side": "server",
+              "type": "bitmap32",
+              "included": 1,
+              "storageOption": "RAM",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": "0",
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "ClusterRevision",
+              "code": 65533,
+              "mfgCode": null,
+              "side": "server",
+              "type": "int16u",
+              "included": 1,
+              "storageOption": "RAM",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": "1",
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            }
+          ]
         }
       ]
     }
diff --git a/scripts/rules.matterlint b/scripts/rules.matterlint
index b4af613c584c29..f44681febd112d 100644
--- a/scripts/rules.matterlint
+++ b/scripts/rules.matterlint
@@ -94,6 +94,7 @@ load "../src/app/zap-templates/zcl/data-model/chip/thermostat-user-interface-con
 load "../src/app/zap-templates/zcl/data-model/chip/thermostat-cluster.xml";
 load "../src/app/zap-templates/zcl/data-model/chip/thread-border-router-management-cluster.xml";
 load "../src/app/zap-templates/zcl/data-model/chip/thread-network-diagnostics-cluster.xml";
+load "../src/app/zap-templates/zcl/data-model/chip/thread-network-directory-cluster.xml";
 load "../src/app/zap-templates/zcl/data-model/chip/time-format-localization-cluster.xml";
 load "../src/app/zap-templates/zcl/data-model/chip/time-synchronization-cluster.xml";
 load "../src/app/zap-templates/zcl/data-model/chip/timer-cluster.xml";
diff --git a/src/app/AttributeAccessInterface.h b/src/app/AttributeAccessInterface.h
index f92b5987684039..50e0812fb61c1a 100644
--- a/src/app/AttributeAccessInterface.h
+++ b/src/app/AttributeAccessInterface.h
@@ -149,6 +149,9 @@ class AttributeAccessInterface
             (!mEndpointId.HasValue() || !aOther.mEndpointId.HasValue() || mEndpointId.Value() == aOther.mEndpointId.Value());
     }
 
+protected:
+    Optional<EndpointId> GetEndpointId() { return mEndpointId; }
+
 private:
     Optional<EndpointId> mEndpointId;
     ClusterId mClusterId;
diff --git a/src/app/CommandHandlerInterface.h b/src/app/CommandHandlerInterface.h
index d36a41e37f076c..6a0d8a4bc36e5c 100644
--- a/src/app/CommandHandlerInterface.h
+++ b/src/app/CommandHandlerInterface.h
@@ -226,6 +226,8 @@ class CommandHandlerInterface
         }
     }
 
+    Optional<EndpointId> GetEndpointId() { return mEndpointId; }
+
 private:
     Optional<EndpointId> mEndpointId;
     ClusterId mClusterId;
diff --git a/src/app/chip_data_model.gni b/src/app/chip_data_model.gni
index a68d193d241541..6b37e857c6b2d8 100644
--- a/src/app/chip_data_model.gni
+++ b/src/app/chip_data_model.gni
@@ -390,6 +390,14 @@ template("chip_data_model") {
           "${_app_root}/clusters/${cluster}/thread-network-diagnostics-provider.cpp",
           "${_app_root}/clusters/${cluster}/thread-network-diagnostics-provider.h",
         ]
+      } else if (cluster == "thread-network-directory-server") {
+        sources += [
+          "${_app_root}/clusters/${cluster}/${cluster}.cpp",
+          "${_app_root}/clusters/${cluster}/${cluster}.h",
+          "${_app_root}/clusters/${cluster}/DefaultThreadNetworkDirectoryStorage.cpp",
+          "${_app_root}/clusters/${cluster}/DefaultThreadNetworkDirectoryStorage.h",
+          "${_app_root}/clusters/${cluster}/ThreadNetworkDirectoryStorage.h",
+        ]
       } else {
         sources += [ "${_app_root}/clusters/${cluster}/${cluster}.cpp" ]
       }
diff --git a/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp b/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp
new file mode 100644
index 00000000000000..6daaffce9ce91c
--- /dev/null
+++ b/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp
@@ -0,0 +1,144 @@
+/**
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "DefaultThreadNetworkDirectoryStorage.h"
+#include <app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h>
+#include <lib/support/DefaultStorageKeyAllocator.h>
+#include <lib/support/SafeInt.h>
+
+namespace chip {
+namespace app {
+
+void DefaultThreadNetworkDirectoryStorage::InitializedIfNeeded()
+{
+    VerifyOrReturn(!mInitialized);
+    mInitialized = true;
+
+    CHIP_ERROR err;
+    StorageKeyName key = DefaultStorageKeyAllocator::ThreadNetworkDirectoryIndex();
+    uint16_t size      = sizeof(mExtendedPanIds);
+    SuccessOrExit(err = mStorage.SyncGetKeyValue(key.KeyName(), mExtendedPanIds, size));
+    VerifyOrExit(size % ExtendedPanId::size() == 0, err = CHIP_ERROR_INTERNAL);
+    mCount = static_cast<index_t>(size / ExtendedPanId::size());
+    return;
+exit:
+    if (err != CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
+    {
+        ChipLogError(Zcl, "Failed to load Thread Network Directory storage: %" CHIP_ERROR_FORMAT, err.Format());
+    }
+}
+
+CHIP_ERROR DefaultThreadNetworkDirectoryStorage::StoreIndex()
+{
+    VerifyOrDie(mInitialized);
+    StorageKeyName key = DefaultStorageKeyAllocator::ThreadNetworkDirectoryIndex();
+    return mStorage.SyncSetKeyValue(key.KeyName(), mExtendedPanIds, mCount * ExtendedPanId::size());
+}
+
+bool DefaultThreadNetworkDirectoryStorage::FindNetwork(const ExtendedPanId & exPanId, index_t & outIndex)
+{
+    for (index_t idx = 0; idx < mCount; idx++)
+    {
+        if (mExtendedPanIds[idx] == exPanId)
+        {
+            outIndex = idx;
+            return true;
+        }
+    }
+    return false;
+}
+
+ThreadNetworkDirectoryStorage::ExtendedPanIdIterator * DefaultThreadNetworkDirectoryStorage::IterateNetworkIds()
+{
+    InitializedIfNeeded();
+    return mIterators.CreateObject(*this);
+}
+
+bool DefaultThreadNetworkDirectoryStorage::ContainsNetwork(const ExtendedPanId & exPanId)
+{
+    InitializedIfNeeded();
+    index_t unused;
+    return FindNetwork(exPanId, unused);
+}
+
+CHIP_ERROR DefaultThreadNetworkDirectoryStorage::GetNetworkDataset(const ExtendedPanId & exPanId, MutableByteSpan & dataset)
+{
+    VerifyOrReturnError(ContainsNetwork(exPanId), CHIP_ERROR_NOT_FOUND);
+    uint16_t size      = static_cast<uint16_t>(CanCastTo<uint16_t>(dataset.size()) ? dataset.size() : UINT16_MAX);
+    StorageKeyName key = DefaultStorageKeyAllocator::ThreadNetworkDirectoryDataset(exPanId.AsNumber());
+    ReturnErrorOnFailure(mStorage.SyncGetKeyValue(key.KeyName(), dataset.data(), size));
+    dataset.reduce_size(size);
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR DefaultThreadNetworkDirectoryStorage::AddOrUpdateNetwork(const ExtendedPanId & exPanId, ByteSpan dataset)
+{
+    VerifyOrReturnError(0 < dataset.size() && dataset.size() <= kMaxThreadDatasetLen, CHIP_ERROR_INVALID_ARGUMENT);
+    bool update = ContainsNetwork(exPanId);
+    VerifyOrReturnError(update || mCount < kCapacity, CHIP_ERROR_NO_MEMORY);
+
+    // Store the dataset first
+    StorageKeyName key = DefaultStorageKeyAllocator::ThreadNetworkDirectoryDataset(exPanId.AsNumber());
+    ReturnErrorOnFailure(mStorage.SyncSetKeyValue(key.KeyName(), dataset.data(), static_cast<uint16_t>(dataset.size())));
+
+    // Update the index if we're adding a new network, rolling back on failure.
+    if (!update)
+    {
+        mExtendedPanIds[mCount++] = exPanId;
+        CHIP_ERROR err            = StoreIndex();
+        if (err != CHIP_NO_ERROR)
+        {
+            mCount--;
+            mStorage.SyncDeleteKeyValue(key.KeyName());
+            return err;
+        }
+    }
+
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR DefaultThreadNetworkDirectoryStorage::RemoveNetwork(const ExtendedPanId & exPanId)
+{
+    InitializedIfNeeded();
+    index_t index;
+    VerifyOrReturnError(FindNetwork(exPanId, index), CHIP_ERROR_NOT_FOUND);
+
+    // Move subsequent elements down to fill the deleted slot
+    static_assert(std::is_trivially_copyable_v<ExtendedPanId>);
+    size_t subsequentCount = mCount - (index + 1u);
+    auto * element         = &mExtendedPanIds[index];
+    memmove(element, element + 1, subsequentCount * sizeof(*element));
+    mCount--;
+
+    CHIP_ERROR err = StoreIndex();
+    if (err != CHIP_NO_ERROR)
+    {
+        // Roll back the change to our in-memory state
+        memmove(element + 1, element, subsequentCount * sizeof(*element));
+        mExtendedPanIds[index] = exPanId;
+        mCount++;
+        return err;
+    }
+
+    // Delete the dataset itself. Ignore errors since we successfully updated the index.
+    StorageKeyName key = DefaultStorageKeyAllocator::ThreadNetworkDirectoryDataset(exPanId.AsNumber());
+    mStorage.SyncDeleteKeyValue(key.KeyName());
+    return CHIP_NO_ERROR;
+}
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h b/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h
new file mode 100644
index 00000000000000..0cda5e3e188c7f
--- /dev/null
+++ b/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h
@@ -0,0 +1,80 @@
+/**
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#pragma once
+
+#include <app/clusters/thread-network-directory-server/ThreadNetworkDirectoryStorage.h>
+#include <lib/core/CHIPPersistentStorageDelegate.h>
+#include <lib/support/Pool.h>
+
+#include <optional>
+
+namespace chip {
+namespace app {
+
+/**
+ * Stores Thread network information via a PersistentStorageDelegate.
+ */
+class DefaultThreadNetworkDirectoryStorage : public ThreadNetworkDirectoryStorage
+{
+public:
+    DefaultThreadNetworkDirectoryStorage(PersistentStorageDelegate & storage) : mStorage(storage) {}
+
+    uint8_t Capacity() override { return kCapacity; }
+    ExtendedPanIdIterator * IterateNetworkIds() override;
+    bool ContainsNetwork(const ExtendedPanId & exPanId) override;
+    CHIP_ERROR GetNetworkDataset(const ExtendedPanId & exPanId, MutableByteSpan & dataset) override;
+    CHIP_ERROR AddOrUpdateNetwork(const ExtendedPanId & exPanId, ByteSpan dataset) override;
+    CHIP_ERROR RemoveNetwork(const ExtendedPanId & exPanId) override;
+
+private:
+    using index_t                         = uint8_t;
+    static constexpr index_t kCapacity    = CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CAPACITY;
+    static constexpr size_t kIteratorsMax = CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CONCURRENT_ITERATORS;
+
+    void InitializedIfNeeded();
+    CHIP_ERROR StoreIndex();
+    bool FindNetwork(const ExtendedPanId & exPanId, index_t & outIndex);
+
+    struct IteratorImpl final : public ExtendedPanIdIterator
+    {
+        IteratorImpl(DefaultThreadNetworkDirectoryStorage & storage) : mContainer(storage) {}
+
+        size_t Count() override { return mContainer.mCount; }
+
+        bool Next(ExtendedPanId & item) override
+        {
+            VerifyOrReturnValue(mIndex < Count(), false);
+            item = mContainer.mExtendedPanIds[mIndex++];
+            return true;
+        }
+
+        void Release() override { mContainer.mIterators.ReleaseObject(this); }
+
+        DefaultThreadNetworkDirectoryStorage & mContainer;
+        index_t mIndex = 0;
+    };
+
+    PersistentStorageDelegate & mStorage;
+    ObjectPool<IteratorImpl, kIteratorsMax> mIterators;
+    ExtendedPanId mExtendedPanIds[kCapacity];
+    index_t mCount    = 0;
+    bool mInitialized = false;
+};
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/thread-network-directory-server/ThreadNetworkDirectoryStorage.h b/src/app/clusters/thread-network-directory-server/ThreadNetworkDirectoryStorage.h
new file mode 100644
index 00000000000000..b31d34e9778bd0
--- /dev/null
+++ b/src/app/clusters/thread-network-directory-server/ThreadNetworkDirectoryStorage.h
@@ -0,0 +1,127 @@
+/**
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#pragma once
+
+#include <lib/core/CHIPEncoding.h>
+#include <lib/support/CommonIterator.h>
+#include <lib/support/Span.h>
+
+#include <cstdint>
+#include <cstring>
+#include <type_traits>
+
+namespace chip {
+namespace app {
+
+class ThreadNetworkDirectoryStorage
+{
+public:
+    static constexpr size_t kMaxThreadDatasetLen = 254;
+
+    /**
+     * A Thread Extended PAN ID is an opaque 8 byte value,
+     * and can optionally be interpreted as a big-endian number.
+     *
+     * ExtendedPanId structs or arrays thereof can be directly
+     * read from or written to storage and are byte-order independent.
+     */
+    struct ExtendedPanId final
+    {
+        uint8_t bytes[8];
+        static constexpr size_t size() { return sizeof(bytes); }
+
+        constexpr ExtendedPanId() : bytes{} {}
+        explicit ExtendedPanId(ByteSpan source)
+        {
+            VerifyOrDie(source.size() == size());
+            memcpy(bytes, source.data(), size());
+        }
+
+        explicit ExtendedPanId(uint64_t number) { Encoding::BigEndian::Put64(bytes, number); }
+
+        constexpr ByteSpan AsSpan() const { return ByteSpan(bytes); }
+        uint64_t AsNumber() const { return Encoding::BigEndian::Get64(bytes); }
+
+        bool operator==(const ExtendedPanId & other) const { return memcmp(bytes, other.bytes, size()) == 0; }
+        bool operator!=(const ExtendedPanId & other) const { return !(*this == other); }
+    };
+
+    static_assert(std::is_trivially_copyable_v<ExtendedPanId> && sizeof(ExtendedPanId) == sizeof(ExtendedPanId::bytes));
+
+    using ExtendedPanIdIterator = CommonIterator<ExtendedPanId>;
+
+    virtual ~ThreadNetworkDirectoryStorage() = default;
+
+    /**
+     * Returns the maximum number of networks that can be stored.
+     */
+    virtual uint8_t Capacity() = 0;
+
+    /**
+     *  Creates an iterator over the list of stored networks.
+     *  Release() must be called on the iterator after the iteration is finished.
+     *  Adding or removing networks during the iteration is not supported.
+     *
+     *  The iteration order of stored networks should remain stable as long as
+     *  as no networks are added or removed.
+     *
+     *  @retval An instance of ExtendedPanIdIterator on success
+     *  @retval nullptr if no iterator instances are available.
+     */
+    virtual ExtendedPanIdIterator * IterateNetworkIds() = 0;
+
+    /**
+     *  Retrieves the dataset associated with the specified Extended PAN ID.
+     *
+     *  @retval CHIP_ERROR_NOT_FOUND if there is no matching network stored.
+     *  @retval CHIP_ERROR_BUFFER_TOO_SMALL if the provided buffer buffer is too small.
+     *  @retval CHIP_ERROR_* for other errors.
+     */
+    virtual CHIP_ERROR GetNetworkDataset(const ExtendedPanId & exPanId, MutableByteSpan & dataset) = 0;
+
+    /**
+     *  Adds (or updates, if a matching network already exists) the network with the specified
+     *  Extended PAN ID and dataset. Note that the dataset must be treated as an opaque blob;
+     *  no validation of any kind is expected to be performed on the dataset contents.
+     *
+     *  @retval CHIP_ERROR_INVALID_ARGUMENT if the dataset is empty or too long.
+     *  @retval CHIP_ERROR_NO_MEMORY if adding the network would exceed the storage capacity.
+     *  @retval CHIP_ERROR_* for other errors.
+     */
+    virtual CHIP_ERROR AddOrUpdateNetwork(const ExtendedPanId & exPanId, ByteSpan dataset) = 0;
+
+    /**
+     *  Removes the network with the specified Extended PAN ID.
+     *
+     *  @retval CHIP_ERROR_NOT_FOUND if there is no matching network stored.
+     *  @retval CHIP_ERROR_* for other errors.
+     */
+    virtual CHIP_ERROR RemoveNetwork(const ExtendedPanId & exPanId) = 0;
+
+    /**
+     *  Returns true if a network with the specified Extended PAN ID is stored, or false otherwise.
+     */
+    virtual bool ContainsNetwork(const ExtendedPanId & exPanId)
+    {
+        MutableByteSpan empty;
+        return GetNetworkDataset(exPanId, empty) != CHIP_ERROR_NOT_FOUND;
+    }
+};
+
+} // namespace app
+} // namespace chip
diff --git a/src/app/clusters/thread-network-directory-server/thread-network-directory-server.cpp b/src/app/clusters/thread-network-directory-server/thread-network-directory-server.cpp
new file mode 100644
index 00000000000000..888972b3075c33
--- /dev/null
+++ b/src/app/clusters/thread-network-directory-server/thread-network-directory-server.cpp
@@ -0,0 +1,288 @@
+/**
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include "thread-network-directory-server.h"
+
+#include <app/AttributeAccessInterfaceRegistry.h>
+#include <app/InteractionModelEngine.h>
+#include <app/MessageDef/StatusIB.h>
+#include <app/SafeAttributePersistenceProvider.h>
+#include <app/data-model/Nullable.h>
+#include <app/reporting/reporting.h>
+#include <lib/support/CodeUtils.h>
+#include <lib/support/ThreadOperationalDataset.h>
+
+using namespace chip;
+using namespace chip::app;
+using namespace chip::app::Clusters;
+using namespace chip::app::Clusters::ThreadNetworkDirectory::Attributes;
+using namespace chip::app::Clusters::ThreadNetworkDirectory::Commands;
+using namespace chip::app::Clusters::ThreadNetworkDirectory::Structs;
+using namespace chip::Thread;
+using IMStatus = chip::Protocols::InteractionModel::Status;
+
+namespace chip {
+namespace app {
+namespace Clusters {
+
+ThreadNetworkDirectoryServer::ThreadNetworkDirectoryServer(EndpointId endpoint, ThreadNetworkDirectoryStorage & storage) :
+    AttributeAccessInterface(MakeOptional(endpoint), ThreadNetworkDirectory::Id),
+    CommandHandlerInterface(MakeOptional(endpoint), ThreadNetworkDirectory::Id), mStorage(storage)
+{}
+
+ThreadNetworkDirectoryServer::~ThreadNetworkDirectoryServer()
+{
+    unregisterAttributeAccessOverride(this);
+    InteractionModelEngine::GetInstance()->UnregisterCommandHandler(this);
+}
+
+CHIP_ERROR ThreadNetworkDirectoryServer::Init()
+{
+    VerifyOrReturnError(registerAttributeAccessOverride(this), CHIP_ERROR_INTERNAL);
+    ReturnErrorOnFailure(InteractionModelEngine::GetInstance()->RegisterCommandHandler(this));
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ThreadNetworkDirectoryServer::Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder)
+{
+    switch (aPath.mAttributeId)
+    {
+    case PreferredExtendedPanID::Id:
+        return ReadPreferredExtendedPanId(aPath, aEncoder);
+    case ThreadNetworks::Id:
+        return ReadThreadNetworks(aPath, aEncoder);
+    case ThreadNetworkTableSize::Id:
+        return aEncoder.Encode(mStorage.Capacity());
+    }
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ThreadNetworkDirectoryServer::Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder)
+{
+    switch (aPath.mAttributeId)
+    {
+    case PreferredExtendedPanID::Id:
+        return WritePreferredExtendedPanId(aPath, aDecoder);
+    }
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ThreadNetworkDirectoryServer::ReadExtendedPanId(const ConcreteDataAttributePath & aPath,
+                                                           std::optional<ExtendedPanId> & outExPanId)
+{
+    MutableByteSpan value(outExPanId.emplace().bytes);
+    CHIP_ERROR err = GetSafeAttributePersistenceProvider()->SafeReadValue(aPath, value);
+    if (err == CHIP_ERROR_PERSISTED_STORAGE_VALUE_NOT_FOUND)
+    {
+        outExPanId.reset(); // default to empty
+        return CHIP_NO_ERROR;
+    }
+    ReturnErrorOnFailure(err);
+
+    if (value.size() == 0)
+    {
+        outExPanId.reset();
+        return CHIP_NO_ERROR;
+    }
+
+    VerifyOrReturnError(value.size() == ExtendedPanId::size(), CHIP_ERROR_INTERNAL);
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR ThreadNetworkDirectoryServer::ReadPreferredExtendedPanId(const ConcreteDataAttributePath & aPath,
+                                                                    AttributeValueEncoder & aEncoder)
+{
+    std::optional<ExtendedPanId> value;
+    ReturnErrorOnFailure(ReadExtendedPanId(aPath, value));
+    return (value.has_value()) ? aEncoder.Encode(value.value().AsSpan()) : aEncoder.EncodeNull();
+}
+
+CHIP_ERROR ThreadNetworkDirectoryServer::WritePreferredExtendedPanId(const ConcreteDataAttributePath & aPath,
+                                                                     AttributeValueDecoder & aDecoder)
+{
+    DataModel::Nullable<ByteSpan> nullableValue;
+    ReturnErrorOnFailure(aDecoder.Decode(nullableValue));
+
+    // "A zero-length value SHALL be allowed for nullable values ... and SHALL have the same semantics as the null value."
+    ByteSpan value = nullableValue.ValueOr(ByteSpan());
+    // Ensure the provided value is valid (correct size) and refers to PAN from the list.
+    VerifyOrReturnError(value.empty() || (value.size() == ExtendedPanId::size() && mStorage.ContainsNetwork(ExtendedPanId(value))),
+                        StatusIB(IMStatus::ConstraintError).ToChipError());
+
+    return GetSafeAttributePersistenceProvider()->SafeWriteValue(aPath, value);
+}
+
+CHIP_ERROR ThreadNetworkDirectoryServer::ReadThreadNetworks(const ConcreteDataAttributePath &, AttributeValueEncoder & aEncoder)
+{
+    return aEncoder.EncodeList([this](const auto & encoder) -> CHIP_ERROR {
+        CHIP_ERROR err = CHIP_NO_ERROR;
+        ExtendedPanId exPanId;
+        auto * iterator = mStorage.IterateNetworkIds();
+        while (iterator->Next(exPanId))
+        {
+            uint8_t datasetBuffer[kSizeOperationalDataset];
+            MutableByteSpan datasetSpan(datasetBuffer);
+            SuccessOrExit(err = mStorage.GetNetworkDataset(exPanId, datasetSpan));
+
+            OperationalDataset dataset;
+            char networkName[kSizeNetworkName + 1];
+            ThreadNetworkStruct::Type network;
+
+            dataset.Init(datasetSpan);
+            SuccessOrExit(err = dataset.GetExtendedPanIdAsByteSpan(network.extendedPanID));
+            SuccessOrExit(err = dataset.GetNetworkName(networkName));
+            network.networkName = CharSpan::fromCharString(networkName);
+            SuccessOrExit(err = dataset.GetChannel(network.channel));
+            SuccessOrExit(err = dataset.GetActiveTimestamp(network.activeTimestamp));
+
+            SuccessOrExit(err = encoder.Encode(network));
+        }
+    exit:
+        iterator->Release();
+        return err;
+    });
+}
+
+void ThreadNetworkDirectoryServer::InvokeCommand(HandlerContext & ctx)
+{
+    switch (ctx.mRequestPath.mCommandId)
+    {
+    case AddNetwork::Id:
+        HandleCommand<AddNetwork::DecodableType>(
+            ctx, [this](HandlerContext & aCtx, const auto & req) { HandleAddNetworkRequest(aCtx, req); });
+        return;
+    case RemoveNetwork::Id:
+        HandleCommand<RemoveNetwork::DecodableType>(
+            ctx, [this](HandlerContext & aCtx, const auto & req) { HandleRemoveNetworkRequest(aCtx, req); });
+        return;
+    case GetOperationalDataset::Id:
+        HandleCommand<GetOperationalDataset::DecodableType>(
+            ctx, [this](HandlerContext & aCtx, const auto & req) { HandleOperationalDatasetRequest(aCtx, req); });
+        return;
+    }
+}
+
+void ThreadNetworkDirectoryServer::HandleAddNetworkRequest(HandlerContext & ctx,
+                                                           const ThreadNetworkDirectory::Commands::AddNetwork::DecodableType & req)
+{
+    OperationalDataset dataset;
+    ByteSpan extendedPanIdSpan;
+    union
+    {
+        uint64_t activeTimestamp;
+        uint16_t channel;
+        uint8_t masterKey[kSizeMasterKey];
+        uint8_t meshLocalPrefix[kSizeMeshLocalPrefix];
+        char networkName[kSizeNetworkName + 1];
+        uint16_t panId;
+        uint8_t pksc[kSizePSKc];
+        uint32_t securityPolicy;
+    } unused;
+    ByteSpan unusedSpan;
+
+    // "It SHALL contain at least the following sub-TLVs: Active Timestamp, Channel, Channel Mask,
+    // Extended PAN ID, Network Key, Network Mesh-Local Prefix, Network Name, PAN ID, PKSc, and Security Policy."
+    CHIP_ERROR err;
+    auto status          = IMStatus::ConstraintError;
+    const char * context = nullptr;
+    // TODO: An immutable OperationalDatasetView on top of a ByteSpan (without copying) would be useful here.
+    SuccessOrExitAction(err = dataset.Init(req.operationalDataset), context = "OperationalDataset");
+    SuccessOrExitAction(err = dataset.GetExtendedPanIdAsByteSpan(extendedPanIdSpan), context = "ExtendedPanID");
+    SuccessOrExitAction(err = dataset.GetActiveTimestamp(unused.activeTimestamp), context = "ActiveTimestamp");
+    SuccessOrExitAction(err = dataset.GetChannel(unused.channel), context = "Channel");
+    SuccessOrExitAction(err = dataset.GetChannelMask(unusedSpan), context = "ChannelMask");
+    SuccessOrExitAction(err = dataset.GetMasterKey(unused.masterKey), context = "NetworkKey");
+    SuccessOrExitAction(err = dataset.GetMeshLocalPrefix(unused.meshLocalPrefix), context = "MeshLocalPrefix");
+    SuccessOrExitAction(err = dataset.GetNetworkName(unused.networkName), context = "NetworkName");
+    SuccessOrExitAction(err = dataset.GetPanId(unused.panId), context = "PanID");
+    SuccessOrExitAction(err = dataset.GetPSKc(unused.pksc), context = "PKSc");
+    SuccessOrExitAction(err = dataset.GetSecurityPolicy(unused.securityPolicy), context = "SecurityContext");
+
+    status = IMStatus::Failure;
+    SuccessOrExit(err = mStorage.AddOrUpdateNetwork(ExtendedPanId(extendedPanIdSpan), req.operationalDataset));
+
+    ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::Success);
+    MatterReportingAttributeChangeCallback(GetEndpointId(), ThreadNetworkDirectory::Id, ThreadNetworks::Id);
+    return;
+
+exit:
+    ChipLogError(Zcl, "AddNetwork: %" CHIP_ERROR_FORMAT, err.Format());
+    ctx.mCommandHandler.AddStatus(
+        ctx.mRequestPath, (status == IMStatus::Failure && err == CHIP_ERROR_NO_MEMORY) ? IMStatus::ResourceExhausted : status,
+        context);
+}
+
+void ThreadNetworkDirectoryServer::HandleRemoveNetworkRequest(
+    HandlerContext & ctx, const ThreadNetworkDirectory::Commands::RemoveNetwork::DecodableType & req)
+{
+    CHIP_ERROR err;
+
+    if (req.extendedPanID.size() != ExtendedPanId::size())
+    {
+        ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::ConstraintError);
+        return;
+    }
+    ExtendedPanId exPanId(req.extendedPanID);
+
+    std::optional<ExtendedPanId> preferredExPanId;
+    ConcreteReadAttributePath preferredExPanIdPath(GetEndpointId(), ThreadNetworkDirectory::Id,
+                                                   ThreadNetworkDirectory::Attributes::PreferredExtendedPanID::Id);
+    SuccessOrExit(err = ReadExtendedPanId(preferredExPanIdPath, preferredExPanId));
+    if (preferredExPanId.has_value() && preferredExPanId.value() == exPanId)
+    {
+        ChipLogError(Zcl, "RemoveNetwork: Rejecting removal of preferred PAN");
+        ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::ConstraintError);
+        return;
+    }
+
+    SuccessOrExit(err = mStorage.RemoveNetwork(exPanId));
+
+    ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::Success);
+    MatterReportingAttributeChangeCallback(GetEndpointId(), ThreadNetworkDirectory::Id, ThreadNetworks::Id);
+    return;
+
+exit:
+    ChipLogError(Zcl, "RemoveNetwork: %" CHIP_ERROR_FORMAT, err.Format());
+    ctx.mCommandHandler.AddStatus(ctx.mRequestPath, (err == CHIP_ERROR_NOT_FOUND) ? IMStatus::NotFound : IMStatus::Failure);
+}
+
+void ThreadNetworkDirectoryServer::HandleOperationalDatasetRequest(
+    HandlerContext & ctx, const ThreadNetworkDirectory::Commands::GetOperationalDataset::DecodableType & req)
+{
+    CHIP_ERROR err;
+
+    if (req.extendedPanID.size() != ExtendedPanId::size())
+    {
+        ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::ConstraintError);
+        return;
+    }
+
+    uint8_t datasetBuffer[kSizeOperationalDataset];
+    MutableByteSpan datasetSpan(datasetBuffer);
+    SuccessOrExit(err = mStorage.GetNetworkDataset(ExtendedPanId(req.extendedPanID), datasetSpan));
+    ctx.mCommandHandler.AddStatus(ctx.mRequestPath, IMStatus::Success);
+    return;
+exit:
+    ChipLogError(Zcl, "GetOperationalDataset: %" CHIP_ERROR_FORMAT, err.Format());
+    ctx.mCommandHandler.AddStatus(ctx.mRequestPath, (err == CHIP_ERROR_NOT_FOUND) ? IMStatus::NotFound : IMStatus::Failure);
+}
+
+} // namespace Clusters
+} // namespace app
+} // namespace chip
+
+void MatterThreadNetworkDirectoryPluginServerInitCallback() {}
diff --git a/src/app/clusters/thread-network-directory-server/thread-network-directory-server.h b/src/app/clusters/thread-network-directory-server/thread-network-directory-server.h
new file mode 100644
index 00000000000000..2f671bdd6b4e09
--- /dev/null
+++ b/src/app/clusters/thread-network-directory-server/thread-network-directory-server.h
@@ -0,0 +1,86 @@
+/**
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#pragma once
+
+#include <app-common/zap-generated/cluster-objects.h>
+#include <app/AttributeAccessInterface.h>
+#include <app/CommandHandlerInterface.h>
+#include <app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h>
+#include <app/clusters/thread-network-directory-server/ThreadNetworkDirectoryStorage.h>
+#include <app/server/Server.h>
+#include <lib/core/CHIPError.h>
+
+#include <optional>
+
+namespace chip {
+namespace app {
+namespace Clusters {
+
+class ThreadNetworkDirectoryServer : private AttributeAccessInterface, private CommandHandlerInterface
+{
+public:
+    ThreadNetworkDirectoryServer(EndpointId endpoint, ThreadNetworkDirectoryStorage & storage);
+    ~ThreadNetworkDirectoryServer();
+
+    CHIP_ERROR Init();
+
+    ThreadNetworkDirectoryServer(ThreadNetworkDirectoryServer const &)             = delete;
+    ThreadNetworkDirectoryServer & operator=(ThreadNetworkDirectoryServer const &) = delete;
+
+private:
+    using ExtendedPanId = ThreadNetworkDirectoryStorage::ExtendedPanId;
+
+    EndpointId GetEndpointId() { return AttributeAccessInterface::GetEndpointId().Value(); }
+
+    CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
+    CHIP_ERROR Write(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder) override;
+    void InvokeCommand(HandlerContext & handlerContext) override;
+
+    CHIP_ERROR ReadExtendedPanId(const ConcreteDataAttributePath & aPath, std::optional<ExtendedPanId> & outExPanId);
+    CHIP_ERROR ReadPreferredExtendedPanId(const ConcreteDataAttributePath & aPath, AttributeValueEncoder & aEncoder);
+    CHIP_ERROR WritePreferredExtendedPanId(const ConcreteDataAttributePath & aPath, AttributeValueDecoder & aDecoder);
+    CHIP_ERROR ReadThreadNetworks(const ConcreteDataAttributePath & aPath, AttributeValueEncoder & aEncoder);
+
+    void HandleAddNetworkRequest(HandlerContext & ctx, const ThreadNetworkDirectory::Commands::AddNetwork::DecodableType & req);
+    void HandleRemoveNetworkRequest(HandlerContext & ctx,
+                                    const ThreadNetworkDirectory::Commands::RemoveNetwork::DecodableType & req);
+    void HandleOperationalDatasetRequest(HandlerContext & ctx,
+                                         const ThreadNetworkDirectory::Commands::GetOperationalDataset::DecodableType & req);
+
+    ThreadNetworkDirectoryStorage & mStorage;
+};
+
+/**
+ * A ThreadNetworkDirectoryServer using DefaultThreadNetworkDirectoryStorage.
+ */
+class DefaultThreadNetworkDirectoryServer final : public ThreadNetworkDirectoryServer
+{
+public:
+    DefaultThreadNetworkDirectoryServer(EndpointId endpoint,
+                                        PersistentStorageDelegate & storage = Server::GetInstance().GetPersistentStorage()) :
+        ThreadNetworkDirectoryServer(endpoint, mStorage),
+        mStorage(storage)
+    {}
+
+private:
+    DefaultThreadNetworkDirectoryStorage mStorage;
+};
+
+} // namespace Clusters
+} // namespace app
+} // namespace chip
diff --git a/src/app/common/templates/config-data.yaml b/src/app/common/templates/config-data.yaml
index 83d1b682153ed7..e7121b2757e2f3 100644
--- a/src/app/common/templates/config-data.yaml
+++ b/src/app/common/templates/config-data.yaml
@@ -41,6 +41,7 @@ CommandHandlerInterfaceOnlyClusters:
     - Electrical Power Measurement
     - Electrical Energy Measurement
     - Wi-Fi Network Management
+    - Thread Network Directory
 
 # We need a more configurable way of deciding which clusters have which init functions....
 # See https://github.com/project-chip/connectedhomeip/issues/4369
diff --git a/src/app/tests/BUILD.gn b/src/app/tests/BUILD.gn
index c1826884923af4..3ca229a6751132 100644
--- a/src/app/tests/BUILD.gn
+++ b/src/app/tests/BUILD.gn
@@ -73,6 +73,19 @@ source_set("ota-requestor-test-srcs") {
   ]
 }
 
+source_set("thread-network-directory-test-srcs") {
+  sources = [
+    "${chip_root}/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.cpp",
+    "${chip_root}/src/app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h",
+    "${chip_root}/src/app/clusters/thread-network-directory-server/ThreadNetworkDirectoryStorage.h",
+  ]
+
+  public_deps = [
+    "${chip_root}/src/app/common:cluster-objects",
+    "${chip_root}/src/lib/core",
+  ]
+}
+
 source_set("time-sync-data-provider-test-srcs") {
   sources = [ "${chip_root}/src/app/clusters/time-synchronization-server/TimeSyncDataProvider.cpp" ]
 
@@ -159,6 +172,7 @@ chip_test_suite("tests") {
     "TestConcreteAttributePath.cpp",
     "TestDataModelSerialization.cpp",
     "TestDefaultOTARequestorStorage.cpp",
+    "TestDefaultThreadNetworkDirectoryStorage.cpp",
     "TestEventLoggingNoUTCTime.cpp",
     "TestEventOverflow.cpp",
     "TestEventPathParams.cpp",
@@ -189,6 +203,7 @@ chip_test_suite("tests") {
     ":operational-state-test-srcs",
     ":ota-requestor-test-srcs",
     ":power-cluster-test-srcs",
+    ":thread-network-directory-test-srcs",
     ":time-sync-data-provider-test-srcs",
     "${chip_root}/src/app",
     "${chip_root}/src/app/common:cluster-objects",
diff --git a/src/app/tests/TestDefaultThreadNetworkDirectoryStorage.cpp b/src/app/tests/TestDefaultThreadNetworkDirectoryStorage.cpp
new file mode 100644
index 00000000000000..761b78afad4ff9
--- /dev/null
+++ b/src/app/tests/TestDefaultThreadNetworkDirectoryStorage.cpp
@@ -0,0 +1,292 @@
+/*
+ *
+ *    Copyright (c) 2024 Project CHIP Authors
+ *
+ *    Licensed under the Apache License, Version 2.0 (the "License");
+ *    you may not use this file except in compliance with the License.
+ *    You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *    Unless required by applicable law or agreed to in writing, software
+ *    distributed under the License is distributed on an "AS IS" BASIS,
+ *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *    See the License for the specific language governing permissions and
+ *    limitations under the License.
+ */
+
+#include <app/clusters/thread-network-directory-server/DefaultThreadNetworkDirectoryStorage.h>
+#include <lib/core/CHIPPersistentStorageDelegate.h>
+#include <lib/support/DefaultStorageKeyAllocator.h>
+#include <lib/support/TestPersistentStorageDelegate.h>
+
+#include <cstdint>
+#include <lib/core/StringBuilderAdapters.h>
+#include <pw_unit_test/framework.h>
+
+using namespace chip;
+using namespace chip::app;
+
+namespace {
+
+class TestDefaultThreadNetworkDirectoryStorage : public ::testing::Test
+{
+public:
+    static void SetUpTestSuite() { ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); }
+    static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); }
+
+protected:
+    TestPersistentStorageDelegate persistentStorageDelegate;
+    DefaultThreadNetworkDirectoryStorage storageImpl{ persistentStorageDelegate };
+    ThreadNetworkDirectoryStorage & storage = storageImpl;
+};
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestEmptyStorage)
+{
+    EXPECT_GE(storage.Capacity(), 2); // can't meaningfully test with less and spec requires 2 per fabric
+
+    {
+        ThreadNetworkDirectoryStorage::ExtendedPanId exPanId;
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_TRUE(iterator != nullptr);
+        EXPECT_EQ(iterator->Count(), 0u);
+        EXPECT_FALSE(iterator->Next(exPanId));
+        iterator->Release();
+    }
+
+    ThreadNetworkDirectoryStorage::ExtendedPanId exPanId(UINT64_C(0x1122334455667788));
+    EXPECT_FALSE(storage.ContainsNetwork(exPanId));
+    EXPECT_EQ(storage.RemoveNetwork(exPanId), CHIP_ERROR_NOT_FOUND);
+    MutableByteSpan mutableSpan;
+    EXPECT_EQ(storage.GetNetworkDataset(exPanId, mutableSpan), CHIP_ERROR_NOT_FOUND);
+}
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddEmptyDataset)
+{
+    ThreadNetworkDirectoryStorage::ExtendedPanId exPanId(UINT64_C(0xd00fd00fdeadbeef));
+    EXPECT_EQ(storage.AddOrUpdateNetwork(exPanId, ByteSpan()), CHIP_ERROR_INVALID_ARGUMENT);
+}
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddDatasetTooLong)
+{
+    ThreadNetworkDirectoryStorage::ExtendedPanId exPanId(UINT64_C(0xd00fd00fdeadbeef));
+    uint8_t dataset[ThreadNetworkDirectoryStorage::kMaxThreadDatasetLen + 1] = { 1, 2, 3 };
+    EXPECT_EQ(storage.AddOrUpdateNetwork(exPanId, ByteSpan(dataset)), CHIP_ERROR_INVALID_ARGUMENT);
+}
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddRemoveNetworks)
+{
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panA(UINT64_C(0xd00fd00fdeadbeef));
+    const uint8_t datasetA[]        = { 1 };
+    const uint8_t updatedDatasetA[] = { 0x11, 0x11 };
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xe475cafef00df00d));
+    const uint8_t datasetB[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
+    uint8_t mutableBytes[ThreadNetworkDirectoryStorage::kMaxThreadDatasetLen];
+
+    // Add Pan A only
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(datasetA)), CHIP_NO_ERROR);
+    EXPECT_TRUE(storage.ContainsNetwork(panA));
+    EXPECT_FALSE(storage.ContainsNetwork(panB));
+
+    {
+        MutableByteSpan retrievedDatasetA(mutableBytes);
+        EXPECT_EQ(storage.GetNetworkDataset(panA, retrievedDatasetA), CHIP_NO_ERROR);
+        EXPECT_TRUE(retrievedDatasetA.data_equal(ByteSpan(datasetA)));
+    }
+    {
+        MutableByteSpan emptyMutableSpan;
+        EXPECT_EQ(storage.GetNetworkDataset(panA, emptyMutableSpan), CHIP_ERROR_BUFFER_TOO_SMALL);
+    }
+    {
+        ThreadNetworkDirectoryStorage::ExtendedPanId exPanId;
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_TRUE(iterator != nullptr);
+        EXPECT_EQ(iterator->Count(), 1u);
+        EXPECT_TRUE(iterator->Next(exPanId));
+        EXPECT_EQ(exPanId, panA);
+        EXPECT_NE(exPanId, panB); // just to ensure operator== is sane
+        EXPECT_FALSE(iterator->Next(exPanId));
+        EXPECT_EQ(iterator->Count(), 1u);
+        iterator->Release();
+    }
+
+    // Update Pan A
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(updatedDatasetA)), CHIP_NO_ERROR);
+    EXPECT_TRUE(storage.ContainsNetwork(panA));
+    EXPECT_FALSE(storage.ContainsNetwork(panB));
+
+    {
+        MutableByteSpan retrievedUpdatedDatasetA(mutableBytes);
+        EXPECT_EQ(storage.GetNetworkDataset(panA, retrievedUpdatedDatasetA), CHIP_NO_ERROR);
+        EXPECT_TRUE(retrievedUpdatedDatasetA.data_equal(ByteSpan(updatedDatasetA)));
+    }
+    {
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_TRUE(iterator != nullptr);
+        EXPECT_EQ(iterator->Count(), 1u); // still 1
+        iterator->Release();
+    }
+
+    // Add Pan B
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panB, ByteSpan(datasetB)), CHIP_NO_ERROR);
+    EXPECT_TRUE(storage.ContainsNetwork(panA));
+    EXPECT_TRUE(storage.ContainsNetwork(panB));
+    {
+        ThreadNetworkDirectoryStorage::ExtendedPanId exPanId1;
+        ThreadNetworkDirectoryStorage::ExtendedPanId exPanId2;
+        ThreadNetworkDirectoryStorage::ExtendedPanId unused;
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_TRUE(iterator != nullptr);
+        EXPECT_EQ(iterator->Count(), 2u);
+        EXPECT_TRUE(iterator->Next(exPanId1));
+        EXPECT_TRUE(iterator->Next(exPanId2));
+        EXPECT_TRUE((exPanId1 == panA && exPanId2 == panB) || (exPanId1 == panB && exPanId2 == panA));
+        EXPECT_FALSE(iterator->Next(unused));
+        EXPECT_EQ(iterator->Count(), 2u);
+        iterator->Release();
+    }
+    {
+        MutableByteSpan retrievedDatasetB(mutableBytes);
+        EXPECT_EQ(storage.GetNetworkDataset(panB, retrievedDatasetB), CHIP_NO_ERROR);
+        EXPECT_TRUE(retrievedDatasetB.data_equal(ByteSpan(datasetB)));
+    }
+
+    // Remove Pan A
+    EXPECT_EQ(storage.RemoveNetwork(panA), CHIP_NO_ERROR);
+    EXPECT_FALSE(storage.ContainsNetwork(panA));
+    EXPECT_TRUE(storage.ContainsNetwork(panB));
+
+    {
+        MutableByteSpan mutableSpan;
+        EXPECT_EQ(storage.GetNetworkDataset(panA, mutableSpan), CHIP_ERROR_NOT_FOUND);
+    }
+    {
+        ThreadNetworkDirectoryStorage::ExtendedPanId exPanId;
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_TRUE(iterator != nullptr);
+        EXPECT_EQ(iterator->Count(), 1u);
+        EXPECT_TRUE(iterator->Next(exPanId));
+        EXPECT_EQ(exPanId, panB);
+        EXPECT_FALSE(iterator->Next(exPanId));
+        iterator->Release();
+    }
+
+    // Remove Pan B
+    EXPECT_EQ(storage.RemoveNetwork(panB), CHIP_NO_ERROR);
+    EXPECT_FALSE(storage.ContainsNetwork(panA));
+    EXPECT_FALSE(storage.ContainsNetwork(panB));
+
+    {
+        MutableByteSpan mutableSpan;
+        EXPECT_EQ(storage.GetNetworkDataset(panB, mutableSpan), CHIP_ERROR_NOT_FOUND);
+    }
+    {
+        ThreadNetworkDirectoryStorage::ExtendedPanId exPanId;
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_TRUE(iterator != nullptr);
+        EXPECT_EQ(iterator->Count(), 0u);
+        EXPECT_FALSE(iterator->Next(exPanId));
+        iterator->Release();
+    }
+}
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestStorageOverflow)
+{
+    // Fill up the storage to capacity
+    uint8_t dataset[] = { 42 };
+    for (uint64_t id = 0; id < storage.Capacity(); id++)
+    {
+        EXPECT_EQ(storage.AddOrUpdateNetwork(ThreadNetworkDirectoryStorage::ExtendedPanId(id), ByteSpan(dataset)), CHIP_NO_ERROR);
+    }
+
+    // Capacity limit should be enforced
+    const ThreadNetworkDirectoryStorage::ExtendedPanId exPanId(UINT64_C(0xd00fd00fdeadbeef));
+    EXPECT_EQ(storage.AddOrUpdateNetwork(exPanId, ByteSpan(dataset)), CHIP_ERROR_NO_MEMORY);
+
+    // Updating an existing network is still possible
+    dataset[0] = 88;
+    EXPECT_EQ(storage.AddOrUpdateNetwork(ThreadNetworkDirectoryStorage::ExtendedPanId(0u), ByteSpan(dataset)), CHIP_NO_ERROR);
+}
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddNetworkStorageFailure)
+{
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panA(UINT64_C(0xd00fd00fdeadbeef));
+    const uint8_t datasetA[] = { 1 };
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xe475cafef00df00d));
+    uint8_t datasetB[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
+
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(datasetA)), CHIP_NO_ERROR);
+
+    // Make the storage delegate return CHIP_ERROR_PERSISTED_STORAGE_FAILED for writes.
+    // Attempting to add a network should fail and state should be unaffected.
+    persistentStorageDelegate.SetRejectWrites(true);
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panB, ByteSpan(datasetB)), CHIP_ERROR_PERSISTED_STORAGE_FAILED);
+    EXPECT_TRUE(storage.ContainsNetwork(panA));
+    EXPECT_FALSE(storage.ContainsNetwork(panB));
+    {
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_EQ(iterator->Count(), 1u);
+        iterator->Release();
+    }
+}
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestAddNetworkStorageWriteIndexFailure)
+{
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panA(UINT64_C(0xd00fd00fdeadbeef));
+    const uint8_t datasetA[] = { 1 };
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xe475cafef00df00d));
+    uint8_t datasetB[] = { 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 };
+
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(datasetA)), CHIP_NO_ERROR);
+
+    // White box test: Poison the index key specifically, so the dataset
+    // can be written but the subsequent update of the index fails.
+    // Attempting to add a network should fail and state should be unaffected.
+    StorageKeyName indexKey = DefaultStorageKeyAllocator::ThreadNetworkDirectoryIndex();
+    persistentStorageDelegate.AddPoisonKey(indexKey.KeyName());
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panB, ByteSpan(datasetB)), CHIP_ERROR_PERSISTED_STORAGE_FAILED);
+    EXPECT_TRUE(storage.ContainsNetwork(panA));
+    EXPECT_FALSE(storage.ContainsNetwork(panB));
+    {
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_EQ(iterator->Count(), 1u);
+        iterator->Release();
+    }
+}
+
+TEST_F(TestDefaultThreadNetworkDirectoryStorage, TestRemoveNetworkStorageFailure)
+{
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panA(UINT64_C(0xd00f0001));
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panB(UINT64_C(0xd00f0002));
+    const ThreadNetworkDirectoryStorage::ExtendedPanId panC(UINT64_C(0xd00f0003));
+    const uint8_t dataset[] = { 1 };
+
+    // Just in case the test is compiled with CHIP_CONFIG_MAX_FABRICS == 1
+    bool canStorePanC = (storage.Capacity() >= 3);
+
+    // Add Pans A, B, and C
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panA, ByteSpan(dataset)), CHIP_NO_ERROR);
+    EXPECT_EQ(storage.AddOrUpdateNetwork(panB, ByteSpan(dataset)), CHIP_NO_ERROR);
+    if (canStorePanC)
+    {
+        EXPECT_EQ(storage.AddOrUpdateNetwork(panC, ByteSpan(dataset)), CHIP_NO_ERROR);
+    }
+
+    // Make the storage delegate return CHIP_ERROR_PERSISTED_STORAGE_FAILED for writes.
+    // Attempting to remove a network should fail and state should be unaffected.
+    persistentStorageDelegate.SetRejectWrites(true);
+    EXPECT_EQ(storage.RemoveNetwork(panA), CHIP_ERROR_PERSISTED_STORAGE_FAILED);
+    EXPECT_TRUE(storage.ContainsNetwork(panA));
+    EXPECT_TRUE(storage.ContainsNetwork(panB));
+    if (canStorePanC)
+    {
+        EXPECT_TRUE(storage.ContainsNetwork(panC));
+    }
+    {
+        auto * iterator = storage.IterateNetworkIds();
+        EXPECT_EQ(iterator->Count(), canStorePanC ? 3u : 2u);
+        iterator->Release();
+    }
+}
+
+} // namespace
diff --git a/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml b/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml
index ce12fb58abb9f5..f0266fffecb872 100644
--- a/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml
+++ b/src/app/zap-templates/zcl/data-model/chip/matter-devices.xml
@@ -2426,6 +2426,7 @@ limitations under the License.
         <scope>Endpoint</scope>
         <clusters lockOthers="true">
             <include cluster="Descriptor" client="false" server="true" clientLocked="true" serverLocked="true"/>
+            <include cluster="Thread Network Directory" client="false" server="true" clientLocked="true" serverLocked="true"/>
             <include cluster="Wi-Fi Network Management" client="false" server="true" clientLocked="true" serverLocked="true"/>
         </clusters>
     </deviceType>
diff --git a/src/app/zap-templates/zcl/data-model/chip/thread-network-directory-cluster.xml b/src/app/zap-templates/zcl/data-model/chip/thread-network-directory-cluster.xml
index f802b7ab4452c0..e97cbc931e6b0e 100644
--- a/src/app/zap-templates/zcl/data-model/chip/thread-network-directory-cluster.xml
+++ b/src/app/zap-templates/zcl/data-model/chip/thread-network-directory-cluster.xml
@@ -19,9 +19,10 @@ limitations under the License.
 
     <struct name="ThreadNetworkStruct">
         <cluster code="0x0453"/>
-        <item name="ExtendedPanID" type="int64u"/>
+        <item name="ExtendedPanID" type="octet_string" length="8"/>
         <item name="NetworkName" type="char_string" length="16"/>
         <item name="Channel" type="int16u"/>
+        <item name="ActiveTimestamp" type="int64u"/>
     </struct>
 
     <cluster>
@@ -37,7 +38,7 @@ limitations under the License.
         <!-- cluster revision -->
         <globalAttribute side="either" code="0xFFFD" value="1"/>
 
-        <attribute side="server" code="0x0000" define="PREFERRED_EXTENDED_PAN_ID" type="int64u" writable="true" isNullable="true" optional="false">
+        <attribute side="server" code="0x0000" define="PREFERRED_EXTENDED_PAN_ID" type="octet_string" length="8" writable="true" isNullable="true" optional="false">
             <description>PreferredExtendedPanID</description>
             <access op="read" privilege="manage"/>
             <access op="write" privilege="manage"/>
@@ -56,22 +57,16 @@ limitations under the License.
         <command source="client" code="0x01" name="RemoveNetwork" mustUseTimedInvoke="true" optional="false">
             <description>Removes an entry from the ThreadNetworks list.</description>
             <access op="invoke" privilege="manage"/>
-            <arg name="ExtendedPanID" type="int64u"/>
+            <arg name="ExtendedPanID" type="octet_string" length="8"/>
         </command>
         <command source="client" code="0x02" name="GetOperationalDataset" mustUseTimedInvoke="true" optional="false" response="OperationalDatasetResponse">
             <description>Retrieves a Thread Operational Dataset from the ThreadNetworks list.</description>
             <access op="invoke" privilege="operate"/>
-            <arg name="ExtendedPanID" type="int64u"/>
+            <arg name="ExtendedPanID" type="octet_string" length="8"/>
         </command>
         <command source="server" code="0x03" name="OperationalDatasetResponse" optional="false">
             <description>This is the response to a GetOperationalDataset request.</description>
             <arg name="OperationalDataset" type="octet_string" length="254"/>
         </command>
-
-        <event side="server" code="0x00" name="NetworkChanged" priority="info" optional="false">
-            <description>This event SHALL be generated when an entry in ThreadNetworks is added, removed, or had its Operational Dataset changed.</description>
-            <field id="0" name="ExtendedPanID" type="int64u"/>
-            <access op="read" privilege="operate"/>
-        </event>
     </cluster>
 </configurator>
diff --git a/src/app/zap-templates/zcl/zcl-with-test-extensions.json b/src/app/zap-templates/zcl/zcl-with-test-extensions.json
index 3b11954f1582b0..1a76205be885f9 100644
--- a/src/app/zap-templates/zcl/zcl-with-test-extensions.json
+++ b/src/app/zap-templates/zcl/zcl-with-test-extensions.json
@@ -645,7 +645,12 @@
         "Valve Configuration and Control": ["RemainingDuration"],
         "Boolean State Configuration": ["CurrentSensitivityLevel"],
         "Water Heater Mode": ["SupportedModes", "CurrentMode", "FeatureMap"],
-        "Wi-Fi Network Management": ["SSID"]
+        "Wi-Fi Network Management": ["SSID"],
+        "Thread Network Directory": [
+            "PreferredExtendedPanID",
+            "ThreadNetworks",
+            "ThreadNetworkTableSize"
+        ]
     },
     "defaultReportingPolicy": "mandatory",
     "ZCLDataTypes": ["ARRAY", "BITMAP", "ENUM", "NUMBER", "STRING", "STRUCT"],
diff --git a/src/app/zap-templates/zcl/zcl.json b/src/app/zap-templates/zcl/zcl.json
index b03da7bc7b1e67..5388a12156601a 100644
--- a/src/app/zap-templates/zcl/zcl.json
+++ b/src/app/zap-templates/zcl/zcl.json
@@ -643,7 +643,12 @@
         "Valve Configuration and Control": ["RemainingDuration"],
         "Boolean State Configuration": ["CurrentSensitivityLevel"],
         "Water Heater Mode": ["SupportedModes", "CurrentMode", "FeatureMap"],
-        "Wi-Fi Network Management": ["SSID"]
+        "Wi-Fi Network Management": ["SSID"],
+        "Thread Network Directory": [
+            "PreferredExtendedPanID",
+            "ThreadNetworks",
+            "ThreadNetworkTableSize"
+        ]
     },
     "defaultReportingPolicy": "mandatory",
     "ZCLDataTypes": ["ARRAY", "BITMAP", "ENUM", "NUMBER", "STRING", "STRUCT"],
diff --git a/src/app/zap_cluster_list.json b/src/app/zap_cluster_list.json
index dc324968991adc..00bcb55adb90a7 100644
--- a/src/app/zap_cluster_list.json
+++ b/src/app/zap_cluster_list.json
@@ -117,6 +117,7 @@
         "THERMOSTAT_USER_INTERFACE_CONFIGURATION_CLUSTER": [],
         "THREAD_BORDER_ROUTER_MANAGEMENT_CLUSTER": [],
         "THREAD_NETWORK_DIAGNOSTICS_CLUSTER": [],
+        "THREAD_NETWORK_DIRECTORY_CLUSTER": [],
         "TIME_CLUSTER": [],
         "TIME_FORMAT_LOCALIZATION_CLUSTER": [],
         "TIME_SYNCHRONIZATION_CLUSTER": [],
@@ -293,7 +294,7 @@
         "THREAD_NETWORK_DIAGNOSTICS_CLUSTER": [
             "thread-network-diagnostics-server"
         ],
-        "THREAD_NETWORK_DIRECTORY_CLUSTER": [],
+        "THREAD_NETWORK_DIRECTORY_CLUSTER": ["thread-network-directory-server"],
         "TIME_CLUSTER": [],
         "TIME_FORMAT_LOCALIZATION_CLUSTER": ["time-format-localization-server"],
         "TIME_SYNCHRONIZATION_CLUSTER": ["time-synchronization-server"],
diff --git a/src/controller/data_model/controller-clusters.matter b/src/controller/data_model/controller-clusters.matter
index 066f34e20d2731..48e39baf07f5dc 100644
--- a/src/controller/data_model/controller-clusters.matter
+++ b/src/controller/data_model/controller-clusters.matter
@@ -8168,16 +8168,13 @@ cluster ThreadNetworkDirectory = 1107 {
   revision 1;
 
   struct ThreadNetworkStruct {
-    int64u extendedPanID = 0;
+    octet_string<8> extendedPanID = 0;
     char_string<16> networkName = 1;
     int16u channel = 2;
+    int64u activeTimestamp = 3;
   }
 
-  info event access(read: operate) NetworkChanged = 0 {
-    int64u extendedPanID = 0;
-  }
-
-  attribute access(read: manage, write: manage) nullable int64u preferredExtendedPanID = 0;
+  attribute access(read: manage, write: manage) nullable octet_string<8> preferredExtendedPanID = 0;
   readonly attribute access(read: operate) ThreadNetworkStruct threadNetworks[] = 1;
   readonly attribute int8u threadNetworkTableSize = 2;
   readonly attribute command_id generatedCommandList[] = 65528;
@@ -8192,11 +8189,11 @@ cluster ThreadNetworkDirectory = 1107 {
   }
 
   request struct RemoveNetworkRequest {
-    int64u extendedPanID = 0;
+    octet_string<8> extendedPanID = 0;
   }
 
   request struct GetOperationalDatasetRequest {
-    int64u extendedPanID = 0;
+    octet_string<8> extendedPanID = 0;
   }
 
   response struct OperationalDatasetResponse = 3 {
diff --git a/src/controller/data_model/controller-clusters.zap b/src/controller/data_model/controller-clusters.zap
index 3c95a3320ba91b..ec4af6eeec7e0a 100644
--- a/src/controller/data_model/controller-clusters.zap
+++ b/src/controller/data_model/controller-clusters.zap
@@ -4816,6 +4816,82 @@
             }
           ]
         },
+        {
+          "name": "Thread Network Directory",
+          "code": 1107,
+          "mfgCode": null,
+          "define": "THREAD_NETWORK_DIRECTORY_CLUSTER",
+          "side": "client",
+          "enabled": 1,
+          "commands": [
+            {
+              "name": "AddNetwork",
+              "code": 0,
+              "mfgCode": null,
+              "source": "client",
+              "isIncoming": 0,
+              "isEnabled": 1
+            },
+            {
+              "name": "RemoveNetwork",
+              "code": 1,
+              "mfgCode": null,
+              "source": "client",
+              "isIncoming": 0,
+              "isEnabled": 1
+            },
+            {
+              "name": "GetOperationalDataset",
+              "code": 2,
+              "mfgCode": null,
+              "source": "client",
+              "isIncoming": 0,
+              "isEnabled": 1
+            },
+            {
+              "name": "OperationalDatasetResponse",
+              "code": 3,
+              "mfgCode": null,
+              "source": "server",
+              "isIncoming": 1,
+              "isEnabled": 1
+            }
+          ],
+          "attributes": [
+            {
+              "name": "FeatureMap",
+              "code": 65532,
+              "mfgCode": null,
+              "side": "client",
+              "type": "bitmap32",
+              "included": 1,
+              "storageOption": "RAM",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": "0",
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            },
+            {
+              "name": "ClusterRevision",
+              "code": 65533,
+              "mfgCode": null,
+              "side": "client",
+              "type": "int16u",
+              "included": 1,
+              "storageOption": "RAM",
+              "singleton": 0,
+              "bounded": 0,
+              "defaultValue": "1",
+              "reportable": 1,
+              "minInterval": 1,
+              "maxInterval": 65534,
+              "reportableChange": 0
+            }
+          ]
+        },
         {
           "name": "Wake on LAN",
           "code": 1283,
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java
index 98e208fd3815ee..5369a5c42d4a26 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ChipClusters.java
@@ -54867,12 +54867,12 @@ public void onResponse(StructType invokeStructValue) {
     }
 
 
-    public void removeNetwork(DefaultClusterCallback callback, Long extendedPanID, int timedInvokeTimeoutMs) {
+    public void removeNetwork(DefaultClusterCallback callback, byte[] extendedPanID, int timedInvokeTimeoutMs) {
       final long commandId = 1L;
 
       ArrayList<StructElement> elements = new ArrayList<>();
       final long extendedPanIDFieldID = 0L;
-      BaseTLVType extendedPanIDtlvValue = new UIntType(extendedPanID);
+      BaseTLVType extendedPanIDtlvValue = new ByteArrayType(extendedPanID);
       elements.add(new StructElement(extendedPanIDFieldID, extendedPanIDtlvValue));
 
       StructType commandArgs = new StructType(elements);
@@ -54884,12 +54884,12 @@ public void onResponse(StructType invokeStructValue) {
     }
 
 
-    public void getOperationalDataset(OperationalDatasetResponseCallback callback, Long extendedPanID, int timedInvokeTimeoutMs) {
+    public void getOperationalDataset(OperationalDatasetResponseCallback callback, byte[] extendedPanID, int timedInvokeTimeoutMs) {
       final long commandId = 2L;
 
       ArrayList<StructElement> elements = new ArrayList<>();
       final long extendedPanIDFieldID = 0L;
-      BaseTLVType extendedPanIDtlvValue = new UIntType(extendedPanID);
+      BaseTLVType extendedPanIDtlvValue = new ByteArrayType(extendedPanID);
       elements.add(new StructElement(extendedPanIDFieldID, extendedPanIDtlvValue));
 
       StructType commandArgs = new StructType(elements);
@@ -54915,7 +54915,7 @@ public interface OperationalDatasetResponseCallback extends BaseClusterCallback
     }
 
     public interface PreferredExtendedPanIDAttributeCallback extends BaseAttributeCallback {
-      void onSuccess(@Nullable Long value);
+      void onSuccess(@Nullable byte[] value);
     }
 
     public interface ThreadNetworksAttributeCallback extends BaseAttributeCallback {
@@ -54945,18 +54945,18 @@ public void readPreferredExtendedPanIDAttribute(
       readAttribute(new ReportCallbackImpl(callback, path) {
           @Override
           public void onSuccess(byte[] tlv) {
-            @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv);
+            @Nullable byte[] value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv);
             callback.onSuccess(value);
           }
         }, PREFERRED_EXTENDED_PAN_I_D_ATTRIBUTE_ID, true);
     }
 
-    public void writePreferredExtendedPanIDAttribute(DefaultClusterCallback callback, Long value) {
+    public void writePreferredExtendedPanIDAttribute(DefaultClusterCallback callback, byte[] value) {
       writePreferredExtendedPanIDAttribute(callback, value, 0);
     }
 
-    public void writePreferredExtendedPanIDAttribute(DefaultClusterCallback callback, Long value, int timedWriteTimeoutMs) {
-      BaseTLVType tlvValue = value != null ? new UIntType(value) : new NullType();
+    public void writePreferredExtendedPanIDAttribute(DefaultClusterCallback callback, byte[] value, int timedWriteTimeoutMs) {
+      BaseTLVType tlvValue = value != null ? new ByteArrayType(value) : new NullType();
       writeAttribute(new WriteAttributesCallbackImpl(callback), PREFERRED_EXTENDED_PAN_I_D_ATTRIBUTE_ID, tlvValue, timedWriteTimeoutMs);
     }
 
@@ -54967,7 +54967,7 @@ public void subscribePreferredExtendedPanIDAttribute(
       subscribeAttribute(new ReportCallbackImpl(callback, path) {
           @Override
           public void onSuccess(byte[] tlv) {
-            @Nullable Long value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv);
+            @Nullable byte[] value = ChipTLVValueDecoder.decodeAttributeValue(path, tlv);
             callback.onSuccess(value);
           }
         }, PREFERRED_EXTENDED_PAN_I_D_ATTRIBUTE_ID, minInterval, maxInterval);
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipEventStructs.java b/src/controller/java/generated/java/chip/devicecontroller/ChipEventStructs.java
index d256c91c32d9ed..6ab2b64ce65000 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ChipEventStructs.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ChipEventStructs.java
@@ -5614,52 +5614,6 @@ public String toString() {
     return output.toString();
   }
 }
-public static class ThreadNetworkDirectoryClusterNetworkChangedEvent {
-  public Long extendedPanID;
-  private static final long EXTENDED_PAN_I_D_ID = 0L;
-
-  public ThreadNetworkDirectoryClusterNetworkChangedEvent(
-    Long extendedPanID
-  ) {
-    this.extendedPanID = extendedPanID;
-  }
-
-  public StructType encodeTlv() {
-    ArrayList<StructElement> values = new ArrayList<>();
-    values.add(new StructElement(EXTENDED_PAN_I_D_ID, new UIntType(extendedPanID)));
-
-    return new StructType(values);
-  }
-
-  public static ThreadNetworkDirectoryClusterNetworkChangedEvent decodeTlv(BaseTLVType tlvValue) {
-    if (tlvValue == null || tlvValue.type() != TLVType.Struct) {
-      return null;
-    }
-    Long extendedPanID = null;
-    for (StructElement element: ((StructType)tlvValue).value()) {
-      if (element.contextTagNum() == EXTENDED_PAN_I_D_ID) {
-        if (element.value(BaseTLVType.class).type() == TLVType.UInt) {
-          UIntType castingValue = element.value(UIntType.class);
-          extendedPanID = castingValue.value(Long.class);
-        }
-      }
-    }
-    return new ThreadNetworkDirectoryClusterNetworkChangedEvent(
-      extendedPanID
-    );
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder output = new StringBuilder();
-    output.append("ThreadNetworkDirectoryClusterNetworkChangedEvent {\n");
-    output.append("\textendedPanID: ");
-    output.append(extendedPanID);
-    output.append("\n");
-    output.append("}\n");
-    return output.toString();
-  }
-}
 public static class TargetNavigatorClusterTargetUpdatedEvent {
   public ArrayList<ChipStructs.TargetNavigatorClusterTargetInfoStruct> targetList;
   public Integer currentTarget;
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java b/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java
index 94bd7622678351..11cba416fb733c 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ChipStructs.java
@@ -10067,28 +10067,33 @@ public String toString() {
   }
 }
 public static class ThreadNetworkDirectoryClusterThreadNetworkStruct {
-  public Long extendedPanID;
+  public byte[] extendedPanID;
   public String networkName;
   public Integer channel;
+  public Long activeTimestamp;
   private static final long EXTENDED_PAN_I_D_ID = 0L;
   private static final long NETWORK_NAME_ID = 1L;
   private static final long CHANNEL_ID = 2L;
+  private static final long ACTIVE_TIMESTAMP_ID = 3L;
 
   public ThreadNetworkDirectoryClusterThreadNetworkStruct(
-    Long extendedPanID,
+    byte[] extendedPanID,
     String networkName,
-    Integer channel
+    Integer channel,
+    Long activeTimestamp
   ) {
     this.extendedPanID = extendedPanID;
     this.networkName = networkName;
     this.channel = channel;
+    this.activeTimestamp = activeTimestamp;
   }
 
   public StructType encodeTlv() {
     ArrayList<StructElement> values = new ArrayList<>();
-    values.add(new StructElement(EXTENDED_PAN_I_D_ID, new UIntType(extendedPanID)));
+    values.add(new StructElement(EXTENDED_PAN_I_D_ID, new ByteArrayType(extendedPanID)));
     values.add(new StructElement(NETWORK_NAME_ID, new StringType(networkName)));
     values.add(new StructElement(CHANNEL_ID, new UIntType(channel)));
+    values.add(new StructElement(ACTIVE_TIMESTAMP_ID, new UIntType(activeTimestamp)));
 
     return new StructType(values);
   }
@@ -10097,14 +10102,15 @@ public static ThreadNetworkDirectoryClusterThreadNetworkStruct decodeTlv(BaseTLV
     if (tlvValue == null || tlvValue.type() != TLVType.Struct) {
       return null;
     }
-    Long extendedPanID = null;
+    byte[] extendedPanID = null;
     String networkName = null;
     Integer channel = null;
+    Long activeTimestamp = null;
     for (StructElement element: ((StructType)tlvValue).value()) {
       if (element.contextTagNum() == EXTENDED_PAN_I_D_ID) {
-        if (element.value(BaseTLVType.class).type() == TLVType.UInt) {
-          UIntType castingValue = element.value(UIntType.class);
-          extendedPanID = castingValue.value(Long.class);
+        if (element.value(BaseTLVType.class).type() == TLVType.ByteArray) {
+          ByteArrayType castingValue = element.value(ByteArrayType.class);
+          extendedPanID = castingValue.value(byte[].class);
         }
       } else if (element.contextTagNum() == NETWORK_NAME_ID) {
         if (element.value(BaseTLVType.class).type() == TLVType.String) {
@@ -10116,12 +10122,18 @@ public static ThreadNetworkDirectoryClusterThreadNetworkStruct decodeTlv(BaseTLV
           UIntType castingValue = element.value(UIntType.class);
           channel = castingValue.value(Integer.class);
         }
+      } else if (element.contextTagNum() == ACTIVE_TIMESTAMP_ID) {
+        if (element.value(BaseTLVType.class).type() == TLVType.UInt) {
+          UIntType castingValue = element.value(UIntType.class);
+          activeTimestamp = castingValue.value(Long.class);
+        }
       }
     }
     return new ThreadNetworkDirectoryClusterThreadNetworkStruct(
       extendedPanID,
       networkName,
-      channel
+      channel,
+      activeTimestamp
     );
   }
 
@@ -10130,7 +10142,7 @@ public String toString() {
     StringBuilder output = new StringBuilder();
     output.append("ThreadNetworkDirectoryClusterThreadNetworkStruct {\n");
     output.append("\textendedPanID: ");
-    output.append(extendedPanID);
+    output.append(Arrays.toString(extendedPanID));
     output.append("\n");
     output.append("\tnetworkName: ");
     output.append(networkName);
@@ -10138,6 +10150,9 @@ public String toString() {
     output.append("\tchannel: ");
     output.append(channel);
     output.append("\n");
+    output.append("\tactiveTimestamp: ");
+    output.append(activeTimestamp);
+    output.append("\n");
     output.append("}\n");
     return output.toString();
   }
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
index b733ca22822c88..fc0a28a6d4eb0c 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterIDMapping.java
@@ -14988,8 +14988,7 @@ public static Attribute value(long id) throws NoSuchFieldError {
             }
         }
 
-        public enum Event {
-            NetworkChanged(0L),;
+        public enum Event {;
             private final long id;
             Event(long id) {
                 this.id = id;
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java
index 23dc3048a5fb3d..1538d4f47415ad 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterInfoMapping.java
@@ -18117,9 +18117,9 @@ public void setCallbackDelegate(ClusterCommandCallback callback) {
     }
 
     @Override
-    public void onSuccess(@Nullable Long value) {
+    public void onSuccess(@Nullable byte[] value) {
       Map<CommandResponseInfo, Object> responseValues = new LinkedHashMap<>();
-      CommandResponseInfo commandResponseInfo = new CommandResponseInfo("value", "Long");
+      CommandResponseInfo commandResponseInfo = new CommandResponseInfo("value", "byte[]");
       responseValues.put(commandResponseInfo, value);
       callback.onSuccess(responseValues);
     }
@@ -27690,13 +27690,13 @@ public Map<String, Map<String, InteractionInfo>> getCommandMap() {
 
     Map<String, CommandParameterInfo> threadNetworkDirectoryremoveNetworkCommandParams = new LinkedHashMap<String, CommandParameterInfo>();
 
-    CommandParameterInfo threadNetworkDirectoryremoveNetworkextendedPanIDCommandParameterInfo = new CommandParameterInfo("extendedPanID", Long.class, Long.class);
+    CommandParameterInfo threadNetworkDirectoryremoveNetworkextendedPanIDCommandParameterInfo = new CommandParameterInfo("extendedPanID", byte[].class, byte[].class);
     threadNetworkDirectoryremoveNetworkCommandParams.put("extendedPanID",threadNetworkDirectoryremoveNetworkextendedPanIDCommandParameterInfo);
     InteractionInfo threadNetworkDirectoryremoveNetworkInteractionInfo = new InteractionInfo(
       (cluster, callback, commandArguments) -> {
         ((ChipClusters.ThreadNetworkDirectoryCluster) cluster)
         .removeNetwork((DefaultClusterCallback) callback
-        , (Long)
+        , (byte[])
         commandArguments.get("extendedPanID"), 10000
         );
       },
@@ -27707,13 +27707,13 @@ public Map<String, Map<String, InteractionInfo>> getCommandMap() {
 
     Map<String, CommandParameterInfo> threadNetworkDirectorygetOperationalDatasetCommandParams = new LinkedHashMap<String, CommandParameterInfo>();
 
-    CommandParameterInfo threadNetworkDirectorygetOperationalDatasetextendedPanIDCommandParameterInfo = new CommandParameterInfo("extendedPanID", Long.class, Long.class);
+    CommandParameterInfo threadNetworkDirectorygetOperationalDatasetextendedPanIDCommandParameterInfo = new CommandParameterInfo("extendedPanID", byte[].class, byte[].class);
     threadNetworkDirectorygetOperationalDatasetCommandParams.put("extendedPanID",threadNetworkDirectorygetOperationalDatasetextendedPanIDCommandParameterInfo);
     InteractionInfo threadNetworkDirectorygetOperationalDatasetInteractionInfo = new InteractionInfo(
       (cluster, callback, commandArguments) -> {
         ((ChipClusters.ThreadNetworkDirectoryCluster) cluster)
           .getOperationalDataset((ChipClusters.ThreadNetworkDirectoryCluster.OperationalDatasetResponseCallback) callback
-           , (Long)
+           , (byte[])
              commandArguments.get("extendedPanID")
 
             , 10000);
diff --git a/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java b/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java
index 715cb477319345..28cba2abd728f8 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java
+++ b/src/controller/java/generated/java/chip/devicecontroller/ClusterWriteMapping.java
@@ -3717,8 +3717,8 @@ public Map<String, Map<String, InteractionInfo>> getWriteAttributeMap() {
     CommandParameterInfo threadNetworkDirectorypreferredExtendedPanIDCommandParameterInfo =
         new CommandParameterInfo(
             "value", 
-            Long.class, 
-            Long.class 
+            byte[].class, 
+            byte[].class 
         );
     writeThreadNetworkDirectoryPreferredExtendedPanIDCommandParams.put(
         "value",
@@ -3728,7 +3728,7 @@ public Map<String, Map<String, InteractionInfo>> getWriteAttributeMap() {
       (cluster, callback, commandArguments) -> {
         ((ChipClusters.ThreadNetworkDirectoryCluster) cluster).writePreferredExtendedPanIDAttribute(
           (DefaultClusterCallback) callback,
-          (Long) commandArguments.get("value")
+          (byte[]) commandArguments.get("value")
         );
       },
       () -> new ClusterInfoMapping.DelegatedDefaultClusterCallback(),
diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt b/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt
deleted file mode 100644
index b90c6e8126260b..00000000000000
--- a/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- *
- *    Copyright (c) 2023 Project CHIP Authors
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-package chip.devicecontroller.cluster.eventstructs
-
-import chip.devicecontroller.cluster.*
-import matter.tlv.ContextSpecificTag
-import matter.tlv.Tag
-import matter.tlv.TlvReader
-import matter.tlv.TlvWriter
-
-class ThreadNetworkDirectoryClusterNetworkChangedEvent(val extendedPanID: ULong) {
-  override fun toString(): String = buildString {
-    append("ThreadNetworkDirectoryClusterNetworkChangedEvent {\n")
-    append("\textendedPanID : $extendedPanID\n")
-    append("}\n")
-  }
-
-  fun toTlv(tlvTag: Tag, tlvWriter: TlvWriter) {
-    tlvWriter.apply {
-      startStructure(tlvTag)
-      put(ContextSpecificTag(TAG_EXTENDED_PAN_I_D), extendedPanID)
-      endStructure()
-    }
-  }
-
-  companion object {
-    private const val TAG_EXTENDED_PAN_I_D = 0
-
-    fun fromTlv(
-      tlvTag: Tag,
-      tlvReader: TlvReader,
-    ): ThreadNetworkDirectoryClusterNetworkChangedEvent {
-      tlvReader.enterStructure(tlvTag)
-      val extendedPanID = tlvReader.getULong(ContextSpecificTag(TAG_EXTENDED_PAN_I_D))
-
-      tlvReader.exitContainer()
-
-      return ThreadNetworkDirectoryClusterNetworkChangedEvent(extendedPanID)
-    }
-  }
-}
diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni b/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni
index c15fb2a01efd80..5a29a90b27372b 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni
+++ b/src/controller/java/generated/java/chip/devicecontroller/cluster/files.gni
@@ -222,7 +222,6 @@ eventstructs_sources = [
   "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/TargetNavigatorClusterTargetUpdatedEvent.kt",
   "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/ThreadNetworkDiagnosticsClusterConnectionStatusEvent.kt",
   "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/ThreadNetworkDiagnosticsClusterNetworkFaultChangeEvent.kt",
-  "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt",
   "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/TimeSynchronizationClusterDSTStatusEvent.kt",
   "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/TimeSynchronizationClusterTimeZoneStatusEvent.kt",
   "${chip_root}/src/controller/java/generated/java/chip/devicecontroller/cluster/eventstructs/UnitTestingClusterTestDifferentVendorMeiEventEvent.kt",
diff --git a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt
index 2ba4a216410ef3..ae988738b7be34 100644
--- a/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt
+++ b/src/controller/java/generated/java/chip/devicecontroller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt
@@ -23,15 +23,17 @@ import matter.tlv.TlvReader
 import matter.tlv.TlvWriter
 
 class ThreadNetworkDirectoryClusterThreadNetworkStruct(
-  val extendedPanID: ULong,
+  val extendedPanID: ByteArray,
   val networkName: String,
   val channel: UInt,
+  val activeTimestamp: ULong,
 ) {
   override fun toString(): String = buildString {
     append("ThreadNetworkDirectoryClusterThreadNetworkStruct {\n")
     append("\textendedPanID : $extendedPanID\n")
     append("\tnetworkName : $networkName\n")
     append("\tchannel : $channel\n")
+    append("\tactiveTimestamp : $activeTimestamp\n")
     append("}\n")
   }
 
@@ -41,6 +43,7 @@ class ThreadNetworkDirectoryClusterThreadNetworkStruct(
       put(ContextSpecificTag(TAG_EXTENDED_PAN_I_D), extendedPanID)
       put(ContextSpecificTag(TAG_NETWORK_NAME), networkName)
       put(ContextSpecificTag(TAG_CHANNEL), channel)
+      put(ContextSpecificTag(TAG_ACTIVE_TIMESTAMP), activeTimestamp)
       endStructure()
     }
   }
@@ -49,19 +52,26 @@ class ThreadNetworkDirectoryClusterThreadNetworkStruct(
     private const val TAG_EXTENDED_PAN_I_D = 0
     private const val TAG_NETWORK_NAME = 1
     private const val TAG_CHANNEL = 2
+    private const val TAG_ACTIVE_TIMESTAMP = 3
 
     fun fromTlv(
       tlvTag: Tag,
       tlvReader: TlvReader,
     ): ThreadNetworkDirectoryClusterThreadNetworkStruct {
       tlvReader.enterStructure(tlvTag)
-      val extendedPanID = tlvReader.getULong(ContextSpecificTag(TAG_EXTENDED_PAN_I_D))
+      val extendedPanID = tlvReader.getByteArray(ContextSpecificTag(TAG_EXTENDED_PAN_I_D))
       val networkName = tlvReader.getString(ContextSpecificTag(TAG_NETWORK_NAME))
       val channel = tlvReader.getUInt(ContextSpecificTag(TAG_CHANNEL))
+      val activeTimestamp = tlvReader.getULong(ContextSpecificTag(TAG_ACTIVE_TIMESTAMP))
 
       tlvReader.exitContainer()
 
-      return ThreadNetworkDirectoryClusterThreadNetworkStruct(extendedPanID, networkName, channel)
+      return ThreadNetworkDirectoryClusterThreadNetworkStruct(
+        extendedPanID,
+        networkName,
+        channel,
+        activeTimestamp,
+      )
     }
   }
 }
diff --git a/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadNetworkDirectoryCluster.kt b/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadNetworkDirectoryCluster.kt
index 6bbf6ffe4f813c..e479f0c79ef0f2 100644
--- a/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadNetworkDirectoryCluster.kt
+++ b/src/controller/java/generated/java/matter/controller/cluster/clusters/ThreadNetworkDirectoryCluster.kt
@@ -49,10 +49,10 @@ class ThreadNetworkDirectoryCluster(
 ) {
   class OperationalDatasetResponse(val operationalDataset: ByteArray)
 
-  class PreferredExtendedPanIDAttribute(val value: ULong?)
+  class PreferredExtendedPanIDAttribute(val value: ByteArray?)
 
   sealed class PreferredExtendedPanIDAttributeSubscriptionState {
-    data class Success(val value: ULong?) : PreferredExtendedPanIDAttributeSubscriptionState()
+    data class Success(val value: ByteArray?) : PreferredExtendedPanIDAttributeSubscriptionState()
 
     data class Error(val exception: Exception) : PreferredExtendedPanIDAttributeSubscriptionState()
 
@@ -131,7 +131,7 @@ class ThreadNetworkDirectoryCluster(
     logger.log(Level.FINE, "Invoke command succeeded: ${response}")
   }
 
-  suspend fun removeNetwork(extendedPanID: ULong, timedInvokeTimeout: Duration) {
+  suspend fun removeNetwork(extendedPanID: ByteArray, timedInvokeTimeout: Duration) {
     val commandId: UInt = 1u
 
     val tlvWriter = TlvWriter()
@@ -153,7 +153,7 @@ class ThreadNetworkDirectoryCluster(
   }
 
   suspend fun getOperationalDataset(
-    extendedPanID: ULong,
+    extendedPanID: ByteArray,
     timedInvokeTimeout: Duration,
   ): OperationalDatasetResponse {
     val commandId: UInt = 2u
@@ -225,9 +225,9 @@ class ThreadNetworkDirectoryCluster(
 
     // Decode the TLV data into the appropriate type
     val tlvReader = TlvReader(attributeData.data)
-    val decodedValue: ULong? =
+    val decodedValue: ByteArray? =
       if (!tlvReader.isNull()) {
-        tlvReader.getULong(AnonymousTag)
+        tlvReader.getByteArray(AnonymousTag)
       } else {
         tlvReader.getNull(AnonymousTag)
         null
@@ -237,7 +237,7 @@ class ThreadNetworkDirectoryCluster(
   }
 
   suspend fun writePreferredExtendedPanIDAttribute(
-    value: ULong,
+    value: ByteArray,
     timedWriteTimeout: Duration? = null,
   ) {
     val ATTRIBUTE_ID: UInt = 0u
@@ -320,9 +320,9 @@ class ThreadNetworkDirectoryCluster(
 
           // Decode the TLV data into the appropriate type
           val tlvReader = TlvReader(attributeData.data)
-          val decodedValue: ULong? =
+          val decodedValue: ByteArray? =
             if (!tlvReader.isNull()) {
-              tlvReader.getULong(AnonymousTag)
+              tlvReader.getByteArray(AnonymousTag)
             } else {
               tlvReader.getNull(AnonymousTag)
               null
diff --git a/src/controller/java/generated/java/matter/controller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt b/src/controller/java/generated/java/matter/controller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt
deleted file mode 100644
index a5b1f26621002e..00000000000000
--- a/src/controller/java/generated/java/matter/controller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- *
- *    Copyright (c) 2023 Project CHIP Authors
- *
- *    Licensed under the Apache License, Version 2.0 (the "License");
- *    you may not use this file except in compliance with the License.
- *    You may obtain a copy of the License at
- *
- *        http://www.apache.org/licenses/LICENSE-2.0
- *
- *    Unless required by applicable law or agreed to in writing, software
- *    distributed under the License is distributed on an "AS IS" BASIS,
- *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *    See the License for the specific language governing permissions and
- *    limitations under the License.
- */
-package matter.controller.cluster.eventstructs
-
-import matter.controller.cluster.*
-import matter.tlv.ContextSpecificTag
-import matter.tlv.Tag
-import matter.tlv.TlvReader
-import matter.tlv.TlvWriter
-
-class ThreadNetworkDirectoryClusterNetworkChangedEvent(val extendedPanID: ULong) {
-  override fun toString(): String = buildString {
-    append("ThreadNetworkDirectoryClusterNetworkChangedEvent {\n")
-    append("\textendedPanID : $extendedPanID\n")
-    append("}\n")
-  }
-
-  fun toTlv(tlvTag: Tag, tlvWriter: TlvWriter) {
-    tlvWriter.apply {
-      startStructure(tlvTag)
-      put(ContextSpecificTag(TAG_EXTENDED_PAN_I_D), extendedPanID)
-      endStructure()
-    }
-  }
-
-  companion object {
-    private const val TAG_EXTENDED_PAN_I_D = 0
-
-    fun fromTlv(
-      tlvTag: Tag,
-      tlvReader: TlvReader,
-    ): ThreadNetworkDirectoryClusterNetworkChangedEvent {
-      tlvReader.enterStructure(tlvTag)
-      val extendedPanID = tlvReader.getULong(ContextSpecificTag(TAG_EXTENDED_PAN_I_D))
-
-      tlvReader.exitContainer()
-
-      return ThreadNetworkDirectoryClusterNetworkChangedEvent(extendedPanID)
-    }
-  }
-}
diff --git a/src/controller/java/generated/java/matter/controller/cluster/files.gni b/src/controller/java/generated/java/matter/controller/cluster/files.gni
index bf7839d661528d..ddb25414b6304d 100644
--- a/src/controller/java/generated/java/matter/controller/cluster/files.gni
+++ b/src/controller/java/generated/java/matter/controller/cluster/files.gni
@@ -222,7 +222,6 @@ matter_eventstructs_sources = [
   "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/eventstructs/TargetNavigatorClusterTargetUpdatedEvent.kt",
   "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/eventstructs/ThreadNetworkDiagnosticsClusterConnectionStatusEvent.kt",
   "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/eventstructs/ThreadNetworkDiagnosticsClusterNetworkFaultChangeEvent.kt",
-  "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/eventstructs/ThreadNetworkDirectoryClusterNetworkChangedEvent.kt",
   "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/eventstructs/TimeSynchronizationClusterDSTStatusEvent.kt",
   "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/eventstructs/TimeSynchronizationClusterTimeZoneStatusEvent.kt",
   "${chip_root}/src/controller/java/generated/java/matter/controller/cluster/eventstructs/UnitTestingClusterTestDifferentVendorMeiEventEvent.kt",
diff --git a/src/controller/java/generated/java/matter/controller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt b/src/controller/java/generated/java/matter/controller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt
index db8dbbb0324745..bacfb457534ddd 100644
--- a/src/controller/java/generated/java/matter/controller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt
+++ b/src/controller/java/generated/java/matter/controller/cluster/structs/ThreadNetworkDirectoryClusterThreadNetworkStruct.kt
@@ -23,15 +23,17 @@ import matter.tlv.TlvReader
 import matter.tlv.TlvWriter
 
 class ThreadNetworkDirectoryClusterThreadNetworkStruct(
-  val extendedPanID: ULong,
+  val extendedPanID: ByteArray,
   val networkName: String,
   val channel: UShort,
+  val activeTimestamp: ULong,
 ) {
   override fun toString(): String = buildString {
     append("ThreadNetworkDirectoryClusterThreadNetworkStruct {\n")
     append("\textendedPanID : $extendedPanID\n")
     append("\tnetworkName : $networkName\n")
     append("\tchannel : $channel\n")
+    append("\tactiveTimestamp : $activeTimestamp\n")
     append("}\n")
   }
 
@@ -41,6 +43,7 @@ class ThreadNetworkDirectoryClusterThreadNetworkStruct(
       put(ContextSpecificTag(TAG_EXTENDED_PAN_I_D), extendedPanID)
       put(ContextSpecificTag(TAG_NETWORK_NAME), networkName)
       put(ContextSpecificTag(TAG_CHANNEL), channel)
+      put(ContextSpecificTag(TAG_ACTIVE_TIMESTAMP), activeTimestamp)
       endStructure()
     }
   }
@@ -49,19 +52,26 @@ class ThreadNetworkDirectoryClusterThreadNetworkStruct(
     private const val TAG_EXTENDED_PAN_I_D = 0
     private const val TAG_NETWORK_NAME = 1
     private const val TAG_CHANNEL = 2
+    private const val TAG_ACTIVE_TIMESTAMP = 3
 
     fun fromTlv(
       tlvTag: Tag,
       tlvReader: TlvReader,
     ): ThreadNetworkDirectoryClusterThreadNetworkStruct {
       tlvReader.enterStructure(tlvTag)
-      val extendedPanID = tlvReader.getULong(ContextSpecificTag(TAG_EXTENDED_PAN_I_D))
+      val extendedPanID = tlvReader.getByteArray(ContextSpecificTag(TAG_EXTENDED_PAN_I_D))
       val networkName = tlvReader.getString(ContextSpecificTag(TAG_NETWORK_NAME))
       val channel = tlvReader.getUShort(ContextSpecificTag(TAG_CHANNEL))
+      val activeTimestamp = tlvReader.getULong(ContextSpecificTag(TAG_ACTIVE_TIMESTAMP))
 
       tlvReader.exitContainer()
 
-      return ThreadNetworkDirectoryClusterThreadNetworkStruct(extendedPanID, networkName, channel)
+      return ThreadNetworkDirectoryClusterThreadNetworkStruct(
+        extendedPanID,
+        networkName,
+        channel,
+        activeTimestamp,
+      )
     }
   }
 }
diff --git a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp
index 9d1d0bd1bb65a3..cff767b424b59d 100644
--- a/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp
+++ b/src/controller/java/zap-generated/CHIPAttributeTLVValueDecoder.cpp
@@ -38832,11 +38832,10 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR
             }
             else
             {
-                std::string valueClassName     = "java/lang/Long";
-                std::string valueCtorSignature = "(J)V";
-                jlong jnivalue                 = static_cast<jlong>(cppValue.Value());
-                chip::JniReferences::GetInstance().CreateBoxedObject<jlong>(valueClassName.c_str(), valueCtorSignature.c_str(),
-                                                                            jnivalue, value);
+                jbyteArray valueByteArray = env->NewByteArray(static_cast<jsize>(cppValue.Value().size()));
+                env->SetByteArrayRegion(valueByteArray, 0, static_cast<jsize>(cppValue.Value().size()),
+                                        reinterpret_cast<const jbyte *>(cppValue.Value().data()));
+                value = valueByteArray;
             }
             return value;
         }
@@ -38857,12 +38856,11 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR
                 auto & entry_0 = iter_value_0.GetValue();
                 jobject newElement_0;
                 jobject newElement_0_extendedPanID;
-                std::string newElement_0_extendedPanIDClassName     = "java/lang/Long";
-                std::string newElement_0_extendedPanIDCtorSignature = "(J)V";
-                jlong jninewElement_0_extendedPanID                 = static_cast<jlong>(entry_0.extendedPanID);
-                chip::JniReferences::GetInstance().CreateBoxedObject<jlong>(
-                    newElement_0_extendedPanIDClassName.c_str(), newElement_0_extendedPanIDCtorSignature.c_str(),
-                    jninewElement_0_extendedPanID, newElement_0_extendedPanID);
+                jbyteArray newElement_0_extendedPanIDByteArray =
+                    env->NewByteArray(static_cast<jsize>(entry_0.extendedPanID.size()));
+                env->SetByteArrayRegion(newElement_0_extendedPanIDByteArray, 0, static_cast<jsize>(entry_0.extendedPanID.size()),
+                                        reinterpret_cast<const jbyte *>(entry_0.extendedPanID.data()));
+                newElement_0_extendedPanID = newElement_0_extendedPanIDByteArray;
                 jobject newElement_0_networkName;
                 LogErrorOnFailure(
                     chip::JniReferences::GetInstance().CharToStringUTF(entry_0.networkName, newElement_0_networkName));
@@ -38873,6 +38871,13 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR
                 chip::JniReferences::GetInstance().CreateBoxedObject<jint>(newElement_0_channelClassName.c_str(),
                                                                            newElement_0_channelCtorSignature.c_str(),
                                                                            jninewElement_0_channel, newElement_0_channel);
+                jobject newElement_0_activeTimestamp;
+                std::string newElement_0_activeTimestampClassName     = "java/lang/Long";
+                std::string newElement_0_activeTimestampCtorSignature = "(J)V";
+                jlong jninewElement_0_activeTimestamp                 = static_cast<jlong>(entry_0.activeTimestamp);
+                chip::JniReferences::GetInstance().CreateBoxedObject<jlong>(
+                    newElement_0_activeTimestampClassName.c_str(), newElement_0_activeTimestampCtorSignature.c_str(),
+                    jninewElement_0_activeTimestamp, newElement_0_activeTimestamp);
 
                 jclass threadNetworkStructStructClass_1;
                 err = chip::JniReferences::GetInstance().GetLocalClassRef(
@@ -38886,7 +38891,7 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR
 
                 jmethodID threadNetworkStructStructCtor_1;
                 err = chip::JniReferences::GetInstance().FindMethod(env, threadNetworkStructStructClass_1, "<init>",
-                                                                    "(Ljava/lang/Long;Ljava/lang/String;Ljava/lang/Integer;)V",
+                                                                    "([BLjava/lang/String;Ljava/lang/Integer;Ljava/lang/Long;)V",
                                                                     &threadNetworkStructStructCtor_1);
                 if (err != CHIP_NO_ERROR || threadNetworkStructStructCtor_1 == nullptr)
                 {
@@ -38894,8 +38899,9 @@ jobject DecodeAttributeValue(const app::ConcreteAttributePath & aPath, TLV::TLVR
                     return nullptr;
                 }
 
-                newElement_0 = env->NewObject(threadNetworkStructStructClass_1, threadNetworkStructStructCtor_1,
-                                              newElement_0_extendedPanID, newElement_0_networkName, newElement_0_channel);
+                newElement_0 =
+                    env->NewObject(threadNetworkStructStructClass_1, threadNetworkStructStructCtor_1, newElement_0_extendedPanID,
+                                   newElement_0_networkName, newElement_0_channel, newElement_0_activeTimestamp);
                 chip::JniReferences::GetInstance().AddToList(value, newElement_0);
             }
             return value;
diff --git a/src/controller/java/zap-generated/CHIPEventTLVValueDecoder.cpp b/src/controller/java/zap-generated/CHIPEventTLVValueDecoder.cpp
index 789e3ea5f1310e..0dbed58b4b93d3 100644
--- a/src/controller/java/zap-generated/CHIPEventTLVValueDecoder.cpp
+++ b/src/controller/java/zap-generated/CHIPEventTLVValueDecoder.cpp
@@ -7481,44 +7481,6 @@ jobject DecodeEventValue(const app::ConcreteEventPath & aPath, TLV::TLVReader &
         using namespace app::Clusters::ThreadNetworkDirectory;
         switch (aPath.mEventId)
         {
-        case Events::NetworkChanged::Id: {
-            Events::NetworkChanged::DecodableType cppValue;
-            *aError = app::DataModel::Decode(aReader, cppValue);
-            if (*aError != CHIP_NO_ERROR)
-            {
-                return nullptr;
-            }
-            jobject value_extendedPanID;
-            std::string value_extendedPanIDClassName     = "java/lang/Long";
-            std::string value_extendedPanIDCtorSignature = "(J)V";
-            jlong jnivalue_extendedPanID                 = static_cast<jlong>(cppValue.extendedPanID);
-            chip::JniReferences::GetInstance().CreateBoxedObject<jlong>(value_extendedPanIDClassName.c_str(),
-                                                                        value_extendedPanIDCtorSignature.c_str(),
-                                                                        jnivalue_extendedPanID, value_extendedPanID);
-
-            jclass networkChangedStructClass;
-            err = chip::JniReferences::GetInstance().GetLocalClassRef(
-                env, "chip/devicecontroller/ChipEventStructs$ThreadNetworkDirectoryClusterNetworkChangedEvent",
-                networkChangedStructClass);
-            if (err != CHIP_NO_ERROR)
-            {
-                ChipLogError(Zcl, "Could not find class ChipEventStructs$ThreadNetworkDirectoryClusterNetworkChangedEvent");
-                return nullptr;
-            }
-
-            jmethodID networkChangedStructCtor;
-            err = chip::JniReferences::GetInstance().FindMethod(env, networkChangedStructClass, "<init>", "(Ljava/lang/Long;)V",
-                                                                &networkChangedStructCtor);
-            if (err != CHIP_NO_ERROR || networkChangedStructCtor == nullptr)
-            {
-                ChipLogError(Zcl, "Could not find ChipEventStructs$ThreadNetworkDirectoryClusterNetworkChangedEvent constructor");
-                return nullptr;
-            }
-
-            jobject value = env->NewObject(networkChangedStructClass, networkChangedStructCtor, value_extendedPanID);
-
-            return value;
-        }
         default:
             *aError = CHIP_ERROR_IM_MALFORMED_EVENT_PATH_IB;
             break;
diff --git a/src/controller/python/chip/clusters/CHIPClusters.py b/src/controller/python/chip/clusters/CHIPClusters.py
index 1901351596228a..3b77856a4121d2 100644
--- a/src/controller/python/chip/clusters/CHIPClusters.py
+++ b/src/controller/python/chip/clusters/CHIPClusters.py
@@ -11989,14 +11989,14 @@ class ChipClusters:
                 "commandId": 0x00000001,
                 "commandName": "RemoveNetwork",
                 "args": {
-                    "extendedPanID": "int",
+                    "extendedPanID": "bytes",
                 },
             },
             0x00000002: {
                 "commandId": 0x00000002,
                 "commandName": "GetOperationalDataset",
                 "args": {
-                    "extendedPanID": "int",
+                    "extendedPanID": "bytes",
                 },
             },
         },
@@ -12004,7 +12004,7 @@ class ChipClusters:
             0x00000000: {
                 "attributeName": "PreferredExtendedPanID",
                 "attributeId": 0x00000000,
-                "type": "int",
+                "type": "bytes",
                 "reportable": True,
                 "writable": True,
             },
diff --git a/src/controller/python/chip/clusters/Objects.py b/src/controller/python/chip/clusters/Objects.py
index 7b0c4a0cd6bc37..52e2be80aa74f5 100644
--- a/src/controller/python/chip/clusters/Objects.py
+++ b/src/controller/python/chip/clusters/Objects.py
@@ -42011,7 +42011,7 @@ class ThreadNetworkDirectory(Cluster):
     def descriptor(cls) -> ClusterObjectDescriptor:
         return ClusterObjectDescriptor(
             Fields=[
-                ClusterObjectFieldDescriptor(Label="preferredExtendedPanID", Tag=0x00000000, Type=typing.Union[Nullable, uint]),
+                ClusterObjectFieldDescriptor(Label="preferredExtendedPanID", Tag=0x00000000, Type=typing.Union[Nullable, bytes]),
                 ClusterObjectFieldDescriptor(Label="threadNetworks", Tag=0x00000001, Type=typing.List[ThreadNetworkDirectory.Structs.ThreadNetworkStruct]),
                 ClusterObjectFieldDescriptor(Label="threadNetworkTableSize", Tag=0x00000002, Type=uint),
                 ClusterObjectFieldDescriptor(Label="generatedCommandList", Tag=0x0000FFF8, Type=typing.List[uint]),
@@ -42022,7 +42022,7 @@ def descriptor(cls) -> ClusterObjectDescriptor:
                 ClusterObjectFieldDescriptor(Label="clusterRevision", Tag=0x0000FFFD, Type=uint),
             ])
 
-    preferredExtendedPanID: 'typing.Union[Nullable, uint]' = None
+    preferredExtendedPanID: 'typing.Union[Nullable, bytes]' = None
     threadNetworks: 'typing.List[ThreadNetworkDirectory.Structs.ThreadNetworkStruct]' = None
     threadNetworkTableSize: 'uint' = None
     generatedCommandList: 'typing.List[uint]' = None
@@ -42039,14 +42039,16 @@ class ThreadNetworkStruct(ClusterObject):
             def descriptor(cls) -> ClusterObjectDescriptor:
                 return ClusterObjectDescriptor(
                     Fields=[
-                        ClusterObjectFieldDescriptor(Label="extendedPanID", Tag=0, Type=uint),
+                        ClusterObjectFieldDescriptor(Label="extendedPanID", Tag=0, Type=bytes),
                         ClusterObjectFieldDescriptor(Label="networkName", Tag=1, Type=str),
                         ClusterObjectFieldDescriptor(Label="channel", Tag=2, Type=uint),
+                        ClusterObjectFieldDescriptor(Label="activeTimestamp", Tag=3, Type=uint),
                     ])
 
-            extendedPanID: 'uint' = 0
+            extendedPanID: 'bytes' = b""
             networkName: 'str' = ""
             channel: 'uint' = 0
+            activeTimestamp: 'uint' = 0
 
     class Commands:
         @dataclass
@@ -42080,14 +42082,14 @@ class RemoveNetwork(ClusterCommand):
             def descriptor(cls) -> ClusterObjectDescriptor:
                 return ClusterObjectDescriptor(
                     Fields=[
-                        ClusterObjectFieldDescriptor(Label="extendedPanID", Tag=0, Type=uint),
+                        ClusterObjectFieldDescriptor(Label="extendedPanID", Tag=0, Type=bytes),
                     ])
 
             @ChipUtility.classproperty
             def must_use_timed_invoke(cls) -> bool:
                 return True
 
-            extendedPanID: 'uint' = 0
+            extendedPanID: 'bytes' = b""
 
         @dataclass
         class GetOperationalDataset(ClusterCommand):
@@ -42100,14 +42102,14 @@ class GetOperationalDataset(ClusterCommand):
             def descriptor(cls) -> ClusterObjectDescriptor:
                 return ClusterObjectDescriptor(
                     Fields=[
-                        ClusterObjectFieldDescriptor(Label="extendedPanID", Tag=0, Type=uint),
+                        ClusterObjectFieldDescriptor(Label="extendedPanID", Tag=0, Type=bytes),
                     ])
 
             @ChipUtility.classproperty
             def must_use_timed_invoke(cls) -> bool:
                 return True
 
-            extendedPanID: 'uint' = 0
+            extendedPanID: 'bytes' = b""
 
         @dataclass
         class OperationalDatasetResponse(ClusterCommand):
@@ -42138,9 +42140,9 @@ def attribute_id(cls) -> int:
 
             @ChipUtility.classproperty
             def attribute_type(cls) -> ClusterObjectFieldDescriptor:
-                return ClusterObjectFieldDescriptor(Type=typing.Union[Nullable, uint])
+                return ClusterObjectFieldDescriptor(Type=typing.Union[Nullable, bytes])
 
-            value: 'typing.Union[Nullable, uint]' = NullValue
+            value: 'typing.Union[Nullable, bytes]' = NullValue
 
         @dataclass
         class ThreadNetworks(ClusterAttributeDescriptor):
@@ -42270,26 +42272,6 @@ def attribute_type(cls) -> ClusterObjectFieldDescriptor:
 
             value: 'uint' = 0
 
-    class Events:
-        @dataclass
-        class NetworkChanged(ClusterEvent):
-            @ChipUtility.classproperty
-            def cluster_id(cls) -> int:
-                return 0x00000453
-
-            @ChipUtility.classproperty
-            def event_id(cls) -> int:
-                return 0x00000000
-
-            @ChipUtility.classproperty
-            def descriptor(cls) -> ClusterObjectDescriptor:
-                return ClusterObjectDescriptor(
-                    Fields=[
-                        ClusterObjectFieldDescriptor(Label="extendedPanID", Tag=0, Type=uint),
-                    ])
-
-            extendedPanID: 'uint' = 0
-
 
 @dataclass
 class WakeOnLan(Cluster):
diff --git a/src/controller/python/chip/clusters/__init__.py b/src/controller/python/chip/clusters/__init__.py
index 1aa2a61a8911d6..f633ca272a48a7 100644
--- a/src/controller/python/chip/clusters/__init__.py
+++ b/src/controller/python/chip/clusters/__init__.py
@@ -44,10 +44,10 @@
                       RefrigeratorAndTemperatureControlledCabinetMode, RelativeHumidityMeasurement, RvcCleanMode,
                       RvcOperationalState, RvcRunMode, ScenesManagement, SmokeCoAlarm, SoftwareDiagnostics, Switch, TargetNavigator,
                       TemperatureControl, TemperatureMeasurement, Thermostat, ThermostatUserInterfaceConfiguration,
-                      ThreadBorderRouterManagement, ThreadNetworkDiagnostics, TimeFormatLocalization, TimeSynchronization,
-                      TotalVolatileOrganicCompoundsConcentrationMeasurement, UnitLocalization, UnitTesting, UserLabel,
-                      ValveConfigurationAndControl, WakeOnLan, WaterHeaterManagement, WaterHeaterMode, WiFiNetworkDiagnostics,
-                      WindowCovering)
+                      ThreadBorderRouterManagement, ThreadNetworkDiagnostics, ThreadNetworkDirectory, TimeFormatLocalization,
+                      TimeSynchronization, TotalVolatileOrganicCompoundsConcentrationMeasurement, UnitLocalization, UnitTesting,
+                      UserLabel, ValveConfigurationAndControl, WakeOnLan, WaterHeaterManagement, WaterHeaterMode,
+                      WiFiNetworkDiagnostics, WindowCovering)
 
 __all__ = [Attribute, CHIPClusters, Command, AccessControl, AccountLogin, Actions, ActivatedCarbonFilterMonitoring, AdministratorCommissioning, AirQuality,
            ApplicationBasic, ApplicationLauncher, AudioOutput, BallastConfiguration, BarrierControl, BasicInformation,
@@ -68,6 +68,6 @@
            RefrigeratorAlarm, RefrigeratorAndTemperatureControlledCabinetMode, RelativeHumidityMeasurement, RvcCleanMode,
            RvcOperationalState, RvcRunMode, ScenesManagement, SmokeCoAlarm, SoftwareDiagnostics,
            Switch, TargetNavigator, TemperatureControl, TemperatureMeasurement, Thermostat, ThermostatUserInterfaceConfiguration,
-           ThreadBorderRouterManagement, ThreadNetworkDiagnostics, TimeFormatLocalization, TimeSynchronization,
+           ThreadBorderRouterManagement, ThreadNetworkDiagnostics, ThreadNetworkDirectory, TimeFormatLocalization, TimeSynchronization,
            TotalVolatileOrganicCompoundsConcentrationMeasurement, UnitLocalization,
            UnitTesting, UserLabel, ValveConfigurationAndControl, WakeOnLan, WaterHeaterManagement, WaterHeaterMode, WiFiNetworkDiagnostics, WindowCovering]
diff --git a/src/darwin/Framework/CHIP/MTRDemuxingStorage.mm b/src/darwin/Framework/CHIP/MTRDemuxingStorage.mm
index 4ce1a3a93a8f72..fff85f8eb4c3d2 100644
--- a/src/darwin/Framework/CHIP/MTRDemuxingStorage.mm
+++ b/src/darwin/Framework/CHIP/MTRDemuxingStorage.mm
@@ -167,6 +167,8 @@ static bool IsMemoryOnlyGlobalKey(NSString * key)
     // We do not expect to see the "g/icdfl" key; that's only used by
     // DefaultICDClientStorage, which Matter.framework does not use.
 
+    // We do not expect to see the "g/tnd/*" Thread Network Directory keys.
+
     return false;
 }
 
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm
index 96f5a211636050..26db9e819103af 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRAttributeTLVValueDecoder.mm
@@ -15779,11 +15779,11 @@ static id _Nullable DecodeAttributeValueForThreadNetworkDirectoryCluster(Attribu
         if (*aError != CHIP_NO_ERROR) {
             return nil;
         }
-        NSNumber * _Nullable value;
+        NSData * _Nullable value;
         if (cppValue.IsNull()) {
             value = nil;
         } else {
-            value = [NSNumber numberWithUnsignedLongLong:cppValue.Value()];
+            value = AsData(cppValue.Value());
         }
         return value;
     }
@@ -15802,7 +15802,7 @@ static id _Nullable DecodeAttributeValueForThreadNetworkDirectoryCluster(Attribu
                 auto & entry_0 = iter_0.GetValue();
                 MTRThreadNetworkDirectoryClusterThreadNetworkStruct * newElement_0;
                 newElement_0 = [MTRThreadNetworkDirectoryClusterThreadNetworkStruct new];
-                newElement_0.extendedPanID = [NSNumber numberWithUnsignedLongLong:entry_0.extendedPanID];
+                newElement_0.extendedPanID = AsData(entry_0.extendedPanID);
                 newElement_0.networkName = AsString(entry_0.networkName);
                 if (newElement_0.networkName == nil) {
                     CHIP_ERROR err = CHIP_ERROR_INVALID_ARGUMENT;
@@ -15810,6 +15810,7 @@ static id _Nullable DecodeAttributeValueForThreadNetworkDirectoryCluster(Attribu
                     return nil;
                 }
                 newElement_0.channel = [NSNumber numberWithUnsignedShort:entry_0.channel];
+                newElement_0.activeTimestamp = [NSNumber numberWithUnsignedLongLong:entry_0.activeTimestamp];
                 [array_0 addObject:newElement_0];
             }
             CHIP_ERROR err = iter_0.GetStatus();
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
index 181d0c70408b24..91b6e9a65a2fc6 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.h
@@ -13547,13 +13547,13 @@ MTR_PROVISIONALLY_AVAILABLE
  */
 - (void)getOperationalDatasetWithParams:(MTRThreadNetworkDirectoryClusterGetOperationalDatasetParams *)params completion:(void (^)(MTRThreadNetworkDirectoryClusterOperationalDatasetResponseParams * _Nullable data, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
 
-- (void)readAttributePreferredExtendedPanIDWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
-- (void)writeAttributePreferredExtendedPanIDWithValue:(NSNumber * _Nullable)value completion:(MTRStatusCompletion)completion MTR_PROVISIONALLY_AVAILABLE;
-- (void)writeAttributePreferredExtendedPanIDWithValue:(NSNumber * _Nullable)value params:(MTRWriteParams * _Nullable)params completion:(MTRStatusCompletion)completion MTR_PROVISIONALLY_AVAILABLE;
+- (void)readAttributePreferredExtendedPanIDWithCompletion:(void (^)(NSData * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
+- (void)writeAttributePreferredExtendedPanIDWithValue:(NSData * _Nullable)value completion:(MTRStatusCompletion)completion MTR_PROVISIONALLY_AVAILABLE;
+- (void)writeAttributePreferredExtendedPanIDWithValue:(NSData * _Nullable)value params:(MTRWriteParams * _Nullable)params completion:(MTRStatusCompletion)completion MTR_PROVISIONALLY_AVAILABLE;
 - (void)subscribeAttributePreferredExtendedPanIDWithParams:(MTRSubscribeParams *)params
                                    subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished
-                                             reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler MTR_PROVISIONALLY_AVAILABLE;
-+ (void)readAttributePreferredExtendedPanIDWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
+                                             reportHandler:(void (^)(NSData * _Nullable value, NSError * _Nullable error))reportHandler MTR_PROVISIONALLY_AVAILABLE;
++ (void)readAttributePreferredExtendedPanIDWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSData * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
 
 - (void)readAttributeThreadNetworksWithCompletion:(void (^)(NSArray * _Nullable value, NSError * _Nullable error))completion MTR_PROVISIONALLY_AVAILABLE;
 - (void)subscribeAttributeThreadNetworksWithParams:(MTRSubscribeParams *)params
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
index 6a15e501af9a7d..9aa9dbfab3dec4 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRBaseClusters.mm
@@ -95301,7 +95301,7 @@ - (void)getOperationalDatasetWithParams:(MTRThreadNetworkDirectoryClusterGetOper
                                         completion:responseHandler];
 }
 
-- (void)readAttributePreferredExtendedPanIDWithCompletion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
+- (void)readAttributePreferredExtendedPanIDWithCompletion:(void (^)(NSData * _Nullable value, NSError * _Nullable error))completion
 {
     using TypeInfo = ThreadNetworkDirectory::Attributes::PreferredExtendedPanID::TypeInfo;
     [self.device _readKnownAttributeWithEndpointID:self.endpointID
@@ -95312,11 +95312,11 @@ - (void)readAttributePreferredExtendedPanIDWithCompletion:(void (^)(NSNumber * _
                                         completion:completion];
 }
 
-- (void)writeAttributePreferredExtendedPanIDWithValue:(NSNumber * _Nullable)value completion:(MTRStatusCompletion)completion
+- (void)writeAttributePreferredExtendedPanIDWithValue:(NSData * _Nullable)value completion:(MTRStatusCompletion)completion
 {
-    [self writeAttributePreferredExtendedPanIDWithValue:(NSNumber * _Nullable) value params:nil completion:completion];
+    [self writeAttributePreferredExtendedPanIDWithValue:(NSData * _Nullable) value params:nil completion:completion];
 }
-- (void)writeAttributePreferredExtendedPanIDWithValue:(NSNumber * _Nullable)value params:(MTRWriteParams * _Nullable)params completion:(MTRStatusCompletion)completion
+- (void)writeAttributePreferredExtendedPanIDWithValue:(NSData * _Nullable)value params:(MTRWriteParams * _Nullable)params completion:(MTRStatusCompletion)completion
 {
     // Make a copy of params before we go async.
     params = [params copy];
@@ -95337,8 +95337,8 @@ - (void)writeAttributePreferredExtendedPanIDWithValue:(NSNumber * _Nullable)valu
             cppValue.SetNull();
           } else {
             auto & nonNullValue_0 = cppValue.SetNonNull();
-                    nonNullValue_0 = value.unsignedLongLongValue;
-  }
+              nonNullValue_0 = AsByteSpan(value);
+          }
 
         chip::Controller::ClusterBase cppCluster(exchangeManager, session, self.endpointID.unsignedShortValue);
         return cppCluster.WriteAttribute<TypeInfo>(cppValue, bridge, successCb, failureCb, timedWriteTimeout); });
@@ -95347,7 +95347,7 @@ - (void)writeAttributePreferredExtendedPanIDWithValue:(NSNumber * _Nullable)valu
 
 - (void)subscribeAttributePreferredExtendedPanIDWithParams:(MTRSubscribeParams * _Nonnull)params
                                    subscriptionEstablished:(MTRSubscriptionEstablishedHandler _Nullable)subscriptionEstablished
-                                             reportHandler:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))reportHandler
+                                             reportHandler:(void (^)(NSData * _Nullable value, NSError * _Nullable error))reportHandler
 {
     using TypeInfo = ThreadNetworkDirectory::Attributes::PreferredExtendedPanID::TypeInfo;
     [self.device _subscribeToKnownAttributeWithEndpointID:self.endpointID
@@ -95359,7 +95359,7 @@ - (void)subscribeAttributePreferredExtendedPanIDWithParams:(MTRSubscribeParams *
                                   subscriptionEstablished:subscriptionEstablished];
 }
 
-+ (void)readAttributePreferredExtendedPanIDWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSNumber * _Nullable value, NSError * _Nullable error))completion
++ (void)readAttributePreferredExtendedPanIDWithClusterStateCache:(MTRClusterStateCacheContainer *)clusterStateCacheContainer endpoint:(NSNumber *)endpoint queue:(dispatch_queue_t)queue completion:(void (^)(NSData * _Nullable value, NSError * _Nullable error))completion
 {
     using TypeInfo = ThreadNetworkDirectory::Attributes::PreferredExtendedPanID::TypeInfo;
     [clusterStateCacheContainer
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h
index 445404818a9573..6c2978795cdafd 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRClusterConstants.h
@@ -7452,9 +7452,6 @@ typedef NS_ENUM(uint32_t, MTREventIDType) {
     MTREventIDTypeClusterPumpConfigurationAndControlEventAirDetectionID MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) = 0x0000000F,
     MTREventIDTypeClusterPumpConfigurationAndControlEventTurbineOperationID MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4)) = 0x00000010,
 
-    // Cluster ThreadNetworkDirectory events
-    MTREventIDTypeClusterThreadNetworkDirectoryEventNetworkChangedID MTR_PROVISIONALLY_AVAILABLE = 0x00000000,
-
     // Cluster TargetNavigator deprecated event names
 
     // Cluster TargetNavigator events
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
index 7c30f13fbe7044..4bdab512cd6d77 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.h
@@ -8950,7 +8950,7 @@ MTR_PROVISIONALLY_AVAILABLE
 MTR_PROVISIONALLY_AVAILABLE
 @interface MTRThreadNetworkDirectoryClusterRemoveNetworkParams : NSObject <NSCopying>
 
-@property (nonatomic, copy) NSNumber * _Nonnull extendedPanID MTR_PROVISIONALLY_AVAILABLE;
+@property (nonatomic, copy) NSData * _Nonnull extendedPanID MTR_PROVISIONALLY_AVAILABLE;
 /**
  * Controls whether the command is a timed command (using Timed Invoke).
  *
@@ -8980,7 +8980,7 @@ MTR_PROVISIONALLY_AVAILABLE
 MTR_PROVISIONALLY_AVAILABLE
 @interface MTRThreadNetworkDirectoryClusterGetOperationalDatasetParams : NSObject <NSCopying>
 
-@property (nonatomic, copy) NSNumber * _Nonnull extendedPanID MTR_PROVISIONALLY_AVAILABLE;
+@property (nonatomic, copy) NSData * _Nonnull extendedPanID MTR_PROVISIONALLY_AVAILABLE;
 /**
  * Controls whether the command is a timed command (using Timed Invoke).
  *
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
index 441fc5cb8361cf..9f7ffd3a16b328 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRCommandPayloadsObjc.mm
@@ -25408,7 +25408,7 @@ - (instancetype)init
 {
     if (self = [super init]) {
 
-        _extendedPanID = @(0);
+        _extendedPanID = [NSData data];
         _timedInvokeTimeoutMs = nil;
         _serverSideProcessingTimeout = nil;
     }
@@ -25428,7 +25428,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone;
 
 - (NSString *)description
 {
-    NSString * descriptionString = [NSString stringWithFormat:@"<%@: extendedPanID:%@; >", NSStringFromClass([self class]), _extendedPanID];
+    NSString * descriptionString = [NSString stringWithFormat:@"<%@: extendedPanID:%@; >", NSStringFromClass([self class]), [_extendedPanID base64EncodedStringWithOptions:0]];
     return descriptionString;
 }
 
@@ -25441,7 +25441,7 @@ - (CHIP_ERROR)_encodeToTLVReader:(chip::System::PacketBufferTLVReader &)reader
     chip::app::Clusters::ThreadNetworkDirectory::Commands::RemoveNetwork::Type encodableStruct;
     ListFreer listFreer;
     {
-        encodableStruct.extendedPanID = self.extendedPanID.unsignedLongLongValue;
+        encodableStruct.extendedPanID = AsByteSpan(self.extendedPanID);
     }
 
     auto buffer = chip::System::PacketBufferHandle::New(chip::System::PacketBuffer::kMaxSizeWithoutReserve, 0);
@@ -25487,7 +25487,7 @@ - (instancetype)init
 {
     if (self = [super init]) {
 
-        _extendedPanID = @(0);
+        _extendedPanID = [NSData data];
         _timedInvokeTimeoutMs = nil;
         _serverSideProcessingTimeout = nil;
     }
@@ -25507,7 +25507,7 @@ - (id)copyWithZone:(NSZone * _Nullable)zone;
 
 - (NSString *)description
 {
-    NSString * descriptionString = [NSString stringWithFormat:@"<%@: extendedPanID:%@; >", NSStringFromClass([self class]), _extendedPanID];
+    NSString * descriptionString = [NSString stringWithFormat:@"<%@: extendedPanID:%@; >", NSStringFromClass([self class]), [_extendedPanID base64EncodedStringWithOptions:0]];
     return descriptionString;
 }
 
@@ -25520,7 +25520,7 @@ - (CHIP_ERROR)_encodeToTLVReader:(chip::System::PacketBufferTLVReader &)reader
     chip::app::Clusters::ThreadNetworkDirectory::Commands::GetOperationalDataset::Type encodableStruct;
     ListFreer listFreer;
     {
-        encodableStruct.extendedPanID = self.extendedPanID.unsignedLongLongValue;
+        encodableStruct.extendedPanID = AsByteSpan(self.extendedPanID);
     }
 
     auto buffer = chip::System::PacketBufferHandle::New(chip::System::PacketBuffer::kMaxSizeWithoutReserve, 0);
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTREventTLVValueDecoder.mm b/src/darwin/Framework/CHIP/zap-generated/MTREventTLVValueDecoder.mm
index 445807b9902bd8..e2fc7e70cba67b 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTREventTLVValueDecoder.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTREventTLVValueDecoder.mm
@@ -4117,23 +4117,6 @@ static id _Nullable DecodeEventPayloadForThreadNetworkDirectoryCluster(EventId a
 {
     using namespace Clusters::ThreadNetworkDirectory;
     switch (aEventId) {
-    case Events::NetworkChanged::Id: {
-        Events::NetworkChanged::DecodableType cppValue;
-        *aError = DataModel::Decode(aReader, cppValue);
-        if (*aError != CHIP_NO_ERROR) {
-            return nil;
-        }
-
-        __auto_type * value = [MTRThreadNetworkDirectoryClusterNetworkChangedEvent new];
-
-        do {
-            NSNumber * _Nonnull memberValue;
-            memberValue = [NSNumber numberWithUnsignedLongLong:cppValue.extendedPanID];
-            value.extendedPanID = memberValue;
-        } while (0);
-
-        return value;
-    }
     default: {
         break;
     }
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h
index dd93b4322ad447..92f82f3496fca9 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.h
@@ -1711,14 +1711,10 @@ MTR_PROVISIONALLY_AVAILABLE
 
 MTR_PROVISIONALLY_AVAILABLE
 @interface MTRThreadNetworkDirectoryClusterThreadNetworkStruct : NSObject <NSCopying>
-@property (nonatomic, copy) NSNumber * _Nonnull extendedPanID MTR_PROVISIONALLY_AVAILABLE;
+@property (nonatomic, copy) NSData * _Nonnull extendedPanID MTR_PROVISIONALLY_AVAILABLE;
 @property (nonatomic, copy) NSString * _Nonnull networkName MTR_PROVISIONALLY_AVAILABLE;
 @property (nonatomic, copy) NSNumber * _Nonnull channel MTR_PROVISIONALLY_AVAILABLE;
-@end
-
-MTR_PROVISIONALLY_AVAILABLE
-@interface MTRThreadNetworkDirectoryClusterNetworkChangedEvent : NSObject <NSCopying>
-@property (nonatomic, copy) NSNumber * _Nonnull extendedPanID MTR_PROVISIONALLY_AVAILABLE;
+@property (nonatomic, copy) NSNumber * _Nonnull activeTimestamp MTR_PROVISIONALLY_AVAILABLE;
 @end
 
 MTR_PROVISIONALLY_AVAILABLE
diff --git a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm
index 6fe345c0621baf..f0869bd61918fb 100644
--- a/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm
+++ b/src/darwin/Framework/CHIP/zap-generated/MTRStructsObjc.mm
@@ -7213,11 +7213,13 @@ - (instancetype)init
 {
     if (self = [super init]) {
 
-        _extendedPanID = @(0);
+        _extendedPanID = [NSData data];
 
         _networkName = @"";
 
         _channel = @(0);
+
+        _activeTimestamp = @(0);
     }
     return self;
 }
@@ -7229,40 +7231,14 @@ - (id)copyWithZone:(NSZone * _Nullable)zone
     other.extendedPanID = self.extendedPanID;
     other.networkName = self.networkName;
     other.channel = self.channel;
+    other.activeTimestamp = self.activeTimestamp;
 
     return other;
 }
 
 - (NSString *)description
 {
-    NSString * descriptionString = [NSString stringWithFormat:@"<%@: extendedPanID:%@; networkName:%@; channel:%@; >", NSStringFromClass([self class]), _extendedPanID, _networkName, _channel];
-    return descriptionString;
-}
-
-@end
-
-@implementation MTRThreadNetworkDirectoryClusterNetworkChangedEvent
-- (instancetype)init
-{
-    if (self = [super init]) {
-
-        _extendedPanID = @(0);
-    }
-    return self;
-}
-
-- (id)copyWithZone:(NSZone * _Nullable)zone
-{
-    auto other = [[MTRThreadNetworkDirectoryClusterNetworkChangedEvent alloc] init];
-
-    other.extendedPanID = self.extendedPanID;
-
-    return other;
-}
-
-- (NSString *)description
-{
-    NSString * descriptionString = [NSString stringWithFormat:@"<%@: extendedPanID:%@; >", NSStringFromClass([self class]), _extendedPanID];
+    NSString * descriptionString = [NSString stringWithFormat:@"<%@: extendedPanID:%@; networkName:%@; channel:%@; activeTimestamp:%@; >", NSStringFromClass([self class]), [_extendedPanID base64EncodedStringWithOptions:0], _networkName, _channel, _activeTimestamp];
     return descriptionString;
 }
 
diff --git a/src/lib/core/CHIPConfig.h b/src/lib/core/CHIPConfig.h
index 2de6a662ee8c4d..32e49ab2e3d45f 100644
--- a/src/lib/core/CHIPConfig.h
+++ b/src/lib/core/CHIPConfig.h
@@ -1702,6 +1702,24 @@ extern const char CHIP_NON_PRODUCTION_MARKER[];
 #define CHIP_CONFIG_MAX_ICD_CLIENTS_INFO_STORAGE_CONCURRENT_ITERATORS 1
 #endif
 
+/**
+ * @def CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CAPACITY
+ *
+ * Defines the number of networks the default ThreadNetworkDirectoryStorage implementation will store.
+ */
+#ifndef CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CAPACITY
+#define CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CAPACITY (CHIP_CONFIG_MAX_FABRICS * 2)
+#endif
+
+/**
+ * @def CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CONCURRENT_ITERATORS
+ *
+ * Defines the number of ThreadNetworkDirectoryStorage iterators that can be allocated at any one time.
+ */
+#ifndef CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CONCURRENT_ITERATORS
+#define CHIP_CONFIG_MAX_THREAD_NETWORK_DIRECTORY_STORAGE_CONCURRENT_ITERATORS 1
+#endif
+
 /**
  * @def CHIP_CONFIG_COMMAND_SENDER_BUILTIN_SUPPORT_FOR_BATCHED_COMMANDS
  *
diff --git a/src/lib/support/CodeUtils.h b/src/lib/support/CodeUtils.h
index 500804bc94912d..800123aa456851 100644
--- a/src/lib/support/CodeUtils.h
+++ b/src/lib/support/CodeUtils.h
@@ -392,6 +392,19 @@ constexpr inline const _T & max(const _T & a, const _T & b)
  */
 #define SuccessOrExit(error) nlEXPECT(::chip::ChipError::IsSuccess((error)), exit)
 
+/**
+ *  @def SuccessOrExitAction(error, anAction)
+ *
+ *  @brief
+ *    This checks for the specified error, which is expected to
+ *    commonly be successful (CHIP_NO_ERROR), and both executes
+ *    @a anAction and branches to the local label 'exit' if the
+ *    status is unsuccessful.
+ *
+ *  @param[in]  error  A ChipError object to be evaluated against success (CHIP_NO_ERROR).
+ */
+#define SuccessOrExitAction(error, action) nlEXPECT_ACTION(::chip::ChipError::IsSuccess((error)), exit, action)
+
 /**
  *  @def VerifyOrExit(aCondition, anAction)
  *
diff --git a/src/lib/support/CommonIterator.h b/src/lib/support/CommonIterator.h
index fc799c33b233eb..0eec391a40e777 100644
--- a/src/lib/support/CommonIterator.h
+++ b/src/lib/support/CommonIterator.h
@@ -46,7 +46,7 @@ class CommonIterator
     virtual bool Next(T & item) = 0;
     /**
      * Release the memory allocated by this iterator.
-     * Must be called before the iterator goes out of scope in the iterator was dynamically allocated.
+     * Must be called before the iterator goes out of scope if the iterator was dynamically allocated.
      */
     virtual void Release() = 0;
 
diff --git a/src/lib/support/DefaultStorageKeyAllocator.h b/src/lib/support/DefaultStorageKeyAllocator.h
index 04825c9ca4d719..9ed8a2f56cfd77 100644
--- a/src/lib/support/DefaultStorageKeyAllocator.h
+++ b/src/lib/support/DefaultStorageKeyAllocator.h
@@ -193,6 +193,17 @@ class DefaultStorageKeyAllocator
         return StorageKeyName::Formatted("f/%x/icd/%x", fabric, index);
     }
 
+    // Thread Network Directory
+
+    static StorageKeyName ThreadNetworkDirectoryIndex() { return StorageKeyName::FromConst("g/tnd/i"); }
+    static StorageKeyName ThreadNetworkDirectoryDataset(uint64_t extendedPanId)
+    {
+        return StorageKeyName::Formatted("g/tnd/n/%08" PRIx32 "%08" PRIx32, // some platforms can't format uint64
+                                         static_cast<uint32_t>(extendedPanId >> 32), static_cast<uint32_t>(extendedPanId));
+    }
+
+    // OTA
+
     static StorageKeyName OTADefaultProviders() { return StorageKeyName::FromConst("g/o/dp"); }
     static StorageKeyName OTACurrentProvider() { return StorageKeyName::FromConst("g/o/cp"); }
     static StorageKeyName OTAUpdateToken() { return StorageKeyName::FromConst("g/o/ut"); }
diff --git a/src/lib/support/Span.h b/src/lib/support/Span.h
index a6f63c7c59f33e..2e6627d937e560 100644
--- a/src/lib/support/Span.h
+++ b/src/lib/support/Span.h
@@ -364,6 +364,8 @@ using ByteSpan        = Span<const uint8_t>;
 using MutableByteSpan = Span<uint8_t>;
 template <size_t N>
 using FixedByteSpan = FixedSpan<const uint8_t, N>;
+template <size_t N>
+using MutableFixedByteSpan = FixedSpan<uint8_t, N>;
 
 using CharSpan        = Span<const char>;
 using MutableCharSpan = Span<char>;
diff --git a/src/lib/support/TestPersistentStorageDelegate.h b/src/lib/support/TestPersistentStorageDelegate.h
index 8d3bfd63e11bb2..7dc1c06008c77c 100644
--- a/src/lib/support/TestPersistentStorageDelegate.h
+++ b/src/lib/support/TestPersistentStorageDelegate.h
@@ -134,6 +134,11 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
      */
     virtual void AddPoisonKey(const std::string & key) { mPoisonKeys.insert(key); }
 
+    /**
+     * Allows subsequent writes to be rejected for unit testing purposes.
+     */
+    virtual void SetRejectWrites(bool rejectWrites) { mRejectWrites = rejectWrites; }
+
     /**
      * @brief Clear all "poison keys"
      *
@@ -233,8 +238,8 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
 
     virtual CHIP_ERROR SyncSetKeyValueInternal(const char * key, const void * value, uint16_t size)
     {
-        // Make sure poison keys are not accessed
-        if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
+        // Make sure writes are allowed and poison keys are not accessed
+        if (mRejectWrites || mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
         {
             return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
         }
@@ -259,8 +264,8 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
 
     virtual CHIP_ERROR SyncDeleteKeyValueInternal(const char * key)
     {
-        // Make sure poison keys are not accessed
-        if (mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
+        // Make sure writes are allowed and poison keys are not accessed
+        if (mRejectWrites || mPoisonKeys.find(std::string(key)) != mPoisonKeys.end())
         {
             return CHIP_ERROR_PERSISTED_STORAGE_FAILED;
         }
@@ -273,6 +278,7 @@ class TestPersistentStorageDelegate : public PersistentStorageDelegate
 
     std::map<std::string, std::vector<uint8_t>> mStorage;
     std::set<std::string> mPoisonKeys;
+    bool mRejectWrites         = false;
     LoggingLevel mLoggingLevel = LoggingLevel::kDisabled;
 };
 
diff --git a/src/lib/support/ThreadOperationalDataset.cpp b/src/lib/support/ThreadOperationalDataset.cpp
index 9f2ae6a1b1a8ec..1c11712166cfa4 100644
--- a/src/lib/support/ThreadOperationalDataset.cpp
+++ b/src/lib/support/ThreadOperationalDataset.cpp
@@ -39,9 +39,11 @@ namespace Thread {
 class ThreadTLV final
 {
     static constexpr uint8_t kLengthEscape = 0xff; ///< This length value indicates the actual length is of two-bytes length, which
-                                                   ///< not allowed in Thread Operational Dataset TLVs.
+                                                   ///< is not allowed in Thread Operational Dataset TLVs.
 
 public:
+    static constexpr uint8_t kMaxLength = kLengthEscape - 1;
+
     enum : uint8_t
     {
         kChannel         = 0,
@@ -51,7 +53,9 @@ class ThreadTLV final
         kPSKc            = 4,
         kMasterKey       = 5,
         kMeshLocalPrefix = 7,
+        kSecurityPolicy  = 12,
         kActiveTimestamp = 14,
+        kChannelMask     = 53,
     };
 
     uint8_t GetSize() const { return static_cast<uint8_t>(sizeof(*this) + GetLength()); }
@@ -127,6 +131,8 @@ class ThreadTLV final
         memcpy(GetValue(), aValue, aLength);
     }
 
+    void SetValue(const ByteSpan & aValue) { SetValue(aValue.data(), static_cast<uint8_t>(aValue.size())); }
+
     const ThreadTLV * GetNext() const
     {
         static_assert(alignof(ThreadTLV) == 1, "Wrong alignment for ThreadTLV header");
@@ -239,12 +245,21 @@ CHIP_ERROR OperationalDataset::GetExtendedPanId(uint8_t (&aExtendedPanId)[kSizeE
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR OperationalDataset::GetExtendedPanId(uint64_t & extendedPanId) const
+{
+    ByteSpan extPanIdSpan;
+    ReturnErrorOnFailure(GetExtendedPanIdAsByteSpan(extPanIdSpan));
+    VerifyOrDie(extPanIdSpan.size() == sizeof(extendedPanId));
+    extendedPanId = Encoding::BigEndian::Get64(extPanIdSpan.data());
+    return CHIP_NO_ERROR;
+}
+
 CHIP_ERROR OperationalDataset::GetExtendedPanIdAsByteSpan(ByteSpan & span) const
 {
     const ThreadTLV * tlv = Locate(ThreadTLV::kExtendedPanId);
     VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
     VerifyOrReturnError(tlv->GetLength() == kSizeExtendedPanId, CHIP_ERROR_INVALID_TLV_ELEMENT);
-    span = ByteSpan(static_cast<const uint8_t *>(tlv->GetValue()), tlv->GetLength());
+    span = tlv->GetValueAsSpan();
     return CHIP_NO_ERROR;
 }
 
@@ -374,6 +389,43 @@ CHIP_ERROR OperationalDataset::SetPSKc(const uint8_t (&aPSKc)[kSizePSKc])
     return CHIP_NO_ERROR;
 }
 
+CHIP_ERROR OperationalDataset::GetChannelMask(ByteSpan & aChannelMask) const
+{
+    const ThreadTLV * tlv = Locate(ThreadTLV::kChannelMask);
+    VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
+    VerifyOrReturnError(tlv->GetLength() > 0, CHIP_ERROR_INVALID_TLV_ELEMENT);
+    aChannelMask = tlv->GetValueAsSpan();
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OperationalDataset::SetChannelMask(ByteSpan aChannelMask)
+{
+    VerifyOrReturnError(0 < aChannelMask.size() && aChannelMask.size() < ThreadTLV::kMaxLength, CHIP_ERROR_INVALID_ARGUMENT);
+    ThreadTLV * tlv = MakeRoom(ThreadTLV::kChannelMask, sizeof(*tlv) + aChannelMask.size());
+    VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
+    tlv->SetValue(aChannelMask);
+    mLength = static_cast<uint8_t>(mLength + tlv->GetSize());
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OperationalDataset::GetSecurityPolicy(uint32_t & aSecurityPolicy) const
+{
+    const ThreadTLV * tlv = Locate(ThreadTLV::kSecurityPolicy);
+    VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_TLV_TAG_NOT_FOUND);
+    VerifyOrReturnError(tlv->GetLength() == sizeof(aSecurityPolicy), CHIP_ERROR_INVALID_TLV_ELEMENT);
+    tlv->Get32(aSecurityPolicy);
+    return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR OperationalDataset::SetSecurityPolicy(uint32_t aSecurityPolicy)
+{
+    ThreadTLV * tlv = MakeRoom(ThreadTLV::kSecurityPolicy, sizeof(*tlv) + sizeof(aSecurityPolicy));
+    VerifyOrReturnError(tlv != nullptr, CHIP_ERROR_NO_MEMORY);
+    tlv->Set32(aSecurityPolicy);
+    mLength = static_cast<uint8_t>(mLength + tlv->GetSize());
+    return CHIP_NO_ERROR;
+}
+
 void OperationalDataset::UnsetMasterKey()
 {
     Remove(ThreadTLV::kMasterKey);
diff --git a/src/lib/support/ThreadOperationalDataset.h b/src/lib/support/ThreadOperationalDataset.h
index 7bb528d691c193..5cd57df6a79db6 100644
--- a/src/lib/support/ThreadOperationalDataset.h
+++ b/src/lib/support/ThreadOperationalDataset.h
@@ -110,10 +110,20 @@ class OperationalDataset
      */
     CHIP_ERROR GetExtendedPanId(uint8_t (&aExtendedPanId)[kSizeExtendedPanId]) const;
 
+    /**
+     * This method retrieves the Thread extended PAN ID from the dataset, interpreted as a big endian number.
+     * @retval CHIP_NO_ERROR                    Successfully retrieved the extended PAN ID.
+     * @retval CHIP_ERROR_TLV_TAG_NOT_FOUND     Thread extended PAN ID is not present in the dataset.
+     */
+    CHIP_ERROR GetExtendedPanId(uint64_t & extendedPanId) const;
+
     /**
      * This method returns a const ByteSpan to the extended PAN ID in the dataset.
      * This can be used to pass the extended PAN ID to a cluster command without the use of external memory.
      *
+     * Note: The returned span points into storage managed by this class,
+     * and must not be dereferenced beyond the lifetime of this object.
+     *
      * @param[out]  span  A reference to receive the location of the extended PAN ID.
      *
      * @retval CHIP_NO_ERROR                    Successfully retrieved the extended PAN ID.
@@ -247,6 +257,43 @@ class OperationalDataset
      */
     void UnsetPSKc(void);
 
+    /**
+     * Returns ByteSpan pointing to the channel mask within the dataset.
+     *
+     * Note: The returned span points into storage managed by this class,
+     * and must not be dereferenced beyond the lifetime of this object.
+     *
+     * @retval CHIP_NO_ERROR on success.
+     * @retval CHIP_ERROR_TLV_TAG_NOT_FOUND if the channel mask is not present in the dataset.
+     * @retval CHIP_ERROR_INVALID_TLV_ELEMENT if the TLV element is invalid.
+     */
+    CHIP_ERROR GetChannelMask(ByteSpan & aChannelMask) const;
+
+    /**
+     * This method sets the channel mask within the dataset.
+     *
+     * @retval CHIP_NO_ERROR on success.
+     * @retval CHIP_ERROR_NO_MEMORY if there is insufficient space within the dataset.
+     */
+    CHIP_ERROR SetChannelMask(ByteSpan aChannelMask);
+
+    /**
+     * Retrieves the security policy from the dataset.
+     *
+     * @retval CHIP_NO_ERROR on success.
+     * @retval CHIP_ERROR_TLV_TAG_NOT_FOUND if no security policy is present in the dataset.
+     * @retval CHIP_ERROR_INVALID_TLV_ELEMENT if the TLV element is invalid.
+     */
+    CHIP_ERROR GetSecurityPolicy(uint32_t & aSecurityPolicy) const;
+
+    /**
+     * This method sets the security policy within the dataset.
+     *
+     * @retval CHIP_NO_ERROR on success.
+     * @retval CHIP_ERROR_NO_MEMORY if there is insufficient space within the dataset.
+     */
+    CHIP_ERROR SetSecurityPolicy(uint32_t aSecurityPolicy);
+
     /**
      * This method clears all data stored in the dataset.
      */
@@ -254,7 +301,6 @@ class OperationalDataset
 
     /**
      * This method checks if the dataset is ready for creating Thread network.
-     *
      */
     bool IsCommissioned(void) const;
 
@@ -267,7 +313,6 @@ class OperationalDataset
      * This method checks whether @p aData is formatted as ThreadTLVs.
      *
      * @note This method doesn't verify ThreadTLV values are valid.
-     *
      */
     static bool IsValid(ByteSpan aData);
 
diff --git a/src/lib/support/tests/TestThreadOperationalDataset.cpp b/src/lib/support/tests/TestThreadOperationalDataset.cpp
index 3c60230a573a2a..2ea4d3c99eba30 100644
--- a/src/lib/support/tests/TestThreadOperationalDataset.cpp
+++ b/src/lib/support/tests/TestThreadOperationalDataset.cpp
@@ -276,6 +276,15 @@ TEST_F(TestThreadOperationalDataset, TestExampleDataset)
         0x3c, 0xa6, 0x7c, 0x96, 0x9e, 0xfb, 0x0d, 0x0c, 0x74, 0xa4, 0xd8, 0xee, 0x92, 0x3b, 0x57, 0x6c
     };
     EXPECT_TRUE(ByteSpan(pksc).data_equal(ByteSpan(expectedPksc)));
+
+    ByteSpan channelMask;
+    EXPECT_EQ(dataset.GetChannelMask(channelMask), CHIP_NO_ERROR);
+    const uint8_t expectedChannelMask[] = { 0x07, 0xff, 0xf8, 0x00 };
+    EXPECT_TRUE(channelMask.data_equal(ByteSpan(expectedChannelMask)));
+
+    uint32_t securityPolicy;
+    EXPECT_EQ(dataset.GetSecurityPolicy(securityPolicy), CHIP_NO_ERROR);
+    EXPECT_EQ(securityPolicy, 0x02a0f7f8u);
 }
 
 TEST_F(TestThreadOperationalDataset, TestInvalidExampleDataset)
@@ -319,6 +328,12 @@ TEST_F(TestThreadOperationalDataset, TestInvalidExampleDataset)
 
     uint8_t pksc[Thread::kSizePSKc];
     EXPECT_EQ(dataset.GetPSKc(pksc), CHIP_ERROR_INVALID_TLV_ELEMENT);
+
+    ByteSpan channelMask;
+    EXPECT_EQ(dataset.GetChannelMask(channelMask), CHIP_ERROR_INVALID_TLV_ELEMENT);
+
+    uint32_t securityPolicy;
+    EXPECT_EQ(dataset.GetSecurityPolicy(securityPolicy), CHIP_ERROR_INVALID_TLV_ELEMENT);
 }
 
 } // namespace
diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp
index a7fef6deeee4df..0e519739bea690 100644
--- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp
+++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.cpp
@@ -35989,142 +35989,6 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint16_t valu
 namespace ThreadNetworkDirectory {
 namespace Attributes {
 
-namespace PreferredExtendedPanID {
-
-Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, DataModel::Nullable<uint64_t> & value)
-{
-    using Traits = NumericAttributeTraits<uint64_t>;
-    Traits::StorageType temp;
-    uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp);
-    Protocols::InteractionModel::Status status =
-        emberAfReadAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, readable, sizeof(temp));
-    VerifyOrReturnError(Protocols::InteractionModel::Status::Success == status, status);
-    if (Traits::IsNullValue(temp))
-    {
-        value.SetNull();
-    }
-    else
-    {
-        value.SetNonNull() = Traits::StorageToWorking(temp);
-    }
-    return status;
-}
-
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value, MarkAttributeDirty markDirty)
-{
-    using Traits = NumericAttributeTraits<uint64_t>;
-    if (!Traits::CanRepresentValue(/* isNullable = */ true, value))
-    {
-        return Protocols::InteractionModel::Status::ConstraintError;
-    }
-    Traits::StorageType storageValue;
-    Traits::WorkingToStorage(value, storageValue);
-    uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue);
-    return emberAfWriteAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, writable, ZCL_INT64U_ATTRIBUTE_TYPE,
-                                 markDirty);
-}
-
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value)
-{
-    using Traits = NumericAttributeTraits<uint64_t>;
-    if (!Traits::CanRepresentValue(/* isNullable = */ true, value))
-    {
-        return Protocols::InteractionModel::Status::ConstraintError;
-    }
-    Traits::StorageType storageValue;
-    Traits::WorkingToStorage(value, storageValue);
-    uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue);
-    return emberAfWriteAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, writable, ZCL_INT64U_ATTRIBUTE_TYPE);
-}
-
-Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint, MarkAttributeDirty markDirty)
-{
-    using Traits = NumericAttributeTraits<uint64_t>;
-    Traits::StorageType value;
-    Traits::SetNull(value);
-    uint8_t * writable = Traits::ToAttributeStoreRepresentation(value);
-    return emberAfWriteAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, writable, ZCL_INT64U_ATTRIBUTE_TYPE,
-                                 markDirty);
-}
-
-Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint)
-{
-    using Traits = NumericAttributeTraits<uint64_t>;
-    Traits::StorageType value;
-    Traits::SetNull(value);
-    uint8_t * writable = Traits::ToAttributeStoreRepresentation(value);
-    return emberAfWriteAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, writable, ZCL_INT64U_ATTRIBUTE_TYPE);
-}
-
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable<uint64_t> & value,
-                                        MarkAttributeDirty markDirty)
-{
-    if (value.IsNull())
-    {
-        return SetNull(endpoint, markDirty);
-    }
-
-    return Set(endpoint, value.Value(), markDirty);
-}
-
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable<uint64_t> & value)
-{
-    if (value.IsNull())
-    {
-        return SetNull(endpoint);
-    }
-
-    return Set(endpoint, value.Value());
-}
-
-} // namespace PreferredExtendedPanID
-
-namespace ThreadNetworkTableSize {
-
-Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, uint8_t * value)
-{
-    using Traits = NumericAttributeTraits<uint8_t>;
-    Traits::StorageType temp;
-    uint8_t * readable = Traits::ToAttributeStoreRepresentation(temp);
-    Protocols::InteractionModel::Status status =
-        emberAfReadAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, readable, sizeof(temp));
-    VerifyOrReturnError(Protocols::InteractionModel::Status::Success == status, status);
-    if (!Traits::CanRepresentValue(/* isNullable = */ false, temp))
-    {
-        return Protocols::InteractionModel::Status::ConstraintError;
-    }
-    *value = Traits::StorageToWorking(temp);
-    return status;
-}
-
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint8_t value, MarkAttributeDirty markDirty)
-{
-    using Traits = NumericAttributeTraits<uint8_t>;
-    if (!Traits::CanRepresentValue(/* isNullable = */ false, value))
-    {
-        return Protocols::InteractionModel::Status::ConstraintError;
-    }
-    Traits::StorageType storageValue;
-    Traits::WorkingToStorage(value, storageValue);
-    uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue);
-    return emberAfWriteAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE, markDirty);
-}
-
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint8_t value)
-{
-    using Traits = NumericAttributeTraits<uint8_t>;
-    if (!Traits::CanRepresentValue(/* isNullable = */ false, value))
-    {
-        return Protocols::InteractionModel::Status::ConstraintError;
-    }
-    Traits::StorageType storageValue;
-    Traits::WorkingToStorage(value, storageValue);
-    uint8_t * writable = Traits::ToAttributeStoreRepresentation(storageValue);
-    return emberAfWriteAttribute(endpoint, Clusters::ThreadNetworkDirectory::Id, Id, writable, ZCL_INT8U_ATTRIBUTE_TYPE);
-}
-
-} // namespace ThreadNetworkTableSize
-
 namespace FeatureMap {
 
 Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, uint32_t * value)
diff --git a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h
index 63493b3f90551d..3fed44373526df 100644
--- a/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h
+++ b/zzz_generated/app-common/app-common/zap-generated/attributes/Accessors.h
@@ -5537,23 +5537,6 @@ Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint16_t valu
 namespace ThreadNetworkDirectory {
 namespace Attributes {
 
-namespace PreferredExtendedPanID {
-Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, DataModel::Nullable<uint64_t> & value); // int64u
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value);
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint64_t value, MarkAttributeDirty markDirty);
-Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint);
-Protocols::InteractionModel::Status SetNull(chip::EndpointId endpoint, MarkAttributeDirty markDirty);
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable<uint64_t> & value);
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, const chip::app::DataModel::Nullable<uint64_t> & value,
-                                        MarkAttributeDirty markDirty);
-} // namespace PreferredExtendedPanID
-
-namespace ThreadNetworkTableSize {
-Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, uint8_t * value); // int8u
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint8_t value);
-Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint8_t value, MarkAttributeDirty markDirty);
-} // namespace ThreadNetworkTableSize
-
 namespace FeatureMap {
 Protocols::InteractionModel::Status Get(chip::EndpointId endpoint, uint32_t * value); // bitmap32
 Protocols::InteractionModel::Status Set(chip::EndpointId endpoint, uint32_t value);
diff --git a/zzz_generated/app-common/app-common/zap-generated/callback.h b/zzz_generated/app-common/app-common/zap-generated/callback.h
index 6e78fd6c09a8bf..ab6514fdf02103 100644
--- a/zzz_generated/app-common/app-common/zap-generated/callback.h
+++ b/zzz_generated/app-common/app-common/zap-generated/callback.h
@@ -6473,24 +6473,6 @@ bool emberAfThreadBorderRouterManagementClusterSetActiveDatasetRequestCallback(
 bool emberAfThreadBorderRouterManagementClusterSetPendingDatasetRequestCallback(
     chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
     const chip::app::Clusters::ThreadBorderRouterManagement::Commands::SetPendingDatasetRequest::DecodableType & commandData);
-/**
- * @brief Thread Network Directory Cluster AddNetwork Command callback (from client)
- */
-bool emberAfThreadNetworkDirectoryClusterAddNetworkCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::ThreadNetworkDirectory::Commands::AddNetwork::DecodableType & commandData);
-/**
- * @brief Thread Network Directory Cluster RemoveNetwork Command callback (from client)
- */
-bool emberAfThreadNetworkDirectoryClusterRemoveNetworkCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::ThreadNetworkDirectory::Commands::RemoveNetwork::DecodableType & commandData);
-/**
- * @brief Thread Network Directory Cluster GetOperationalDataset Command callback (from client)
- */
-bool emberAfThreadNetworkDirectoryClusterGetOperationalDatasetCallback(
-    chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath,
-    const chip::app::Clusters::ThreadNetworkDirectory::Commands::GetOperationalDataset::DecodableType & commandData);
 /**
  * @brief Channel Cluster ChangeChannel Command callback (from client)
  */
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
index fcae2110b2ce53..3244826b526af4 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.cpp
@@ -23948,6 +23948,7 @@ CHIP_ERROR Type::Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const
     encoder.Encode(to_underlying(Fields::kExtendedPanID), extendedPanID);
     encoder.Encode(to_underlying(Fields::kNetworkName), networkName);
     encoder.Encode(to_underlying(Fields::kChannel), channel);
+    encoder.Encode(to_underlying(Fields::kActiveTimestamp), activeTimestamp);
     return encoder.Finalize();
 }
 
@@ -23977,6 +23978,10 @@ CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader)
         {
             err = DataModel::Decode(reader, channel);
         }
+        else if (__context_tag == to_underlying(Fields::kActiveTimestamp))
+        {
+            err = DataModel::Decode(reader, activeTimestamp);
+        }
         else
         {
         }
@@ -24156,43 +24161,7 @@ CHIP_ERROR TypeInfo::DecodableType::Decode(TLV::TLVReader & reader, const Concre
 }
 } // namespace Attributes
 
-namespace Events {
-namespace NetworkChanged {
-CHIP_ERROR Type::Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const
-{
-    TLV::TLVType outer;
-    ReturnErrorOnFailure(aWriter.StartContainer(aTag, TLV::kTLVType_Structure, outer));
-    ReturnErrorOnFailure(DataModel::Encode(aWriter, TLV::ContextTag(Fields::kExtendedPanID), extendedPanID));
-    return aWriter.EndContainer(outer);
-}
-
-CHIP_ERROR DecodableType::Decode(TLV::TLVReader & reader)
-{
-    detail::StructDecodeIterator __iterator(reader);
-    while (true)
-    {
-        auto __element = __iterator.Next();
-        if (std::holds_alternative<CHIP_ERROR>(__element))
-        {
-            return std::get<CHIP_ERROR>(__element);
-        }
-
-        CHIP_ERROR err              = CHIP_NO_ERROR;
-        const uint8_t __context_tag = std::get<uint8_t>(__element);
-
-        if (__context_tag == to_underlying(Fields::kExtendedPanID))
-        {
-            err = DataModel::Decode(reader, extendedPanID);
-        }
-        else
-        {
-        }
-
-        ReturnErrorOnFailure(err);
-    }
-}
-} // namespace NetworkChanged.
-} // namespace Events
+namespace Events {} // namespace Events
 
 } // namespace ThreadNetworkDirectory
 namespace WakeOnLan {
diff --git a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
index 9377e6e1739a75..2e3dddde6d0fc0 100644
--- a/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
+++ b/zzz_generated/app-common/app-common/zap-generated/cluster-objects.h
@@ -36291,17 +36291,19 @@ namespace Structs {
 namespace ThreadNetworkStruct {
 enum class Fields : uint8_t
 {
-    kExtendedPanID = 0,
-    kNetworkName   = 1,
-    kChannel       = 2,
+    kExtendedPanID   = 0,
+    kNetworkName     = 1,
+    kChannel         = 2,
+    kActiveTimestamp = 3,
 };
 
 struct Type
 {
 public:
-    uint64_t extendedPanID = static_cast<uint64_t>(0);
+    chip::ByteSpan extendedPanID;
     chip::CharSpan networkName;
-    uint16_t channel = static_cast<uint16_t>(0);
+    uint16_t channel         = static_cast<uint16_t>(0);
+    uint64_t activeTimestamp = static_cast<uint64_t>(0);
 
     CHIP_ERROR Decode(TLV::TLVReader & reader);
 
@@ -36386,7 +36388,7 @@ struct Type
     static constexpr CommandId GetCommandId() { return Commands::RemoveNetwork::Id; }
     static constexpr ClusterId GetClusterId() { return Clusters::ThreadNetworkDirectory::Id; }
 
-    uint64_t extendedPanID = static_cast<uint64_t>(0);
+    chip::ByteSpan extendedPanID;
 
     CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const;
 
@@ -36401,7 +36403,7 @@ struct DecodableType
     static constexpr CommandId GetCommandId() { return Commands::RemoveNetwork::Id; }
     static constexpr ClusterId GetClusterId() { return Clusters::ThreadNetworkDirectory::Id; }
 
-    uint64_t extendedPanID = static_cast<uint64_t>(0);
+    chip::ByteSpan extendedPanID;
     CHIP_ERROR Decode(TLV::TLVReader & reader);
 };
 }; // namespace RemoveNetwork
@@ -36418,7 +36420,7 @@ struct Type
     static constexpr CommandId GetCommandId() { return Commands::GetOperationalDataset::Id; }
     static constexpr ClusterId GetClusterId() { return Clusters::ThreadNetworkDirectory::Id; }
 
-    uint64_t extendedPanID = static_cast<uint64_t>(0);
+    chip::ByteSpan extendedPanID;
 
     CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const;
 
@@ -36433,7 +36435,7 @@ struct DecodableType
     static constexpr CommandId GetCommandId() { return Commands::GetOperationalDataset::Id; }
     static constexpr ClusterId GetClusterId() { return Clusters::ThreadNetworkDirectory::Id; }
 
-    uint64_t extendedPanID = static_cast<uint64_t>(0);
+    chip::ByteSpan extendedPanID;
     CHIP_ERROR Decode(TLV::TLVReader & reader);
 };
 }; // namespace GetOperationalDataset
@@ -36476,13 +36478,14 @@ namespace Attributes {
 namespace PreferredExtendedPanID {
 struct TypeInfo
 {
-    using Type             = chip::app::DataModel::Nullable<uint64_t>;
-    using DecodableType    = chip::app::DataModel::Nullable<uint64_t>;
-    using DecodableArgType = const chip::app::DataModel::Nullable<uint64_t> &;
+    using Type             = chip::app::DataModel::Nullable<chip::ByteSpan>;
+    using DecodableType    = chip::app::DataModel::Nullable<chip::ByteSpan>;
+    using DecodableArgType = const chip::app::DataModel::Nullable<chip::ByteSpan> &;
 
     static constexpr ClusterId GetClusterId() { return Clusters::ThreadNetworkDirectory::Id; }
     static constexpr AttributeId GetAttributeId() { return Attributes::PreferredExtendedPanID::Id; }
     static constexpr bool MustUseTimedWrite() { return false; }
+    static constexpr size_t MaxLength() { return 8; }
 };
 } // namespace PreferredExtendedPanID
 namespace ThreadNetworks {
@@ -36568,41 +36571,6 @@ struct TypeInfo
     };
 };
 } // namespace Attributes
-namespace Events {
-namespace NetworkChanged {
-static constexpr PriorityLevel kPriorityLevel = PriorityLevel::Info;
-
-enum class Fields : uint8_t
-{
-    kExtendedPanID = 0,
-};
-
-struct Type
-{
-public:
-    static constexpr PriorityLevel GetPriorityLevel() { return kPriorityLevel; }
-    static constexpr EventId GetEventId() { return Events::NetworkChanged::Id; }
-    static constexpr ClusterId GetClusterId() { return Clusters::ThreadNetworkDirectory::Id; }
-    static constexpr bool kIsFabricScoped = false;
-
-    uint64_t extendedPanID = static_cast<uint64_t>(0);
-
-    CHIP_ERROR Encode(TLV::TLVWriter & aWriter, TLV::Tag aTag) const;
-};
-
-struct DecodableType
-{
-public:
-    static constexpr PriorityLevel GetPriorityLevel() { return kPriorityLevel; }
-    static constexpr EventId GetEventId() { return Events::NetworkChanged::Id; }
-    static constexpr ClusterId GetClusterId() { return Clusters::ThreadNetworkDirectory::Id; }
-
-    uint64_t extendedPanID = static_cast<uint64_t>(0);
-
-    CHIP_ERROR Decode(TLV::TLVReader & reader);
-};
-} // namespace NetworkChanged
-} // namespace Events
 } // namespace ThreadNetworkDirectory
 namespace WakeOnLan {
 
diff --git a/zzz_generated/app-common/app-common/zap-generated/ids/Events.h b/zzz_generated/app-common/app-common/zap-generated/ids/Events.h
index cc619b581dc3e6..6e6191fe45c21c 100644
--- a/zzz_generated/app-common/app-common/zap-generated/ids/Events.h
+++ b/zzz_generated/app-common/app-common/zap-generated/ids/Events.h
@@ -611,16 +611,6 @@ static constexpr EventId Id = 0x00000010;
 } // namespace Events
 } // namespace PumpConfigurationAndControl
 
-namespace ThreadNetworkDirectory {
-namespace Events {
-
-namespace NetworkChanged {
-static constexpr EventId Id = 0x00000000;
-} // namespace NetworkChanged
-
-} // namespace Events
-} // namespace ThreadNetworkDirectory
-
 namespace TargetNavigator {
 namespace Events {
 
diff --git a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
index c5971984fed334..6bc2808777acad 100644
--- a/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
+++ b/zzz_generated/chip-tool/zap-generated/cluster/Commands.h
@@ -11547,7 +11547,6 @@ class ThreadBorderRouterManagementSetPendingDatasetRequest : public ClusterComma
 | * ClusterRevision                                                   | 0xFFFD |
 |------------------------------------------------------------------------------|
 | Events:                                                             |        |
-| * NetworkChanged                                                    | 0x0000 |
 \*----------------------------------------------------------------------------*/
 
 /*
@@ -11597,7 +11596,7 @@ class ThreadNetworkDirectoryRemoveNetwork : public ClusterCommand
     ThreadNetworkDirectoryRemoveNetwork(CredentialIssuerCommands * credsIssuerConfig) :
         ClusterCommand("remove-network", credsIssuerConfig)
     {
-        AddArgument("ExtendedPanID", 0, UINT64_MAX, &mRequest.extendedPanID);
+        AddArgument("ExtendedPanID", &mRequest.extendedPanID);
         ClusterCommand::AddArguments();
     }
 
@@ -11635,7 +11634,7 @@ class ThreadNetworkDirectoryGetOperationalDataset : public ClusterCommand
     ThreadNetworkDirectoryGetOperationalDataset(CredentialIssuerCommands * credsIssuerConfig) :
         ClusterCommand("get-operational-dataset", credsIssuerConfig)
     {
-        AddArgument("ExtendedPanID", 0, UINT64_MAX, &mRequest.extendedPanID);
+        AddArgument("ExtendedPanID", &mRequest.extendedPanID);
         ClusterCommand::AddArguments();
     }
 
@@ -25693,9 +25692,9 @@ void registerClusterThreadNetworkDirectory(Commands & commands, CredentialIssuer
         make_unique<ReadAttribute>(Id, "feature-map", Attributes::FeatureMap::Id, credsIssuerConfig),                           //
         make_unique<ReadAttribute>(Id, "cluster-revision", Attributes::ClusterRevision::Id, credsIssuerConfig),                 //
         make_unique<WriteAttribute<>>(Id, credsIssuerConfig),                                                                   //
-        make_unique<WriteAttribute<chip::app::DataModel::Nullable<uint64_t>>>(Id, "preferred-extended-pan-id", 0, UINT64_MAX,
-                                                                              Attributes::PreferredExtendedPanID::Id,
-                                                                              WriteCommandType::kWrite, credsIssuerConfig), //
+        make_unique<WriteAttribute<chip::app::DataModel::Nullable<chip::ByteSpan>>>(Id, "preferred-extended-pan-id",
+                                                                                    Attributes::PreferredExtendedPanID::Id,
+                                                                                    WriteCommandType::kWrite, credsIssuerConfig), //
         make_unique<WriteAttributeAsComplex<
             chip::app::DataModel::List<const chip::app::Clusters::ThreadNetworkDirectory::Structs::ThreadNetworkStruct::Type>>>(
             Id, "thread-networks", Attributes::ThreadNetworks::Id, WriteCommandType::kForceWrite, credsIssuerConfig), //
@@ -25729,10 +25728,8 @@ void registerClusterThreadNetworkDirectory(Commands & commands, CredentialIssuer
         //
         // Events
         //
-        make_unique<ReadEvent>(Id, credsIssuerConfig),                                                     //
-        make_unique<ReadEvent>(Id, "network-changed", Events::NetworkChanged::Id, credsIssuerConfig),      //
-        make_unique<SubscribeEvent>(Id, credsIssuerConfig),                                                //
-        make_unique<SubscribeEvent>(Id, "network-changed", Events::NetworkChanged::Id, credsIssuerConfig), //
+        make_unique<ReadEvent>(Id, credsIssuerConfig),      //
+        make_unique<SubscribeEvent>(Id, credsIssuerConfig), //
     };
 
     commands.RegisterCluster(clusterName, clusterCommands);
diff --git a/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp b/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp
index 035f26c348a135..624acd23feb594 100644
--- a/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp
+++ b/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp
@@ -4340,6 +4340,8 @@ CHIP_ERROR ComplexArgumentParser::Setup(const char * label,
         ComplexArgumentParser::EnsureMemberExist("ThreadNetworkStruct.networkName", "networkName", value.isMember("networkName")));
     ReturnErrorOnFailure(
         ComplexArgumentParser::EnsureMemberExist("ThreadNetworkStruct.channel", "channel", value.isMember("channel")));
+    ReturnErrorOnFailure(ComplexArgumentParser::EnsureMemberExist("ThreadNetworkStruct.activeTimestamp", "activeTimestamp",
+                                                                  value.isMember("activeTimestamp")));
 
     char labelWithMember[kMaxLabelLength];
     snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "extendedPanID");
@@ -4354,6 +4356,10 @@ CHIP_ERROR ComplexArgumentParser::Setup(const char * label,
     ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.channel, value["channel"]));
     valueCopy.removeMember("channel");
 
+    snprintf(labelWithMember, sizeof(labelWithMember), "%s.%s", label, "activeTimestamp");
+    ReturnErrorOnFailure(ComplexArgumentParser::Setup(labelWithMember, request.activeTimestamp, value["activeTimestamp"]));
+    valueCopy.removeMember("activeTimestamp");
+
     return ComplexArgumentParser::EnsureNoMembersRemaining(label, valueCopy);
 }
 
@@ -4362,6 +4368,7 @@ void ComplexArgumentParser::Finalize(chip::app::Clusters::ThreadNetworkDirectory
     ComplexArgumentParser::Finalize(request.extendedPanID);
     ComplexArgumentParser::Finalize(request.networkName);
     ComplexArgumentParser::Finalize(request.channel);
+    ComplexArgumentParser::Finalize(request.activeTimestamp);
 }
 
 CHIP_ERROR ComplexArgumentParser::Setup(const char * label,
diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp
index 9de841aaaa4af3..d0fba2b8f0e0f3 100644
--- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp
+++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp
@@ -3854,6 +3854,14 @@ DataModelLogger::LogValue(const char * label, size_t indent,
             return err;
         }
     }
+    {
+        CHIP_ERROR err = LogValue("ActiveTimestamp", indent + 1, value.activeTimestamp);
+        if (err != CHIP_NO_ERROR)
+        {
+            DataModelLogger::LogString(indent + 1, "Struct truncated due to invalid value for 'ActiveTimestamp'");
+            return err;
+        }
+    }
     DataModelLogger::LogString(indent, "}");
 
     return CHIP_NO_ERROR;
@@ -7316,22 +7324,6 @@ CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent,
 
     return CHIP_NO_ERROR;
 }
-CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent,
-                                     const ThreadNetworkDirectory::Events::NetworkChanged::DecodableType & value)
-{
-    DataModelLogger::LogString(label, indent, "{");
-    {
-        CHIP_ERROR err = DataModelLogger::LogValue("ExtendedPanID", indent + 1, value.extendedPanID);
-        if (err != CHIP_NO_ERROR)
-        {
-            DataModelLogger::LogString(indent + 1, "Event truncated due to invalid value for 'ExtendedPanID'");
-            return err;
-        }
-    }
-    DataModelLogger::LogString(indent, "}");
-
-    return CHIP_NO_ERROR;
-}
 CHIP_ERROR DataModelLogger::LogValue(const char * label, size_t indent,
                                      const TargetNavigator::Events::TargetUpdated::DecodableType & value)
 {
@@ -16872,7 +16864,7 @@ CHIP_ERROR DataModelLogger::LogAttribute(const chip::app::ConcreteDataAttributeP
         switch (path.mAttributeId)
         {
         case ThreadNetworkDirectory::Attributes::PreferredExtendedPanID::Id: {
-            chip::app::DataModel::Nullable<uint64_t> value;
+            chip::app::DataModel::Nullable<chip::ByteSpan> value;
             ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value));
             return DataModelLogger::LogValue("PreferredExtendedPanID", 1, value);
         }
@@ -20318,17 +20310,6 @@ CHIP_ERROR DataModelLogger::LogEvent(const chip::app::EventHeader & header, chip
         }
         break;
     }
-    case ThreadNetworkDirectory::Id: {
-        switch (header.mPath.mEventId)
-        {
-        case ThreadNetworkDirectory::Events::NetworkChanged::Id: {
-            chip::app::Clusters::ThreadNetworkDirectory::Events::NetworkChanged::DecodableType value;
-            ReturnErrorOnFailure(chip::app::DataModel::Decode(*data, value));
-            return DataModelLogger::LogValue("NetworkChanged", 1, value);
-        }
-        }
-        break;
-    }
     case TargetNavigator::Id: {
         switch (header.mPath.mEventId)
         {
diff --git a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h
index 8ca6e2ef3904d6..7ee50893968ccd 100644
--- a/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h
+++ b/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.h
@@ -627,8 +627,6 @@ static CHIP_ERROR LogValue(const char * label, size_t indent,
                            const chip::app::Clusters::PumpConfigurationAndControl::Events::AirDetection::DecodableType & value);
 static CHIP_ERROR LogValue(const char * label, size_t indent,
                            const chip::app::Clusters::PumpConfigurationAndControl::Events::TurbineOperation::DecodableType & value);
-static CHIP_ERROR LogValue(const char * label, size_t indent,
-                           const chip::app::Clusters::ThreadNetworkDirectory::Events::NetworkChanged::DecodableType & value);
 static CHIP_ERROR LogValue(const char * label, size_t indent,
                            const chip::app::Clusters::TargetNavigator::Events::TargetUpdated::DecodableType & value);
 static CHIP_ERROR LogValue(const char * label, size_t indent,
diff --git a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
index c9386dc42a6a7b..0319e41eeaec91 100644
--- a/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
+++ b/zzz_generated/darwin-framework-tool/zap-generated/cluster/Commands.h
@@ -148571,7 +148571,6 @@ class SubscribeAttributeThreadBorderRouterManagementClusterRevision : public Sub
 | * ClusterRevision                                                   | 0xFFFD |
 |------------------------------------------------------------------------------|
 | Events:                                                             |        |
-| * NetworkChanged                                                    | 0x0000 |
 \*----------------------------------------------------------------------------*/
 
 #if MTR_ENABLE_PROVISIONAL
@@ -148637,7 +148636,7 @@ class ThreadNetworkDirectoryRemoveNetwork : public ClusterCommand {
         : ClusterCommand("remove-network")
     {
 #if MTR_ENABLE_PROVISIONAL
-        AddArgument("ExtendedPanID", 0, UINT64_MAX, &mRequest.extendedPanID);
+        AddArgument("ExtendedPanID", &mRequest.extendedPanID);
 #endif // MTR_ENABLE_PROVISIONAL
         ClusterCommand::AddArguments();
     }
@@ -148654,7 +148653,7 @@ class ThreadNetworkDirectoryRemoveNetwork : public ClusterCommand {
         __auto_type * params = [[MTRThreadNetworkDirectoryClusterRemoveNetworkParams alloc] init];
         params.timedInvokeTimeoutMs = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil;
 #if MTR_ENABLE_PROVISIONAL
-        params.extendedPanID = [NSNumber numberWithUnsignedLongLong:mRequest.extendedPanID];
+        params.extendedPanID = [NSData dataWithBytes:mRequest.extendedPanID.data() length:mRequest.extendedPanID.size()];
 #endif // MTR_ENABLE_PROVISIONAL
         uint16_t repeatCount = mRepeatCount.ValueOr(1);
         uint16_t __block responsesNeeded = repeatCount;
@@ -148690,7 +148689,7 @@ class ThreadNetworkDirectoryGetOperationalDataset : public ClusterCommand {
         : ClusterCommand("get-operational-dataset")
     {
 #if MTR_ENABLE_PROVISIONAL
-        AddArgument("ExtendedPanID", 0, UINT64_MAX, &mRequest.extendedPanID);
+        AddArgument("ExtendedPanID", &mRequest.extendedPanID);
 #endif // MTR_ENABLE_PROVISIONAL
         ClusterCommand::AddArguments();
     }
@@ -148707,7 +148706,7 @@ class ThreadNetworkDirectoryGetOperationalDataset : public ClusterCommand {
         __auto_type * params = [[MTRThreadNetworkDirectoryClusterGetOperationalDatasetParams alloc] init];
         params.timedInvokeTimeoutMs = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil;
 #if MTR_ENABLE_PROVISIONAL
-        params.extendedPanID = [NSNumber numberWithUnsignedLongLong:mRequest.extendedPanID];
+        params.extendedPanID = [NSData dataWithBytes:mRequest.extendedPanID.data() length:mRequest.extendedPanID.size()];
 #endif // MTR_ENABLE_PROVISIONAL
         uint16_t repeatCount = mRepeatCount.ValueOr(1);
         uint16_t __block responsesNeeded = repeatCount;
@@ -148765,7 +148764,7 @@ class ReadThreadNetworkDirectoryPreferredExtendedPanID : public ReadAttribute {
 
         dispatch_queue_t callbackQueue = dispatch_queue_create("com.chip.command", DISPATCH_QUEUE_SERIAL);
         __auto_type * cluster = [[MTRBaseClusterThreadNetworkDirectory alloc] initWithDevice:device endpointID:@(endpointId) queue:callbackQueue];
-        [cluster readAttributePreferredExtendedPanIDWithCompletion:^(NSNumber * _Nullable value, NSError * _Nullable error) {
+        [cluster readAttributePreferredExtendedPanIDWithCompletion:^(NSData * _Nullable value, NSError * _Nullable error) {
             NSLog(@"ThreadNetworkDirectory.PreferredExtendedPanID response %@", [value description]);
             if (error == nil) {
                 RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value);
@@ -148785,7 +148784,7 @@ class WriteThreadNetworkDirectoryPreferredExtendedPanID : public WriteAttribute
         : WriteAttribute("preferred-extended-pan-id")
     {
         AddArgument("attr-name", "preferred-extended-pan-id");
-        AddArgument("attr-value", 0, UINT64_MAX, &mValue);
+        AddArgument("attr-value", &mValue);
         WriteAttribute::AddArguments();
     }
 
@@ -148804,9 +148803,9 @@ class WriteThreadNetworkDirectoryPreferredExtendedPanID : public WriteAttribute
         __auto_type * params = [[MTRWriteParams alloc] init];
         params.timedWriteTimeout = mTimedInteractionTimeoutMs.HasValue() ? [NSNumber numberWithUnsignedShort:mTimedInteractionTimeoutMs.Value()] : nil;
         params.dataVersion = mDataVersion.HasValue() ? [NSNumber numberWithUnsignedInt:mDataVersion.Value()] : nil;
-        NSNumber * _Nullable value = nil;
+        NSData * _Nullable value = nil;
         if (!mValue.IsNull()) {
-            value = [NSNumber numberWithUnsignedLongLong:mValue.Value()];
+            value = [[NSData alloc] initWithBytes:mValue.Value().data() length:mValue.Value().size()];
         }
 
         [cluster writeAttributePreferredExtendedPanIDWithValue:value params:params completion:^(NSError * _Nullable error) {
@@ -148820,7 +148819,7 @@ class WriteThreadNetworkDirectoryPreferredExtendedPanID : public WriteAttribute
     }
 
 private:
-    chip::app::DataModel::Nullable<uint64_t> mValue;
+    chip::app::DataModel::Nullable<chip::ByteSpan> mValue;
 };
 
 class SubscribeAttributeThreadNetworkDirectoryPreferredExtendedPanID : public SubscribeAttribute {
@@ -148854,7 +148853,7 @@ class SubscribeAttributeThreadNetworkDirectoryPreferredExtendedPanID : public Su
         }
         [cluster subscribeAttributePreferredExtendedPanIDWithParams:params
             subscriptionEstablished:^() { mSubscriptionEstablished = YES; }
-            reportHandler:^(NSNumber * _Nullable value, NSError * _Nullable error) {
+            reportHandler:^(NSData * _Nullable value, NSError * _Nullable error) {
                 NSLog(@"ThreadNetworkDirectory.PreferredExtendedPanID response %@", [value description]);
                 if (error == nil) {
                     RemoteDataModelLogger::LogAttributeAsJSON(@(endpointId), @(clusterId), @(attributeId), value);
@@ -195631,8 +195630,6 @@ void registerClusterThreadNetworkDirectory(Commands & commands)
         make_unique<ReadThreadNetworkDirectoryClusterRevision>(), //
         make_unique<SubscribeAttributeThreadNetworkDirectoryClusterRevision>(), //
 #endif // MTR_ENABLE_PROVISIONAL
-        make_unique<ReadEvent>(Id), //
-        make_unique<SubscribeEvent>(Id), //
     };
 
     commands.RegisterCluster(clusterName, clusterCommands);