libyang NEW support for user type plugins

Refs #448
diff --git a/src/context.c b/src/context.c
index e9f8d87..8c0925e 100644
--- a/src/context.c
+++ b/src/context.c
@@ -95,7 +95,7 @@
     lydict_init(&ctx->dict);
 
     /* plugins */
-    lyext_load_plugins();
+    ly_load_plugins();
 
     /* initialize thread-specific key */
     while ((i = pthread_key_create(&ctx->errlist_key, ly_err_free)) == EAGAIN);
@@ -103,7 +103,6 @@
     /* models list */
     ctx->models.list = calloc(16, sizeof *ctx->models.list);
     LY_CHECK_ERR_RETURN(!ctx->models.list, LOGMEM(NULL); free(ctx), NULL);
-    ext_plugins_ref++;
     ctx->models.flags = options;
     ctx->models.used = 0;
     ctx->models.size = 16;
@@ -477,8 +476,7 @@
     lydict_clean(&ctx->dict);
 
     /* plugins - will be removed only if this is the last context */
-    ext_plugins_ref--;
-    lyext_clean_plugins();
+    ly_clean_plugins();
 
     free(ctx);
 }
diff --git a/src/extensions.c b/src/extensions.c
deleted file mode 100644
index e38f40b..0000000
--- a/src/extensions.c
+++ /dev/null
@@ -1,354 +0,0 @@
-/**
- * @file extensions.c
- * @author Radek Krejci <rkrejci@cesnet.cz>
- * @brief YANG extensions routines implementation
- *
- * Copyright (c) 2015 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
- */
-#define _GNU_SOURCE
-#include <assert.h>
-#include <errno.h>
-#include <dirent.h>
-#include <dlfcn.h>
-#include <pthread.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <sys/types.h>
-#include <limits.h>
-
-#include "common.h"
-#include "extensions.h"
-#include "extensions_config.h"
-#include "libyang.h"
-#include "parser.h"
-
-/* internal structures storing the extension plugins */
-struct lyext_plugin_list *ext_plugins = NULL;
-unsigned int ext_plugins_count = 0; /* size of the ext_plugins array */
-unsigned int ext_plugins_ref = 0;   /* number of contexts that may reference the ext_plugins */
-struct ly_set dlhandlers = {0, 0, {NULL}};
-pthread_mutex_t ext_lock = PTHREAD_MUTEX_INITIALIZER;
-
-/**
- * @brief reference counter for the ext_plugins, it actually counts number of contexts
- */
-unsigned int ext_plugins_references = 0;
-
-API int
-lyext_clean_plugins(void)
-{
-    unsigned int u;
-
-    if (ext_plugins_ref) {
-        /* there is a context that may refer to the plugins, so we cannot remove them */
-        return EXIT_FAILURE;
-    }
-
-    if (!ext_plugins_count) {
-        /* no plugin loaded - nothing to do */
-        return EXIT_SUCCESS;
-    }
-
-    /* lock the extension plugins list */
-    pthread_mutex_lock(&ext_lock);
-
-    /* clean the list */
-    free(ext_plugins);
-    ext_plugins = NULL;
-    ext_plugins_count = 0;
-
-    /* close the dl handlers */
-    for (u = 0; u < dlhandlers.number; u++) {
-        dlclose(dlhandlers.set.g[u]);
-    }
-    free(dlhandlers.set.g);
-    dlhandlers.set.g = NULL;
-    dlhandlers.size = 0;
-    dlhandlers.number = 0;
-
-    /* unlock the global structures */
-    pthread_mutex_unlock(&ext_lock);
-
-    return EXIT_SUCCESS;
-}
-
-API void
-lyext_load_plugins(void)
-{
-    DIR* dir;
-    struct dirent *file;
-    size_t len;
-    char *str;
-    char name[NAME_MAX];
-    void *dlhandler;
-    struct lyext_plugin_list *plugin, *p;
-    struct lyext_plugin_complex *pluginc;
-    unsigned int u, v;
-    const char *pluginsdir;
-
-    /* try to get the plugins directory from environment variable */
-    pluginsdir = getenv("LIBYANG_EXTENSIONS_PLUGINS_DIR");
-    if (!pluginsdir) {
-        pluginsdir = LYEXT_PLUGINS_DIR;
-    }
-
-    dir = opendir(pluginsdir);
-    if (!dir) {
-        /* no directory (or no access to it), no plugins */
-        LOGWRN(NULL, "libyang extensions plugins directory \"%s\" does not exist.", pluginsdir);
-        return;
-    }
-
-    /* lock the extension plugins list */
-    pthread_mutex_lock(&ext_lock);
-
-    while ((file = readdir(dir))) {
-        /* required format of the filename is *LYEXT_PLUGIN_SUFFIX */
-        len = strlen(file->d_name);
-        if (len < LYEXT_PLUGIN_SUFFIX_LEN + 1 ||
-                strcmp(&file->d_name[len - LYEXT_PLUGIN_SUFFIX_LEN], LYEXT_PLUGIN_SUFFIX)) {
-            continue;
-        }
-
-        /* store the name without the suffix */
-        memcpy(name, file->d_name, len - LYEXT_PLUGIN_SUFFIX_LEN);
-        name[len - LYEXT_PLUGIN_SUFFIX_LEN] = '\0';
-
-        /* and construct the filepath */
-        asprintf(&str, "%s/%s", pluginsdir, file->d_name);
-
-        /* load the plugin - first, try if it is already loaded... */
-        dlhandler = dlopen(str, RTLD_NOW | RTLD_NOLOAD);
-        dlerror();    /* Clear any existing error */
-        if (dlhandler) {
-            /* the plugin is already loaded */
-            LOGVRB("Extension plugin \"%s\" already loaded.", str);
-            free(str);
-
-            /* keep the refcount of the shared object correct */
-            dlclose(dlhandler);
-            continue;
-        }
-
-        /* ... and if not, load it */
-        dlhandler = dlopen(str, RTLD_NOW);
-        if (!dlhandler) {
-            LOGERR(NULL, LY_ESYS, "Loading \"%s\" as an extension plugin failed (%s).", str, dlerror());
-            free(str);
-            continue;
-        }
-        LOGVRB("Extension plugin \"%s\" successfully loaded.", str);
-        free(str);
-        dlerror();    /* Clear any existing error */
-
-        /* get the plugin data */
-        plugin = dlsym(dlhandler, name);
-        str = dlerror();
-        if (str) {
-            LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed, missing plugin list object (%s).", name, str);
-            dlclose(dlhandler);
-            continue;
-        }
-
-        for(u = 0; plugin[u].name; u++) {
-            /* check extension implementations for collisions */
-            for (v = 0; v < ext_plugins_count; v++) {
-                if (!strcmp(plugin[u].name, ext_plugins[v].name) &&
-                        !strcmp(plugin[u].module, ext_plugins[v].module) &&
-                        (!plugin[u].revision || !ext_plugins[v].revision || !strcmp(plugin[u].revision, ext_plugins[v].revision))) {
-                    LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed,"
-                           "implementation collision for extension %s from module %s%s%s.",
-                           name, plugin[u].name, plugin[u].module, plugin[u].revision ? "@" : "",
-                           plugin[u].revision ? plugin[u].revision : "");
-                    dlclose(dlhandler);
-                    goto nextplugin;
-                }
-            }
-
-            /* check for valid supported substatements in case of complex extension */
-            if (plugin[u].plugin->type == LYEXT_COMPLEX && ((struct lyext_plugin_complex *)plugin[u].plugin)->substmt) {
-                pluginc = (struct lyext_plugin_complex *)plugin[u].plugin;
-                for (v = 0; pluginc->substmt[v].stmt; v++) {
-                    if (pluginc->substmt[v].stmt >= LY_STMT_SUBMODULE ||
-                            pluginc->substmt[v].stmt == LY_STMT_VERSION ||
-                            pluginc->substmt[v].stmt == LY_STMT_YINELEM) {
-                        LOGERR(NULL, LY_EINVAL,
-                               "Extension plugin \"%s\" (extension %s) allows not supported extension substatement (%s)",
-                               name, plugin[u].name, ly_stmt_str[pluginc->substmt[v].stmt]);
-                        dlclose(dlhandler);
-                        goto nextplugin;
-                    }
-                    if (pluginc->substmt[v].cardinality > LY_STMT_CARD_MAND &&
-                             pluginc->substmt[v].stmt >= LY_STMT_MODIFIER &&
-                             pluginc->substmt[v].stmt <= LY_STMT_STATUS) {
-                        LOGERR(NULL, LY_EINVAL, "Extension plugin \"%s\" (extension %s) allows multiple instances on \"%s\" "
-                               "substatement, which is not supported.",
-                               name, plugin[u].name, ly_stmt_str[pluginc->substmt[v].stmt]);
-                        dlclose(dlhandler);
-                        goto nextplugin;
-                    }
-                }
-            }
-        }
-
-
-        /* add the new plugins, we have number of new plugins as u */
-        p = realloc(ext_plugins, (ext_plugins_count + u) * sizeof *ext_plugins);
-        if (!p) {
-            LOGMEM(NULL);
-            dlclose(dlhandler);
-            closedir(dir);
-
-            /* unlock the global structures */
-            pthread_mutex_unlock(&ext_lock);
-
-            return;
-        }
-        ext_plugins = p;
-        for( ; u; u--) {
-            memcpy(&ext_plugins[ext_plugins_count], &plugin[u - 1], sizeof *plugin);
-            ext_plugins_count++;
-        }
-
-        /* keep the handler */
-        ly_set_add(&dlhandlers, dlhandler, LY_SET_OPT_USEASLIST);
-
-nextplugin:;
-    }
-
-    closedir(dir);
-
-    /* unlock the global structures */
-    pthread_mutex_unlock(&ext_lock);
-}
-
-struct lyext_plugin *
-ext_get_plugin(const char *name, const char *module, const char *revision)
-{
-    unsigned int u;
-
-    assert(name);
-    assert(module);
-
-    for (u = 0; u < ext_plugins_count; u++) {
-        if (!strcmp(name, ext_plugins[u].name) &&
-                !strcmp(module, ext_plugins[u].module) &&
-                (!ext_plugins[u].revision || !strcmp(revision, ext_plugins[u].revision))) {
-            /* we have the match */
-            return ext_plugins[u].plugin;
-        }
-    }
-
-    /* plugin not found */
-    return NULL;
-}
-
-API int
-lys_ext_instance_presence(struct lys_ext *def, struct lys_ext_instance **ext, uint8_t ext_size)
-{
-    uint8_t index;
-
-    if (!def || (ext_size && !ext)) {
-        LOGARG;
-        return -1;
-    }
-
-    /* search for the extension instance */
-    for (index = 0; index < ext_size; index++) {
-        if (ext[index]->def == def) {
-            return index;
-        }
-    }
-
-    /* not found */
-    return -1;
-}
-
-API void *
-lys_ext_complex_get_substmt(LY_STMT stmt, struct lys_ext_instance_complex *ext, struct lyext_substmt **info)
-{
-    int i;
-
-    if (!ext || !ext->def || !ext->def->plugin || ext->def->plugin->type != LYEXT_COMPLEX) {
-        LOGARG;
-        return NULL;
-    }
-
-    if (!ext->substmt) {
-        /* no substatement defined in the plugin */
-        if (info) {
-            *info = NULL;
-        }
-        return NULL;
-    }
-
-    /* search the substatements defined by the plugin */
-    for (i = 0; ext->substmt[i].stmt; i++) {
-        if (stmt == LY_STMT_NODE) {
-            if (ext->substmt[i].stmt >= LY_STMT_ACTION && ext->substmt[i].stmt <= LY_STMT_USES) {
-                if (info) {
-                    *info = &ext->substmt[i];
-                }
-                break;
-            }
-        } else if (ext->substmt[i].stmt == stmt) {
-            if (info) {
-                *info = &ext->substmt[i];
-            }
-            break;
-        }
-    }
-
-    if (ext->substmt[i].stmt) {
-        return &ext->content[ext->substmt[i].offset];
-    } else {
-        return NULL;
-    }
-}
-
-LY_STMT
-lys_snode2stmt(LYS_NODE nodetype)
-{
-    switch(nodetype) {
-    case LYS_CONTAINER:
-        return LY_STMT_CONTAINER;
-    case LYS_CHOICE:
-        return LY_STMT_CHOICE;
-    case LYS_LEAF:
-        return LY_STMT_LEAF;
-    case LYS_LEAFLIST:
-        return LY_STMT_LEAFLIST;
-    case LYS_LIST:
-        return LY_STMT_LIST;
-    case LYS_ANYXML:
-    case LYS_ANYDATA:
-        return LY_STMT_ANYDATA;
-    case LYS_CASE:
-        return LY_STMT_CASE;
-    case LYS_NOTIF:
-        return LY_STMT_NOTIFICATION;
-    case LYS_RPC:
-        return LY_STMT_RPC;
-    case LYS_INPUT:
-        return LY_STMT_INPUT;
-    case LYS_OUTPUT:
-        return LY_STMT_OUTPUT;
-    case LYS_GROUPING:
-        return LY_STMT_GROUPING;
-    case LYS_USES:
-        return LY_STMT_USES;
-    case LYS_AUGMENT:
-        return LY_STMT_AUGMENT;
-    case LYS_ACTION:
-        return LY_STMT_ACTION;
-    default:
-        return LY_STMT_NODE;
-    }
-}
diff --git a/src/extensions/CMakeLists.txt b/src/extensions/CMakeLists.txt
index 7a26de3..46ed5cf 100644
--- a/src/extensions/CMakeLists.txt
+++ b/src/extensions/CMakeLists.txt
@@ -1,8 +1,8 @@
 macro(EXTENSION_PLUGIN PLUGIN_NAME SRCS)
-	add_library(${PLUGIN_NAME} SHARED ${SRCS})
-	set_target_properties(${PLUGIN_NAME} PROPERTIES PREFIX "")
-	target_link_libraries(${PLUGIN_NAME} yang)
-	install(TARGETS ${PLUGIN_NAME} DESTINATION ${LIBYANG_EXT_PLUGINS_DIR})	
+    add_library(${PLUGIN_NAME} SHARED ${SRCS})
+    set_target_properties(${PLUGIN_NAME} PROPERTIES PREFIX "")
+    target_link_libraries(${PLUGIN_NAME} yang)
+    install(TARGETS ${PLUGIN_NAME} DESTINATION ${EXTENSIONS_PLUGINS_DIR_MACRO})
 endmacro(EXTENSION_PLUGIN)
 
 EXTENSION_PLUGIN(nacm "nacm.c")
diff --git a/src/extensions_config.h.in b/src/extensions_config.h.in
deleted file mode 100644
index ac91590..0000000
--- a/src/extensions_config.h.in
+++ /dev/null
@@ -1,28 +0,0 @@
-/**
- * @file extensions_config.h
- * @author Radek Krejci <rkrejci@cesnet.cz>
- * @brief libyang support for YANG extension implementations - internal configuration values.
- *
- * Copyright (c) 2017 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_CONFIG_H_
-#define LY_EXTENSIONS_CONFIG_H_
-
-#define LYEXT_PLUGINS_DIR "@LIBYANG_EXT_PLUGINS_DIR@" /**< directory with YANG extension plugins */
-
-#if defined __linux__ || defined __unix__
-#  define LYEXT_PLUGIN_SUFFIX ".so"
-#  define LYEXT_PLUGIN_SUFFIX_LEN 3
-#elif defined __APPLE__
-#  define LYEXT_PLUGIN_SUFFIX ".dylib"
-#  define LYEXT_PLUGIN_SUFFIX_LEN 6
-#endif
-
-#endif /* LY_EXTENSIONS_CONFIG_H_ */
diff --git a/src/libyang.h.in b/src/libyang.h.in
index 4334d7b..88cce31 100644
--- a/src/libyang.h.in
+++ b/src/libyang.h.in
@@ -49,7 +49,7 @@
  *   ([RFC 7951](https://tools.ietf.org/html/rfc7951)).
  * - [Manipulation with the instance data](@ref howtodatamanipulators).
  * - Support for [default values in the instance data](@ref howtodatawd) ([RFC 6243](https://tools.ietf.org/html/rfc6243)).
- * - Support for [YANG extensions](@ref howtoschemaextensions).
+ * - Support for [YANG extensions and user types](@ref howtoschemaplugins).
  * - Support for [YANG Metadata](@ref howtoschemametadata) ([RFC 7952](https://tools.ietf.org/html/rfc6243)).
  *
  * The current implementation covers YANG 1.0 ([RFC 6020](https://tools.ietf.org/html/rfc6020)) as well as
@@ -234,7 +234,7 @@
  *
  * - @subpage howtoschemasparsers
  * - @subpage howtoschemasfeatures
- * - @subpage howtoschemaextensions
+ * - @subpage howtoschemaplugins
  * - @subpage howtoschemasprinters
  *
  * \note There are many functions to access information from the schema trees. Details are available in
@@ -327,7 +327,20 @@
  */
 
 /**
- * @page howtoschemaextensions YANG Extensions Support
+ * @page howtoschemaplugins YANG Extension and User Type Support
+ *
+ * Extensions and user types are supported in the form of **plugins**. These are loaded from the plugin directory
+ * (`LIBDIR/libyang/`) whenever a context is created. However, the list of plugins can be refreshed manually by ly_load_plugins().
+ * The extension plugin directory path (default `LIBDIR/libyang/extensions/`) can be change via the
+ * `LIBYANG_EXTENSIONS_PLUGINS_DIR` environment variable and similarly the user type directory (default `LIBDIR/libyang/user_types/`)
+ * via `LIBYANG_USER_TYPES_PLUGINS_DIR`. Note, that unavailable plugins are not removed, only
+ * any new plugins are loaded. Also note that the availability of new plugins does not affect the current schemas in the
+ * contexts, they are applied only to the newly parsed schemas.
+ *
+ * The plugins list can be cleaned by ly_clean_plugins(). However, since various contexts (respectively their
+ * schemas) can link to the plugins, the cleanup is successful only when there is no remaining context.
+ *
+ * @section extensions Extensions
  *
  * YANG provides extensions as a mechanism how to add new statements into the language. Since they are very generic -
  * extension instance can appear anywhere, they can contain any other YANG statement including extension instances and
@@ -336,7 +349,7 @@
  * use cases should be covered and supported.
  *
  * Since libyang does not understand human text, it is not possible to get the complete defintion of the extension from
- * its description statement. Therefore, libyang allows the schema authors to provide @link lyext_plugin extension
+ * its description statement. Therefore, libyang allows the schema authors to provide @link extplugins extension
  * plugin@endlink that provides information from the extension description to libyang.
  *
  * Here are some notes about the implementation of the particular YANG extensions features
@@ -380,8 +393,8 @@
  * ::lys_ext_instance#ext_type is set to a different value than #LYEXT_FLAG, the structure can be cast to the particular
  * extension instance structure to access the type-specific members.
  *
- * Extension Plugins
- * -----------------
+ * @subsection extplugins Extension Plugins
+ *
  * Extension plugins provide more detailed information about the extension in a understandable form for libyang. These
  * information is usually provided in a text form in the extension's description statement. libyang provides several
  * plugins for the common IETF extensions (NACM, Metadata, ...) that can be used as a code examples for other
@@ -407,18 +420,7 @@
  *     stored (as offset to the ::lys_ext_instance_complex#content member). The way how the data are stored is
  *     specified descriptions of #LY_STMT values.
  *
- * The plugins are loaded from the plugin directory (LIBDIR/libyang/) whenever a context is created. However, the list
- * of plugins can be refreshed manually by lyext_load_plugins(). The plugin directory path can be change via the
- * `LIBYANG_EXTENSIONS_PLUGINS_DIR` environment variable. Note, that no more available plugins are not removed, only
- * the new plugins are loaded. Also note that availability of new plugins does not affect the current schemas in the
- * contexts, they are applied only to the newly parsed schemas.
- *
- * The plugins list can be cleaned by lyext_clean_plugins(). However, since various contexts (respectively their
- * schemas) can link to the plugins, the cleanup is successful only when there is no remaining context.
- *
- * Metadata Support
- * ----------------
- * @anchor howtoschemametadata
+ * @subsection howtoschemametadata Metadata Support
  *
  * YANG Metadata annotations are defined in [RFC 7952](https://tools.ietf.org/html/rfc6243) as YANG extension. In
  * practice, it allows to have XML attributes (there is also a special encoding for JSON) in YANG modeled data.
@@ -467,12 +469,32 @@
  * - the `select`'s content is XPath and it is internally transformed by libyang into the format where the
  *   XML namespace prefixes are replaced by the YANG module names.
  *
+ * @section usertypes User Types
+ *
+ * Using this plugin mechanism, it is also possible to define what can be called **user types**. Values are
+ * always stored as a string in addition to being in a #lyd_val union. It is possible to customize how
+ * the value is stored in the union using a #lytype_store_clb callback.
+ *
+ * Generally, it is meant for storing certain types more effectively. For instance, when working with **ipv4-address**
+ * from the *ietf-inet-types* model, an application will most likely use the address in a binary form, not as a string.
+ * So, in the callback the value is simply transformed into the desired format and saved into #lyd_val value. However,
+ * the callback is allowed to store anything in the union. Another example, if there are many strings being created and
+ * handled, is to store the string length instead having 2 pointers to the same string.
+ *
+ * @subsection typeplugins User Type Plugins
+ *
+ * There is a simple example user type plugin in `src/user_types` that is not compiled nor installed.
+ *
+ * - ::lytype_plugin_list - plugin is supposed to provide callbacks for:
+ *   + @link lytype_store_clb storing the value itself @endlink
+ *   + freeing the stored value (optionally, if the store callback allocates memory)
+ *
  * Functions List
  * --------------
  * - lys_ext_instance_presence()
  * - lys_ext_instance_substmt()
- * - lyext_load_plugins()
- * - lyext_clean_plugins()
+ * - ly_load_plugins()
+ * - ly_clean_plugins()
  */
 
 /**
@@ -1804,7 +1826,7 @@
     LY_EINVAL,      /**< Invalid value */
     LY_EINT,        /**< Internal error */
     LY_EVALID,      /**< Validation failure */
-    LY_EEXT         /**< Extension error reported by an extension plugin */
+    LY_EPLUGIN      /**< Error reported by a plugin */
 } LY_ERR;
 
 /**
diff --git a/src/log.c b/src/log.c
index b66e63a..36e5bb1 100644
--- a/src/log.c
+++ b/src/log.c
@@ -290,13 +290,13 @@
         return;
     }
 
-    if (asprintf(&plugin_msg, "%s (reported by extension plugin %s, %s())", format, plugin, function) == -1) {
+    if (asprintf(&plugin_msg, "%s (reported by plugin %s, %s())", format, plugin, function) == -1) {
         LOGMEM(ctx);
         return;
     }
 
     va_start(ap, format);
-    log_vprintf(ctx, level, (level == LY_LLERR ? LY_EEXT : 0), 0, NULL, plugin_msg, ap);
+    log_vprintf(ctx, level, (level == LY_LLERR ? LY_EPLUGIN : 0), 0, NULL, plugin_msg, ap);
     va_end(ap);
 
     free(plugin_msg);
diff --git a/src/parser.c b/src/parser.c
index d8d330c..883de3f 100644
--- a/src/parser.c
+++ b/src/parser.c
@@ -1412,9 +1412,8 @@
         itemname = attr->name;
     }
 
-    if (store && ((*val_type & LY_DATA_TYPE_MASK) == LY_TYPE_BITS)) {
-        free(val->bit);
-        val->bit = NULL;
+    if (store) {
+        lyd_free_value(*val, *val_type, type);
     }
 
     switch (type->base) {
@@ -1427,7 +1426,7 @@
             for (uind = 0; isspace(value[uind]); ++uind);
             ptr = &value[uind];
             u = strlen(ptr);
-            while(u && isspace(ptr[u - 1])) {
+            while (u && isspace(ptr[u - 1])) {
                 --u;
             }
             unum = u;
@@ -2067,9 +2066,7 @@
 
             if (store) {
                 /* erase possible present and invalid value data */
-                if (t->base == LY_TYPE_BITS) {
-                    free(val->bit);
-                }
+                lyd_free_value(*val, *val_type, t);
                 memset(val, 0, sizeof(lyd_val));
             }
         }
@@ -2096,6 +2093,16 @@
         return NULL;
     }
 
+    /* search user types in case this value is supposed to be stored in a custom way */
+    if (store && type->der && type->der->module) {
+        c = lytype_store(type->der->module, type->der->name, *value_, val);
+        if (c == -1) {
+            return NULL;
+        } else if (!c) {
+            *val_type |= LY_TYPE_USER;
+        }
+    }
+
     ret = type;
 
 cleanup:
diff --git a/src/parser.h b/src/parser.h
index eda50ce..def99dc 100644
--- a/src/parser.h
+++ b/src/parser.h
@@ -201,20 +201,6 @@
 unsigned int pututf8(struct ly_ctx *ctx, char *dst, int32_t value);
 unsigned int copyutf8(struct ly_ctx *ctx, char *dst, const char *src);
 
-/*
- * Internal functions implementing YANG extensions support
- * - implemented in extensions.c
- */
-
-/**
- * @brief If available, get the extension plugin for the specified extension
- * @param[in] name Name of the extension
- * @param[in] module Name of the extension's module
- * @param[in] revision Revision of the extension's module
- * @return pointer to the extension plugin structure, NULL if no plugin available
- */
-struct lyext_plugin *ext_get_plugin(const char *name, const char *module, const char *revision);
-
 /**
  * @brief Find a module. First, imports from \p module with matching \p prefix, \p name, or both are checked,
  * \p module itself is also compared, and lastly a callback is used if allowed.
@@ -240,4 +226,39 @@
  */
 const struct lys_module *lyp_get_import_module_ns(const struct lys_module *module, const char *ns);
 
+/*
+ * Internal functions implementing YANG (extension and user type) plugin support
+ * - implemented in plugins.c
+ */
+
+/**
+ * @brief If available, get the extension plugin for the specified extension
+ *
+ * @param[in] name Name of the extension
+ * @param[in] module Name of the extension's module
+ * @param[in] revision Revision of the extension's module
+ * @return pointer to the extension plugin structure, NULL if no plugin available
+ */
+struct lyext_plugin *ext_get_plugin(const char *name, const char *module, const char *revision);
+
+/**
+ * @brief Try to store a value as a user type defined by a plugin.
+ *
+ * @param[in] mod Module of the type.
+ * @param[in] type_name Type (typedef) name.
+ * @param[in] value_str Value to store as a string.
+ * @param[in,out] value Filled value to be overwritten by the user store callback.
+ * @return 0 on successful storing, 1 if the type is not a user type, -1 on error.
+ */
+int lytype_store(const struct lys_module *mod, const char *type_name, const char *value_str, lyd_val *value);
+
+/**
+ * @brief Free a user type stored value.
+ *
+ * @param[in] mod Module of the type.
+ * @param[in] type_name Type (typedef) name.
+ * @param[in] value Value union to free.
+ */
+void lytype_free(const struct lys_module *mod, const char *type_name, lyd_val value);
+
 #endif /* LY_PARSER_H_ */
diff --git a/src/plugin_config.h.in b/src/plugin_config.h.in
new file mode 100644
index 0000000..4578492
--- /dev/null
+++ b/src/plugin_config.h.in
@@ -0,0 +1,29 @@
+/**
+ * @file plugin_config.h
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief libyang plugin config file
+ *
+ * Copyright (c) 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_PLUGIN_CONFIG_H_
+#define LY_PLUGIN_CONFIG_H_
+
+#define LYEXT_PLUGINS_DIR "@EXTENSIONS_PLUGINS_DIR_MACRO@" /**< directory with YANG extension plugins */
+#define LY_USER_TYPES_PLUGINS_DIR "@USER_TYPES_PLUGINS_DIR_MACRO@" /**< directory with user YANG types plugins */
+
+#if defined __linux__ || defined __unix__
+#  define LY_PLUGIN_SUFFIX ".so"
+#  define LY_PLUGIN_SUFFIX_LEN 3
+#elif defined __APPLE__
+#  define LY_PLUGIN_SUFFIX ".dylib"
+#  define LY_PLUGIN_SUFFIX_LEN 6
+#endif
+
+#endif /* LY_PLUGIN_CONFIG_H_ */
diff --git a/src/plugins.c b/src/plugins.c
new file mode 100644
index 0000000..c80e963
--- /dev/null
+++ b/src/plugins.c
@@ -0,0 +1,505 @@
+/**
+ * @file plugins.c
+ * @author Radek Krejci <rkrejci@cesnet.cz>
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief YANG plugin routines 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
+ */
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <errno.h>
+#include <dirent.h>
+#include <dlfcn.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <limits.h>
+
+#include "common.h"
+#include "extensions.h"
+#include "user_types.h"
+#include "plugin_config.h"
+#include "libyang.h"
+#include "parser.h"
+
+/* internal structures storing the plugins */
+static struct lyext_plugin_list *ext_plugins = NULL;
+static uint16_t ext_plugins_count = 0; /* size of the ext_plugins array */
+
+static struct lytype_plugin_list *type_plugins = NULL;
+static uint16_t type_plugins_count = 0;
+
+static struct ly_set dlhandlers = {0, 0, {NULL}};
+static pthread_mutex_t plugins_lock = PTHREAD_MUTEX_INITIALIZER;
+
+/**
+ * @brief reference counter for the plugins, it actually counts number of contexts
+ */
+static uint32_t plugin_refs;
+
+API int
+ly_clean_plugins(void)
+{
+    unsigned int u;
+    int ret = EXIT_SUCCESS;
+
+    /* lock the extension plugins list */
+    pthread_mutex_lock(&plugins_lock);
+
+    if (--plugin_refs) {
+        /* there is a context that may refer to the plugins, so we cannot remove them */
+        ret = EXIT_FAILURE;
+        goto cleanup;
+    }
+
+    if (!ext_plugins_count && !type_plugins_count) {
+        /* no plugin loaded - nothing to do */
+        goto cleanup;
+    }
+
+    /* clean the lists */
+    free(ext_plugins);
+    ext_plugins = NULL;
+    ext_plugins_count = 0;
+
+    free(type_plugins);
+    type_plugins = NULL;
+    type_plugins_count = 0;
+
+    /* close the dl handlers */
+    for (u = 0; u < dlhandlers.number; u++) {
+        dlclose(dlhandlers.set.g[u]);
+    }
+    free(dlhandlers.set.g);
+    dlhandlers.set.g = NULL;
+    dlhandlers.size = 0;
+    dlhandlers.number = 0;
+
+cleanup:
+    /* unlock the global structures */
+    pthread_mutex_unlock(&plugins_lock);
+
+    return ret;
+}
+
+static int
+lytype_load_plugin(void *dlhandler, const char *file_name)
+{
+    struct lytype_plugin_list *plugin, *p;
+    uint32_t u, v;
+    char *str;
+
+    /* get the plugin data */
+    plugin = dlsym(dlhandler, file_name);
+    str = dlerror();
+    if (str) {
+        LOGERR(NULL, LY_ESYS, "Processing \"%s\" user type plugin failed, missing plugin list object (%s).", file_name, str);
+        return 1;
+    }
+
+    for (u = 0; plugin[u].name; u++) {
+        /* check user type implementations for collisions */
+        for (v = 0; v < type_plugins_count; v++) {
+            if (!strcmp(plugin[u].name, type_plugins[v].name) &&
+                    !strcmp(plugin[u].module, type_plugins[v].module) &&
+                    (!plugin[u].revision || !type_plugins[v].revision || !strcmp(plugin[u].revision, type_plugins[v].revision))) {
+                LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed,"
+                        "implementation collision for extension %s from module %s%s%s.",
+                        file_name, plugin[u].name, plugin[u].module, plugin[u].revision ? "@" : "",
+                        plugin[u].revision ? plugin[u].revision : "");
+                return 1;
+            }
+        }
+    }
+
+    /* add the new plugins, we have number of new plugins as u */
+    p = realloc(type_plugins, (type_plugins_count + u) * sizeof *type_plugins);
+    if (!p) {
+        LOGMEM(NULL);
+        return -1;
+    }
+    type_plugins = p;
+    for (; u; u--) {
+        memcpy(&type_plugins[type_plugins_count], &plugin[u - 1], sizeof *plugin);
+        type_plugins_count++;
+    }
+
+    return 0;
+}
+
+static int
+lyext_load_plugin(void *dlhandler, const char *file_name)
+{
+    struct lyext_plugin_list *plugin, *p;
+    struct lyext_plugin_complex *pluginc;
+    uint32_t u, v;
+    char *str;
+
+    /* get the plugin data */
+    plugin = dlsym(dlhandler, file_name);
+    str = dlerror();
+    if (str) {
+        LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed, missing plugin list object (%s).", file_name, str);
+        return 1;
+    }
+
+    for (u = 0; plugin[u].name; u++) {
+        /* check extension implementations for collisions */
+        for (v = 0; v < ext_plugins_count; v++) {
+            if (!strcmp(plugin[u].name, ext_plugins[v].name) &&
+                    !strcmp(plugin[u].module, ext_plugins[v].module) &&
+                    (!plugin[u].revision || !ext_plugins[v].revision || !strcmp(plugin[u].revision, ext_plugins[v].revision))) {
+                LOGERR(NULL, LY_ESYS, "Processing \"%s\" extension plugin failed,"
+                        "implementation collision for extension %s from module %s%s%s.",
+                        file_name, plugin[u].name, plugin[u].module, plugin[u].revision ? "@" : "",
+                        plugin[u].revision ? plugin[u].revision : "");
+                return 1;
+            }
+        }
+
+        /* check for valid supported substatements in case of complex extension */
+        if (plugin[u].plugin->type == LYEXT_COMPLEX && ((struct lyext_plugin_complex *)plugin[u].plugin)->substmt) {
+            pluginc = (struct lyext_plugin_complex *)plugin[u].plugin;
+            for (v = 0; pluginc->substmt[v].stmt; v++) {
+                if (pluginc->substmt[v].stmt >= LY_STMT_SUBMODULE ||
+                        pluginc->substmt[v].stmt == LY_STMT_VERSION ||
+                        pluginc->substmt[v].stmt == LY_STMT_YINELEM) {
+                    LOGERR(NULL, LY_EINVAL,
+                            "Extension plugin \"%s\" (extension %s) allows not supported extension substatement (%s)",
+                            file_name, plugin[u].name, ly_stmt_str[pluginc->substmt[v].stmt]);
+                    return 1;
+                }
+                if (pluginc->substmt[v].cardinality > LY_STMT_CARD_MAND &&
+                        pluginc->substmt[v].stmt >= LY_STMT_MODIFIER &&
+                        pluginc->substmt[v].stmt <= LY_STMT_STATUS) {
+                    LOGERR(NULL, LY_EINVAL, "Extension plugin \"%s\" (extension %s) allows multiple instances on \"%s\" "
+                           "substatement, which is not supported.",
+                           file_name, plugin[u].name, ly_stmt_str[pluginc->substmt[v].stmt]);
+                    return 1;
+                }
+            }
+        }
+    }
+
+    /* add the new plugins, we have number of new plugins as u */
+    p = realloc(ext_plugins, (ext_plugins_count + u) * sizeof *ext_plugins);
+    if (!p) {
+        LOGMEM(NULL);
+        return -1;
+    }
+    ext_plugins = p;
+    for (; u; u--) {
+        memcpy(&ext_plugins[ext_plugins_count], &plugin[u - 1], sizeof *plugin);
+        ext_plugins_count++;
+    }
+
+    return 0;
+}
+
+static void
+ly_load_plugins_dir(DIR *dir, const char *dir_path, int ext_or_type)
+{
+    struct dirent *file;
+    size_t len;
+    char *str;
+    char name[NAME_MAX];
+    void *dlhandler;
+    int ret;
+
+    while ((file = readdir(dir))) {
+        /* required format of the filename is *LY_PLUGIN_SUFFIX */
+        len = strlen(file->d_name);
+        if (len < LY_PLUGIN_SUFFIX_LEN + 1 ||
+                strcmp(&file->d_name[len - LY_PLUGIN_SUFFIX_LEN], LY_PLUGIN_SUFFIX)) {
+            continue;
+        }
+
+        /* store the name without the suffix */
+        memcpy(name, file->d_name, len - LY_PLUGIN_SUFFIX_LEN);
+        name[len - LY_PLUGIN_SUFFIX_LEN] = '\0';
+
+        /* and construct the filepath */
+        asprintf(&str, "%s/%s", dir_path, file->d_name);
+
+        /* load the plugin - first, try if it is already loaded... */
+        dlhandler = dlopen(str, RTLD_NOW | RTLD_NOLOAD);
+        dlerror();    /* Clear any existing error */
+        if (dlhandler) {
+            /* the plugin is already loaded */
+            LOGVRB("Plugin \"%s\" already loaded.", str);
+            free(str);
+
+            /* keep the refcount of the shared object correct */
+            dlclose(dlhandler);
+            continue;
+        }
+
+        /* ... and if not, load it */
+        dlhandler = dlopen(str, RTLD_NOW);
+        if (!dlhandler) {
+            LOGERR(NULL, LY_ESYS, "Loading \"%s\" as a plugin failed (%s).", str, dlerror());
+            free(str);
+            continue;
+        }
+        LOGVRB("Plugin \"%s\" successfully loaded.", str);
+        free(str);
+        dlerror();    /* Clear any existing error */
+
+        if (ext_or_type) {
+            ret = lyext_load_plugin(dlhandler, name);
+        } else {
+            ret = lytype_load_plugin(dlhandler, name);
+        }
+        if (ret == 1) {
+            dlclose(dlhandler);
+            continue;
+        } else if (ret == -1) {
+            dlclose(dlhandler);
+            break;
+        }
+
+        /* keep the handler */
+        ly_set_add(&dlhandlers, dlhandler, LY_SET_OPT_USEASLIST);
+    }
+}
+
+API void
+ly_load_plugins(void)
+{
+    DIR* dir;
+    const char *pluginsdir;
+
+    /* lock the extension plugins list */
+    pthread_mutex_lock(&plugins_lock);
+
+    /* increase references */
+    ++plugin_refs;
+
+    /* try to get the plugins directory from environment variable */
+    pluginsdir = getenv("LIBYANG_EXTENSIONS_PLUGINS_DIR");
+    if (!pluginsdir) {
+        pluginsdir = LYEXT_PLUGINS_DIR;
+    }
+
+    dir = opendir(pluginsdir);
+    if (!dir) {
+        /* no directory (or no access to it), no extension plugins */
+        LOGWRN(NULL, "Failed to open libyang extensions plugins directory \"%s\" (%s).", pluginsdir, strerror(errno));
+    } else {
+        ly_load_plugins_dir(dir, pluginsdir, 1);
+        closedir(dir);
+    }
+
+    /* try to get the plugins directory from environment variable */
+    pluginsdir = getenv("LIBYANG_USER_TYPES_PLUGINS_DIR");
+    if (!pluginsdir) {
+        pluginsdir = LY_USER_TYPES_PLUGINS_DIR;
+    }
+
+    dir = opendir(pluginsdir);
+    if (!dir) {
+        /* no directory (or no access to it), no extension plugins */
+        LOGWRN(NULL, "Failed to open libyang user types plugins directory \"%s\" (%s).", pluginsdir, strerror(errno));
+    } else {
+        ly_load_plugins_dir(dir, pluginsdir, 0);
+        closedir(dir);
+    }
+
+    /* unlock the global structures */
+    pthread_mutex_unlock(&plugins_lock);
+}
+
+struct lyext_plugin *
+ext_get_plugin(const char *name, const char *module, const char *revision)
+{
+    uint16_t u;
+
+    assert(name);
+    assert(module);
+
+    for (u = 0; u < ext_plugins_count; u++) {
+        if (!strcmp(name, ext_plugins[u].name) &&
+                !strcmp(module, ext_plugins[u].module) &&
+                (!ext_plugins[u].revision || !strcmp(revision, ext_plugins[u].revision))) {
+            /* we have the match */
+            return ext_plugins[u].plugin;
+        }
+    }
+
+    /* plugin not found */
+    return NULL;
+}
+
+API int
+lys_ext_instance_presence(struct lys_ext *def, struct lys_ext_instance **ext, uint8_t ext_size)
+{
+    uint8_t index;
+
+    if (!def || (ext_size && !ext)) {
+        LOGARG;
+        return -1;
+    }
+
+    /* search for the extension instance */
+    for (index = 0; index < ext_size; index++) {
+        if (ext[index]->def == def) {
+            return index;
+        }
+    }
+
+    /* not found */
+    return -1;
+}
+
+API void *
+lys_ext_complex_get_substmt(LY_STMT stmt, struct lys_ext_instance_complex *ext, struct lyext_substmt **info)
+{
+    int i;
+
+    if (!ext || !ext->def || !ext->def->plugin || ext->def->plugin->type != LYEXT_COMPLEX) {
+        LOGARG;
+        return NULL;
+    }
+
+    if (!ext->substmt) {
+        /* no substatement defined in the plugin */
+        if (info) {
+            *info = NULL;
+        }
+        return NULL;
+    }
+
+    /* search the substatements defined by the plugin */
+    for (i = 0; ext->substmt[i].stmt; i++) {
+        if (stmt == LY_STMT_NODE) {
+            if (ext->substmt[i].stmt >= LY_STMT_ACTION && ext->substmt[i].stmt <= LY_STMT_USES) {
+                if (info) {
+                    *info = &ext->substmt[i];
+                }
+                break;
+            }
+        } else if (ext->substmt[i].stmt == stmt) {
+            if (info) {
+                *info = &ext->substmt[i];
+            }
+            break;
+        }
+    }
+
+    if (ext->substmt[i].stmt) {
+        return &ext->content[ext->substmt[i].offset];
+    } else {
+        return NULL;
+    }
+}
+
+LY_STMT
+lys_snode2stmt(LYS_NODE nodetype)
+{
+    switch(nodetype) {
+    case LYS_CONTAINER:
+        return LY_STMT_CONTAINER;
+    case LYS_CHOICE:
+        return LY_STMT_CHOICE;
+    case LYS_LEAF:
+        return LY_STMT_LEAF;
+    case LYS_LEAFLIST:
+        return LY_STMT_LEAFLIST;
+    case LYS_LIST:
+        return LY_STMT_LIST;
+    case LYS_ANYXML:
+    case LYS_ANYDATA:
+        return LY_STMT_ANYDATA;
+    case LYS_CASE:
+        return LY_STMT_CASE;
+    case LYS_NOTIF:
+        return LY_STMT_NOTIFICATION;
+    case LYS_RPC:
+        return LY_STMT_RPC;
+    case LYS_INPUT:
+        return LY_STMT_INPUT;
+    case LYS_OUTPUT:
+        return LY_STMT_OUTPUT;
+    case LYS_GROUPING:
+        return LY_STMT_GROUPING;
+    case LYS_USES:
+        return LY_STMT_USES;
+    case LYS_AUGMENT:
+        return LY_STMT_AUGMENT;
+    case LYS_ACTION:
+        return LY_STMT_ACTION;
+    default:
+        return LY_STMT_NODE;
+    }
+}
+
+static struct lytype_plugin_list *
+lytype_find(const char *module, const char *revision, const char *type_name)
+{
+    uint16_t u;
+
+    for (u = 0; u < type_plugins_count; ++u) {
+        if (ly_strequal(module, type_plugins[u].module, 0) && ((!revision && !type_plugins[u].revision)
+                || (revision && ly_strequal(revision, type_plugins[u].revision, 0)))
+                && ly_strequal(type_name, type_plugins[u].name, 0)) {
+            return &(type_plugins[u]);
+        }
+    }
+
+    return NULL;
+}
+
+int
+lytype_store(const struct lys_module *mod, const char *type_name, const char *value_str, lyd_val *value)
+{
+    struct lytype_plugin_list *p;
+    char *err_msg = NULL;
+
+    assert(mod && type_name && value_str && value);
+
+    p = lytype_find(mod->name, mod->rev_size ? mod->rev[0].date : NULL, type_name);
+    if (p) {
+        if (p->store_clb(type_name, value_str, value, &err_msg)) {
+            if (!err_msg) {
+                if (asprintf(&err_msg, "Failed to store value \"%s\" of user type \"%s\".", value_str, type_name) == -1) {
+                    LOGMEM(mod->ctx);
+                    return -1;
+                }
+            }
+            LOGERR(mod->ctx, LY_EPLUGIN, err_msg);
+            free(err_msg);
+            return -1;
+        }
+
+        /* value successfully stored */
+        return 0;
+    }
+
+    return 1;
+}
+
+void
+lytype_free(const struct lys_module *mod, const char *type_name, lyd_val value)
+{
+    struct lytype_plugin_list *p;
+
+    p = lytype_find(mod->name, mod->rev_size ? mod->rev[0].date : NULL, type_name);
+    if (!p) {
+        LOGINT(mod->ctx);
+        return;
+    }
+
+    if (p->free_clb) {
+        p->free_clb(value.ptr);
+    }
+}
diff --git a/src/resolve.c b/src/resolve.c
index 176fb97..920cef9 100644
--- a/src/resolve.c
+++ b/src/resolve.c
@@ -3448,9 +3448,7 @@
     }
 
 cleanup:
-    if (node.value_type == LY_TYPE_BITS) {
-        free(node.value.bit);
-    }
+    lyd_free_value(node.value, node.value_type, type);
     lydict_remove(ctx, node.value_str);
     if (tpdf && node.schema) {
         free((char *)node.schema->name);
@@ -6891,7 +6889,7 @@
         /* final check */
         if (eplugin->check_result) {
             if ((*eplugin->check_result)(ext)) {
-                LOGERR(ctx, LY_EEXT, "Resolving extension failed.");
+                LOGERR(ctx, LY_EPLUGIN, "Resolving extension failed.");
                 return -1;
             }
         }
@@ -7683,13 +7681,11 @@
 
     if ((leaf->value_type == LY_TYPE_UNION) || (leaf->value_type == (LY_TYPE_INST | LY_TYPE_INST_UNRES))) {
         /* either NULL or instid previously converted to JSON */
-        json_val = leaf->value.string;
+        json_val = lydict_insert(ctx, leaf->value.string, 0);
     }
 
     if (store) {
-        if ((leaf->value_type & LY_DATA_TYPE_MASK) == LY_TYPE_BITS) {
-            free(leaf->value.bit);
-        }
+        lyd_free_value(leaf->value, leaf->value_type, &((struct lys_node_leaf *)leaf->schema)->type);
         memset(&leaf->value, 0, sizeof leaf->value);
     }
 
@@ -7777,9 +7773,7 @@
 
         /* erase possible present and invalid value data */
         if (store) {
-            if (t->base == LY_TYPE_BITS) {
-                free(leaf->value.bit);
-            }
+            lyd_free_value(leaf->value, leaf->value_type, t);
             memset(&leaf->value, 0, sizeof leaf->value);
         }
     }
diff --git a/src/tree_data.c b/src/tree_data.c
index d888c55..7211232 100644
--- a/src/tree_data.c
+++ b/src/tree_data.c
@@ -1848,9 +1848,7 @@
                 trg_leaf->validity |= LYD_VAL_LEAFREF;
                 trg_leaf->value.leafref = NULL;
             } else {
-                if ((trg_leaf->value_type & LY_DATA_TYPE_MASK) == LY_TYPE_BITS) {
-                    free(trg_leaf->value.bit);
-                }
+                lyd_free_value(trg_leaf->value, trg_leaf->value_type, &((struct lys_node_leaf *)trg_leaf->schema)->type);
                 trg_leaf->value = src_leaf->value;
             }
             src_leaf->value = (lyd_val)0;
@@ -1895,9 +1893,7 @@
 
             lydict_remove(ctx, trg_leaf->value_str);
             trg_leaf->value_str = lydict_insert(ctx, src_leaf->value_str, 0);
-            if ((trg_leaf->value_type & LY_DATA_TYPE_MASK) == LY_TYPE_BITS) {
-                free(trg_leaf->value.bit);
-            }
+            lyd_free_value(trg_leaf->value, trg_leaf->value_type, &((struct lys_node_leaf *)trg_leaf->schema)->type);
             trg_leaf->value_type = src_leaf->value_type;
             trg_leaf->dflt = src_leaf->dflt;
 
@@ -4844,6 +4840,7 @@
 lyd_free_attr(struct ly_ctx *ctx, struct lyd_node *parent, struct lyd_attr *attr, int recursive)
 {
     struct lyd_attr *iter;
+    struct lys_type **type;
 
     if (!ctx || !attr) {
         return;
@@ -4877,19 +4874,9 @@
         iter = iter->next;
 
         lydict_remove(ctx, attr->name);
-        switch (attr->value_type & LY_DATA_TYPE_MASK) {
-        case LY_TYPE_BITS:
-            if (attr->value.bit) {
-                free(attr->value.bit);
-            }
-            break;
-        case LY_TYPE_UNION:
-            /* unresolved union leaf */
-            lydict_remove(ctx, attr->value.string);
-            break;
-        default:
-            break;
-        }
+        type = lys_ext_complex_get_substmt(LY_STMT_TYPE, attr->annotation, NULL);
+        assert(type);
+        lyd_free_value(attr->value, attr->value_type, *type);
         lydict_remove(ctx, attr->value_str);
         free(attr);
     }
@@ -4997,10 +4984,39 @@
     return a;
 }
 
+void
+lyd_free_value(lyd_val value, uint16_t value_type, struct lys_type *type)
+{
+    if (value_type & LY_TYPE_USER) {
+        assert(type->der && type->der->module);
+        lytype_free(type->der->module, type->der->name, value);
+    } else {
+        switch (value_type & LY_DATA_TYPE_MASK) {
+        case LY_TYPE_BITS:
+            if (value.bit) {
+                free(value.bit);
+            }
+            break;
+        case LY_TYPE_INST:
+            if (!(value_type & LY_TYPE_INST_UNRES)) {
+                break;
+            }
+            /* fallthrough */
+        case LY_TYPE_UNION:
+            /* unresolved union leaf */
+            lydict_remove(type->parent->module->ctx, value.string);
+            break;
+        default:
+            break;
+        }
+    }
+}
+
 API void
 lyd_free(struct lyd_node *node)
 {
     struct lyd_node *next, *iter;
+    struct lyd_node_leaf_list *leaf;
 
     if (!node) {
         return;
@@ -5032,22 +5048,9 @@
             break;
         }
     } else { /* LYS_LEAF | LYS_LEAFLIST */
-        /* free value */
-        switch (((struct lyd_node_leaf_list *)node)->value_type & LY_DATA_TYPE_MASK) {
-        case LY_TYPE_BITS:
-            if (((struct lyd_node_leaf_list *)node)->value.bit) {
-                free(((struct lyd_node_leaf_list *)node)->value.bit);
-            }
-            break;
-        case LY_TYPE_UNION:
-            /* unresolved union leaf */
-            lydict_remove(node->schema->module->ctx, ((struct lyd_node_leaf_list *)node)->value.string);
-            break;
-        default:
-            break;
-        }
-
-        lydict_remove(node->schema->module->ctx, ((struct lyd_node_leaf_list *)node)->value_str);
+        leaf = (struct lyd_node_leaf_list *)node;
+        lyd_free_value(leaf->value, leaf->value_type, &((struct lys_node_leaf *)leaf->schema)->type);
+        lydict_remove(leaf->schema->module->ctx, leaf->value_str);
     }
 
     lyd_unlink(node);
diff --git a/src/tree_data.h b/src/tree_data.h
index cf05d81..5480c86 100644
--- a/src/tree_data.h
+++ b/src/tree_data.h
@@ -104,6 +104,7 @@
     uint16_t uint16;             /**< 16-bit signed integer */
     uint32_t uint32;             /**< 32-bit signed integer */
     uint64_t uint64;             /**< 64-bit signed integer */
+    void *ptr;                   /**< arbitrary data stored using a type plugin */
 } lyd_val;
 
 /**
diff --git a/src/tree_internal.h b/src/tree_internal.h
index c5d3bfd..d3c9ec0 100644
--- a/src/tree_internal.h
+++ b/src/tree_internal.h
@@ -439,6 +439,8 @@
 
 int lyd_get_unique_default(const char* unique_expr, struct lyd_node *list, const char **dflt);
 
+void lyd_free_value(lyd_val value, uint16_t value_type, struct lys_type *type);
+
 /**
  * @brief Check for (validate) mandatory nodes of a data tree. Checks recursively whole data tree. Requires all when
  * statement to be solved.
diff --git a/src/tree_schema.h b/src/tree_schema.h
index 2cbe1bf..3a09b73 100644
--- a/src/tree_schema.h
+++ b/src/tree_schema.h
@@ -582,16 +582,16 @@
 void *lys_ext_complex_get_substmt(LY_STMT stmt, struct lys_ext_instance_complex *ext, struct lyext_substmt **info);
 
 /**
- * @brief Load the available YANG extensions plugins from the plugin directory (LIBDIR/libyang/).
+ * @brief Load the available YANG extension and type plugins from the plugin directory (LIBDIR/libyang/).
  *
  * This function is automatically called whenever a new context is created. Note that the removed plugins are kept
  * in use until all the created contexts are destroyed via ly_ctx_destroy(), so only the newly added plugins are
  * usually loaded by this function.
  */
-void lyext_load_plugins(void);
+void ly_load_plugins(void);
 
 /**
- * @brief Unload all the YANG extensions plugins.
+ * @brief Unload all the YANG extension and type plugins.
  *
  * This function is automatically called whenever the context is destroyed. Note, that in case there is still a
  * libyang context in use, the function does nothing since unloading the plugins would break the context's modules
@@ -599,7 +599,7 @@
  *
  * Since the function is called with ly_ctx_destroy(), there is usually no need to call this function manually.
  */
-int lyext_clean_plugins(void);
+int ly_clean_plugins(void);
 
 /**
  * @}
@@ -764,6 +764,7 @@
                                           the value union is filled as if being the target node's type */
 #define LY_TYPE_INST_UNRES 0x80      /**< flag for unresolved instance-identifier, always used in conjunction with LY_TYPE_INST
                                           and the value union should not be accessed */
+#define LY_TYPE_USER 0x100           /**< flag for a user type stored value */
 
 /**
  *
diff --git a/src/user_types.h b/src/user_types.h
new file mode 100644
index 0000000..e6b0572
--- /dev/null
+++ b/src/user_types.h
@@ -0,0 +1,64 @@
+/**
+ * @file user_types.h
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief libyang support for user YANG type implementations.
+ *
+ * Copyright (c) 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_USER_TYPES_H_
+#define LY_USER_TYPES_H_
+
+#include "libyang.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @defgroup user_types User Types
+ * @ingroup schematree
+ * @{
+ */
+
+/**
+ * @brief Callback for storing user type values.
+ *
+ * This callback should overwrite the value stored in \p value using some custom encoding. Be careful,
+ * if the type is #LY_TYPE_BITS, the bits must be freed before overwritting the union value.
+ *
+ * @param[in] type_name Name of the type being stored.
+ * @param[in] value_str String value to be stored.
+ * @param[in,out] value Value union for the value to be stored in (already is but in the standard way).
+ * @param[out] err_msg Can be filled on error. If not, a generic error message will be printed.
+ * @return 0 on success, non-zero if an error occured and the value could not be stored for any reason.
+ */
+typedef int (*lytype_store_clb)(const char *type_name, const char *value_str, lyd_val *value, char **err_msg);
+
+struct lytype_plugin_list {
+    const char *module;          /**< Name of the module where the type is defined. */
+    const char *revision;        /**< Optional module revision - if not specified, the plugin applies to any revision,
+                                      which is not the best 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 store callback. The only valid use case
+                                      for the NULL revision is the case when the module has no revision. */
+    const char *name;            /**< Name of the type to be stored in a custom way. */
+    lytype_store_clb store_clb;  /**< Callback used for storing values of this type. */
+    void (*free_clb)(void *ptr); /**< Callback used for freeing values of this type. */
+};
+
+/**
+ * @}
+ */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* LY_USER_TYPES_H_ */
diff --git a/src/user_types/user_ipv4.c b/src/user_types/user_ipv4.c
new file mode 100644
index 0000000..b3d0be8
--- /dev/null
+++ b/src/user_types/user_ipv4.c
@@ -0,0 +1,37 @@
+/**
+ * @file user_ipv4.h
+ * @author Michal Vasko <mvasko@cesnet.cz>
+ * @brief example implementation of an ipv4-address as a user type
+ *
+ * Copyright (c) 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
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <arpa/inet.h>
+
+#include <libyang/user_types.h>
+
+static int
+ipv4_store_clb(const char *type_name, const char *value_str, lyd_val *value, char **err_msg)
+{
+    return 1;
+    value->ptr = malloc(INET_ADDRSTRLEN);
+    if (inet_pton(AF_INET, value_str, value->ptr) != 1) {
+        free(value->ptr);
+        return 1;
+    }
+    return 0;
+}
+
+struct lytype_plugin_list user_ipv4[] = {
+    {"ietf-inet-types", "2013-07-15", "ipv4-address", ipv4_store_clb, free},
+    {"ietf-inet-types", "2013-07-15", "ipv4-address-no-zone", ipv4_store_clb, free},
+    {NULL, NULL, NULL, NULL, NULL} /* terminating item */
+};
diff --git a/src/user_types/user_ipv4.so b/src/user_types/user_ipv4.so
new file mode 100755
index 0000000..513dfbf
--- /dev/null
+++ b/src/user_types/user_ipv4.so
Binary files differ