diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c
index 121be08f7d7265d6fe1ec86b2a3d8822108d1240..a8669b8c61b8b65d4d3c49637f2028a8a57bdc81 100644
--- a/src/box/lua/tuple.c
+++ b/src/box/lua/tuple.c
@@ -34,6 +34,8 @@
 #include "lua/msgpack.h" /* luamp_encode_XXX() */
 #include "diag.h" /* diag_set() */
 #include <small/ibuf.h>
+#include <small/region.h>
+#include <fiber.h>
 
 #include "box/tuple.h"
 #include "box/errcode.h"
@@ -358,6 +360,22 @@ lbox_tuple_transform(struct lua_State *L)
 	return 1;
 }
 
+
+static int
+lbox_tuple_to_string(struct lua_State *L)
+{
+	struct tuple *tuple = lua_checktuple(L, 1);
+	size_t used = region_used(&fiber()->gc);
+	char *res = tuple_to_yaml(tuple);
+	if (res == NULL) {
+		region_truncate(&fiber()->gc, used);
+		return luaT_error(L);
+	}
+	lua_pushstring(L, res);
+	region_truncate(&fiber()->gc, used);
+	return 1;
+}
+
 void
 luaT_pushtuple(struct lua_State *L, box_tuple_t *tuple)
 {
@@ -376,6 +394,7 @@ luaT_pushtuple(struct lua_State *L, box_tuple_t *tuple)
 
 static const struct luaL_Reg lbox_tuple_meta[] = {
 	{"__gc", lbox_tuple_gc},
+	{"tostring", lbox_tuple_to_string},
 	{"slice", lbox_tuple_slice},
 	{"transform", lbox_tuple_transform},
 	{NULL, NULL}
diff --git a/src/box/lua/tuple.lua b/src/box/lua/tuple.lua
index 0174b745a572cd8babf7aa8cf2ef9fe37eea107e..aa10c7a3716ea446238d632d7de693ad82dcf327 100644
--- a/src/box/lua/tuple.lua
+++ b/src/box/lua/tuple.lua
@@ -309,11 +309,7 @@ ffi.metatype(tuple_t, {
     __len = function(tuple)
         return builtin.box_tuple_field_count(tuple)
     end;
-    __tostring = function(tuple)
-        -- Unpack tuple, call yaml.encode, remove yaml header and footer
-        -- 5 = '---\n\n' (header), -6 = '\n...\n' (footer)
-        return yaml.encode(methods.totable(tuple)):sub(5, -6)
-    end;
+    __tostring = internal.tuple.tostring;
     __index = function(tuple, key)
         if type(key) == "number" then
             return tuple_field(tuple, key)
diff --git a/src/box/tuple.h b/src/box/tuple.h
index e842d5885d10f0b1ab9c1b7c7237d9609e9352f8..9e1e44e1bae13a2466e44e8bd19137a4cb4f0876 100644
--- a/src/box/tuple.h
+++ b/src/box/tuple.h
@@ -722,6 +722,16 @@ tuple_to_obuf(struct tuple *tuple, struct obuf *buf);
 ssize_t
 tuple_to_buf(const struct tuple *tuple, char *buf, size_t size);
 
+/**
+ * Convert tuple to yaml string
+ *
+ * \param tuple tuple
+ * \retval NULL in case of error written in diag
+ * \retval pointer to string allocated on fiber()->gc region
+ */
+char *
+tuple_to_yaml(const struct tuple *tuple);
+
 #if defined(__cplusplus)
 } /* extern "C" */
 
diff --git a/src/box/tuple_convert.c b/src/box/tuple_convert.c
index cbea4cd80bfa7d323fa14292f0ec19a0ad98665d..6cb79e80131479056dbe87dd3869c2886d524bd8 100644
--- a/src/box/tuple_convert.c
+++ b/src/box/tuple_convert.c
@@ -30,6 +30,12 @@
  */
 #include "tuple.h"
 #include "iobuf.h"
+#include <msgpuck/msgpuck.h>
+#include <yaml.h>
+#include "third_party/base64.h"
+#include <small/region.h>
+#include "fiber.h"
+#include <trivia/util.h>
 
 int
 tuple_to_obuf(struct tuple *tuple, struct obuf *buf)
@@ -53,3 +59,224 @@ tuple_to_buf(const struct tuple *tuple, char *buf, size_t size)
 	}
 	return bsize;
 }
+
+int
+append_output(void *arg, unsigned char *buf, size_t len)
+{
+	(void) arg;
+	char *buf_out = region_alloc(&fiber()->gc, len);
+	if (!buf_out) {
+		diag_set(OutOfMemory, len , "region_alloc", "append_output");
+		return 0;
+	}
+	memcpy(buf_out, buf, len);
+	return 1;
+}
+
+static int
+encode_node(yaml_emitter_t *emitter, const char **data);
+
+static int
+encode_table(yaml_emitter_t *emitter, const char **data)
+{
+	yaml_event_t ev;
+	yaml_mapping_style_t yaml_style = YAML_FLOW_MAPPING_STYLE;
+	if (!yaml_mapping_start_event_initialize(&ev, NULL, NULL, 0, yaml_style)
+			|| !yaml_emitter_emit(emitter, &ev)) {
+		diag_set(SystemError, "failed to init event libyaml");
+		return 0;
+	}
+
+	uint32_t size = mp_decode_map(data);
+	for (uint32_t i = 0; i < size; i++) {
+		if (!encode_node(emitter, data))
+			return 0;
+		if (!encode_node(emitter, data))
+			return 0;
+	}
+
+	if (!yaml_mapping_end_event_initialize(&ev) ||
+	    !yaml_emitter_emit(emitter, &ev)) {
+		diag_set(SystemError, "failed to end event libyaml");
+		return 0;
+	}
+
+	return 1;
+}
+
+
+static int
+encode_array(yaml_emitter_t *emitter, const char **data)
+{
+	yaml_event_t ev;
+	yaml_sequence_style_t yaml_style = YAML_FLOW_SEQUENCE_STYLE;
+	if (!yaml_sequence_start_event_initialize(&ev, NULL, NULL, 0,
+				yaml_style) ||
+			!yaml_emitter_emit(emitter, &ev)) {
+		diag_set(SystemError, "failed to init event libyaml");
+		return 0;
+	}
+
+	uint32_t size = mp_decode_array(data);
+	for (uint32_t i = 0; i < size; i++) {
+		if (!encode_node(emitter, data))
+		   return 0;
+	}
+
+	if (!yaml_sequence_end_event_initialize(&ev) ||
+	    !yaml_emitter_emit(emitter, &ev)) {
+		diag_set(SystemError, "failed to end event libyaml");
+		return 0;
+	}
+
+	return 1;
+}
+
+static int
+encode_node(yaml_emitter_t *emitter, const char **data)
+{
+	size_t len = 0;
+	const char *str = "";
+	yaml_char_t *tag = NULL;
+	yaml_event_t ev;
+	yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
+	int is_binary = 0;
+	char buf[FPCONV_G_FMT_BUFSIZE];
+	char *binary_encode = NULL;
+	int type = mp_typeof(**data);
+	switch(type) {
+	case MP_UINT:
+		len = snprintf(buf, sizeof(buf), "%llu",
+			       (unsigned long long) mp_decode_uint(data));
+		buf[len] = 0;
+		str = buf;
+		break;
+	case MP_INT:
+		len = snprintf(buf, sizeof(buf), "%lld",
+			       (long long) mp_decode_int(data));
+		buf[len] = 0;
+		str = buf;
+		break;
+	case MP_FLOAT:
+		fpconv_g_fmt(buf, mp_decode_float(data),
+			     FPCONV_G_FMT_MAX_PRECISION);
+		str = buf;
+		len = strlen(buf);
+		break;
+	case MP_DOUBLE:
+		fpconv_g_fmt(buf, mp_decode_double(data),
+			     FPCONV_G_FMT_MAX_PRECISION);
+		str = buf;
+		len = strlen(buf);
+		break;
+	case MP_ARRAY:
+		return encode_array(emitter, data);
+	case MP_MAP:
+		return encode_table(emitter, data);
+	case MP_STR:
+	case MP_BIN:
+		len = mp_decode_strbinl(data);
+		str = *data;
+		*data += len;
+		if (type == MP_STR && utf8_check_printable(str, len)) {
+			style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
+			break;
+		}
+		style = YAML_ANY_SCALAR_STYLE;
+		/* Binary or not UTF8 */
+		is_binary = 1;
+		binary_encode = (char *) malloc(base64_bufsize(len));
+		if (binary_encode == NULL) {
+			diag_set(OutOfMemory, base64_bufsize(len),
+				 "malloc", "tuple_to_yaml");
+			return 0;
+		}
+		base64_encode(str, len, binary_encode, base64_bufsize(len));
+		str = binary_encode;
+		tag = (yaml_char_t *) "binary";
+		break;
+	case MP_BOOL:
+		if (mp_decode_bool(data)) {
+			str = "true";
+			len = 4;
+		} else {
+			str = "false";
+			len = 5;
+		}
+		break;
+	case MP_NIL:
+	case MP_EXT:
+		mp_decode_nil(data);
+		style = YAML_PLAIN_SCALAR_STYLE;
+		str = "null";
+		len = 4;
+		break;
+	default:
+		unreachable();
+	}
+
+	int rc = 1;
+	if (!yaml_scalar_event_initialize(&ev, NULL, tag, (unsigned char *)str,
+					  len, !is_binary, !is_binary,
+					  style) ||
+	    !yaml_emitter_emit(emitter, &ev)) {
+		diag_set(OutOfMemory, len, "malloc", "tuple_to_yaml");
+		rc = 0;
+	}
+	if (is_binary)
+		free(binary_encode);
+
+	return rc;
+}
+
+char *
+tuple_to_yaml(const struct tuple *tuple)
+{
+	const char *data = tuple_data(tuple);
+	yaml_emitter_t emitter;
+	yaml_event_t ev;
+
+	size_t used = region_used(&fiber()->gc);
+
+	if (!yaml_emitter_initialize(&emitter)) {
+		diag_set(SystemError, "failed to init libyaml");
+		return NULL;
+	}
+	yaml_emitter_set_unicode(&emitter, 1);
+	yaml_emitter_set_indent(&emitter, 2);
+	yaml_emitter_set_width(&emitter, 2);
+	yaml_emitter_set_break(&emitter, YAML_LN_BREAK);
+	yaml_emitter_set_output(&emitter, &append_output, NULL);
+
+	if (!yaml_stream_start_event_initialize(&ev, YAML_UTF8_ENCODING) ||
+	    !yaml_emitter_emit(&emitter, &ev) ||
+	    !yaml_document_start_event_initialize(&ev, NULL, NULL, NULL, 1) ||
+	    !yaml_emitter_emit(&emitter, &ev)) {
+		diag_set(SystemError, "failed to init event libyaml");
+		goto error;
+	}
+	if (!encode_node(&emitter, &data))
+		goto error;
+
+	if (!yaml_document_end_event_initialize(&ev, 1) ||
+	    !yaml_emitter_emit(&emitter, &ev) ||
+	    !yaml_stream_end_event_initialize(&ev) ||
+	    !yaml_emitter_emit(&emitter, &ev) ||
+	    !yaml_emitter_flush(&emitter)) {
+		diag_set(SystemError, "failed to end event libyaml");
+		goto error;
+	}
+
+	yaml_emitter_delete(&emitter);
+
+	size_t total_len = region_used(&fiber()->gc) - used;
+	char *buf = (char *) region_join(&fiber()->gc, total_len);
+	if (buf == NULL) {
+		diag_set(OutOfMemory, total_len, "region", "tuple_to_yaml");
+		return NULL;
+	}
+	return buf;
+error:
+	yaml_emitter_delete(&emitter);
+	return NULL;
+}