extensions FEATURE YANG extension plugins support with NACM as proof-of-concept
diff --git a/src/common.c b/src/common.c
index 56a8a3e..6f8cfe9 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/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 2547c99..34db6e5 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/extensions.h b/src/plugins_exts.h
similarity index 75%
rename from src/extensions.h
rename to src/plugins_exts.h
index 6b8076b..6684d6a 100644
--- a/src/extensions.h
+++ b/src/plugins_exts.h
@@ -1,9 +1,9 @@
 /**
- * @file extesnions.h
+ * @file plugins_exts.h
  * @author Radek Krejci <rkrejci@cesnet.cz>
  * @brief libyang support for YANG extensions implementation.
  *
- * Copyright (c) 2015 - 2018 CESNET, z.s.p.o.
+ * 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.
@@ -12,8 +12,8 @@
  *     https://opensource.org/licenses/BSD-3-Clause
  */
 
-#ifndef LY_EXTENSIONS_H_
-#define LY_EXTENSIONS_H_
+#ifndef LY_PLUGINS_EXTS_H_
+#define LY_PLUGINS_EXTS_H_
 
 #include "set.h"
 #include "tree_schema.h"
@@ -30,6 +30,17 @@
  */
 
 /**
+ * @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.
@@ -124,10 +135,33 @@
     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_TREE_SCHEMA_H_ */
+#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 c18f9b9..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.
  *
diff --git a/src/printer_yang.c b/src/printer_yang.c
index 8736639..a3242a6 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 d828c67..ad8e695 100644
--- a/src/tree_schema.c
+++ b/src/tree_schema.c
@@ -751,8 +751,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 */
@@ -797,8 +798,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 9ba9db3..050c7be 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -32,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 {
@@ -81,27 +165,39 @@
  * @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_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 {
@@ -915,10 +1011,9 @@
 struct lysc_ext {
     const char *name;                /**< extension name */
     const char *argument;            /**< argument name, NULL if not specified */
-    const char *dsc;                 /**< description statement */
-    const char *ref;                 /**< reference statement */
     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) */
 };
 
@@ -926,7 +1021,7 @@
  * @brief YANG extension instance
  */
 struct lysc_ext_instance {
-    struct lysc_ext *ext;            /**< pointer to the extension definition */
+    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 */
@@ -1445,6 +1540,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)) */
 };
 
@@ -1569,6 +1665,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:
@@ -1759,6 +1860,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 6259161..61019e2 100644
--- a/src/tree_schema_compile.c
+++ b/src/tree_schema_compile.c
@@ -24,9 +24,10 @@
 
 #include "dict.h"
 #include "log.h"
+#include "plugins_exts.h"
 #include "set.h"
 #include "plugins_types.h"
-#include "extensions.h"
+#include "plugins_exts_internal.h"
 #include "tree.h"
 #include "tree_schema.h"
 #include "tree_schema_internal.h"
@@ -62,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); \
@@ -414,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);
@@ -437,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;
 }
@@ -655,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);
@@ -664,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;
@@ -680,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);
@@ -690,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;
@@ -706,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.
@@ -767,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);
@@ -1062,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) {
@@ -1859,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 */
@@ -1884,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;
@@ -2055,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 */
@@ -2611,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);
             }
         }
 
@@ -2682,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);
             }
         }
 
@@ -2699,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);
@@ -2759,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);
             }
         }
 
@@ -3206,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);
@@ -3357,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));
@@ -3373,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));
@@ -3437,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) {
@@ -5376,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);
@@ -6513,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);
     }
 
@@ -6574,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;
@@ -6602,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) {
@@ -6630,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 adbc70b..64728f2 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 a3b201d..2f8a2b8 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"
@@ -925,7 +925,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 c81a9fc..8e9e029 100644
--- a/src/tree_schema_internal.h
+++ b/src/tree_schema_internal.h
@@ -17,9 +17,9 @@
 
 #include <stdint.h>
 
+#include "plugins_exts.h"
 #include "set.h"
 #include "tree_schema.h"
-#include "extensions.h"
 
 #define LOGVAL_YANG(CTX, ...) LOGVAL((CTX)->ctx, LY_VLOG_LINE, &(CTX)->line, __VA_ARGS__)
 
@@ -366,13 +366,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.
@@ -530,13 +523,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
@@ -547,6 +541,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.
  */