diff --git a/src/box/lua/misc.cc b/src/box/lua/misc.cc
index 98dabf2dd63093f2cffb1cb1dcee8ef5266d0be9..2cc1c74cafa82933cac1db287b017ce4bce7a30d 100644
--- a/src/box/lua/misc.cc
+++ b/src/box/lua/misc.cc
@@ -61,6 +61,83 @@ lbox_encode_tuple_on_gc(lua_State *L, int idx, size_t *p_len)
 	return (char *) region_join_xc(gc, *p_len);
 }
 
+/**
+ * __index metamethod for the formatted array table that lookups field by name.
+ * Metatable of the table is expected to have `field_map` that provides
+ * name->index dictionary.
+ */
+static int
+lua_formatted_array_index(lua_State *L)
+{
+	/* L stack: table, field_name. */
+
+	assert(lua_gettop(L) == 2);
+
+	if (lua_getmetatable(L, 1) == 0) {
+		lua_settop(L, 0);
+		return 0;
+	}
+
+	/* L stack: table, field_name, metatable. */
+
+	lua_getfield(L, 3, "field_map");
+	if (lua_type(L, 4) != LUA_TTABLE) {
+		lua_settop(L, 0);
+		return 0;
+	}
+	lua_remove(L, 3);
+
+	/* L stack: table, field_name, field_map. */
+
+	lua_pushvalue(L, 2);
+	lua_remove(L, 2);
+
+	/* L stack: table, field_map, field_name. */
+
+	lua_gettable(L, 2);
+	if (lua_type(L, 3) != LUA_TNUMBER) {
+		lua_settop(L, 0);
+		return 0;
+	}
+	lua_remove(L, 2);
+
+	/* L stack: table, field_index. */
+
+	lua_gettable(L, 1);
+	lua_remove(L, 1);
+
+	return 1;
+}
+
+/**
+ * Set metatable for lua table on the top of lua stack @a L that would provide
+ * access by names in it according to given @a format.
+ * Lua table (that is on the top of L) is expected to be array-like.
+ */
+static void
+lua_wrap_formatted_array(struct lua_State *L, struct tuple_format *format)
+{
+	assert(format != NULL);
+	assert(lua_type(L, -1) == LUA_TTABLE);
+	if (format->dict->name_count == 0)
+		/* No names - no reason to wrap. */
+		return;
+
+	lua_newtable(L); /* metatable */
+	lua_newtable(L); /* metatable.field_map */
+
+	for (size_t i = 0; i < format->dict->name_count; i++) {
+		lua_pushnumber(L, i + 1);
+		lua_setfield(L, -2, format->dict->names[i]);
+	}
+
+	lua_setfield(L, -2, "field_map");
+
+	lua_pushcfunction(L, lua_formatted_array_index);
+	lua_setfield(L, -2, "__index");
+	lua_setmetatable(L, -2);
+}
+
 extern "C" void
 port_c_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 {
@@ -75,6 +152,11 @@ port_c_dump_lua(struct port *base, struct lua_State *L, bool is_flat)
 		} else {
 			mp = pe->mp;
 			luamp_decode(L, luaL_msgpack_default, &mp);
+
+			if (pe->mp_format != NULL) {
+				assert(mp_typeof(*pe->mp) == MP_ARRAY);
+				lua_wrap_formatted_array(L, pe->mp_format);
+			}
 		}
 		if (!is_flat)
 			lua_rawseti(L, -2, ++i);
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index b38ed88a853dcb93448043aa802be02c79d98096..865406dc954e604dedb40547a2b176937872a4c8 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -85,7 +85,7 @@ ffi.cdef[[
 
     struct port {
         const struct port_vtab *vtab;
-        char pad[60];
+        char pad[68];
     };
 
     struct port_c_entry {
@@ -95,6 +95,7 @@ ffi.cdef[[
             char *mp;
         };
         uint32_t mp_size;
+        struct tuple_format *mp_format;
     };
 
     struct port_c {
diff --git a/src/box/port.c b/src/box/port.c
index 20dea5d6586a0ea16227ddf2f94e7ccc1586a0b2..dd020777f3052b632cdb153c56669111461f8ffd 100644
--- a/src/box/port.c
+++ b/src/box/port.c
@@ -60,6 +60,8 @@ port_c_destroy_entry(struct port_c_entry *pe)
 		mempool_free(&port_entry_pool, pe->mp);
 	else
 		free(pe->mp);
+	if (pe->mp_format != NULL)
+		tuple_format_unref(pe->mp_format);
 }
 
 static void
@@ -102,6 +104,7 @@ port_c_new_entry(struct port_c *port)
 		port->last = e;
 	}
 	e->next = NULL;
+	e->mp_format = NULL;
 	++port->size;
 	return e;
 }
@@ -175,6 +178,19 @@ port_c_add_mp(struct port *base, const char *mp, const char *mp_end)
 	return 0;
 }
 
+int
+port_c_add_formatted_mp(struct port *base, const char *mp, const char *mp_end,
+			struct tuple_format *format)
+{
+	int rc = port_c_add_mp(base, mp, mp_end);
+	if (rc != 0)
+		return rc;
+	struct port_c *port = (struct port_c *)base;
+	port->last->mp_format = format;
+	tuple_format_ref(format);
+	return 0;
+}
+
 int
 port_c_add_str(struct port *base, const char *str, uint32_t len)
 {
diff --git a/src/box/port.h b/src/box/port.h
index c02ac5bd0b8872353c2c9a9c2da5efe7364f72e9..08c5bf669597b825151072207e66deba1e29eeb4 100644
--- a/src/box/port.h
+++ b/src/box/port.h
@@ -119,6 +119,11 @@ struct port_c_entry {
 		char *mp;
 	};
 	uint32_t mp_size;
+	/**
+	 * Optional format of MsgPack data (that must be MP_ARR in that case).
+	 * Is NULL if format is not specified.
+	 */
+	struct tuple_format *mp_format;
 };
 
 /**
@@ -150,6 +155,16 @@ port_c_add_tuple(struct port *port, struct tuple *tuple);
 int
 port_c_add_mp(struct port *port, const char *mp, const char *mp_end);
 
+struct tuple_format;
+
+/**
+ * Append raw msgpack array to the port with given format.
+ * Msgpack is copied, the format is referenced for port's lifetime.
+ */
+int
+port_c_add_formatted_mp(struct port *port, const char *mp, const char *mp_end,
+			struct tuple_format *format);
+
 /** Append a string to the port. The string is copied as msgpack string. */
 int
 port_c_add_str(struct port *port, const char *str, uint32_t len);
diff --git a/src/box/tuple_constraint.h b/src/box/tuple_constraint.h
index 66c45e0dcfbe60f0156a6f606181309b9289065e..8f98c73cecb73dbf69a5c85317839118efc1e6ff 100644
--- a/src/box/tuple_constraint.h
+++ b/src/box/tuple_constraint.h
@@ -16,6 +16,7 @@ struct tuple_constraint;
 struct tuple_field;
 struct tuple_format;
 struct space;
+struct tuple_format;
 
 /**
  * Type of constraint check function.
diff --git a/src/box/tuple_constraint_func.c b/src/box/tuple_constraint_func.c
index f3ec7aba84d83741ae1ed0130e876b8435f11f6b..8b74c62a6bb0c4a71d65722adb213767db6158f3 100644
--- a/src/box/tuple_constraint_func.c
+++ b/src/box/tuple_constraint_func.c
@@ -79,7 +79,11 @@ tuple_constraint_call_func(const struct tuple_constraint *constr,
 {
 	struct port out_port, in_port;
 	port_c_create(&in_port);
-	port_c_add_mp(&in_port, mp_data, mp_data_end);
+	if (field != NULL)
+		port_c_add_mp(&in_port, mp_data, mp_data_end);
+	else
+		port_c_add_formatted_mp(&in_port, mp_data, mp_data_end,
+					constr->space->format);
 	port_c_add_str(&in_port, constr->def.name, constr->def.name_len);
 
 	int rc = func_call(constr->func_cache_holder.func, &in_port, &out_port);
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index 80ad5518ab6c7d22fcd13f2906777428effd35df..e5b254f6723fae89ab0f892705f26146d48af6ae 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -971,7 +971,7 @@ tuple_field_check_constraint(const struct tuple_field *field,
  * Check whole tuple constraints. Note that field constraints are not checked.
  */
 static int
-tuple_check_constraint(const struct tuple_format *format, const char *mp_data)
+tuple_check_constraint(struct tuple_format *format, const char *mp_data)
 {
 	if (format->constraint_count == 0)
 		return 0;
diff --git a/src/lib/core/port.h b/src/lib/core/port.h
index 2f884270980b0c2193f0454614f791943186ba14..d0002c114737f2f1fd7c086b81251ca7253c7c03 100644
--- a/src/lib/core/port.h
+++ b/src/lib/core/port.h
@@ -124,7 +124,7 @@ struct port {
 	 * Implementation dependent content. Needed to declare
 	 * an abstract port instance on stack.
 	 */
-	char pad[60];
+	char pad[68];
 };
 
 /** Is not inlined just to be exported. */
diff --git a/test/engine-luatest/gh_6436_tuple_constraint_test.lua b/test/engine-luatest/gh_6436_tuple_constraint_test.lua
index 9c16a2ba90297f489cb947cb8ca41301447e8b90..58c31cc084b008379978f4110f50824df29115c8 100644
--- a/test/engine-luatest/gh_6436_tuple_constraint_test.lua
+++ b/test/engine-luatest/gh_6436_tuple_constraint_test.lua
@@ -39,14 +39,18 @@ g.test_tuple_constraint_basics = function(cg)
     cg.server:exec(function(engine)
         local constr_tuple_body1 = "function(tuple, name) " ..
             "if name ~= 'tuple_constr1' then error('wrong name!') end " ..
-            "return tuple[1] + tuple[2] < 100 end"
+            "if tuple[1] ~= tuple.id1 then error('wrong format!') end " ..
+            "if tuple[2] ~= tuple.id2 then error('wrong format!') end " ..
+            "return tuple[1] + tuple.id2 < 100 end"
 
         local function func_opts(body)
             return {language = 'LUA', is_deterministic = true, body = body}
         end
         box.schema.func.create('tuple_constr1', func_opts(constr_tuple_body1))
 
+        local fmt = {'id1', 'id2', 'id3'}
         local s = box.schema.create_space('test', {engine=engine,
+                                                   format=fmt,
                                                    constraint='tuple_constr1'})
         s:create_index('pk')
         box.schema.user.grant('guest', 'read,write', 'space', 'test')