Merge branch 'libyang2' of https://github.com/CESNET/libyang into libyang2
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 149a3e4..9b325cc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -224,6 +224,7 @@
     src/printer_schema.c
     src/printer_yang.c
     src/plugins_types.c
+    src/plugins_exts.c
     src/xml.c
     src/xpath.c)
 
@@ -246,7 +247,7 @@
     src/printer_data.h
     src/tree_schema.h
     src/printer_schema.h
-    src/extensions.h
+    src/plugins_exts.h
     src/plugins_types.h
     src/dict.h
     src/log.h
diff --git a/src/common.c b/src/common.c
index 4ce64fd..a59d5af 100644
--- a/src/common.c
+++ b/src/common.c
@@ -24,7 +24,7 @@
 #include <sys/stat.h>
 #include <unistd.h>
 
-#include "extensions.h"
+#include "plugins_exts.h"
 #include "tree_schema.h"
 #include "tree_schema_internal.h"
 
diff --git a/src/extensions.h b/src/extensions.h
deleted file mode 100644
index 588f63a..0000000
--- a/src/extensions.h
+++ /dev/null
@@ -1,108 +0,0 @@
-/**
- * @file extesnions.h
- * @author Radek Krejci <rkrejci@cesnet.cz>
- * @brief libyang support for YANG extensions implementation.
- *
- * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
- *
- * This source code is licensed under BSD 3-Clause License (the "License").
- * You may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://opensource.org/licenses/BSD-3-Clause
- */
-
-#ifndef LY_EXTENSIONS_H_
-#define LY_EXTENSIONS_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-/**
- * @defgroup extensions YANG Extensions
- *
- * @{
- */
-
-/**
- * @brief Extension instance structure parent enumeration
- */
-typedef enum {
-    LYEXT_PAR_MODULE, /**< ::lys_module or ::lys_submodule */
-    LYEXT_PAR_NODE, /**< ::lys_node (and the derived structures) */
-    LYEXT_PAR_TPDF, /**< ::lys_tpdf */
-    LYEXT_PAR_TYPE, /**< ::lys_type */
-    LYEXT_PAR_TYPE_BIT, /**< ::lys_type_bit */
-    LYEXT_PAR_TYPE_ENUM, /**< ::lys_type_enum */
-    LYEXT_PAR_FEATURE, /**< ::lys_feature */
-    LYEXT_PAR_RESTR, /**< ::lys_restr - YANG's must, range, length and pattern statements */
-    LYEXT_PAR_WHEN, /**< ::lys_when */
-    LYEXT_PAR_IDENT, /**< ::lys_ident */
-    LYEXT_PAR_EXT, /**< ::lys_ext */
-    LYEXT_PAR_EXTINST, /**< ::lys_ext_instance */
-    LYEXT_PAR_REFINE, /**< ::lys_refine */
-    LYEXT_PAR_DEVIATION, /**< ::lys_deviation */
-    LYEXT_PAR_DEVIATE, /**< ::lys_deviate */
-    LYEXT_PAR_IMPORT, /**< ::lys_import */
-    LYEXT_PAR_INCLUDE,           /**< ::lysp_include */
-    LYEXT_PAR_REVISION,          /**< ::lysc_revision */
-} LYEXT_PARENT;
-
-/**
- * @brief Enum of substatements in which extension instances can appear.
- */
-typedef enum {
-    LYEXT_SUBSTMT_SELF = 0,      /**< extension of the structure itself, not substatement's */
-    LYEXT_SUBSTMT_ARGUMENT,      /**< extension of the argument statement, can appear in lys_ext */
-    LYEXT_SUBSTMT_BASE,          /**< extension of the base statement, can appear (repeatedly) in lys_type and lys_ident */
-    LYEXT_SUBSTMT_BELONGSTO,     /**< extension of the belongs-to statement, can appear in lys_submodule */
-    LYEXT_SUBSTMT_CONTACT,       /**< extension of the contact statement, can appear in lys_module */
-    LYEXT_SUBSTMT_DEFAULT,       /**< extension of the default statement, can appear in lys_node_leaf, lys_node_leaflist,
-                                      lys_node_choice and lys_deviate */
-    LYEXT_SUBSTMT_DESCRIPTION,   /**< extension of the description statement, can appear in lys_module, lys_submodule,
-                                      lys_node, lys_import, lys_include, lys_ext, lys_feature, lys_tpdf, lys_restr,
-                                      lys_ident, lys_deviation, lys_type_enum, lys_type_bit, lys_when and lys_revision */
-    LYEXT_SUBSTMT_ERRTAG,        /**< extension of the error-app-tag statement, can appear in lys_restr */
-    LYEXT_SUBSTMT_ERRMSG,        /**< extension of the error-message statement, can appear in lys_restr */
-    LYEXT_SUBSTMT_KEY,           /**< extension of the key statement, can appear in lys_node_list */
-    LYEXT_SUBSTMT_NAMESPACE,     /**< extension of the namespace statement, can appear in lys_module */
-    LYEXT_SUBSTMT_ORGANIZATION,  /**< extension of the organization statement, can appear in lys_module and lys_submodule */
-    LYEXT_SUBSTMT_PATH,          /**< extension of the path statement, can appear in lys_type */
-    LYEXT_SUBSTMT_PREFIX,        /**< extension of the prefix statement, can appear in lys_module, lys_submodule (for
-                                      belongs-to's prefix) and lys_import */
-    LYEXT_SUBSTMT_PRESENCE,      /**< extension of the presence statement, can appear in lys_node_container */
-    LYEXT_SUBSTMT_REFERENCE,     /**< extension of the reference statement, can appear in lys_module, lys_submodule,
-                                      lys_node, lys_import, lys_include, lys_revision, lys_tpdf, lys_restr, lys_ident,
-                                      lys_ext, lys_feature, lys_deviation, lys_type_enum, lys_type_bit and lys_when */
-    LYEXT_SUBSTMT_REVISIONDATE,  /**< extension of the revision-date statement, can appear in lys_import and lys_include */
-    LYEXT_SUBSTMT_UNITS,         /**< extension of the units statement, can appear in lys_tpdf, lys_node_leaf,
-                                      lys_node_leaflist and lys_deviate */
-    LYEXT_SUBSTMT_VALUE,         /**< extension of the value statement, can appear in lys_type_enum */
-    LYEXT_SUBSTMT_VERSION,       /**< extension of the yang-version statement, can appear in lys_module and lys_submodule */
-    LYEXT_SUBSTMT_MODIFIER,      /**< extension of the modifier statement, can appear in lys_restr */
-    LYEXT_SUBSTMT_REQINSTANCE,   /**< extension of the require-instance statement, can appear in lys_type */
-    LYEXT_SUBSTMT_YINELEM,       /**< extension of the yin-element statement, can appear in lys_ext */
-    LYEXT_SUBSTMT_CONFIG,        /**< extension of the config statement, can appear in lys_node and lys_deviate */
-    LYEXT_SUBSTMT_MANDATORY,     /**< extension of the mandatory statement, can appear in lys_node_leaf, lys_node_choice,
-                                      lys_node_anydata and lys_deviate */
-    LYEXT_SUBSTMT_ORDEREDBY,     /**< extension of the ordered-by statement, can appear in lys_node_list and lys_node_leaflist */
-    LYEXT_SUBSTMT_STATUS,        /**< extension of the status statement, can appear in lys_tpdf, lys_node, lys_ident,
-                                      lys_ext, lys_feature, lys_type_enum and lys_type_bit */
-    LYEXT_SUBSTMT_FRACDIGITS,    /**< extension of the fraction-digits statement, can appear in lys_type */
-    LYEXT_SUBSTMT_MAX,           /**< extension of the max-elements statement, can appear in lys_node_list,
-                                      lys_node_leaflist and lys_deviate */
-    LYEXT_SUBSTMT_MIN,           /**< extension of the min-elements statement, can appear in lys_node_list,
-                                      lys_node_leaflist and lys_deviate */
-    LYEXT_SUBSTMT_POSITION,      /**< extension of the position statement, can appear in lys_type_bit */
-    LYEXT_SUBSTMT_UNIQUE,        /**< extension of the unique statement, can appear in lys_node_list and lys_deviate */
-    LYEXT_SUBSTMT_IFFEATURE,     /**< extension of the if-feature statement */
-} LYEXT_SUBSTMT;
-
-/** @} */
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif /* LY_TREE_SCHEMA_H_ */
diff --git a/src/log.c b/src/log.c
index b600b62..8efac1a 100644
--- a/src/log.c
+++ b/src/log.c
@@ -24,6 +24,7 @@
 #include <string.h>
 
 #include "log.h"
+#include "plugins_exts.h"
 
 THREAD_LOCAL enum int_log_opts log_opt;
 volatile uint8_t ly_log_level = LY_LLWRN;
@@ -464,6 +465,29 @@
 }
 
 API void
+lyext_log(const struct lysc_ext_instance *ext, LY_LOG_LEVEL level, LY_ERR err_no, const char *path, const char *format, ...)
+{
+    va_list ap;
+    char *plugin_msg;
+    int ret;
+
+    if (ly_log_level < level) {
+        return;
+    }
+    ret = asprintf(&plugin_msg, "Extension plugin \"%s\": %s)", ext->def->plugin->id, format);
+    if (ret == -1) {
+        LOGMEM(ext->def->module->ctx);
+        return;
+    }
+
+    va_start(ap, format);
+    log_vprintf(ext->def->module->ctx, level, (level == LY_LLERR ? LY_EPLUGIN : 0), err_no, path ? strdup(path) : NULL, plugin_msg, ap);
+    va_end(ap);
+
+    free(plugin_msg);
+}
+
+API void
 ly_err_print(struct ly_err_item *eitem)
 {
     if (ly_log_opts & LY_LOLOG) {
diff --git a/src/parser_yang.c b/src/parser_yang.c
index e5dde2f..3949b4a 100644
--- a/src/parser_yang.c
+++ b/src/parser_yang.c
@@ -24,8 +24,8 @@
 
 #include "context.h"
 #include "dict.h"
-#include "extensions.h"
 #include "log.h"
+#include "plugins_exts.h"
 #include "set.h"
 #include "tree.h"
 #include "tree_schema.h"
diff --git a/src/plugins_exts.c b/src/plugins_exts.c
new file mode 100644
index 0000000..720f828
--- /dev/null
+++ b/src/plugins_exts.c
@@ -0,0 +1,106 @@
+/**
+ * @file plugins_exts.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief Internally implemented YANG extensions.
+ *
+ * Copyright (c) 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+#include "common.h"
+
+#include "plugins_exts.h"
+
+#include "plugins_exts_nacm.c"
+
+/**
+ * @brief list of all extension plugins implemented internally
+ */
+struct lyext_plugins_list lyext_plugins_internal[5] = {
+    {"ietf-netconf-acm", "2012-02-22", "default-deny-write", &nacm_plugin},
+    {"ietf-netconf-acm", "2018-02-14", "default-deny-write", &nacm_plugin},
+    {"ietf-netconf-acm", "2012-02-22", "default-deny-all", &nacm_plugin},
+    {"ietf-netconf-acm", "2018-02-14", "default-deny-all", &nacm_plugin},
+    {NULL, NULL, NULL, NULL} /* terminating item */
+};
+
+/* TODO support for external extension plugins */
+
+struct lyext_plugin *
+lyext_get_plugin(struct lysc_ext *ext)
+{
+    unsigned int u;
+
+    for (u = 0; lyext_plugins_internal[u].module; ++u) {
+        if (!strcmp(ext->name, lyext_plugins_internal[u].name) &&
+                !strcmp(ext->module->name, lyext_plugins_internal[u].module) &&
+                (!lyext_plugins_internal[u].revision || !strcmp(ext->module->revision, lyext_plugins_internal[u].revision))) {
+            /* we have the match */
+            return lyext_plugins_internal[u].plugin;
+        }
+    }
+
+    return NULL;
+}
+
+API const char *
+lyext_parent2str(LYEXT_PARENT type)
+{
+    switch(type) {
+    case LYEXT_PAR_MODULE:
+        return "module";
+    case LYEXT_PAR_NODE:
+        return "data node";
+    case LYEXT_PAR_INPUT:
+        return "input";
+    case LYEXT_PAR_OUTPUT:
+        return "output";
+    case LYEXT_PAR_TYPE:
+        return "type";
+    case LYEXT_PAR_TYPE_BIT:
+        return "bit";
+    case LYEXT_PAR_TYPE_ENUM:
+        return "enum";
+    case LYEXT_PAR_FEATURE:
+        return "feature";
+    case LYEXT_PAR_MUST:
+        return "must";
+    case LYEXT_PAR_PATTERN:
+        return "pattern";
+    case LYEXT_PAR_LENGTH:
+        return "length";
+    case LYEXT_PAR_RANGE:
+        return "range";
+    case LYEXT_PAR_WHEN:
+        return "when";
+    case LYEXT_PAR_IDENT:
+        return "identity";
+    case LYEXT_PAR_EXT:
+        return "extension instance";
+    case LYEXT_PAR_IMPORT:
+        return "import";
+/* YANG allows extension instances inside the following statements,
+ * but they do not have any meaning in current libyang
+    case LYEXT_PAR_TPDF:
+        return "typedef";
+    case LYEXT_PAR_EXTINST:
+        return "extension";
+    case LYEXT_PAR_REFINE:
+        return "refine";
+    case LYEXT_PAR_DEVIATION:
+        return "deviation";
+    case LYEXT_PAR_DEVIATE:
+        return "deviate";
+    case LYEXT_PAR_INCLUDE:
+        return "include";
+    case LYEXT_PAR_REVISION:
+        return "revision";
+ */
+    default:
+        return "unknown";
+    }
+}
diff --git a/src/plugins_exts.h b/src/plugins_exts.h
new file mode 100644
index 0000000..6684d6a
--- /dev/null
+++ b/src/plugins_exts.h
@@ -0,0 +1,167 @@
+/**
+ * @file plugins_exts.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang support for YANG extensions implementation.
+ *
+ * Copyright (c) 2015 - 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef LY_PLUGINS_EXTS_H_
+#define LY_PLUGINS_EXTS_H_
+
+#include "set.h"
+#include "tree_schema.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/**
+ * @defgroup extensions YANG Extensions
+ *
+ * @{
+ */
+
+/**
+ * @brief Extensions API version
+ */
+#define LYEXT_API_VERSION 1
+
+/**
+ * @brief Macro to store version of extension plugins API in the plugins.
+ * It is matched when the plugin is being loaded by libyang.
+ */
+#define LYEXT_VERSION_CHECK int lyext_api_version = LYEXT_API_VERSION;
+
+/**
+ * @defgroup extensionscompile YANG Extensions - Compilation Helpers
+ * @ingroup extensions
+ * @brief Helper functions to compile (via lyext_clb_compile callback) statements inside the extension instance.
+ *
+ * NOTE: There is a lot of useful static functions in the tree_schema_compile.c which could be provided here. Since we don't want
+ * to have a large API with functions which will be never used, we provide here just the functions which are evidently needed.
+ * If you, as an extension plugin author, need to make some of the compile functions available, please contact libyang maintainers
+ * via the GITHUB issue tracker.
+ *
+ * @{
+ */
+
+/**
+ * @brief internal context for compilation
+ */
+struct lysc_ctx {
+    struct ly_ctx *ctx;
+    struct lys_module *mod;
+    struct lys_module *mod_def; /**< context module for the definitions of the nodes being currently
+                                     processed - groupings are supposed to be evaluated in place where
+                                     defined, but its content instances are supposed to be placed into
+                                     the target module (mod) */
+    struct ly_set groupings;    /**< stack for groupings circular check */
+    struct ly_set unres;        /**< to validate leafref's target and xpath of when/must */
+    struct ly_set dflts;        /**< set of incomplete default values */
+    struct ly_set tpdf_chain;
+    uint16_t path_len;
+    int options;                /**< various @ref scflags. */
+#define LYSC_CTX_BUFSIZE 4078
+    char path[LYSC_CTX_BUFSIZE];
+};
+
+/**
+ * @brief Update path in the compile context, which is used for logging where the compilation failed.
+ *
+ * @param[in] ctx Compile context with the path.
+ * @param[in] parent Parent of the current node to check difference of the node's module. The current module is taken from lysc_ctx::mod.
+ * @param[in] name Name of the node to update path with. If NULL, the last segment is removed. If the format is `{keyword}`, the following
+ * call updates the segment to the form `{keyword='name'}` (to remove this compound segment, 2 calls with NULL @p name must be used).
+ */
+void lysc_update_path(struct lysc_ctx *ctx, struct lysc_node *parent, const char *name);
+
+/** @} extensionscompile */
+
+/**
+ * @brief Callback to compile extension from the lysp_ext_instance to the lysc_ext_instance. The later structure is generally prepared
+ * and only the extension specific data are supposed to be added (if any).
+ *
+ * @param[in] cctx Current compile context.
+ * @param[in] p_ext Parsed extension instance data.
+ * @param[in,out] c_ext Prepared compiled extension instance structure where an addition, extension-specific, data are supposed to be placed
+ * for later use (data validation or use of external tool).
+ * @return LY_SUCCESS in case of success.
+ * @return LY_EVALID in case of non-conforming parsed data.
+ */
+typedef LY_ERR (*lyext_clb_compile)(struct lysc_ctx *cctx, const struct lysp_ext_instance *p_ext, struct lysc_ext_instance *c_ext);
+
+/**
+ * @brief Callback to free the extension specific data created by the lyext_clb_compile callback of the same extension plugin.
+ *
+ * @param[in,out] ext Compiled extension structure where the data to free are placed.
+ */
+typedef void (*lyext_clb_free)(struct lysc_ext_instance *ext);
+
+/**
+ * @brief Callback to decide if data instance is valid according to the schema.
+ *
+ * The callback is used only for the extension instances placed in the following parent statements
+ * (which is specified as lysc_ext_instance::parent_type):
+ *     - LYEXT_PAR_NODE - @p node is instance of the schema node where the extension instance was specified.
+ *     - LYEXT_PAR_TPDF - @p node is instance of the schema node with the value of the typedef's type where the extension instance was specified.
+ *     - LYEXT_PAR_TYPE - @p node is instance of the schema node with the value of the type where the extension instance was specified.
+ *     - LYEXT_PAR_TYPE_BIT - @p node is instance of the schema node with the value of the bit where the extension instance was specified.
+ *     - LYEXT_PAR_TYPE_ENUM - @p node is instance of the schema node with the value of the enum where the extension instance was specified.
+ *
+ * @param[in] ext Extension instance to be checked.
+ * @param[in] node Data node, where the extension data are supposed to be placed.
+ *
+ * @return LY_SUCCESS on data validation success.
+ * @return LY_EVALID in case the validation fails.
+ */
+typedef LY_ERR (*lyext_clb_data_validation)(struct lysc_ext_instance *ext, struct lyd_node *node);
+
+/**
+ * @brief Extension plugin implementing various aspects of a YANG extension
+ */
+struct lyext_plugin {
+    const char *id;                     /**< Plugin identification (mainly for distinguish incompatible versions of the plugins for external tools) */
+    lyext_clb_compile compile;          /**< Callback to compile extension instance from the parsed data */
+    lyext_clb_data_validation validate; /**< Callback to decide if data instance is valid according to the schema. */
+    /* TODO printers? (schema/data) */
+    lyext_clb_free free;                /**< Free the extension instance specific data created by lyext_plugin::compile callback */
+};
+
+struct lyext_plugins_list {
+    const char *module;          /**< name of the module where the extension is defined */
+    const char *revision;        /**< optional module revision - if not specified, the plugin applies to any revision,
+                                      which is not an optimal approach due to a possible future revisions of the module.
+                                      Instead, there should be defined multiple items in the plugins list, each with the
+                                      different revision, but all with the same pointer to the plugin extension. The
+                                      only valid use case for the NULL revision is the case the module has no revision. */
+    const char *name;            /**< name of the extension */
+    struct lyext_plugin *plugin; /**< plugin for the extension */
+};
+
+
+/**
+ * @brief Provide a log message from an extension plugin.
+ *
+ * @param[in] ext Compiled extension structure providing generic information about the extension/plugin causing the message.
+ * @param[in] level Log message level (error, warning, etc.)
+ * @param[in] err_no Error type code.
+ * @param[in] path Path relevant to the message.
+ * @param[in] format Format string to print.
+ */
+void lyext_log(const struct lysc_ext_instance *ext, LY_LOG_LEVEL level, LY_ERR err_no, const char *path, const char *format, ...);
+
+/** @} extensions */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LY_PLUGINS_EXTS_H_ */
diff --git a/src/plugins_exts_internal.h b/src/plugins_exts_internal.h
new file mode 100644
index 0000000..4f0eb5f
--- /dev/null
+++ b/src/plugins_exts_internal.h
@@ -0,0 +1,27 @@
+/**
+ * @file plugins_exts_internal.h
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief internal functions to support extension plugins.
+ *
+ * Copyright (c) 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#ifndef LY_PLUGINS_EXTS_INTERNAL_H_
+#define LY_PLUGINS_EXTS_INTERNAL_H_
+
+#include "tree_schema.h"
+
+/**
+ * @brief Find the extension plugin for the specified extension instance.
+ *
+ * @param[in] mod YANG module where the
+ */
+struct lyext_plugin *lyext_get_plugin(struct lysc_ext *ext);
+
+#endif /* LY_PLUGINS_EXTS_INTERNAL_H_ */
diff --git a/src/plugins_exts_nacm.c b/src/plugins_exts_nacm.c
new file mode 100644
index 0000000..992aa04
--- /dev/null
+++ b/src/plugins_exts_nacm.c
@@ -0,0 +1,126 @@
+/**
+ * @file plugins_exts_nacm.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @brief libyang extension plugin - NACM (RFC 6536)
+ *
+ * Copyright (c) 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+#include "common.h"
+
+#include <stdlib.h>
+
+#include "plugins_exts.h"
+#include "tree_schema.h"
+
+/**
+ * @brief Storage for ID used to check plugin API version compatibility.
+ * Ignored here in the internal plugin.
+LYEXT_VERSION_CHECK
+ */
+
+/**
+ * @brief Compile NAMC's extension instances.
+ *
+ * Implementation of lyext_clb_compile callback set as lyext_plugin::compile.
+ */
+LY_ERR
+nacm_compile(struct lysc_ctx *cctx, const struct lysp_ext_instance *p_ext, struct lysc_ext_instance *c_ext)
+{
+    struct lysc_node *parent = NULL, *iter;
+    struct lysc_ext_instance *inherited;
+    unsigned int u;
+
+    static const uint8_t nacm_deny_all = 1;
+    static const uint8_t nacm_deny_write = 2;
+
+    /* store the NACM flag */
+    if (!strcmp(c_ext->def->name, "default-deny-write")) {
+        c_ext->data = (void*)&nacm_deny_write;
+    } else if (!strcmp(c_ext->def->name, "default-deny-all")) {
+        c_ext->data = (void*)&nacm_deny_all;
+    } else {
+        return LY_EINT;
+    }
+
+    /* check that the extension is instantiated at an allowed place - data node */
+    if (c_ext->parent_type != LYEXT_PAR_NODE) {
+        lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path, "Extension %s is allowed only in a data nodes, but it is placed in \"%s\" statement.",
+                  p_ext->name, lyext_parent2str(c_ext->parent_type));
+        return LY_EVALID;
+    } else {
+        parent = (struct lysc_node*)c_ext->parent;
+        if (!(parent->nodetype & (LYS_CONTAINER | LYS_LEAF | LYS_LEAFLIST | LYS_LIST | LYS_CHOICE | LYS_ANYDATA | LYS_CASE | LYS_ACTION | LYS_NOTIF))) {
+            /* note LYS_AUGMENT and LYS_USES is not in the list since they are not present in the compiled tree. Instead, libyang
+             * passes all their extensions to their children nodes */
+invalid_parent:
+            lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path,
+                      "Extension %s is not allowed in %s statement.", p_ext->name, lys_nodetype2str(parent->nodetype));
+            return LY_EVALID;
+        }
+        if (c_ext->data == (void*)&nacm_deny_write && (parent->nodetype & (LYS_ACTION | LYS_NOTIF))) {
+            goto invalid_parent;
+        }
+    }
+
+    /* check for duplication */
+    LY_ARRAY_FOR(parent->exts, u) {
+        if (&parent->exts[u] != c_ext && parent->exts[u].def->plugin == c_ext->def->plugin) {
+            /* duplication of a NACM extension on a single node
+             * We check plugin since we want to catch even the situation that there is default-deny-all
+             * AND default-deny-write */
+            if (parent->exts[u].def == c_ext->def) {
+                lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path, "Extension %s is instantiated multiple times.", p_ext->name);
+            } else {
+                lyext_log(c_ext, LY_LLERR, LY_EVALID, cctx->path, "Extension nacm:default-deny-write is mixed with nacm:default-deny-all.");
+            }
+            return LY_EVALID;
+        }
+    }
+
+    /* inherit the extension instance to all the children nodes */
+    LYSC_TREE_DFS_BEGIN(parent, iter) {
+        if (iter != parent) { /* ignore the parent from which we inherit */
+            /* check that the node does not have its own NACM extension instance */
+            LY_ARRAY_FOR(iter->exts, u) {
+                if (iter->exts[u].def == c_ext->def) {
+                    /* the child already have its own NACM flag, so skip the subtree */
+                    LYSC_TREE_DFS_continue = 1;
+                    break;
+                }
+            }
+            if (!LYSC_TREE_DFS_continue) {
+                /* duplicate this one to inherit it to the child */
+                LY_ARRAY_NEW_RET(cctx->ctx, iter->exts, inherited, LY_EMEM);
+
+                inherited->def = c_ext->def;
+                inherited->parent = iter;
+                inherited->parent_type = LYEXT_PAR_NODE;
+                if (c_ext->argument) {
+                    inherited->argument = lydict_insert(cctx->ctx, c_ext->argument, strlen(c_ext->argument));
+                }
+                /* TODO duplicate extension instances */
+                inherited->data = c_ext->data;
+            }
+        }
+        LYSC_TREE_DFS_END(parent, iter)
+    }
+
+    return LY_SUCCESS;
+}
+
+
+/**
+ * @brief Plugin for the NACM's default-deny-write and default-deny-all extensions
+ */
+struct lyext_plugin nacm_plugin = {
+    .id = "libyang 2 - NACM, version 1",
+    .compile = &nacm_compile,
+    .validate = NULL,
+    .free = NULL
+};
diff --git a/src/plugins_types.c b/src/plugins_types.c
index 1be7af2..9d6a339 100644
--- a/src/plugins_types.c
+++ b/src/plugins_types.c
@@ -1,5 +1,5 @@
 /**
- * @file plugin_types.c
+ * @file plugins_types.c
  * @author Radek Krejci <rkrejci@cesnet.cz>
  * @brief Built-in types plugins and interface for user types plugins.
  *
@@ -2601,41 +2601,41 @@
 struct lysc_type_plugin ly_builtin_type_plugins[LY_DATA_TYPE_COUNT] = {
     {0}, /* LY_TYPE_UNKNOWN */
     {.type = LY_TYPE_BINARY, .store = ly_type_store_binary, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_canonical, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_canonical, .free = ly_type_free_canonical, .id = "libyang 2 - binary, version 1"},
     {.type = LY_TYPE_UINT8, .store = ly_type_store_uint, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical, .id = "libyang 2 - unsigned integer, version 1"},
     {.type = LY_TYPE_UINT16, .store = ly_type_store_uint, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical, .id = "libyang 2 - unsigned integer, version 1"},
     {.type = LY_TYPE_UINT32, .store = ly_type_store_uint, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical, .id = "libyang 2 - unsigned integer, version 1"},
     {.type = LY_TYPE_UINT64, .store = ly_type_store_uint, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_uint, .free = ly_type_free_canonical, .id = "libyang 2 - unsigned integer, version 1"},
     {.type = LY_TYPE_STRING, .store = ly_type_store_string, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_canonical, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_canonical, .free = ly_type_free_canonical, .id = "libyang 2 - string, version 1"},
     {.type = LY_TYPE_BITS, .store = ly_type_store_bits, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_bits, .free = ly_type_free_bits},
+     .duplicate = ly_type_dup_bits, .free = ly_type_free_bits, .id = "libyang 2 - bits, version 1"},
     {.type = LY_TYPE_BOOL, .store = ly_type_store_boolean, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical, .id = "libyang 2 - boolean, version 1"},
     {.type = LY_TYPE_DEC64, .store = ly_type_store_decimal64, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_decimal64, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_decimal64, .free = ly_type_free_canonical, .id = "libyang 2 - decimal64, version 1"},
     {.type = LY_TYPE_EMPTY, .store = ly_type_store_empty, .compare = ly_type_compare_empty, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_canonical, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_canonical, .free = ly_type_free_canonical, .id = "libyang 2 - empty, version 1"},
     {.type = LY_TYPE_ENUM, .store = ly_type_store_enum, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_enum, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_enum, .free = ly_type_free_canonical, .id = "libyang 2 - enumeration, version 1"},
     {.type = LY_TYPE_IDENT, .store = ly_type_store_identityref, .compare = ly_type_compare_identityref, .print = ly_type_print_identityref,
-     .duplicate = ly_type_dup_identityref, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_identityref, .free = ly_type_free_canonical, .id = "libyang 2 - identityref, version 1"},
     {.type = LY_TYPE_INST, .store = ly_type_store_instanceid, .compare = ly_type_compare_instanceid, .print = ly_type_print_instanceid,
-     .duplicate = ly_type_dup_instanceid, .free = ly_type_free_instanceid},
+     .duplicate = ly_type_dup_instanceid, .free = ly_type_free_instanceid, .id = "libyang 2 - instance-identifier, version 1"},
     {.type = LY_TYPE_LEAFREF, .store = ly_type_store_leafref, .compare = ly_type_compare_leafref, .print = ly_type_print_leafref,
-     .duplicate = ly_type_dup_leafref, .free = ly_type_free_leafref},
+     .duplicate = ly_type_dup_leafref, .free = ly_type_free_leafref, .id = "libyang 2 - leafref, version 1"},
     {.type = LY_TYPE_UNION, .store = ly_type_store_union, .compare = ly_type_compare_union, .print = ly_type_print_union,
-     .duplicate = ly_type_dup_union, .free = ly_type_free_union},
+     .duplicate = ly_type_dup_union, .free = ly_type_free_union, .id = "libyang 2 - union,version 1"},
     {.type = LY_TYPE_INT8, .store = ly_type_store_int, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical, .id = "libyang 2 - integer, version 1"},
     {.type = LY_TYPE_INT16, .store = ly_type_store_int, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical, .id = "libyang 2 - integer, version 1"},
     {.type = LY_TYPE_INT32, .store = ly_type_store_int, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical, .id = "libyang 2 - integer, version 1"},
     {.type = LY_TYPE_INT64, .store = ly_type_store_int, .compare = ly_type_compare_canonical, .print = ly_type_print_canonical,
-     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical},
+     .duplicate = ly_type_dup_int, .free = ly_type_free_canonical, .id = "libyang 2 - integer, version 1"},
 };
diff --git a/src/plugins_types.h b/src/plugins_types.h
index 0f1ff77..a071d38 100644
--- a/src/plugins_types.h
+++ b/src/plugins_types.h
@@ -184,6 +184,7 @@
     ly_type_print_clb print;         /**< printer callback to get string representing the value */
     ly_type_dup_clb duplicate;       /**< data duplication callback */
     ly_type_free_clb free;           /**< optional function to free the type-spceific way stored value */
+    const char *id;                  /**< Plugin identification (mainly for distinguish incompatible versions when used by external tools) */
     uint32_t flags;                  /**< [type flags ](@ref plugintypeflags). */
 };
 
diff --git a/src/printer_yang.c b/src/printer_yang.c
index 5156935..65ce5b5 100755
--- a/src/printer_yang.c
+++ b/src/printer_yang.c
@@ -20,8 +20,8 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "extensions.h"
 #include "log.h"
+#include "plugins_exts.h"
 #include "printer_internal.h"
 #include "tree.h"
 #include "tree_schema.h"
diff --git a/src/tree_data.h b/src/tree_data.h
index e9b1c32..22e9d3c 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -48,7 +48,7 @@
      * 3   5   6
  * </pre>
  *
- * Use the same parameters for #LY_TREE_DFS_BEGIN and #LY_TREE_DFS_END. While
+ * Use the same parameters for #LYD_TREE_DFS_BEGIN and #LYD_TREE_DFS_END. While
  * START can be any of the lyd_node* types, NEXT and ELEM variables are expected
  * to be pointers to a generic struct lyd_node.
  *
diff --git a/src/tree_schema.c b/src/tree_schema.c
index 6f45540..3fc8b08 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -752,8 +752,9 @@
 #endif
 
     if (!mod->implemented) {
-        /* pre-compile features of the module */
+        /* pre-compile features and extension definitions of the module */
         LY_CHECK_GOTO(lys_feature_precompile(NULL, ctx, mod, mod->parsed->features, &mod->off_features), error);
+        LY_CHECK_GOTO(lys_extension_precompile(NULL, ctx, mod, mod->parsed->extensions, &mod->off_extensions), error);
     }
 
     /* decide the latest revision */
@@ -798,8 +799,9 @@
             goto error_ctx;
         }
         if (!mod->implemented) {
-            /* pre-compile features of the module */
+            /* pre-compile features and extension definitions of the module */
             LY_CHECK_GOTO(lys_feature_precompile(NULL, ctx, mod, inc->submodule->features, &mod->off_features), error);
+            LY_CHECK_GOTO(lys_extension_precompile(NULL, ctx, mod, mod->parsed->extensions, &mod->off_extensions), error);
         }
     }
     mod->parsed->parsing = 0;
diff --git a/src/tree_schema.h b/src/tree_schema.h
index e3b00c5..4a3dbec 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -23,7 +23,6 @@
 
 #include "log.h"
 #include "tree.h"
-#include "extensions.h"
 #include "tree_data.h"
 
 struct ly_ctx;
@@ -33,6 +32,90 @@
 #endif
 
 /**
+ * @brief Macro to iterate via all elements in a schema tree which can be instantiated in data tree
+ * (skips cases, input, output). This is the opening part to the #LYS_TREE_DFS_END - they always have to be used together.
+ *
+ * The function follows deep-first search algorithm:
+ * <pre>
+ *     1
+ *    / \
+ *   2   4
+ *  /   / \
+ * 3   5   6
+ * </pre>
+ *
+ * Use the same parameters for #LYSC_TREE_DFS_BEGIN and #LYSC_TREE_DFS_END. While
+ * START can be any of the lysc_node* types (including lysc_action and lysc_notif),
+ * ELEM variable must be of the struct lysc_node* type.
+ *
+ * To skip a particular subtree, instead of the continue statement, set LYSC_TREE_DFS_continue
+ * variable to non-zero value.
+ *
+ * Use with opening curly bracket '{' after the macro.
+ *
+ * @param START Pointer to the starting element processed first.
+ * @param ELEM Iterator intended for use in the block.
+ */
+#define LYSC_TREE_DFS_BEGIN(START, ELEM) \
+    { int LYSC_TREE_DFS_continue = 0; struct lysc_node *LYSC_TREE_DFS_next; \
+    for ((ELEM) = (LYSC_TREE_DFS_next) = (START); \
+         (ELEM); \
+         (ELEM) = (LYSC_TREE_DFS_next), LYSC_TREE_DFS_continue = 0)
+
+/**
+ * @brief Macro to iterate via all elements in a (sub)tree. This is the closing part
+ * to the #LYSC_TREE_DFS_BEGIN - they always have to be used together.
+ *
+ * Use the same parameters for #LYSC_TREE_DFS_BEGIN and #LYSC_TREE_DFS_END. While
+ * START can be a pointer to any of the lysc_node* types (including lysc_action and lysc_notif),
+ * ELEM variable must be pointer to the lysc_node type.
+ *
+ * Use with closing curly bracket '}' after the macro.
+ *
+ * @param START Pointer to the starting element processed first.
+ * @param ELEM Iterator intended for use in the block.
+ */
+
+#define LYSC_TREE_DFS_END(START, ELEM) \
+    /* select element for the next run - children first */ \
+    if (LYSC_TREE_DFS_continue) { \
+        (LYSC_TREE_DFS_next) = NULL; \
+    } else { \
+        (LYSC_TREE_DFS_next) = (struct lysc_node*)lysc_node_children(ELEM, 0); \
+    }\
+    if (!(LYSC_TREE_DFS_next)) { \
+        /* in case of RPC/action, get also the output children */ \
+        if (!LYSC_TREE_DFS_continue && (ELEM)->nodetype == LYS_ACTION) { \
+            (LYSC_TREE_DFS_next) = (struct lysc_node*)lysc_node_children(ELEM, LYS_CONFIG_R); \
+        } \
+        if (!(LYSC_TREE_DFS_next)) { \
+            /* no children */ \
+            if ((ELEM) == (struct lysc_node*)(START)) { \
+                /* we are done, (START) has no children */ \
+                break; \
+            } \
+            /* try siblings */ \
+            (LYSC_TREE_DFS_next) = (ELEM)->next; \
+        } \
+    } \
+    while (!(LYSC_TREE_DFS_next)) { \
+        /* parent is already processed, go to its sibling */ \
+        (ELEM) = (ELEM)->parent; \
+        /* no siblings, go back through parents */ \
+        if ((ELEM) == (struct lysc_node*)(START)) { \
+            /* we are done, no next element to process */ \
+            break; \
+        } \
+        if ((ELEM)->nodetype == LYS_ACTION) { \
+            /* there is actually next node as a child of action's output */ \
+            (LYSC_TREE_DFS_next) = (struct lysc_node*)lysc_node_children(ELEM, LYS_CONFIG_R); \
+        } \
+        if (!(LYSC_TREE_DFS_next)) { \
+            (LYSC_TREE_DFS_next) = (ELEM)->next; \
+        } \
+    } } \
+
+/**
  * @brief Schema input formats accepted by libyang [parser functions](@ref howtoschemasparsers).
  */
 typedef enum {
@@ -79,6 +162,92 @@
 #define LYS_AUGMENT 0x2000
 
 /**
+ * @brief Extension instance structure parent enumeration
+ */
+typedef enum {
+    LYEXT_PAR_MODULE,    /**< ::lysc_module */
+    LYEXT_PAR_NODE,      /**< ::lysc_node (and the derived structures including ::lysc_action and ::lysc_notif) */
+    LYEXT_PAR_INPUT,     /**< ::lysc_action_inout */
+    LYEXT_PAR_OUTPUT,    /**< ::lysc_action_inout */
+    LYEXT_PAR_TYPE,      /**< ::lysc_type */
+    LYEXT_PAR_TYPE_BIT,  /**< ::lysc_type_bitenum_item */
+    LYEXT_PAR_TYPE_ENUM, /**< ::lysc_type_bitenum_item */
+    LYEXT_PAR_FEATURE,   /**< ::lysc_feature */
+    LYEXT_PAR_MUST,      /**< ::lysc_must */
+    LYEXT_PAR_PATTERN,   /**< ::lysc_pattern */
+    LYEXT_PAR_LENGTH,    /**< ::lysc_range */
+    LYEXT_PAR_RANGE,     /**< ::lysc_range */
+    LYEXT_PAR_WHEN,      /**< ::lysc_when */
+    LYEXT_PAR_IDENT,     /**< ::lysc_ident */
+    LYEXT_PAR_EXT,       /**< ::lysc_ext */
+    LYEXT_PAR_IMPORT,    /**< ::lysc_import */
+//    LYEXT_PAR_TPDF,      /**< ::lysp_tpdf */
+//    LYEXT_PAR_EXTINST,   /**< ::lysp_ext_instance */
+//    LYEXT_PAR_REFINE,    /**< ::lysp_refine */
+//    LYEXT_PAR_DEVIATION, /**< ::lysp_deviation */
+//    LYEXT_PAR_DEVIATE,   /**< ::lysp_deviate */
+//    LYEXT_PAR_INCLUDE,   /**< ::lysp_include */
+//    LYEXT_PAR_REVISION,  /**< ::lysp_revision */
+} LYEXT_PARENT;
+
+/**
+ * @brief Stringify extension instance parent type.
+ * @param[in] type Parent type to stringify.
+ * @return Constant string with the name of the parent statement.
+ */
+const char *lyext_parent2str(LYEXT_PARENT type);
+
+/**
+ * @brief Enum of substatements in which extension instances can appear.
+ */
+typedef enum {
+    LYEXT_SUBSTMT_SELF = 0,      /**< extension of the structure itself, not substatement's */
+    LYEXT_SUBSTMT_ARGUMENT,      /**< extension of the argument statement, can appear in lys_ext */
+    LYEXT_SUBSTMT_BASE,          /**< extension of the base statement, can appear (repeatedly) in lys_type and lys_ident */
+    LYEXT_SUBSTMT_BELONGSTO,     /**< extension of the belongs-to statement, can appear in lys_submodule */
+    LYEXT_SUBSTMT_CONTACT,       /**< extension of the contact statement, can appear in lys_module */
+    LYEXT_SUBSTMT_DEFAULT,       /**< extension of the default statement, can appear in lys_node_leaf, lys_node_leaflist,
+                                      lys_node_choice and lys_deviate */
+    LYEXT_SUBSTMT_DESCRIPTION,   /**< extension of the description statement, can appear in lys_module, lys_submodule,
+                                      lys_node, lys_import, lys_include, lys_ext, lys_feature, lys_tpdf, lys_restr,
+                                      lys_ident, lys_deviation, lys_type_enum, lys_type_bit, lys_when and lys_revision */
+    LYEXT_SUBSTMT_ERRTAG,        /**< extension of the error-app-tag statement, can appear in lys_restr */
+    LYEXT_SUBSTMT_ERRMSG,        /**< extension of the error-message statement, can appear in lys_restr */
+    LYEXT_SUBSTMT_KEY,           /**< extension of the key statement, can appear in lys_node_list */
+    LYEXT_SUBSTMT_NAMESPACE,     /**< extension of the namespace statement, can appear in lys_module */
+    LYEXT_SUBSTMT_ORGANIZATION,  /**< extension of the organization statement, can appear in lys_module and lys_submodule */
+    LYEXT_SUBSTMT_PATH,          /**< extension of the path statement, can appear in lys_type */
+    LYEXT_SUBSTMT_PREFIX,        /**< extension of the prefix statement, can appear in lys_module, lys_submodule (for
+                                      belongs-to's prefix) and lys_import */
+    LYEXT_SUBSTMT_PRESENCE,      /**< extension of the presence statement, can appear in lys_node_container */
+    LYEXT_SUBSTMT_REFERENCE,     /**< extension of the reference statement, can appear in lys_module, lys_submodule,
+                                      lys_node, lys_import, lys_include, lys_revision, lys_tpdf, lys_restr, lys_ident,
+                                      lys_ext, lys_feature, lys_deviation, lys_type_enum, lys_type_bit and lys_when */
+    LYEXT_SUBSTMT_REVISIONDATE,  /**< extension of the revision-date statement, can appear in lys_import and lys_include */
+    LYEXT_SUBSTMT_UNITS,         /**< extension of the units statement, can appear in lys_tpdf, lys_node_leaf,
+                                      lys_node_leaflist and lys_deviate */
+    LYEXT_SUBSTMT_VALUE,         /**< extension of the value statement, can appear in lys_type_enum */
+    LYEXT_SUBSTMT_VERSION,       /**< extension of the yang-version statement, can appear in lys_module and lys_submodule */
+    LYEXT_SUBSTMT_MODIFIER,      /**< extension of the modifier statement, can appear in lys_restr */
+    LYEXT_SUBSTMT_REQINSTANCE,   /**< extension of the require-instance statement, can appear in lys_type */
+    LYEXT_SUBSTMT_YINELEM,       /**< extension of the yin-element statement, can appear in lys_ext */
+    LYEXT_SUBSTMT_CONFIG,        /**< extension of the config statement, can appear in lys_node and lys_deviate */
+    LYEXT_SUBSTMT_MANDATORY,     /**< extension of the mandatory statement, can appear in lys_node_leaf, lys_node_choice,
+                                      lys_node_anydata and lys_deviate */
+    LYEXT_SUBSTMT_ORDEREDBY,     /**< extension of the ordered-by statement, can appear in lys_node_list and lys_node_leaflist */
+    LYEXT_SUBSTMT_STATUS,        /**< extension of the status statement, can appear in lys_tpdf, lys_node, lys_ident,
+                                      lys_ext, lys_feature, lys_type_enum and lys_type_bit */
+    LYEXT_SUBSTMT_FRACDIGITS,    /**< extension of the fraction-digits statement, can appear in lys_type */
+    LYEXT_SUBSTMT_MAX,           /**< extension of the max-elements statement, can appear in lys_node_list,
+                                      lys_node_leaflist and lys_deviate */
+    LYEXT_SUBSTMT_MIN,           /**< extension of the min-elements statement, can appear in lys_node_list,
+                                      lys_node_leaflist and lys_deviate */
+    LYEXT_SUBSTMT_POSITION,      /**< extension of the position statement, can appear in lys_type_bit */
+    LYEXT_SUBSTMT_UNIQUE,        /**< extension of the unique statement, can appear in lys_node_list and lys_deviate */
+    LYEXT_SUBSTMT_IFFEATURE,     /**< extension of the if-feature statement */
+} LYEXT_SUBSTMT;
+
+/**
  * @brief YANG import-stmt
  */
 struct lysp_import {
@@ -842,10 +1011,22 @@
 void lysp_module_free(struct lysp_module *module);
 
 /**
+ * @brief Compiled YANG extension-stmt
+ */
+struct lysc_ext {
+    const char *name;                /**< extension name */
+    const char *argument;            /**< argument name, NULL if not specified */
+    struct lysc_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
+    struct lyext_plugin *plugin;     /**< Plugin implementing the specific extension */
+    struct lys_module *module;       /**< module structure */
+    uint16_t flags;                  /**< LYS_STATUS_* value (@ref snodeflags) */
+};
+
+/**
  * @brief YANG extension instance
  */
 struct lysc_ext_instance {
-    struct lyext_plugin *plugin;     /**< pointer to the plugin implementing the extension (if present) */
+    struct lysc_ext *def;            /**< pointer to the extension definition */
     void *parent;                    /**< pointer to the parent element holding the extension instance(s), use
                                           ::lysc_ext_instance#parent_type to access the schema element */
     const char *argument;            /**< optional value of the extension's argument */
@@ -853,14 +1034,8 @@
     uint32_t insubstmt_index;        /**< in case the instance is in a substatement that can appear multiple times,
                                           this identifies the index of the substatement for this extension instance */
     LYEXT_PARENT parent_type;        /**< type of the parent structure */
-#if 0
-    uint8_t ext_type;                /**< extension type (#LYEXT_TYPE) */
-    uint8_t padding;                 /**< 32b padding */
-    struct lys_module *module;       /**< pointer to the extension instance's module (mandatory) */
-    LYS_NODE nodetype;               /**< LYS_EXT */
-#endif
     struct lysc_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
-    void *priv;                      /**< private caller's data, not used by libyang */
+    void *data;                      /**< private plugins's data, not used by libyang */
 };
 
 /**
@@ -1370,6 +1545,7 @@
     struct lysc_node *data;          /**< list of module's top-level data nodes (linked list) */
     struct lysc_action *rpcs;        /**< list of RPCs ([sized array](@ref sizedarrays)) */
     struct lysc_notif *notifs;       /**< list of notifications ([sized array](@ref sizedarrays)) */
+    struct lysc_ext *extensions;     /**< list of the extension definitions ([sized array](@ref sizedarrays)) */
     struct lysc_ext_instance *exts;  /**< list of the extension instances ([sized array](@ref sizedarrays)) */
 };
 
@@ -1494,6 +1670,11 @@
                                           from if-feature statements of the compiled schemas and their proper use in case
                                           the module became implemented in future (no matter if implicitly via augment/deviate
                                           or explicitly via ly_ctx_module_implement()). */
+    struct lysc_ext *off_extensions; /**< List of pre-compiled extension definitions of the module in non-implemented modules
+                                          ([sized array](@ref sizedarrays)). These extensions are prepared to be linked with the extension instances,
+                                          but they are not implemented (connected with any extension plugin). In case the module become
+                                          implemented, the list is moved into the compiled module structure and available extension plugins
+                                          are connected with the appropriate extension definision. */
 
     uint8_t implemented;             /**< flag if the module is implemented, not just imported. The module is implemented if
                                           the flag has non-zero value. Specific values are used internally:
@@ -1684,6 +1865,13 @@
 LY_ERR lys_value_validate(struct ly_ctx *ctx, const struct lysc_node *node, const char *value, size_t value_len,
                           ly_clb_resolve_prefix get_prefix, void *get_prefix_data, LYD_FORMAT format);
 
+/**
+ * @brief Stringify schema nodetype.
+ * @param[in] nodetype Nodetype to stringify.
+ * @return Constant string with the name of the node's type.
+ */
+const char *lys_nodetype2str(uint16_t nodetype);
+
 /** @} */
 
 #ifdef __cplusplus
diff --git a/src/tree_schema_compile.c b/src/tree_schema_compile.c
index bea32c1..61019e2 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -24,8 +24,10 @@
 
 #include "dict.h"
 #include "log.h"
+#include "plugins_exts.h"
 #include "set.h"
 #include "plugins_types.h"
+#include "plugins_exts_internal.h"
 #include "tree.h"
 #include "tree_schema.h"
 #include "tree_schema_internal.h"
@@ -61,6 +63,16 @@
         } \
     }
 
+#define COMPILE_EXTS_GOTO(CTX, EXTS_P, EXT_C, PARENT, PARENT_TYPE, RET, GOTO) \
+    if (EXTS_P) { \
+        LY_ARRAY_CREATE_GOTO((CTX)->ctx, EXT_C, LY_ARRAY_SIZE(EXTS_P), RET, GOTO); \
+        for (uint32_t __exts_iter = 0, __array_offset = LY_ARRAY_SIZE(EXT_C); __exts_iter < LY_ARRAY_SIZE(EXTS_P); ++__exts_iter) { \
+            LY_ARRAY_INCREMENT(EXT_C); \
+            RET = lys_compile_ext(CTX, &(EXTS_P)[__exts_iter], &(EXT_C)[__exts_iter + __array_offset], PARENT, PARENT_TYPE); \
+            LY_CHECK_GOTO(RET != LY_SUCCESS, GOTO); \
+        } \
+    }
+
 #define COMPILE_ARRAY_UNIQUE_GOTO(CTX, ARRAY_P, ARRAY_C, ITER, FUNC, RET, GOTO) \
     if (ARRAY_P) { \
         LY_ARRAY_CREATE_GOTO((CTX)->ctx, ARRAY_C, LY_ARRAY_SIZE(ARRAY_P), RET, GOTO); \
@@ -153,15 +165,7 @@
     }
 }
 
-/**
- * @brief Update path in the compile context.
- *
- * @param[in] ctx Compile context with the path.
- * @param[in] parent Parent of the current node to check difference of the node's module. The current module is taken from lysc_ctx::mod.
- * @param[in] name Name of the node to update path with. If NULL, the last segment is removed. If the format is `{keyword}`, the following
- * call updates the segment to the form `{keyword='name'}` (to remove this compound segment, 2 calls with NULL @p name must be used).
- */
-static void
+void
 lysc_update_path(struct lysc_ctx *ctx, struct lysc_node *parent, const char *name)
 {
     int len;
@@ -421,16 +425,18 @@
 }
 
 static LY_ERR
-lys_compile_ext(struct lysc_ctx *ctx, struct lysp_ext_instance *ext_p, struct lysc_ext_instance *ext)
+lys_compile_ext(struct lysc_ctx *ctx, struct lysp_ext_instance *ext_p, struct lysc_ext_instance *ext, void *parent, LYEXT_PARENT parent_type)
 {
     const char *name;
     unsigned int u;
     const struct lys_module *mod;
-    struct lysp_ext *edef = NULL;
+    struct lysc_ext *elist = NULL;
 
     DUP_STRING(ctx->ctx, ext_p->argument, ext->argument);
     ext->insubstmt = ext_p->insubstmt;
     ext->insubstmt_index = ext_p->insubstmt_index;
+    ext->parent = parent;
+    ext->parent_type = parent_type;
 
     /* get module where the extension definition should be placed */
     for (u = 0; ext_p->name[u] != ':'; ++u);
@@ -444,17 +450,100 @@
                             ext_p->name, mod->name),
                      LY_EVALID);
     name = &ext_p->name[u + 1];
+
     /* find the extension definition there */
-    for (ext = NULL, u = 0; u < LY_ARRAY_SIZE(mod->parsed->extensions); ++u) {
-        if (!strcmp(name, mod->parsed->extensions[u].name)) {
-            edef = &mod->parsed->extensions[u];
+    if (mod->off_extensions) {
+        elist = mod->off_extensions;
+    } else {
+        elist = mod->compiled->extensions;
+    }
+    LY_ARRAY_FOR(elist, u) {
+        if (!strcmp(name, elist[u].name)) {
+            ext->def = &elist[u];
             break;
         }
     }
-    LY_CHECK_ERR_RET(!edef, LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
-                                   "Extension definition of extension instance \"%s\" not found.", ext_p->name),
+    LY_CHECK_ERR_RET(!ext->def,
+                     LOGVAL(ctx->ctx, LY_VLOG_STR, ctx->path, LYVE_REFERENCE,
+                            "Extension definition of extension instance \"%s\" not found.", ext_p->name),
                      LY_EVALID);
-    /* TODO extension plugins */
+
+    if (ext->def->plugin && ext->def->plugin->compile) {
+        LY_CHECK_RET(ext->def->plugin->compile(ctx, ext_p, ext),LY_EVALID);
+    }
+
+    return LY_SUCCESS;
+}
+
+/**
+ * @brief Fill in the prepared compiled extensions definition structure according to the parsed extension definition.
+ */
+static LY_ERR
+lys_compile_extension(struct lysc_ctx *ctx, struct lysp_ext *ext_p, struct lysc_ext *ext)
+{
+    LY_ERR ret = LY_SUCCESS;
+
+    DUP_STRING(ctx->ctx, ext_p->name, ext->name);
+    DUP_STRING(ctx->ctx, ext_p->argument, ext->argument);
+    ext->module = ctx->mod_def;
+    COMPILE_EXTS_GOTO(ctx, ext_p->exts, ext->exts, ext, LYEXT_PAR_EXT, ret, done);
+
+done:
+    return ret;
+}
+
+/**
+ * @brief Link the extensions definitions with the available extension plugins.
+ *
+ * This is done only in the compiled (implemented) module. Extensions of a non-implemented modules
+ * are not connected with even available extension plugins.
+ *
+ * @param[in] extensions List of extensions to be processed ([sized array](@ref sizedarrays)).
+ */
+static void
+lys_compile_extension_plugins(struct lysc_ext *extensions)
+{
+    unsigned int u;
+
+    LY_ARRAY_FOR(extensions, u) {
+        extensions[u].plugin = lyext_get_plugin(&extensions[u]);
+    }
+}
+
+LY_ERR
+lys_extension_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module,
+                         struct lysp_ext *extensions_p, struct lysc_ext **extensions)
+{
+    unsigned int offset = 0, u;
+    struct lysc_ctx context = {0};
+
+    assert(ctx_sc || ctx);
+
+    if (!ctx_sc) {
+        context.ctx = ctx;
+        context.mod = module;
+        context.path_len = 1;
+        context.path[0] = '/';
+        ctx_sc = &context;
+    }
+
+    if (!extensions_p) {
+        return LY_SUCCESS;
+    }
+    if (*extensions) {
+        offset = LY_ARRAY_SIZE(*extensions);
+    }
+
+    lysc_update_path(ctx_sc, NULL, "{extension}");
+    LY_ARRAY_CREATE_RET(ctx_sc->ctx, *extensions, LY_ARRAY_SIZE(extensions_p), LY_EMEM);
+    LY_ARRAY_FOR(extensions_p, u) {
+        lysc_update_path(ctx_sc, NULL, extensions_p[u].name);
+        LY_ARRAY_INCREMENT(*extensions);
+        COMPILE_CHECK_UNIQUENESS(ctx_sc, *extensions, name, &(*extensions)[offset + u], "extension", extensions_p[u].name);
+        LY_CHECK_RET(lys_compile_extension(ctx_sc, &extensions_p[u], &(*extensions)[offset + u]));
+        lysc_update_path(ctx_sc, NULL, NULL);
+    }
+    lysc_update_path(ctx_sc, NULL, NULL);
 
     return LY_SUCCESS;
 }
@@ -662,7 +751,6 @@
 static LY_ERR
 lys_compile_when(struct lysc_ctx *ctx, struct lysp_when *when_p, struct lysc_when **when)
 {
-    unsigned int u;
     LY_ERR ret = LY_SUCCESS;
 
     *when = calloc(1, sizeof **when);
@@ -671,7 +759,7 @@
     DUP_STRING(ctx->ctx, when_p->dsc, (*when)->dsc);
     DUP_STRING(ctx->ctx, when_p->ref, (*when)->ref);
     LY_CHECK_ERR_GOTO(!(*when)->cond, ret = ly_errcode(ctx->ctx), done);
-    COMPILE_ARRAY_GOTO(ctx, when_p->exts, (*when)->exts, u, lys_compile_ext, ret, done);
+    COMPILE_EXTS_GOTO(ctx, when_p->exts, (*when)->exts, (*when), LYEXT_PAR_WHEN, ret, done);
 
 done:
     return ret;
@@ -687,7 +775,6 @@
 static LY_ERR
 lys_compile_must(struct lysc_ctx *ctx, struct lysp_restr *must_p, struct lysc_must *must)
 {
-    unsigned int u;
     LY_ERR ret = LY_SUCCESS;
 
     must->cond = lyxp_expr_parse(ctx->ctx, must_p->arg);
@@ -697,7 +784,7 @@
     DUP_STRING(ctx->ctx, must_p->emsg, must->emsg);
     DUP_STRING(ctx->ctx, must_p->dsc, must->dsc);
     DUP_STRING(ctx->ctx, must_p->ref, must->ref);
-    COMPILE_ARRAY_GOTO(ctx, must_p->exts, must->exts, u, lys_compile_ext, ret, done);
+    COMPILE_EXTS_GOTO(ctx, must_p->exts, must->exts, must, LYEXT_PAR_MUST, ret, done);
 
 done:
     return ret;
@@ -713,12 +800,11 @@
 static LY_ERR
 lys_compile_import(struct lysc_ctx *ctx, struct lysp_import *imp_p, struct lysc_import *imp)
 {
-    unsigned int u;
     struct lys_module *mod = NULL;
     LY_ERR ret = LY_SUCCESS;
 
     DUP_STRING(ctx->ctx, imp_p->prefix, imp->prefix);
-    COMPILE_ARRAY_GOTO(ctx, imp_p->exts, imp->exts, u, lys_compile_ext, ret, done);
+    COMPILE_EXTS_GOTO(ctx, imp_p->exts, imp->exts, imp, LYEXT_PAR_IMPORT, ret, done);
     imp->module = imp_p->module;
 
     /* make sure that we have the parsed version (lysp_) of the imported module to import groupings or typedefs.
@@ -774,7 +860,7 @@
     ident->module = ctx->mod;
     COMPILE_ARRAY_GOTO(ctx, ident_p->iffeatures, ident->iffeatures, u, lys_compile_iffeature, ret, done);
     /* backlings (derived) can be added no sooner than when all the identities in the current module are present */
-    COMPILE_ARRAY_GOTO(ctx, ident_p->exts, ident->exts, u, lys_compile_ext, ret, done);
+    COMPILE_EXTS_GOTO(ctx, ident_p->exts, ident->exts, ident, LYEXT_PAR_IDENT, ret, done);
     ident->flags = ident_p->flags;
 
     lysc_update_path(ctx, NULL, NULL);
@@ -1069,7 +1155,7 @@
         lysc_update_path(ctx, NULL, feature_p->name);
 
         /* finish compilation started in lys_feature_precompile() */
-        COMPILE_ARRAY_GOTO(ctx, feature_p->exts, feature->exts, u, lys_compile_ext, ret, done);
+        COMPILE_EXTS_GOTO(ctx, feature_p->exts, feature->exts, feature, LYEXT_PAR_FEATURE, ret, done);
         COMPILE_ARRAY_GOTO(ctx, feature_p->iffeatures, feature->iffeatures, u, lys_compile_iffeature, ret, done);
         if (feature->iffeatures) {
             for (u = 0; u < LY_ARRAY_SIZE(feature->iffeatures); ++u) {
@@ -1866,7 +1952,7 @@
                           struct lysc_pattern **base_patterns, struct lysc_pattern ***patterns)
 {
     struct lysc_pattern **pattern;
-    unsigned int u, v;
+    unsigned int u;
     LY_ERR ret = LY_SUCCESS;
 
     /* first, copy the patterns from the base type */
@@ -1891,7 +1977,7 @@
         DUP_STRING(ctx->ctx, patterns_p[u].emsg, (*pattern)->emsg);
         DUP_STRING(ctx->ctx, patterns_p[u].dsc, (*pattern)->dsc);
         DUP_STRING(ctx->ctx, patterns_p[u].ref, (*pattern)->ref);
-        COMPILE_ARRAY_GOTO(ctx, patterns_p[u].exts, (*pattern)->exts, v, lys_compile_ext, ret, done);
+        COMPILE_EXTS_GOTO(ctx, patterns_p[u].exts, (*pattern)->exts, (*pattern), LYEXT_PAR_PATTERN, ret, done);
     }
 done:
     return ret;
@@ -2062,7 +2148,7 @@
         }
 
         COMPILE_ARRAY_GOTO(ctx, enums_p[u].iffeatures, e->iffeatures, v, lys_compile_iffeature, ret, done);
-        COMPILE_ARRAY_GOTO(ctx, enums_p[u].exts, e->exts, v, lys_compile_ext, ret, done);
+        COMPILE_EXTS_GOTO(ctx, enums_p[u].exts, e->exts, e, basetype == LY_TYPE_ENUM ? LYEXT_PAR_TYPE_ENUM : LYEXT_PAR_TYPE_BIT, ret, done);
 
         if (basetype == LY_TYPE_BITS) {
             /* keep bits ordered by position */
@@ -2618,7 +2704,7 @@
             LY_CHECK_RET(lys_compile_type_range(ctx, type_p->length, basetype, 1, 0,
                                                 base ? ((struct lysc_type_bin*)base)->length : NULL, &bin->length));
             if (!tpdfname) {
-                COMPILE_ARRAY_GOTO(ctx, type_p->length->exts, bin->length->exts, u, lys_compile_ext, ret, done);
+                COMPILE_EXTS_GOTO(ctx, type_p->length->exts, bin->length->exts, bin->length, LYEXT_PAR_LENGTH, ret, done);
             }
         }
 
@@ -2689,7 +2775,7 @@
             LY_CHECK_RET(lys_compile_type_range(ctx, type_p->range, basetype, 0, dec->fraction_digits,
                                                 base ? ((struct lysc_type_dec*)base)->range : NULL, &dec->range));
             if (!tpdfname) {
-                COMPILE_ARRAY_GOTO(ctx, type_p->range->exts, dec->range->exts, u, lys_compile_ext, ret, done);
+                COMPILE_EXTS_GOTO(ctx, type_p->range->exts, dec->range->exts, dec->range, LYEXT_PAR_RANGE, ret, done);
             }
         }
 
@@ -2706,7 +2792,7 @@
             LY_CHECK_RET(lys_compile_type_range(ctx, type_p->length, basetype, 1, 0,
                                                 base ? ((struct lysc_type_str*)base)->length : NULL, &str->length));
             if (!tpdfname) {
-                COMPILE_ARRAY_GOTO(ctx, type_p->length->exts, str->length->exts, u, lys_compile_ext, ret, done);
+                COMPILE_EXTS_GOTO(ctx, type_p->length->exts, str->length->exts, str->length, LYEXT_PAR_LENGTH, ret, done);
             }
         } else if (base && ((struct lysc_type_str*)base)->length) {
             str->length = lysc_range_dup(ctx->ctx, ((struct lysc_type_str*)base)->length);
@@ -2766,7 +2852,7 @@
             LY_CHECK_RET(lys_compile_type_range(ctx, type_p->range, basetype, 0, 0,
                                                 base ? ((struct lysc_type_num*)base)->range : NULL, &num->range));
             if (!tpdfname) {
-                COMPILE_ARRAY_GOTO(ctx, type_p->range->exts, num->range->exts, u, lys_compile_ext, ret, done);
+                COMPILE_EXTS_GOTO(ctx, type_p->range->exts, num->range->exts, num->range, LYEXT_PAR_RANGE, ret, done);
             }
         }
 
@@ -3213,7 +3299,7 @@
         LY_CHECK_GOTO(ret, cleanup);
     }
 
-    COMPILE_ARRAY_GOTO(ctx, type_p->exts, (*type)->exts, u, lys_compile_ext, ret, cleanup);
+    COMPILE_EXTS_GOTO(ctx, type_p->exts, (*type)->exts, (*type), LYEXT_PAR_TYPE, ret, cleanup);
 
 cleanup:
     ly_set_erase(&tpdf_chain, free);
@@ -3364,12 +3450,12 @@
     DUP_STRING(ctx->ctx, action_p->dsc, action->dsc);
     DUP_STRING(ctx->ctx, action_p->ref, action->ref);
     COMPILE_ARRAY_GOTO(ctx, action_p->iffeatures, action->iffeatures, u, lys_compile_iffeature, ret, cleanup);
-    COMPILE_ARRAY_GOTO(ctx, action_p->exts, action->exts, u, lys_compile_ext, ret, cleanup);
+    COMPILE_EXTS_GOTO(ctx, action_p->exts, action->exts, action, LYEXT_PAR_NODE, ret, cleanup);
 
     /* input */
     lysc_update_path(ctx, (struct lysc_node*)action, "input");
     COMPILE_ARRAY_GOTO(ctx, action_p->input.musts, action->input.musts, u, lys_compile_must, ret, cleanup);
-    COMPILE_ARRAY_GOTO(ctx, action_p->input.exts, action->input_exts, u, lys_compile_ext, ret, cleanup);
+    COMPILE_EXTS_GOTO(ctx, action_p->input.exts, action->input_exts, &action->input, LYEXT_PAR_INPUT, ret, cleanup);
     ctx->options |= LYSC_OPT_RPC_INPUT;
     LY_LIST_FOR(action_p->input.data, child_p) {
         LY_CHECK_RET(lys_compile_node(ctx, child_p, (struct lysc_node*)action, uses_status));
@@ -3380,7 +3466,7 @@
     /* output */
     lysc_update_path(ctx, (struct lysc_node*)action, "output");
     COMPILE_ARRAY_GOTO(ctx, action_p->output.musts, action->output.musts, u, lys_compile_must, ret, cleanup);
-    COMPILE_ARRAY_GOTO(ctx, action_p->output.exts, action->output_exts, u, lys_compile_ext, ret, cleanup);
+    COMPILE_EXTS_GOTO(ctx, action_p->output.exts, action->output_exts, &action->output, LYEXT_PAR_OUTPUT, ret, cleanup);
     ctx->options |= LYSC_OPT_RPC_OUTPUT;
     LY_LIST_FOR(action_p->output.data, child_p) {
         LY_CHECK_RET(lys_compile_node(ctx, child_p, (struct lysc_node*)action, uses_status));
@@ -3444,8 +3530,8 @@
     DUP_STRING(ctx->ctx, notif_p->dsc, notif->dsc);
     DUP_STRING(ctx->ctx, notif_p->ref, notif->ref);
     COMPILE_ARRAY_GOTO(ctx, notif_p->iffeatures, notif->iffeatures, u, lys_compile_iffeature, ret, cleanup);
-    COMPILE_ARRAY_GOTO(ctx, notif_p->exts, notif->exts, u, lys_compile_ext, ret, cleanup);
     COMPILE_ARRAY_GOTO(ctx, notif_p->musts, notif->musts, u, lys_compile_must, ret, cleanup);
+    COMPILE_EXTS_GOTO(ctx, notif_p->exts, notif->exts, notif, LYEXT_PAR_NODE, ret, cleanup);
 
     ctx->options |= LYSC_OPT_NOTIFICATION;
     LY_LIST_FOR(notif_p->data, child_p) {
@@ -5383,11 +5469,12 @@
         (*when)->context = lysc_xpath_context(node);
     }
     COMPILE_ARRAY_GOTO(ctx, node_p->iffeatures, node->iffeatures, u, lys_compile_iffeature, ret, error);
-    COMPILE_ARRAY_GOTO(ctx, node_p->exts, node->exts, u, lys_compile_ext, ret, error);
 
     /* nodetype-specific part */
     LY_CHECK_GOTO(node_compile_spec(ctx, node_p, node), error);
 
+    COMPILE_EXTS_GOTO(ctx, node_p->exts, node->exts, node, LYEXT_PAR_NODE, ret, error);
+
     /* inherit LYS_MAND_TRUE in parent containers */
     if (node->flags & LYS_MAND_TRUE) {
         lys_compile_mandatory_parents(parent, 1);
@@ -6520,8 +6607,12 @@
         /* features are compiled directly into the compiled module structure,
          * but it must be done in two steps to allow forward references (via if-feature) between the features themselves.
          * The features compilation is finished in the main module (lys_compile()). */
-        ret = lys_feature_precompile(ctx, NULL, NULL, submod->features,
-                                     mainmod->mod->off_features ? &mainmod->mod->off_features : &mainmod->features);
+        ret = lys_feature_precompile(ctx, NULL, NULL, submod->features, &mainmod->features);
+        LY_CHECK_GOTO(ret, error);
+    }
+    if (!mainmod->mod->off_extensions) {
+        /* extensions are compiled directly into the compiled module structure, compilation is finished in the main module (lys_compile()). */
+        ret = lys_extension_precompile(ctx, NULL, NULL, submod->extensions, &mainmod->extensions);
         LY_CHECK_GOTO(ret, error);
     }
 
@@ -6581,6 +6672,8 @@
         ret = lys_compile_submodule(&ctx, &sp->includes[u]);
         LY_CHECK_GOTO(ret != LY_SUCCESS, error);
     }
+
+    /* features */
     if (mod->off_features) {
         /* there is already precompiled array of features */
         mod_c->features = mod->off_features;
@@ -6609,6 +6702,20 @@
     }
     lysc_update_path(&ctx, NULL, NULL);
 
+    /* extensions */
+    /* 2-steps: a) prepare compiled structures and ... */
+    if (mod->off_extensions) {
+        /* there is already precompiled array of extension definitions */
+        mod_c->extensions = mod->off_extensions;
+        mod->off_extensions = NULL;
+    } else {
+        /* extension definitions are compiled directly into the compiled module structure */
+        ret = lys_extension_precompile(&ctx, NULL, NULL, sp->extensions, &mod_c->extensions);
+    }
+    /* ... b) connect the extension definitions with the appropriate extension plugins */
+    lys_compile_extension_plugins(mod_c->extensions);
+
+    /* identities */
     lysc_update_path(&ctx, NULL, "{identity}");
     COMPILE_ARRAY_UNIQUE_GOTO(&ctx, sp->identities, mod_c->identities, u, lys_compile_identity, ret, error);
     if (sp->identities) {
@@ -6637,7 +6744,8 @@
     ret = lys_compile_deviations(&ctx, sp);
     LY_CHECK_GOTO(ret, error);
 
-    COMPILE_ARRAY_GOTO(&ctx, sp->exts, mod_c->exts, u, lys_compile_ext, ret, error);
+    /* extension instances TODO cover extension instances from submodules */
+    COMPILE_EXTS_GOTO(&ctx, sp->exts, mod_c->exts, mod_c, LYEXT_PAR_MODULE, ret, error);
 
     /* validate leafref's paths and when/must xpaths */
     /* for leafref, we need 2 rounds - first detects circular chain by storing the first referred type (which
diff --git a/src/tree_schema_free.c b/src/tree_schema_free.c
index 20112ca..68c8252 100644
--- a/src/tree_schema_free.c
+++ b/src/tree_schema_free.c
@@ -472,6 +472,14 @@
 }
 
 void
+lysc_extension_free(struct ly_ctx *ctx, struct lysc_ext *ext)
+{
+    FREE_STRING(ctx, ext->name);
+    FREE_STRING(ctx, ext->argument);
+    FREE_ARRAY(ctx, ext->exts, lysc_ext_instance_free);
+}
+
+void
 lysc_iffeature_free(struct ly_ctx *UNUSED(ctx), struct lysc_iffeature *iff)
 {
     LY_ARRAY_FREE(iff->features);
@@ -822,6 +830,7 @@
     FREE_ARRAY(ctx, module->rpcs, lysc_action_free);
     FREE_ARRAY(ctx, module->notifs, lysc_notif_free);
 
+    FREE_ARRAY(ctx, module->extensions, lysc_extension_free);
     FREE_ARRAY(ctx, module->exts, lysc_ext_instance_free);
 
     free(module);
@@ -847,6 +856,7 @@
 
     lysc_module_free(module->compiled, private_destructor);
     FREE_ARRAY(module->ctx, module->off_features, lysc_feature_free);
+    FREE_ARRAY(module->ctx, module->off_extensions, lysc_extension_free);
     lysp_module_free(module->parsed);
 
     FREE_STRING(module->ctx, module->name);
diff --git a/src/tree_schema_helpers.c b/src/tree_schema_helpers.c
index 5e931a7..46d7801 100644
--- a/src/tree_schema_helpers.c
+++ b/src/tree_schema_helpers.c
@@ -26,9 +26,9 @@
 
 #include "context.h"
 #include "dict.h"
-#include "extensions.h"
 #include "hash_table.h"
 #include "log.h"
+#include "plugins_exts.h"
 #include "set.h"
 #include "tree.h"
 #include "tree_schema.h"
@@ -1074,7 +1074,7 @@
     return NULL;
 }
 
-const char *
+API const char *
 lys_nodetype2str(uint16_t nodetype)
 {
     switch(nodetype) {
diff --git a/src/tree_schema_internal.h b/src/tree_schema_internal.h
index 33f5c7c..1ff883d 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -17,6 +17,7 @@
 
 #include <stdint.h>
 
+#include "plugins_exts.h"
 #include "set.h"
 #include "tree_schema.h"
 #include "xml.h"
@@ -127,26 +128,6 @@
 };
 
 /**
- * @brief internal context for compilation
- */
-struct lysc_ctx {
-    struct ly_ctx *ctx;
-    struct lys_module *mod;
-    struct lys_module *mod_def; /**< context module for the definitions of the nodes being currently
-                                     processed - groupings are supposed to be evaluated in place where
-                                     defined, but its content instances are supposed to be placed into
-                                     the target module (mod) */
-    struct ly_set groupings;    /**< stack for groupings circular check */
-    struct ly_set unres;        /**< to validate leafref's target and xpath of when/must */
-    struct ly_set dflts;        /**< set of incomplete default values */
-    struct ly_set tpdf_chain;
-    uint16_t path_len;
-    int options;                /**< various @ref scflags. */
-#define LYSC_CTX_BUFSIZE 4078
-    char path[LYSC_CTX_BUFSIZE];
-};
-
-/**
  * @brief Check that \p c is valid UTF8 code point for YANG string.
  *
  * @param[in] ctx yang parser context for logging.
@@ -494,13 +475,6 @@
 const char *lys_prefix_find_module(const struct lys_module *mod, const struct lys_module *import);
 
 /**
- * @brief Stringify schema nodetype.
- * @param[in] nodetype Nodetype to stringify.
- * @return Constant string with the name of the node's type.
- */
-const char *lys_nodetype2str(uint16_t nodetype);
-
-/**
  * @brief Stringify YANG built-in type.
  * @param[in] basetype Built-in tyep ID to stringify.
  * @return Constant string with the name of the built-in type.
@@ -658,13 +632,14 @@
  * @param[in] ctx_sc Compile context - alternative to the combination of @p ctx and @p module.
  * @param[in] ctx libyang context.
  * @param[in] module Module of the features.
- * @param[in] features_p Array if the parsed features definitions to precompile.
+ * @param[in] features_p Array of the parsed features definitions to precompile.
  * @param[in,out] features Pointer to the storage of the (pre)compiled features array where the new features are
  * supposed to be added. The storage is supposed to be initiated to NULL when the first parsed features are going
  * to be processed.
  * @return LY_ERR value.
  */
-LY_ERR lys_feature_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module, struct lysp_feature *features_p, struct lysc_feature **features);
+LY_ERR lys_feature_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module,
+                              struct lysp_feature *features_p, struct lysc_feature **features);
 
 /**
  * @brief Get the @ref ifftokens from the given position in the 2bits array
@@ -675,6 +650,21 @@
 uint8_t lysc_iff_getop(uint8_t *list, int pos);
 
 /**
+ * @brief Internal wrapper around lys_compile_extension() to be able to prepare list of compiled extension definition
+ * even for the parsed (not-implemented) module - see lys_module::off_extensions.
+ *
+ * @param[in] ctx_sc Compile context - alternative to the combination of @p ctx and @p module.
+ * @param[in] ctx libyang context.
+ * @param[in] module Module of the extensions.
+ * @param[in] extensions_p Array of the parsed extension definitions to precompile.
+ * @param[in,out] extensions Pointer to the storage of the (pre)compiled extensions array where the new extensions are
+ * supposed to be added. The storage is supposed to be initiated to NULL when the first parsed extensions are going
+ * to be processed.
+ * @return LY_ERR value.
+ */
+LY_ERR lys_extension_precompile(struct lysc_ctx *ctx_sc, struct ly_ctx *ctx, struct lys_module *module,
+                                struct lysp_ext *extensions_p, struct lysc_ext **extensions);
+/**
  * @brief Macro to free [sized array](@ref sizedarrays) of items using the provided free function. The ARRAY itself is also freed,
  * but the memory is not sanitized.
  */
diff --git a/tests/config.h.in b/tests/config.h.in
index a7a5b7e..5943c07 100644
--- a/tests/config.h.in
+++ b/tests/config.h.in
@@ -3,7 +3,7 @@
  * @author Radek Krejci <rkrejci@cesnet.cz>
  * @brief cmocka tests configuration header.
  *
- * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
+ * Copyright (c) 2015 - 2098 CESNET, z.s.p.o.
  *
  * This source code is licensed under BSD 3-Clause License (the "License").
  * You may not use this file except in compliance with the License.
@@ -19,4 +19,6 @@
 #define TESTS_SRC "@CMAKE_CURRENT_SOURCE_DIR@"
 #define TESTS_BIN "@CMAKE_CURRENT_BINARY_DIR@"
 
+#define TESTS_DIR_MODULES_YANG TESTS_SRC"/modules/yang"
+
 #endif /* LYTEST_CONFIG_H_ */
diff --git a/tests/features/CMakeLists.txt b/tests/features/CMakeLists.txt
index 00f3423..20c5d94 100644
--- a/tests/features/CMakeLists.txt
+++ b/tests/features/CMakeLists.txt
@@ -1,6 +1,8 @@
 set(local_tests
-    features_types)
+    features_types
+    features_nacm)
 set(local_tests_wraps
+    " "
     " ")
 set(tests ${tests} ${local_tests} PARENT_SCOPE)
 set(tests_wraps ${tests_wraps} ${local_tests_wraps} PARENT_SCOPE)
diff --git a/tests/features/test_nacm.c b/tests/features/test_nacm.c
new file mode 100644
index 0000000..87882da
--- /dev/null
+++ b/tests/features/test_nacm.c
@@ -0,0 +1,204 @@
+/*
+ * @file test_nacm.c
+ * @author: Radek Krejci <rkrejci@cesnet.cz>
+ * @brief unit tests for NACM extensions support
+ *
+ * Copyright (c) 2019 CESNET, z.s.p.o.
+ *
+ * This source code is licensed under BSD 3-Clause License (the "License").
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://opensource.org/licenses/BSD-3-Clause
+ */
+
+#include "tests/config.h"
+
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <cmocka.h>
+
+#include <stdio.h>
+#include <string.h>
+
+#include "../../src/libyang.h"
+
+#define BUFSIZE 1024
+char logbuf[BUFSIZE] = {0};
+int store = -1; /* negative for infinite logging, positive for limited logging */
+
+struct state_s {
+    void *func;
+    struct ly_ctx *ctx;
+};
+
+/* set to 0 to printing error messages to stderr instead of checking them in code */
+#define ENABLE_LOGGER_CHECKING 1
+
+#if ENABLE_LOGGER_CHECKING
+static void
+logger(LY_LOG_LEVEL level, const char *msg, const char *path)
+{
+    (void) level; /* unused */
+    if (store) {
+        if (path && path[0]) {
+            snprintf(logbuf, BUFSIZE - 1, "%s %s", msg, path);
+        } else {
+            strncpy(logbuf, msg, BUFSIZE - 1);
+        }
+        if (store > 0) {
+            --store;
+        }
+    }
+}
+#endif
+
+static int
+setup(void **state)
+{
+    struct state_s *s;
+
+    s = calloc(1, sizeof *s);
+    assert_non_null(s);
+
+#if ENABLE_LOGGER_CHECKING
+    ly_set_log_clb(logger, 1);
+#endif
+
+    assert_int_equal(LY_SUCCESS, ly_ctx_new(TESTS_DIR_MODULES_YANG, 0, &s->ctx));
+    assert_non_null(ly_ctx_load_module(s->ctx, "ietf-netconf-acm", "2018-02-14"));
+
+    *state = s;
+
+    return 0;
+}
+
+static int
+teardown(void **state)
+{
+    struct state_s *s = (struct state_s*)(*state);
+
+#if ENABLE_LOGGER_CHECKING
+    if (s->func) {
+        fprintf(stderr, "%s\n", logbuf);
+    }
+#endif
+
+    ly_ctx_destroy(s->ctx, NULL);
+    free(s);
+
+    return 0;
+}
+
+void
+logbuf_clean(void)
+{
+    logbuf[0] = '\0';
+}
+
+#if ENABLE_LOGGER_CHECKING
+#   define logbuf_assert(str) assert_string_equal(logbuf, str)
+#else
+#   define logbuf_assert(str)
+#endif
+
+static void
+test_deny_all(void **state)
+{
+    struct state_s *s = (struct state_s*)(*state);
+    s->func = test_deny_all;
+
+    struct lys_module *mod;
+    struct lysc_node_container *cont;
+    struct lysc_node_leaf *leaf;
+    struct lysc_ext_instance *e;
+
+    const char *data = "module a {yang-version 1.1; namespace urn:tests:extensions:nacm:a; prefix en;"
+            "import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
+            "container a { nacm:default-deny-all; leaf aa {type string;}}"
+            "leaf b {type string;}}";
+
+    /* valid data */
+    assert_non_null(mod = lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+    assert_non_null(cont = (struct lysc_node_container*)mod->compiled->data);
+    assert_non_null(leaf = (struct lysc_node_leaf*)cont->child);
+    assert_non_null(e = &cont->exts[0]);
+    assert_int_equal(LY_ARRAY_SIZE(cont->exts), 1);
+    assert_int_equal(LY_ARRAY_SIZE(leaf->exts), 1); /* NACM extensions inherit */
+    assert_ptr_equal(e->def, leaf->exts[0].def);
+    assert_int_equal(1, *((uint8_t*)e->data)); /* plugin's value for default-deny-all */
+    assert_null(cont->next->exts);
+
+    /* invalid */
+    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;"
+            "import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
+            "nacm:default-deny-all;}";
+    assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+    logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
+            "Extension nacm:default-deny-all is allowed only in a data nodes, but it is placed in \"module\" statement.) /");
+
+    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;"
+            "import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
+            "leaf l { type string; nacm:default-deny-all; nacm:default-deny-write;}}";
+    assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+    logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
+            "Extension nacm:default-deny-write is mixed with nacm:default-deny-all.) /aa:l");
+
+    s->func = NULL;
+}
+
+static void
+test_deny_write(void **state)
+{
+    struct state_s *s = (struct state_s*)(*state);
+    s->func = test_deny_write;
+
+    struct lys_module *mod;
+    struct lysc_node_container *cont;
+    struct lysc_node_leaf *leaf;
+    struct lysc_ext_instance *e;
+
+    const char *data = "module a {yang-version 1.1; namespace urn:tests:extensions:nacm:a; prefix en;"
+            "import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
+            "container a { nacm:default-deny-write; leaf aa {type string;}}"
+            "leaf b {type string;}}";
+
+    /* valid data */
+    assert_non_null(mod = lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+    assert_non_null(cont = (struct lysc_node_container*)mod->compiled->data);
+    assert_non_null(leaf = (struct lysc_node_leaf*)cont->child);
+    assert_non_null(e = &cont->exts[0]);
+    assert_int_equal(LY_ARRAY_SIZE(cont->exts), 1);
+    assert_int_equal(LY_ARRAY_SIZE(leaf->exts), 1); /* NACM extensions inherit */
+    assert_ptr_equal(e->def, leaf->exts[0].def);
+    assert_int_equal(2, *((uint8_t*)e->data)); /* plugin's value for default-deny-write */
+    assert_null(cont->next->exts);
+
+    /* invalid */
+    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;"
+            "import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
+            "notification notif {nacm:default-deny-write;}}";
+    assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+    logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
+            "Extension nacm:default-deny-write is not allowed in Notification statement.) /aa:notif");
+
+    data = "module aa {yang-version 1.1; namespace urn:tests:extensions:nacm:aa; prefix en;"
+            "import ietf-netconf-acm {revision-date 2018-02-14; prefix nacm;}"
+            "leaf l { type string; nacm:default-deny-write; nacm:default-deny-write;}}";
+    assert_null(lys_parse_mem(s->ctx, data, LYS_IN_YANG));
+    logbuf_assert("Extension plugin \"libyang 2 - NACM, version 1\": "
+            "Extension nacm:default-deny-write is instantiated multiple times.) /aa:l");
+
+    s->func = NULL;
+}
+
+int main(void)
+{
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_deny_all, setup, teardown),
+        cmocka_unit_test_setup_teardown(test_deny_write, setup, teardown),
+    };
+
+    return cmocka_run_group_tests(tests, NULL, NULL);
+}
diff --git a/tests/features/test_types.c b/tests/features/test_types.c
index b60b4eb..f4dd506 100644
--- a/tests/features/test_types.c
+++ b/tests/features/test_types.c
@@ -1,7 +1,7 @@
 /*
- * @file test_parser_xml.c
+ * @file test_types.c
  * @author: Radek Krejci <rkrejci@cesnet.cz>
- * @brief unit tests for functions from parser_xml.c
+ * @brief unit tests for support of YANG data types
  *
  * Copyright (c) 2019 CESNET, z.s.p.o.
  *
diff --git a/tests/modules/yang/ietf-netconf-acm@2018-02-14.yang b/tests/modules/yang/ietf-netconf-acm@2018-02-14.yang
new file mode 100644
index 0000000..bf4855f
--- /dev/null
+++ b/tests/modules/yang/ietf-netconf-acm@2018-02-14.yang
@@ -0,0 +1,464 @@
+module ietf-netconf-acm {
+
+  namespace "urn:ietf:params:xml:ns:yang:ietf-netconf-acm";
+
+  prefix nacm;
+
+  import ietf-yang-types {
+    prefix yang;
+  }
+
+  organization
+    "IETF NETCONF (Network Configuration) Working Group";
+
+  contact
+    "WG Web:   <https://datatracker.ietf.org/wg/netconf/>
+     WG List:  <mailto:netconf@ietf.org>
+
+     Author:   Andy Bierman
+               <mailto:andy@yumaworks.com>
+
+     Author:   Martin Bjorklund
+               <mailto:mbj@tail-f.com>";
+
+  description
+    "Network Configuration Access Control Model.
+
+     Copyright (c) 2012 - 2018 IETF Trust and the persons
+     identified as authors of the code.  All rights reserved.
+
+     Redistribution and use in source and binary forms, with or
+     without modification, is permitted pursuant to, and subject
+     to the license terms contained in, the Simplified BSD
+     License set forth in Section 4.c of the IETF Trust's
+     Legal Provisions Relating to IETF Documents
+     (https://trustee.ietf.org/license-info).
+
+     This version of this YANG module is part of RFC 8341; see
+     the RFC itself for full legal notices.";
+
+  revision "2018-02-14" {
+    description
+      "Added support for YANG 1.1 actions and notifications tied to
+       data nodes.  Clarified how NACM extensions can be used by
+       other data models.";
+    reference
+      "RFC 8341: Network Configuration Access Control Model";
+  }
+
+  revision "2012-02-22" {
+    description
+      "Initial version.";
+    reference
+      "RFC 6536: Network Configuration Protocol (NETCONF)
+                 Access Control Model";
+  }
+
+  /*
+   * Extension statements
+   */
+
+  extension default-deny-write {
+    description
+      "Used to indicate that the data model node
+       represents a sensitive security system parameter.
+
+       If present, the NETCONF server will only allow the designated
+       'recovery session' to have write access to the node.  An
+       explicit access control rule is required for all other users.
+
+       If the NACM module is used, then it must be enabled (i.e.,
+       /nacm/enable-nacm object equals 'true'), or this extension
+       is ignored.
+
+       The 'default-deny-write' extension MAY appear within a data
+       definition statement.  It is ignored otherwise.";
+  }
+
+  extension default-deny-all {
+    description
+      "Used to indicate that the data model node
+       controls a very sensitive security system parameter.
+
+       If present, the NETCONF server will only allow the designated
+       'recovery session' to have read, write, or execute access to
+       the node.  An explicit access control rule is required for all
+       other users.
+
+       If the NACM module is used, then it must be enabled (i.e.,
+       /nacm/enable-nacm object equals 'true'), or this extension
+       is ignored.
+
+       The 'default-deny-all' extension MAY appear within a data
+       definition statement, 'rpc' statement, or 'notification'
+       statement.  It is ignored otherwise.";
+  }
+
+  /*
+   * Derived types
+   */
+
+  typedef user-name-type {
+    type string {
+      length "1..max";
+    }
+    description
+      "General-purpose username string.";
+  }
+
+  typedef matchall-string-type {
+    type string {
+      pattern '\*';
+    }
+    description
+      "The string containing a single asterisk '*' is used
+       to conceptually represent all possible values
+       for the particular leaf using this data type.";
+  }
+
+  typedef access-operations-type {
+    type bits {
+      bit create {
+        description
+          "Any protocol operation that creates a
+           new data node.";
+      }
+      bit read {
+        description
+          "Any protocol operation or notification that
+           returns the value of a data node.";
+      }
+      bit update {
+        description
+          "Any protocol operation that alters an existing
+           data node.";
+      }
+      bit delete {
+        description
+          "Any protocol operation that removes a data node.";
+      }
+      bit exec {
+        description
+          "Execution access to the specified protocol operation.";
+      }
+    }
+    description
+      "Access operation.";
+  }
+
+  typedef group-name-type {
+    type string {
+      length "1..max";
+      pattern '[^\*].*';
+    }
+    description
+      "Name of administrative group to which
+       users can be assigned.";
+  }
+
+  typedef action-type {
+    type enumeration {
+      enum permit {
+        description
+          "Requested action is permitted.";
+      }
+      enum deny {
+        description
+          "Requested action is denied.";
+      }
+    }
+    description
+      "Action taken by the server when a particular
+       rule matches.";
+  }
+
+  typedef node-instance-identifier {
+    type yang:xpath1.0;
+    description
+      "Path expression used to represent a special
+       data node, action, or notification instance-identifier
+       string.
+
+       A node-instance-identifier value is an
+       unrestricted YANG instance-identifier expression.
+       All the same rules as an instance-identifier apply,
+       except that predicates for keys are optional.  If a key
+       predicate is missing, then the node-instance-identifier
+       represents all possible server instances for that key.
+
+       This XML Path Language (XPath) expression is evaluated in the
+       following context:
+
+          o  The set of namespace declarations are those in scope on
+             the leaf element where this type is used.
+
+          o  The set of variable bindings contains one variable,
+             'USER', which contains the name of the user of the
+             current session.
+
+          o  The function library is the core function library, but
+             note that due to the syntax restrictions of an
+             instance-identifier, no functions are allowed.
+
+          o  The context node is the root node in the data tree.
+
+       The accessible tree includes actions and notifications tied
+       to data nodes.";
+  }
+
+  /*
+   * Data definition statements
+   */
+
+  container nacm {
+    nacm:default-deny-all;
+
+    description
+      "Parameters for NETCONF access control model.";
+
+    leaf enable-nacm {
+      type boolean;
+      default "true";
+      description
+        "Enables or disables all NETCONF access control
+         enforcement.  If 'true', then enforcement
+         is enabled.  If 'false', then enforcement
+         is disabled.";
+    }
+
+    leaf read-default {
+      type action-type;
+      default "permit";
+      description
+        "Controls whether read access is granted if
+         no appropriate rule is found for a
+         particular read request.";
+    }
+
+    leaf write-default {
+      type action-type;
+      default "deny";
+      description
+        "Controls whether create, update, or delete access
+         is granted if no appropriate rule is found for a
+         particular write request.";
+    }
+
+    leaf exec-default {
+      type action-type;
+      default "permit";
+      description
+        "Controls whether exec access is granted if no appropriate
+         rule is found for a particular protocol operation request.";
+    }
+
+    leaf enable-external-groups {
+      type boolean;
+      default "true";
+      description
+        "Controls whether the server uses the groups reported by the
+         NETCONF transport layer when it assigns the user to a set of
+         NACM groups.  If this leaf has the value 'false', any group
+         names reported by the transport layer are ignored by the
+         server.";
+    }
+
+    leaf denied-operations {
+      type yang:zero-based-counter32;
+      config false;
+      mandatory true;
+      description
+        "Number of times since the server last restarted that a
+         protocol operation request was denied.";
+    }
+
+    leaf denied-data-writes {
+      type yang:zero-based-counter32;
+      config false;
+      mandatory true;
+      description
+        "Number of times since the server last restarted that a
+         protocol operation request to alter
+         a configuration datastore was denied.";
+    }
+
+    leaf denied-notifications {
+      type yang:zero-based-counter32;
+      config false;
+      mandatory true;
+      description
+        "Number of times since the server last restarted that
+         a notification was dropped for a subscription because
+         access to the event type was denied.";
+    }
+
+    container groups {
+      description
+        "NETCONF access control groups.";
+
+      list group {
+        key name;
+
+        description
+          "One NACM group entry.  This list will only contain
+           configured entries, not any entries learned from
+           any transport protocols.";
+
+        leaf name {
+          type group-name-type;
+          description
+            "Group name associated with this entry.";
+        }
+
+        leaf-list user-name {
+          type user-name-type;
+          description
+            "Each entry identifies the username of
+             a member of the group associated with
+             this entry.";
+        }
+      }
+    }
+
+    list rule-list {
+      key name;
+      ordered-by user;
+      description
+        "An ordered collection of access control rules.";
+
+      leaf name {
+        type string {
+          length "1..max";
+        }
+        description
+          "Arbitrary name assigned to the rule-list.";
+      }
+      leaf-list group {
+        type union {
+          type matchall-string-type;
+          type group-name-type;
+        }
+        description
+          "List of administrative groups that will be
+           assigned the associated access rights
+           defined by the 'rule' list.
+
+           The string '*' indicates that all groups apply to the
+           entry.";
+      }
+
+      list rule {
+        key name;
+        ordered-by user;
+        description
+          "One access control rule.
+
+           Rules are processed in user-defined order until a match is
+           found.  A rule matches if 'module-name', 'rule-type', and
+           'access-operations' match the request.  If a rule
+           matches, the 'action' leaf determines whether or not
+           access is granted.";
+
+        leaf name {
+          type string {
+            length "1..max";
+          }
+          description
+            "Arbitrary name assigned to the rule.";
+        }
+
+        leaf module-name {
+          type union {
+            type matchall-string-type;
+            type string;
+          }
+          default "*";
+          description
+            "Name of the module associated with this rule.
+
+             This leaf matches if it has the value '*' or if the
+             object being accessed is defined in the module with the
+             specified module name.";
+        }
+        choice rule-type {
+          description
+            "This choice matches if all leafs present in the rule
+             match the request.  If no leafs are present, the
+             choice matches all requests.";
+          case protocol-operation {
+            leaf rpc-name {
+              type union {
+                type matchall-string-type;
+                type string;
+              }
+              description
+                "This leaf matches if it has the value '*' or if
+                 its value equals the requested protocol operation
+                 name.";
+            }
+          }
+          case notification {
+            leaf notification-name {
+              type union {
+                type matchall-string-type;
+                type string;
+              }
+              description
+                "This leaf matches if it has the value '*' or if its
+                 value equals the requested notification name.";
+            }
+          }
+
+          case data-node {
+            leaf path {
+              type node-instance-identifier;
+              mandatory true;
+              description
+                "Data node instance-identifier associated with the
+                 data node, action, or notification controlled by
+                 this rule.
+
+                 Configuration data or state data
+                 instance-identifiers start with a top-level
+                 data node.  A complete instance-identifier is
+                 required for this type of path value.
+
+                 The special value '/' refers to all possible
+                 datastore contents.";
+            }
+          }
+        }
+
+        leaf access-operations {
+          type union {
+            type matchall-string-type;
+            type access-operations-type;
+          }
+          default "*";
+          description
+            "Access operations associated with this rule.
+
+             This leaf matches if it has the value '*' or if the
+             bit corresponding to the requested operation is set.";
+        }
+
+        leaf action {
+          type action-type;
+          mandatory true;
+          description
+            "The access control action associated with the
+             rule.  If a rule has been determined to match a
+             particular request, then this object is used
+             to determine whether to permit or deny the
+             request.";
+        }
+
+        leaf comment {
+          type string;
+          description
+            "A textual description of the access rule.";
+        }
+      }
+    }
+  }
+}