diff --git a/src/box/alter.cc b/src/box/alter.cc
index 88b785a3c66669b582b7e862d42d1c91beab0f9e..1fabaa69ef395536580db5606bfa3ba1dda1d9e4 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -447,11 +447,140 @@ space_opts_decode(struct space_opts *opts, const char *data)
 	}
 }
 
+/**
+ * Get an array of named fields of @a format. Necessary to create
+ * a new fields array after drop of an index. In such a case some
+ * fields from the space format could be deleted together with
+ * a deleted index, and the fields array must be rebuilt from
+ * the named and indexed fields.
+ * @param format Format to get named fields.
+ * @param[out] out_count Count of named fields.
+ * @param region Region to allocate a result array.
+ *
+ * @retval Array of named fields.
+ */
+static struct field_def *
+tuple_format_named_fields(const struct tuple_format *format,
+			  uint32_t *out_count, struct region *region)
+{
+	assert(format != NULL);
+	uint32_t count = 0;
+	for (uint32_t i = 0; i < format->field_count; ++i) {
+		if (format->fields[i].name != NULL)
+			++count;
+	}
+	*out_count = count;
+	if (count == 0)
+		return NULL;
+	size_t size = sizeof(struct field_def) * count;
+	struct field_def *ret =
+		(struct field_def *) region_alloc_xc(region, size);
+	for (uint32_t i = 0; i < format->field_count; ++i) {
+		if (format->fields[i].name != NULL) {
+			/*
+			 * No need to copy names on a region,
+			 * because format->fields names remains
+			 * valid until ret is already copied into
+			 * a new tuple_format.
+			 */
+			memcpy(&ret[i], &format->fields[i], sizeof(ret[i]));
+		}
+	}
+	return ret;
+}
+
+/**
+ * Decode field definition from MessagePack map. Format:
+ * {name: <string>, type: <string>}. Type is optional.
+ * @param[out] field Field to decode to.
+ * @param data MessagePack map to decode.
+ * @param space_name Name of a space, from which the field is got.
+ *        Used in error messages.
+ * @param errcode Error code to use for client errors. Either
+ *        create or modify space errors.
+ * @param fieldno Field number to decode. Used in error messages.
+ * @param region Region to allocate field name.
+ */
+static void
+field_def_decode(struct field_def *field, const char **data,
+		 const char *space_name, uint32_t errcode, uint32_t fieldno,
+		 struct region *region)
+{
+	if (mp_typeof(**data) != MP_MAP) {
+		tnt_raise(ClientError, errcode, space_name,
+			  tt_sprintf("field %d is not map",
+				     fieldno + TUPLE_INDEX_BASE));
+	}
+	int count = mp_decode_map(data);
+	*field = field_def_default;
+	for (int i = 0; i < count; ++i) {
+		if (mp_typeof(**data) != MP_STR) {
+			tnt_raise(ClientError, errcode, space_name,
+				  tt_sprintf("field %d format is not map"\
+					     " with string keys",
+					     fieldno + TUPLE_INDEX_BASE));
+		}
+		uint32_t key_len;
+		const char *key = mp_decode_str(data, &key_len);
+		opts_parse_key(field, field_def_reg, key, key_len, data,
+			       ER_WRONG_SPACE_FORMAT,
+			       fieldno + TUPLE_INDEX_BASE, region);
+	}
+	if (field->name == NULL) {
+		tnt_raise(ClientError, errcode, space_name,
+			  tt_sprintf("field %d name is not specified",
+				     fieldno + TUPLE_INDEX_BASE));
+	}
+	if (strlen(field->name) > BOX_NAME_MAX) {
+		tnt_raise(ClientError, errcode, space_name,
+			  tt_sprintf("field %d name is too long",
+				     fieldno + TUPLE_INDEX_BASE));
+	}
+	if (field->type == field_type_MAX) {
+		tnt_raise(ClientError, errcode, space_name,
+			  tt_sprintf("field %d has unknown field type",
+				     fieldno + TUPLE_INDEX_BASE));
+	}
+}
+
+/**
+ * Decode MessagePack array of fields.
+ * @param data MessagePack array of fields.
+ * @param[out] out_count Length of a result array.
+ * @param space_name Space name to use in error messages.
+ * @param errcode Errcode for client errors.
+ * @param region Region to allocate result array.
+ *
+ * @retval Array of fields.
+ */
+static struct field_def *
+space_format_decode(const char *data, uint32_t *out_count,
+		    const char *space_name, uint32_t errcode,
+		    struct region *region)
+{
+	/* Type is checked by _space format. */
+	assert(mp_typeof(*data) == MP_ARRAY);
+	uint32_t count = mp_decode_array(&data);
+	*out_count = count;
+	if (count == 0)
+		return NULL;
+	size_t size = count * sizeof(struct field_def);
+	struct field_def *region_defs =
+		(struct field_def *) region_alloc_xc(region, size);
+	for (uint32_t i = 0; i < count; ++i) {
+		field_def_decode(&region_defs[i], &data, space_name, errcode,
+				 i, region);
+	}
+	return region_defs;
+}
+
 /**
  * Fill space_def structure from struct tuple.
  */
 static struct space_def *
-space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode)
+space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode,
+			 struct field_def **fields, uint32_t *field_count,
+			 struct region *region)
 {
 	uint32_t name_len;
 	const char *name =
@@ -492,7 +621,30 @@ space_def_new_from_tuple(struct tuple *tuple, uint32_t errcode)
 	memcpy(def->engine_name, engine_name, name_len);
 	def->engine_name[name_len] = 0;
 	identifier_check_xc(def->engine_name);
-	space_opts_decode(&def->opts, tuple_field(tuple, BOX_SPACE_FIELD_OPTS));
+	const char *space_opts;
+	if (dd_version_id >= version_id(1, 7, 6)) {
+		/* Check space opts. */
+		space_opts =
+			tuple_field_with_type_xc(tuple, BOX_SPACE_FIELD_OPTS,
+						 MP_MAP);
+		/* Check space format */
+		const char *format =
+			tuple_field_with_type_xc(tuple, BOX_SPACE_FIELD_FORMAT,
+						 MP_ARRAY);
+		*fields = space_format_decode(format, field_count, def->name,
+					      errcode, region);
+		if (def->exact_field_count != 0 &&
+		    def->exact_field_count < *field_count) {
+			tnt_raise(ClientError, errcode, def->name,
+				  "exact_field_count must be either 0 or >= "\
+				  "formatted field count");
+		}
+	} else {
+		*fields = NULL;
+		*field_count = 0;
+		space_opts = tuple_field(tuple, BOX_SPACE_FIELD_OPTS);
+	}
+	space_opts_decode(&def->opts, space_opts);
 	Engine *engine = engine_find(def->engine_name);
 	engine->checkSpaceDef(def);
 	access_check_ddl(def->uid, SC_SPACE);
@@ -556,16 +708,23 @@ struct alter_space {
 	 * substantially.
 	 */
 	struct key_def *pk_def;
+	/** New space format. */
+	struct field_def *new_fields;
+	/** Length of @a new_fields. */
+	uint32_t field_count;
 };
 
-struct alter_space *
-alter_space_new(struct space *old_space)
+static struct alter_space *
+alter_space_new(struct space *old_space, struct field_def *fields,
+		uint32_t field_count)
 {
 	struct alter_space *alter =
 		region_calloc_object_xc(&fiber()->gc, struct alter_space);
 	rlist_create(&alter->ops);
 	alter->old_space = old_space;
 	alter->space_def = space_def_dup_xc(alter->old_space->def);
+	alter->new_fields = fields;
+	alter->field_count = field_count;
 	return alter;
 }
 
@@ -701,7 +860,8 @@ alter_space_do(struct txn *txn, struct alter_space *alter)
 	 * Create a new (empty) space for the new definition.
 	 * Sic: the triggers are not moved over yet.
 	 */
-	alter->new_space = space_new(alter->space_def, &alter->key_list);
+	alter->new_space = space_new(alter->space_def, &alter->key_list,
+				     alter->new_fields, alter->field_count);
 	/*
 	 * Copy the replace function, the new space is at the same recovery
 	 * phase as the old one. This hack is especially necessary for
@@ -1227,6 +1387,7 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 	struct txn_stmt *stmt = txn_current_stmt(txn);
 	struct tuple *old_tuple = stmt->old_tuple;
 	struct tuple *new_tuple = stmt->new_tuple;
+	struct region *region = &fiber()->gc;
 	/*
 	 * Things to keep in mind:
 	 * - old_tuple is set only in case of UPDATE.  For INSERT
@@ -1245,12 +1406,16 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 					     BOX_SPACE_FIELD_ID);
 	struct space *old_space = space_by_id(old_id);
 	if (new_tuple != NULL && old_space == NULL) { /* INSERT */
+		struct field_def *fields;
+		uint32_t field_count;
 		struct space_def *def =
-			space_def_new_from_tuple(new_tuple, ER_CREATE_SPACE);
+			space_def_new_from_tuple(new_tuple, ER_CREATE_SPACE,
+						 &fields, &field_count, region);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		RLIST_HEAD(empty_list);
-		struct space *space = space_new(def, &empty_list);
+		struct space *space = space_new(def, &empty_list, fields,
+						field_count);
 		/**
 		 * The new space must be inserted in the space
 		 * cache right away to achieve linearisable
@@ -1295,8 +1460,11 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		txn_on_rollback(txn, on_rollback);
 	} else { /* UPDATE, REPLACE */
 		assert(old_space != NULL && new_tuple != NULL);
+		struct field_def *fields;
+		uint32_t field_count;
 		struct space_def *def =
-			space_def_new_from_tuple(new_tuple, ER_ALTER_SPACE);
+			space_def_new_from_tuple(new_tuple, ER_ALTER_SPACE,
+						 &fields, &field_count, region);
 		auto def_guard =
 			make_scoped_guard([=] { space_def_delete(def); });
 		if (def->id != space_id(old_space))
@@ -1329,7 +1497,8 @@ on_replace_dd_space(struct trigger * /* trigger */, void *event)
 		 * Allow change of space properties, but do it
 		 * in WAL-error-safe mode.
 		 */
-		struct alter_space *alter = alter_space_new(old_space);
+		struct alter_space *alter = alter_space_new(old_space, fields,
+							    field_count);
 		auto alter_guard =
 			make_scoped_guard([=] {alter_space_delete(alter);});
 		(void) new ModifySpace(alter, def);
@@ -1422,7 +1591,16 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 			  "can not add a secondary key before primary");
 	}
 
-	struct alter_space *alter = alter_space_new(old_space);
+	struct alter_space *alter;
+	struct tuple_format *format = old_space->handler->format();
+	if (format != NULL) {
+		uint32_t count;
+		struct field_def *fields =
+			tuple_format_named_fields(format, &count, &fiber()->gc);
+		alter = alter_space_new(old_space, fields, count);
+	} else {
+		alter = alter_space_new(old_space, NULL, 0);
+	}
 	auto scoped_guard =
 		make_scoped_guard([=] { alter_space_delete(alter); });
 
@@ -1591,7 +1769,14 @@ on_replace_dd_truncate(struct trigger * /* trigger */, void *event)
 	/* Create an empty copy of the old space. */
 	struct rlist key_list;
 	space_dump_def(old_space, &key_list);
-	struct space *new_space = space_new(old_space->def, &key_list);
+	struct space *new_space;
+	struct tuple_format *format = old_space->handler->format();
+	if (format != NULL) {
+		new_space = space_new(old_space->def, &key_list,
+				      format->fields, format->field_count);
+	} else {
+		new_space = space_new(old_space->def, &key_list, NULL, 0);
+	}
 	new_space->truncate_count = truncate_count;
 	auto space_guard = make_scoped_guard([=] { space_delete(new_space); });
 
diff --git a/src/box/engine.h b/src/box/engine.h
index f082b6e1bdc5a833fb47015482b6982cdf55cd70..e2ff8ddd5824e5451c088fcad60979bf6f5631d7 100644
--- a/src/box/engine.h
+++ b/src/box/engine.h
@@ -45,6 +45,7 @@ engine_backup_cb(const char *path, void *arg);
 #if defined(__cplusplus)
 
 struct Handler;
+struct field_def;
 
 /** Engine instance */
 class Engine {
@@ -58,7 +59,8 @@ class Engine {
 	virtual void init();
 	/** Create a new engine instance for a space. */
 	virtual Handler *createSpace(struct rlist *key_list,
-				     uint32_t index_count,
+				     struct field_def *fields,
+				     uint32_t field_count, uint32_t index_count,
 				     uint32_t exact_field_count) = 0;
 	/**
 	 * Write statements stored in checkpoint @vclock to @stream.
diff --git a/src/box/errcode.h b/src/box/errcode.h
index f52a29ad1f64156a88afb1deedf4324a39c95161..90f15015b35fc47eda7566ee9bc49daefb6a04df 100644
--- a/src/box/errcode.h
+++ b/src/box/errcode.h
@@ -193,6 +193,7 @@ struct errcode_record {
 	/*138 */_(ER_LOAD_MODULE,		"Failed to dynamically load module '%.*s': %s") \
 	/*139 */_(ER_VINYL_MAX_TUPLE_SIZE,	"Failed to allocate %u bytes for tuple: tuple is too large. Check 'vinyl_max_tuple_size' configuration option.") \
 	/*140 */_(ER_WRONG_DD_VERSION,		"Wrong _schema version: expected 'major.minor[.patch]'") \
+	/*141 */_(ER_WRONG_SPACE_FORMAT,	"Wrong space format (field %u): %s") \
 
 /*
  * !IMPORTANT! Please follow instructions at start of the file
diff --git a/src/box/field_def.c b/src/box/field_def.c
index fa39d5a86f342d786561d59cf5c19a1edf7aed73..3fc19918f1deb80f44a97f1b95f7c353a405593f 100644
--- a/src/box/field_def.c
+++ b/src/box/field_def.c
@@ -43,18 +43,37 @@ const char *field_type_strs[] = {
 	/* [FIELD_TYPE_MAP]      = */ "map",
 };
 
+static int64_t
+field_type_by_name_wrapper(const char *str, uint32_t len)
+{
+	return field_type_by_name(str, len);
+}
+
+const struct opt_def field_def_reg[] = {
+	OPT_DEF_ENUM("type", field_type, struct field_def, type,
+		     field_type_by_name_wrapper),
+	OPT_DEF("name", OPT_STRPTR, struct field_def, name),
+	OPT_END,
+};
+
+const struct field_def field_def_default = {
+	.type = FIELD_TYPE_ANY,
+	.name = NULL,
+};
+
 enum field_type
-field_type_by_name(const char *name)
+field_type_by_name(const char *name, size_t len)
 {
-	enum field_type field_type = STR2ENUM(field_type, name);
+	enum field_type field_type = strnindex(field_type_strs, name, len,
+					       field_type_MAX);
 	if (field_type != field_type_MAX)
 		return field_type;
 	/* 'num' and 'str' in _index are deprecated since Tarantool 1.7 */
-	if (strcasecmp(name, "num") == 0)
+	if (strncasecmp(name, "num", len) == 0)
 		return FIELD_TYPE_UNSIGNED;
-	else if (strcasecmp(name, "str") == 0)
+	else if (strncasecmp(name, "str", len) == 0)
 		return FIELD_TYPE_STRING;
-	else if (strcmp(name, "*") == 0)
+	else if (len == 1 && name[0] == '*')
 		return FIELD_TYPE_ANY;
 	return field_type_MAX;
 }
diff --git a/src/box/field_def.h b/src/box/field_def.h
index 8c20b00eeaa37af594608d8283ed47fe5f67141d..17a336607d6b5b4b99baf9924a81a9439d687a0e 100644
--- a/src/box/field_def.h
+++ b/src/box/field_def.h
@@ -32,6 +32,7 @@
  */
 
 #include <stdint.h>
+#include "opt_def.h"
 
 #if defined(__cplusplus)
 extern "C" {
@@ -60,8 +61,14 @@ enum field_type {
 
 extern const char *field_type_strs[];
 
+/**
+ * Get field type by name
+ */
 enum field_type
-field_type_by_name(const char *name);
+field_type_by_name(const char *name, size_t len);
+
+extern const struct opt_def field_def_reg[];
+extern const struct field_def field_def_default;
 
 /**
  * @brief Field definition
@@ -76,6 +83,8 @@ struct field_def {
 	 * then UNKNOWN is stored for it.
 	 */
 	enum field_type type;
+	/** 0-terminated field name. */
+	char *name;
 	/**
 	 * Offset slot in field map in tuple. Normally tuple
 	 * stores field map - offsets of all fields participating
diff --git a/src/box/key_def.cc b/src/box/key_def.cc
index 6132ac5a8ea3619835e2acf6465c7c7be06513c5..d638cfa6eda866f6414607a29a0fadd3722511ff 100644
--- a/src/box/key_def.cc
+++ b/src/box/key_def.cc
@@ -205,7 +205,6 @@ key_def_encode_parts(char *data, const struct key_def *key_def)
 int
 key_def_decode_parts(struct key_def *key_def, const char **data)
 {
-	char buf[FIELD_TYPE_NAME_MAX];
 	for (uint32_t i = 0; i < key_def->part_count; i++) {
 		if (mp_typeof(**data) != MP_ARRAY) {
 			diag_set(ClientError, ER_WRONG_INDEX_PARTS,
@@ -238,8 +237,7 @@ key_def_decode_parts(struct key_def *key_def, const char **data)
 		const char *str = mp_decode_str(data, &len);
 		for (uint32_t j = 2; j < item_count; j++)
 			mp_next(data);
-		snprintf(buf, sizeof(buf), "%.*s", len, str);
-		enum field_type field_type = field_type_by_name(buf);
+		enum field_type field_type = field_type_by_name(str, len);
 		if (field_type == field_type_MAX) {
 			diag_set(ClientError, ER_WRONG_INDEX_PARTS,
 				 "unknown field type");
@@ -253,13 +251,11 @@ key_def_decode_parts(struct key_def *key_def, const char **data)
 int
 key_def_decode_parts_165(struct key_def *key_def, const char **data)
 {
-	char buf[FIELD_TYPE_NAME_MAX];
 	for (uint32_t i = 0; i < key_def->part_count; i++) {
 		uint32_t field_no = (uint32_t) mp_decode_uint(data);
 		uint32_t len;
 		const char *str = mp_decode_str(data, &len);
-		snprintf(buf, sizeof(buf), "%.*s", len, str);
-		enum field_type field_type = field_type_by_name(buf);
+		enum field_type field_type = field_type_by_name(str, len);
 		if (field_type == field_type_MAX) {
 			diag_set(ClientError, ER_WRONG_INDEX_PARTS,
 				 "unknown field type");
diff --git a/src/box/lua/upgrade.lua b/src/box/lua/upgrade.lua
index eb118504f77b2ff9a621569a51317003eff87503..44beef9b5ce77c8c7cc7000264267337de379f1e 100644
--- a/src/box/lua/upgrade.lua
+++ b/src/box/lua/upgrade.lua
@@ -636,10 +636,7 @@ end
 
 --------------------------------------------------------------------------------
 
-local function upgrade(options)
-    options = options or {}
-    setmetatable(options, {__index = {auto = false}})
-
+local function get_version()
     local version = box.space._schema:get{'version'}
     if version == nil then
         error('Missing "version" in box.space._schema')
@@ -648,7 +645,14 @@ local function upgrade(options)
     local minor = version[3]
     local patch = version[4] or 0
 
-    version = mkversion(major, minor, patch)
+    return mkversion(major, minor, patch)
+end
+
+local function upgrade(options)
+    options = options or {}
+    setmetatable(options, {__index = {auto = false}})
+
+    local version = get_version()
 
     local handlers = {
         {version = mkversion(1, 6, 8), func = upgrade_to_1_6_8, auto = false},
@@ -680,11 +684,23 @@ end
 
 local function bootstrap()
     set_system_triggers(false)
+    local version = get_version()
+
+    -- Initial() creates a spaces with 1.6.0 format, but 1.7.6
+    -- checks space formats, that fails initial(). It is because
+    -- bootstrap() is called after box.cfg{}. If box.cfg{} is run
+    -- on 1.7.6, then spaces in the cache contains new 1.7.6
+    -- formats (gh-2754). Spaces in the cache are not updated on
+    -- erase(), because system triggers are turned off.
+    if version ~= mkversion(1, 7, 6) then
+        -- erase current schema
+        erase()
+        -- insert initial schema
+        initial()
+    else
+        log.info('version is 1.7.6, do not reset schema to initial')
+    end
 
-    -- erase current schema
-    erase()
-    -- insert initial schema
-    initial()
     -- upgrade schema to the latest version
     upgrade()
 
diff --git a/src/box/memtx_engine.cc b/src/box/memtx_engine.cc
index 2432a45fcbb2a830ed7a49375ae7dde557f4f3a3..24f6d67167b49e0e4af6a5466e7c3e85908b7ab4 100644
--- a/src/box/memtx_engine.cc
+++ b/src/box/memtx_engine.cc
@@ -283,7 +283,8 @@ MemtxEngine::endRecovery()
 }
 
 Handler *MemtxEngine::createSpace(struct rlist *key_list,
-				  uint32_t index_count,
+				  struct field_def *fields,
+				  uint32_t field_count, uint32_t index_count,
 				  uint32_t exact_field_count)
 {
 	struct index_def *index_def;
@@ -296,7 +297,8 @@ Handler *MemtxEngine::createSpace(struct rlist *key_list,
 			keys[key_no++] = index_def->key_def;
 
 	struct tuple_format *format =
-		tuple_format_new(&memtx_tuple_format_vtab, keys, index_count, 0);
+		tuple_format_new(&memtx_tuple_format_vtab, keys, index_count, 0,
+				 fields, field_count);
 	if (format == NULL)
 		diag_raise();
 	tuple_format_ref(format);
diff --git a/src/box/memtx_engine.h b/src/box/memtx_engine.h
index 52cc1449086479a73e29551f110d1eebbf25c4a1..8ff444286665cc27da3314513cb76e2be8ef7f62 100644
--- a/src/box/memtx_engine.h
+++ b/src/box/memtx_engine.h
@@ -76,7 +76,8 @@ struct MemtxEngine: public Engine {
 		    uint32_t objsize_min, float alloc_factor);
 	~MemtxEngine();
 	virtual Handler *createSpace(struct rlist *key_list,
-				     uint32_t index_count,
+				     struct field_def *fields,
+				     uint32_t field_count, uint32_t index_count,
 				     uint32_t exact_field_count) override;
 	virtual void begin(struct txn *txn) override;
 	virtual void rollbackStatement(struct txn *,
diff --git a/src/box/schema.cc b/src/box/schema.cc
index 75d3cc8c2032e6594f93e3384b8c6d10e391bbc2..6469451734615af5d9b28de7da933dd95261ba4b 100644
--- a/src/box/schema.cc
+++ b/src/box/schema.cc
@@ -199,7 +199,7 @@ sc_space_new(uint32_t id, const char *name, struct key_def *key_def,
 	struct rlist key_list;
 	rlist_create(&key_list);
 	rlist_add_entry(&key_list, index_def, link);
-	struct space *space = space_new(def, &key_list);
+	struct space *space = space_new(def, &key_list, NULL, 0);
 	(void) space_cache_replace(space);
 	if (replace_trigger)
 		trigger_add(&space->on_replace, replace_trigger);
diff --git a/src/box/space.cc b/src/box/space.cc
index d3e5ac00610fc3aaca22b222eba22a232b33eced..e37b9c1c5d8e8c998905c0d9810603f4b0030527 100644
--- a/src/box/space.cc
+++ b/src/box/space.cc
@@ -78,7 +78,8 @@ space_fill_index_map(struct space *space)
 }
 
 struct space *
-space_new(struct space_def *def, struct rlist *key_list)
+space_new(struct space_def *def, struct rlist *key_list,
+	  struct field_def *fields, uint32_t field_count)
 {
 	uint32_t index_id_max = 0;
 	uint32_t index_count = 0;
@@ -110,7 +111,8 @@ space_new(struct space_def *def, struct rlist *key_list)
 				      index_count * sizeof(Index *));
 	Engine *engine = engine_find(def->engine_name);
 	/* init space engine instance */
-	space->handler = engine->createSpace(key_list, index_count,
+	space->handler = engine->createSpace(key_list, fields, field_count,
+					     index_count,
 					     def->exact_field_count);
 	rlist_foreach_entry(index_def, key_list, link) {
 		space->index_map[index_def->iid] =
diff --git a/src/box/space.h b/src/box/space.h
index 5f299f3cd52d37281a02b13505cda78180a8a141..ca0a48727f9f9bfdae6b4a8a5cde663165266077 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -192,17 +192,21 @@ void space_noop(struct space *space);
 uint32_t
 space_size(struct space *space);
 
+struct field_def;
 /**
  * Allocate and initialize a space. The space
  * needs to be loaded before it can be used
  * (see space->handler->recover()).
  * @param space_def Space definition.
  * @param key_list List of index_defs.
+ * @param fields Array of fields specified in space format.
+ * @param field_count Length of @a fields.
  *
  * @retval Space object.
  */
 struct space *
-space_new(struct space_def *space_def, struct rlist *key_list);
+space_new(struct space_def *space_def, struct rlist *key_list,
+	  struct field_def *fields, uint32_t field_count);
 
 /** Destroy and free a space. */
 void
diff --git a/src/box/sysview_engine.cc b/src/box/sysview_engine.cc
index 0334249ce1ce8a70cf16adbb1b78fc86c65b1236..01474f33e589122d3c68345bd36c317fb1bc7a71 100644
--- a/src/box/sysview_engine.cc
+++ b/src/box/sysview_engine.cc
@@ -114,10 +114,13 @@ SysviewEngine::SysviewEngine()
 }
 
 Handler *SysviewEngine::createSpace(struct rlist *key_list,
-				    uint32_t index_count,
+				    struct field_def *fields,
+				    uint32_t field_count, uint32_t index_count,
 				    uint32_t exact_field_count)
 {
 	(void) key_list;
+	(void) fields;
+	(void) field_count;
 	(void) index_count;
 	(void) exact_field_count;
 	return new SysviewSpace(this);
diff --git a/src/box/sysview_engine.h b/src/box/sysview_engine.h
index 118e49d33b5723aa81ce724ead05b72eabb78d70..fb5c67050c10d3358d68f08a0d1056e07faed1d5 100644
--- a/src/box/sysview_engine.h
+++ b/src/box/sysview_engine.h
@@ -36,7 +36,8 @@ struct SysviewEngine: public Engine {
 public:
 	SysviewEngine();
 	virtual Handler *createSpace(struct rlist *key_list,
-				     uint32_t index_count,
+				     struct field_def *fields,
+				     uint32_t field_count, uint32_t index_count,
 				     uint32_t exact_field_count) override;
 };
 
diff --git a/src/box/tuple.c b/src/box/tuple.c
index ed58fcf2d8e2456168fc3398d920ae6c25884c98..40c79b045b13b1eb1705dde37e5853c5fd6b72e8 100644
--- a/src/box/tuple.c
+++ b/src/box/tuple.c
@@ -386,7 +386,7 @@ tuple_init(void)
 	 */
 	RLIST_HEAD(empty_list);
 	tuple_format_runtime = tuple_format_new(&tuple_format_runtime_vtab,
-						NULL, 0, 0);
+						NULL, 0, 0, NULL, 0);
 	if (tuple_format_runtime == NULL)
 		return -1;
 
@@ -463,7 +463,7 @@ box_tuple_format_new(struct key_def **keys, uint16_t key_count)
 {
 	box_tuple_format_t *format =
 		tuple_format_new(&tuple_format_runtime_vtab,
-				 keys, key_count, 0);
+				 keys, key_count, 0, NULL, 0);
 	if (format != NULL)
 		tuple_format_ref(format);
 	return format;
diff --git a/src/box/tuple_format.c b/src/box/tuple_format.c
index fc6d1191823d2189dc53c243a64331574ff47417..7779e08cc799eeb33eadc8b79c1af862c9de03d5 100644
--- a/src/box/tuple_format.c
+++ b/src/box/tuple_format.c
@@ -36,20 +36,40 @@ static intptr_t recycled_format_ids = FORMAT_ID_NIL;
 
 static uint32_t formats_size = 0, formats_capacity = 0;
 
-/** Extract all available type info from keys. */
+/**
+ * Extract all available type info from keys and field
+ * definitions.
+ */
 static int
 tuple_format_create(struct tuple_format *format, struct key_def **keys,
-		    uint16_t key_count)
+		    uint16_t key_count, struct field_def *fields,
+		    uint32_t field_count)
 {
 	if (format->field_count == 0) {
 		format->field_map_size = 0;
 		return 0;
 	}
-
-	/* There may be fields between indexed fields (gaps). */
-	for (uint32_t i = 0; i < format->field_count; i++) {
+	/* Initialize defined fields */
+	char *name_pos = (char *)format + sizeof(struct tuple_format) +
+			 format->field_count * sizeof(struct field_def);
+	for (uint32_t i = 0; i < field_count; ++i) {
+		format->fields[i].type = fields[i].type;
+		format->fields[i].offset_slot = TUPLE_OFFSET_SLOT_NIL;
+		if (fields[i].name != NULL) {
+			format->fields[i].name = name_pos;
+			size_t len = strlen(fields[i].name);
+			memcpy(name_pos, fields[i].name, len);
+			name_pos[len] = 0;
+			name_pos += len + 1;
+		} else {
+			format->fields[i].name = NULL;
+		}
+	}
+	/* Initialize remaining fields */
+	for (uint32_t i = field_count; i < format->field_count; i++) {
 		format->fields[i].type = FIELD_TYPE_ANY;
 		format->fields[i].offset_slot = TUPLE_OFFSET_SLOT_NIL;
+		format->fields[i].name = NULL;
 	}
 
 	int current_slot = 0;
@@ -70,9 +90,9 @@ tuple_format_create(struct tuple_format *format, struct key_def **keys,
 				field->type = part->type;
 			} else if (field->type != part->type) {
 				/**
-				 * Check that two different indexes do not
-				 * put contradicting constraints on
-				 * indexed field type.
+				 * Check that there are no
+				 * conflicts between index part
+				 * types and space fields.
 				 */
 				diag_set(ClientError, ER_FIELD_TYPE_MISMATCH,
 					 part->fieldno + TUPLE_INDEX_BASE,
@@ -153,22 +173,25 @@ tuple_format_deregister(struct tuple_format *format)
 }
 
 static struct tuple_format *
-tuple_format_alloc(struct key_def **keys, uint16_t key_count)
+tuple_format_alloc(struct key_def **keys, uint16_t key_count,
+		   struct field_def *space_fields, uint32_t space_field_count)
 {
-	uint32_t max_fieldno = 0;
-
+	uint32_t field_count = space_field_count;
 	/* find max max field no */
 	for (uint16_t key_no = 0; key_no < key_count; ++key_no) {
 		struct key_def *key_def = keys[key_no];
 		struct key_part *part = key_def->parts;
 		struct key_part *pend = part + key_def->part_count;
 		for (; part < pend; part++)
-			max_fieldno = MAX(max_fieldno, part->fieldno);
+			field_count = MAX(field_count, part->fieldno + 1);
 	}
-	uint32_t field_count = key_count > 0 ? max_fieldno + 1 : 0;
 
 	uint32_t total = sizeof(struct tuple_format) +
 			 field_count * sizeof(struct field_def);
+	for (uint32_t i = 0; i < space_field_count; ++i) {
+		if (space_fields[i].name != NULL)
+			total += strlen(space_fields[i].name) + 1;
+	}
 
 	struct tuple_format *format = (struct tuple_format *) malloc(total);
 	if (format == NULL) {
@@ -193,9 +216,12 @@ tuple_format_delete(struct tuple_format *format)
 
 struct tuple_format *
 tuple_format_new(struct tuple_format_vtab *vtab, struct key_def **keys,
-		 uint16_t key_count, uint16_t extra_size)
+		 uint16_t key_count, uint16_t extra_size,
+		 struct field_def *space_fields, uint32_t space_field_count)
 {
-	struct tuple_format *format = tuple_format_alloc(keys, key_count);
+	struct tuple_format *format =
+		tuple_format_alloc(keys, key_count, space_fields,
+				   space_field_count);
 	if (format == NULL)
 		return NULL;
 	format->vtab = *vtab;
@@ -204,7 +230,8 @@ tuple_format_new(struct tuple_format_vtab *vtab, struct key_def **keys,
 		tuple_format_delete(format);
 		return NULL;
 	}
-	if (tuple_format_create(format, keys, key_count) < 0) {
+	if (tuple_format_create(format, keys, key_count, space_fields,
+				space_field_count) < 0) {
 		tuple_format_delete(format);
 		return NULL;
 	}
@@ -221,6 +248,13 @@ tuple_format_eq(const struct tuple_format *a, const struct tuple_format *b)
 		if (a->fields[i].type != b->fields[i].type ||
 		    a->fields[i].offset_slot != b->fields[i].offset_slot)
 			return false;
+		if (a->fields[i].name != NULL) {
+			assert(b->fields[i].name != NULL);
+			assert(strcmp(a->fields[i].name,
+				      b->fields[i].name) == 0);
+		} else {
+			assert(b->fields[i].name == NULL);
+		}
 	}
 	return true;
 }
@@ -230,14 +264,28 @@ tuple_format_dup(const struct tuple_format *src)
 {
 	uint32_t total = sizeof(struct tuple_format) +
 			 src->field_count * sizeof(struct field_def);
+	uint32_t name_offset = total;
+	for (uint32_t i = 0; i < src->field_count; ++i) {
+		if (src->fields[i].name != NULL)
+			total += strlen(src->fields[i].name) + 1;
+	}
 
 	struct tuple_format *format = (struct tuple_format *) malloc(total);
 	if (format == NULL) {
-		diag_set(OutOfMemory, sizeof(struct tuple_format), "malloc",
-			 "tuple format");
+		diag_set(OutOfMemory, total, "malloc", "tuple format");
 		return NULL;
 	}
 	memcpy(format, src, total);
+	char *name_pos = (char *)format + name_offset;
+	for (uint32_t i = 0; i < src->field_count; ++i) {
+		if (src->fields[i].name != NULL) {
+			int len = strlen(src->fields[i].name);
+			format->fields[i].name = name_pos;
+			memcpy(name_pos, src->fields[i].name, len);
+			name_pos[len] = 0;
+			name_pos += len + 1;
+		}
+	}
 	format->id = FORMAT_ID_NIL;
 	format->refs = 0;
 	if (tuple_format_register(format) != 0) {
diff --git a/src/box/tuple_format.h b/src/box/tuple_format.h
index ac84b28a20e7ce27158f13cfc6d278bd90037e6b..667bc34ff2ad689ed4fffa7d9b839df81a14eee3 100644
--- a/src/box/tuple_format.h
+++ b/src/box/tuple_format.h
@@ -140,17 +140,20 @@ tuple_format_unref(struct tuple_format *format)
 
 /**
  * Allocate, construct and register a new in-memory tuple format.
- * @param vtab             Virtual function table for specific engines.
- * @param keys             Array of key_defs of a space.
- * @param key_count        The number of keys in @a keys array.
- * @param extra_size       Extra bytes to reserve in tuples metadata.
+ * @param vtab Virtual function table for specific engines.
+ * @param keys Array of key_defs of a space.
+ * @param key_count The number of keys in @a keys array.
+ * @param extra_size Extra bytes to reserve in tuples metadata.
+ * @param space_fields Array of fields, defined in a space format.
+ * @param space_field_count Length of @a space_fields.
  *
  * @retval not NULL Tuple format.
  * @retval     NULL Memory error.
  */
 struct tuple_format *
 tuple_format_new(struct tuple_format_vtab *vtab, struct key_def **keys,
-		 uint16_t key_count, uint16_t extra_size);
+		 uint16_t key_count, uint16_t extra_size,
+		 struct field_def *space_fields, uint32_t space_field_count);
 
 /**
  * Check that two tuple formats are identical.
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index ea124632ca3424dcfb7fd56a42fc19c14d120c6e..e9196866c23d20c68a7126a230560e66a27eb316 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -4174,7 +4174,8 @@ vy_join_cb(const struct vy_log_record *record, void *arg)
 		if (ctx->format != NULL)
 			tuple_format_unref(ctx->format);
 		ctx->format = tuple_format_new(&vy_tuple_format_vtab,
-				(struct key_def **)&ctx->key_def, 1, 0);
+				(struct key_def **)&ctx->key_def, 1, 0,
+				NULL, 0);
 		if (ctx->format == NULL)
 			return -1;
 		tuple_format_ref(ctx->format);
diff --git a/src/box/vinyl_engine.cc b/src/box/vinyl_engine.cc
index 60223255e9d01245d4693a03bf3c46b006ddd567..2d0a4d89189649abb82fde25af955d368a722e6e 100644
--- a/src/box/vinyl_engine.cc
+++ b/src/box/vinyl_engine.cc
@@ -112,8 +112,8 @@ VinylEngine::endRecovery()
 }
 
 Handler *
-VinylEngine::createSpace(struct rlist *key_list,
-			 uint32_t index_count,
+VinylEngine::createSpace(struct rlist *key_list, struct field_def *fields,
+			 uint32_t field_count, uint32_t index_count,
 			 uint32_t exact_field_count)
 {
 	struct index_def *index_def;
@@ -126,7 +126,8 @@ VinylEngine::createSpace(struct rlist *key_list,
 			keys[key_no++] = index_def->key_def;
 
 	struct tuple_format *format =
-		tuple_format_new(&vy_tuple_format_vtab, keys, index_count, 0);
+		tuple_format_new(&vy_tuple_format_vtab, keys, index_count, 0,
+				 fields, field_count);
 	if (format == NULL)
 		diag_raise();
 	tuple_format_ref(format);
diff --git a/src/box/vinyl_engine.h b/src/box/vinyl_engine.h
index 5b80896793f49faf42eb187de21d6b9c8c15b1c2..aae83b876d6b25b25d951b4aafc61b27718d993f 100644
--- a/src/box/vinyl_engine.h
+++ b/src/box/vinyl_engine.h
@@ -39,7 +39,8 @@ struct VinylEngine: public Engine {
 	~VinylEngine();
 	virtual void init() override;
 	virtual Handler *createSpace(struct rlist *key_list,
-				     uint32_t index_count,
+				     struct field_def *fields,
+				     uint32_t field_count, uint32_t index_count,
 				     uint32_t exact_field_count) override;
 	virtual void beginStatement(struct txn *txn) override;
 	virtual void begin(struct txn *txn) override;
diff --git a/src/box/vy_index.c b/src/box/vy_index.c
index acb052f9f292862dba2251c986091e81f14f603e..1b038eb71ddca68b12fa38f23544c9925585da70 100644
--- a/src/box/vy_index.c
+++ b/src/box/vy_index.c
@@ -60,7 +60,7 @@ vy_index_env_create(struct vy_index_env *env, const char *path,
 		    void *upsert_thresh_arg)
 {
 	env->key_format = tuple_format_new(&vy_tuple_format_vtab,
-					   NULL, 0, 0);
+					   NULL, 0, 0, NULL, 0);
 	if (env->key_format == NULL)
 		return -1;
 	tuple_format_ref(env->key_format);
@@ -130,8 +130,19 @@ vy_index_new(struct vy_index_env *index_env, struct vy_cache_env *cache_env,
 
 	index->cmp_def = cmp_def;
 	index->key_def = key_def;
-	index->disk_format = tuple_format_new(&vy_tuple_format_vtab,
-					      &cmp_def, 1, 0);
+	if (index_def->iid == 0) {
+		/*
+		 * Disk tuples can be returned to an user from a
+		 * primary key. And they must have field
+		 * definitions as well as space->format tuples.
+		 */
+		index->disk_format =
+			tuple_format_new(&vy_tuple_format_vtab, &cmp_def, 1, 0,
+					 format->fields, format->field_count);
+	} else {
+		index->disk_format = tuple_format_new(&vy_tuple_format_vtab,
+						      &cmp_def, 1, 0, NULL, 0);
+	}
 	if (index->disk_format == NULL)
 		goto fail_format;
 	tuple_format_ref(index->disk_format);
diff --git a/test/box/access.result b/test/box/access.result
index 5fc84b9ca735a111a06d68ff5b05b454ed3067cc..8337bf1cd522db7650152141557234216c2778b2 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -514,20 +514,14 @@ box.space._priv:select{id}
 -- -----------------------------------------------------------
 -- Be a bit more rigorous in what is accepted in space _user
 -- -----------------------------------------------------------
-box.space._user:insert{10, 1, 'name'}
+utils = require('utils')
 ---
-- error: Field 4 was not found in the tuple
 ...
-box.space._user:insert{10, 1, 'name', 'strange-object-type'}
+box.space._user:insert{10, 1, 'name', 'strange-object-type', utils.setmap({})}
 ---
 - error: 'Failed to create user ''name'': unknown user type'
 ...
-box.space._user:insert{10, 1, 'name', 'user', 'password'}
----
-- error: 'Failed to create user ''name'': invalid password format, use box.schema.user.passwd()
-    to reset password'
-...
-box.space._user:insert{10, 1, 'name', 'role', 'password'}
+box.space._user:insert{10, 1, 'name', 'role', utils.setmap{'password'}}
 ---
 - error: 'Failed to create role ''name'': authentication data can not be set for a
     role'
diff --git a/test/box/access.test.lua b/test/box/access.test.lua
index 35dfa1ee70a8acfc2be4bc36aa7199bea504854b..cde4e0f9aa00aa04b4b4e4c946270d5f4e4c3062 100644
--- a/test/box/access.test.lua
+++ b/test/box/access.test.lua
@@ -211,10 +211,9 @@ box.space._priv:select{id}
 -- -----------------------------------------------------------
 -- Be a bit more rigorous in what is accepted in space _user
 -- -----------------------------------------------------------
-box.space._user:insert{10, 1, 'name'}
-box.space._user:insert{10, 1, 'name', 'strange-object-type'}
-box.space._user:insert{10, 1, 'name', 'user', 'password'}
-box.space._user:insert{10, 1, 'name', 'role', 'password'}
+utils = require('utils')
+box.space._user:insert{10, 1, 'name', 'strange-object-type', utils.setmap({})}
+box.space._user:insert{10, 1, 'name', 'role', utils.setmap{'password'}}
 session = nil
 -- -----------------------------------------------------------
 -- admin can't manage grants on not owned objects
diff --git a/test/box/alter.result b/test/box/alter.result
index abc38c2595cab3d51a6f25b92b147ccc610ae6d0..9401122600b0b28d15acd079e972c2ef29d36904 100644
--- a/test/box/alter.result
+++ b/test/box/alter.result
@@ -67,9 +67,9 @@ _space:replace{_index.id, ADMIN, '_index', 'memtx', 0, EMPTY_MAP, {}}
 --
 -- Can't change properties of a space
 --
-_space:replace{_space.id, ADMIN, '_space', 'memtx', 0}
+_space:replace{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
 ---
-- [280, 1, '_space', 'memtx', 0]
+- [280, 1, '_space', 'memtx', 0, {}, []]
 ...
 --
 -- Can't drop a system space
@@ -98,7 +98,7 @@ _space:update({_space.id}, {{'-', 1, 2}})
 --
 -- Create a space
 --
-t = _space:auto_increment{ADMIN, 'hello', 'memtx', 0}
+t = _space:auto_increment{ADMIN, 'hello', 'memtx', 0, EMPTY_MAP, {}}
 ---
 ...
 -- Check that a space exists
@@ -212,9 +212,9 @@ _index:delete{_index.id, 0}
 ---
 - error: Can't drop the primary key in a system space, space '_index'
 ...
-_space:insert{1000, ADMIN, 'hello', 'memtx', 0}
+_space:insert{1000, ADMIN, 'hello', 'memtx', 0, EMPTY_MAP, {}}
 ---
-- [1000, 1, 'hello', 'memtx', 0]
+- [1000, 1, 'hello', 'memtx', 0, {}, []]
 ...
 _index:insert{1000, 0, 'primary', 'tree', 1, 1, 0, 'unsigned'}
 ---
@@ -743,3 +743,243 @@ n
 ts:drop()
 ---
 ...
+--
+-- gh-2652: validate space format.
+--
+s = box.schema.space.create('test', { format = "format" })
+---
+- error: Illegal parameters, options parameter 'format' should be of type table
+...
+s = box.schema.space.create('test', { format = { "not_map" } })
+---
+- error: 'Failed to create space ''test'': field 1 is not map'
+...
+format = { utils.setmap({'unsigned'}) }
+---
+...
+s = box.schema.space.create('test', { format = format })
+---
+- error: 'Failed to create space ''test'': field 1 format is not map with string keys'
+...
+format = { { name = 100 } }
+---
+...
+s = box.schema.space.create('test', { format = format })
+---
+- error: 'Wrong space format (field 1): ''name'' must be string'
+...
+long = string.rep('a', box.schema.NAME_MAX + 1)
+---
+...
+format = { { name = long } }
+---
+...
+s = box.schema.space.create('test', { format = format })
+---
+- error: 'Failed to create space ''test'': field 1 name is too long'
+...
+format = { { name = 'id', type = '100' } }
+---
+...
+s = box.schema.space.create('test', { format = format })
+---
+- error: 'Failed to create space ''test'': field 1 has unknown field type'
+...
+format = { utils.setmap({}) }
+---
+...
+s = box.schema.space.create('test', { format = format })
+---
+- error: 'Failed to create space ''test'': field 1 name is not specified'
+...
+-- Ensure the format is updated after index drop.
+format = { { name = 'id', type = 'unsigned' } }
+---
+...
+s = box.schema.space.create('test', { format = format })
+---
+...
+pk = s:create_index('pk')
+---
+...
+sk = s:create_index('sk', { parts = { 2, 'string' } })
+---
+...
+s:replace{1, 1}
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected string'
+...
+sk:drop()
+---
+...
+s:replace{1, 1}
+---
+- [1, 1]
+...
+s:drop()
+---
+...
+-- Check index parts conflicting with space format.
+format = { { name='field1', type='unsigned' }, { name='field2', type='string' }, { name='field3', type='scalar' } }
+---
+...
+s = box.schema.space.create('test', { format = format })
+---
+...
+pk = s:create_index('pk')
+---
+...
+sk1 = s:create_index('sk1', { parts = { 2, 'unsigned' } })
+---
+- error: Ambiguous field type, field 2. Requested type is unsigned but the field has
+    previously been defined as string
+...
+sk2 = s:create_index('sk2', { parts = { 3, 'number' } })
+---
+- error: Ambiguous field type, field 3. Requested type is number but the field has
+    previously been defined as scalar
+...
+-- Check space format conflicting with index parts.
+sk3 = s:create_index('sk3', { parts = { 2, 'string' } })
+---
+...
+format[2].type = 'unsigned'
+---
+...
+s:format(format)
+---
+- error: Ambiguous field type, field 2. Requested type is string but the field has
+    previously been defined as unsigned
+...
+s:format()
+---
+- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'string'}, {
+    'name': 'field3', 'type': 'scalar'}]
+...
+s.index.sk3.parts
+---
+- - type: string
+    fieldno: 2
+...
+-- Space format can be updated, if conflicted index is deleted.
+sk3:drop()
+---
+...
+s:format(format)
+---
+...
+s:format()
+---
+- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'unsigned'},
+  {'name': 'field3', 'type': 'scalar'}]
+...
+-- Check deprecated field types.
+format[2].type = 'num'
+---
+...
+format[3].type = 'str'
+---
+...
+format[4] = { name = 'field4', type = '*' }
+---
+...
+format
+---
+- - name: field1
+    type: unsigned
+  - name: field2
+    type: num
+  - name: field3
+    type: str
+  - name: field4
+    type: '*'
+...
+s:format(format)
+---
+...
+s:format()
+---
+- [{'name': 'field1', 'type': 'unsigned'}, {'name': 'field2', 'type': 'num'}, {'name': 'field3',
+    'type': 'str'}, {'name': 'field4', 'type': '*'}]
+...
+s:replace{1, 2, '3', {4, 4, 4}}
+---
+- [1, 2, '3', [4, 4, 4]]
+...
+-- Check not indexed fields checking.
+s:truncate()
+---
+...
+format[2] = {name='field2', type='string'}
+---
+...
+format[3] = {name='field3', type='array'}
+---
+...
+format[4] = {name='field4', type='number'}
+---
+...
+format[5] = {name='field5', type='integer'}
+---
+...
+format[6] = {name='field6', type='scalar'}
+---
+...
+format[7] = {name='field7', type='map'}
+---
+...
+format[8] = {name='field8', type='any'}
+---
+...
+format[9] = {name='field9'}
+---
+...
+s:format(format)
+---
+...
+-- Check incorrect field types.
+format[9] = {name='err', type='any'}
+---
+...
+s:format(format)
+---
+...
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8, 9}
+---
+- [1, '2', [3, 3], 4.4, -5, true, {'value': 7}, 8, 9]
+...
+s:replace{1, 2, {3, 3}, 4.4, -5, true, {value=7}, 8, 9}
+---
+- error: 'Tuple field 2 type does not match one required by operation: expected string'
+...
+s:replace{1, '2', 3, 4.4, -5, true, {value=7}, 8, 9}
+---
+- error: 'Tuple field 3 type does not match one required by operation: expected array'
+...
+s:replace{1, '2', {3, 3}, '4', -5, true, {value=7}, 8, 9}
+---
+- error: 'Tuple field 4 type does not match one required by operation: expected number'
+...
+s:replace{1, '2', {3, 3}, 4.4, -5.5, true, {value=7}, 8, 9}
+---
+- error: 'Tuple field 5 type does not match one required by operation: expected integer'
+...
+s:replace{1, '2', {3, 3}, 4.4, -5, {6, 6}, {value=7}, 8, 9}
+---
+- error: 'Tuple field 6 type does not match one required by operation: expected scalar'
+...
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {7}, 8, 9}
+---
+- error: 'Tuple field 7 type does not match one required by operation: expected map'
+...
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}}
+---
+- error: Tuple field count 7 is less than required by a defined index (expected 9)
+...
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8}
+---
+- error: Tuple field count 8 is less than required by a defined index (expected 9)
+...
+s:drop()
+---
+...
diff --git a/test/box/alter.test.lua b/test/box/alter.test.lua
index cfc51799ad4d7299d2207eb747254667cb144ae2..c6d35c0b4c8d3b57f9fa8bdfb12c9fd0222db8a2 100644
--- a/test/box/alter.test.lua
+++ b/test/box/alter.test.lua
@@ -30,7 +30,7 @@ _space:replace{_index.id, ADMIN, '_index', 'memtx', 0, EMPTY_MAP, {}}
 --
 -- Can't change properties of a space
 --
-_space:replace{_space.id, ADMIN, '_space', 'memtx', 0}
+_space:replace{_space.id, ADMIN, '_space', 'memtx', 0, EMPTY_MAP, {}}
 --
 -- Can't drop a system space
 --
@@ -44,7 +44,7 @@ _space:update({_space.id}, {{'-', 1, 2}})
 --
 -- Create a space
 --
-t = _space:auto_increment{ADMIN, 'hello', 'memtx', 0}
+t = _space:auto_increment{ADMIN, 'hello', 'memtx', 0, EMPTY_MAP, {}}
 -- Check that a space exists
 space = box.space[t[1]]
 space.id
@@ -69,7 +69,7 @@ _index:replace{_index.id, 0, 'primary', 'tree', 1, 2, 0, 'unsigned', 1, 'unsigne
 _index:select{}
 -- modify indexes of a system space
 _index:delete{_index.id, 0}
-_space:insert{1000, ADMIN, 'hello', 'memtx', 0}
+_space:insert{1000, ADMIN, 'hello', 'memtx', 0, EMPTY_MAP, {}}
 _index:insert{1000, 0, 'primary', 'tree', 1, 1, 0, 'unsigned'}
 box.space[1000]:insert{0, 'hello, world'}
 box.space[1000]:drop()
@@ -286,3 +286,86 @@ o
 n
 
 ts:drop()
+
+--
+-- gh-2652: validate space format.
+--
+s = box.schema.space.create('test', { format = "format" })
+s = box.schema.space.create('test', { format = { "not_map" } })
+format = { utils.setmap({'unsigned'}) }
+s = box.schema.space.create('test', { format = format })
+format = { { name = 100 } }
+s = box.schema.space.create('test', { format = format })
+long = string.rep('a', box.schema.NAME_MAX + 1)
+format = { { name = long } }
+s = box.schema.space.create('test', { format = format })
+format = { { name = 'id', type = '100' } }
+s = box.schema.space.create('test', { format = format })
+format = { utils.setmap({}) }
+s = box.schema.space.create('test', { format = format })
+
+-- Ensure the format is updated after index drop.
+format = { { name = 'id', type = 'unsigned' } }
+s = box.schema.space.create('test', { format = format })
+pk = s:create_index('pk')
+sk = s:create_index('sk', { parts = { 2, 'string' } })
+s:replace{1, 1}
+sk:drop()
+s:replace{1, 1}
+s:drop()
+
+-- Check index parts conflicting with space format.
+format = { { name='field1', type='unsigned' }, { name='field2', type='string' }, { name='field3', type='scalar' } }
+s = box.schema.space.create('test', { format = format })
+pk = s:create_index('pk')
+sk1 = s:create_index('sk1', { parts = { 2, 'unsigned' } })
+sk2 = s:create_index('sk2', { parts = { 3, 'number' } })
+
+-- Check space format conflicting with index parts.
+sk3 = s:create_index('sk3', { parts = { 2, 'string' } })
+format[2].type = 'unsigned'
+s:format(format)
+s:format()
+s.index.sk3.parts
+
+-- Space format can be updated, if conflicted index is deleted.
+sk3:drop()
+s:format(format)
+s:format()
+
+-- Check deprecated field types.
+format[2].type = 'num'
+format[3].type = 'str'
+format[4] = { name = 'field4', type = '*' }
+format
+s:format(format)
+s:format()
+s:replace{1, 2, '3', {4, 4, 4}}
+
+-- Check not indexed fields checking.
+s:truncate()
+format[2] = {name='field2', type='string'}
+format[3] = {name='field3', type='array'}
+format[4] = {name='field4', type='number'}
+format[5] = {name='field5', type='integer'}
+format[6] = {name='field6', type='scalar'}
+format[7] = {name='field7', type='map'}
+format[8] = {name='field8', type='any'}
+format[9] = {name='field9'}
+s:format(format)
+
+-- Check incorrect field types.
+format[9] = {name='err', type='any'}
+s:format(format)
+
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8, 9}
+s:replace{1, 2, {3, 3}, 4.4, -5, true, {value=7}, 8, 9}
+s:replace{1, '2', 3, 4.4, -5, true, {value=7}, 8, 9}
+s:replace{1, '2', {3, 3}, '4', -5, true, {value=7}, 8, 9}
+s:replace{1, '2', {3, 3}, 4.4, -5.5, true, {value=7}, 8, 9}
+s:replace{1, '2', {3, 3}, 4.4, -5, {6, 6}, {value=7}, 8, 9}
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {7}, 8, 9}
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}}
+s:replace{1, '2', {3, 3}, 4.4, -5, true, {value=7}, 8}
+
+s:drop()
diff --git a/test/box/misc.result b/test/box/misc.result
index e06e0a298d58dda9933319f49fc8dde83c0acc5e..6b362c0dca5b54e8b73aef4bf6ae93da93377b4d 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -308,6 +308,7 @@ t;
   - 'box.error.NO_SUCH_TRIGGER : 34'
   - 'box.error.CHECKPOINT_IN_PROGRESS : 120'
   - 'box.error.FIELD_TYPE : 23'
+  - 'box.error.WRONG_SPACE_FORMAT : 141'
   - 'box.error.UNKNOWN_UPDATE_OP : 28'
   - 'box.error.CURSOR_NO_TRANSACTION : 80'
   - 'box.error.TUPLE_REF_OVERFLOW : 86'
diff --git a/test/box/rtree_misc.result b/test/box/rtree_misc.result
index 4ee9a545704f2df9662ffa2f5f3baf11dd6c9302..5c64ad4bc9f0a57117fce88ee9e8c279041e367c 100644
--- a/test/box/rtree_misc.result
+++ b/test/box/rtree_misc.result
@@ -466,6 +466,9 @@ box.snapshot()
 - ok
 ...
 test_run:cmd("restart server default")
+utils = require('utils')
+---
+...
 s = box.space.spatial
 ---
 ...
@@ -547,31 +550,12 @@ f(box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false}, {{2, 'array'}
 s.index.s:drop()
 ---
 ...
--- support of 1.6.5 _index structure
-f(box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'array'})
----
-- [0, 2, 's', 'rtree', 0, 1, 2, 'array']
-...
-s.index.s:drop()
----
-...
 -- with wrong args
-empty_map = setmetatable({}, {__serialize = 'map'})
----
-...
 box.space._index:insert{s.id, 2, 's', 'rtree', nil, {{2, 'array'}}}
 ---
-- error: 'Wrong record in _index space: got {number, number, string, string, unknown,
-    array}, expected {space id (number), index id (number), name (string), type (string),
-    options (map), parts (array)}'
+- error: 'Tuple field 5 type does not match one required by operation: expected map'
 ...
-box.space._index:insert{s.id, 2, 's', 'rtree', {}, {{2, 'array'}}}
----
-- error: 'Wrong record in _index space: got {number, number, string, string, array,
-    array}, expected {space id (number), index id (number), name (string), type (string),
-    options (map), parts (array)}'
-...
-box.space._index:insert{s.id, 2, 's', 'rtree', empty_map, {{2, 'array'}}}
+box.space._index:insert{s.id, 2, 's', 'rtree', utils.setmap({}), {{2, 'array'}}}
 ---
 - error: 'Can''t create or modify index ''s'' in space ''s'': RTREE index can not
     be unique'
@@ -613,22 +597,6 @@ box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false}, {{}}}
 - error: 'Wrong index parts: expected a non-empty array; expected field1 id (number),
     field1 type (string), ...'
 ...
-box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'thing'}
----
-- error: 'Wrong index parts: unknown field type; expected field1 id (number), field1
-    type (string), ...'
-...
-box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'array', 'wtf'}
----
-- error: 'Wrong record in _index space: got {number, number, string, string, number,
-    number, number, string, string}, expected {space id (number), index id (number),
-    name (string), type (string), is_unique (number), part count (number) part0 field
-    no (number), part0 field type (string), ...}'
-...
-box.space._index:insert{s.id, 2, 's', 'rtree', 0, 0}
----
-- error: 'Can''t create or modify index ''s'' in space ''s'': part count must be positive'
-...
 -- unknown args checked
 f(box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false, holy = 'cow'}, {{2, 'array'}}})
 ---
diff --git a/test/box/rtree_misc.test.lua b/test/box/rtree_misc.test.lua
index 7f84d589496c6242465f675301a1f6cbdc09b18c..1a2cdd0f5f966e643babad4c95ec66fe989d3521 100644
--- a/test/box/rtree_misc.test.lua
+++ b/test/box/rtree_misc.test.lua
@@ -164,6 +164,8 @@ box.snapshot()
 
 test_run:cmd("restart server default")
 
+utils = require('utils')
+
 s = box.space.spatial
 i = s.index.spatial
 s.index.spatial:select({{0, 0}}, {iterator = 'neighbor'})
@@ -199,15 +201,10 @@ function f(t) local r = {} for i, v in ipairs(t) do r[i] = v end r[1] = 0 return
 -- new index through inserting to _index space
 f(box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false}, {{2, 'array'}}})
 s.index.s:drop()
--- support of 1.6.5 _index structure
-f(box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'array'})
-s.index.s:drop()
 
 -- with wrong args
-empty_map = setmetatable({}, {__serialize = 'map'})
 box.space._index:insert{s.id, 2, 's', 'rtree', nil, {{2, 'array'}}}
-box.space._index:insert{s.id, 2, 's', 'rtree', {}, {{2, 'array'}}}
-box.space._index:insert{s.id, 2, 's', 'rtree', empty_map, {{2, 'array'}}}
+box.space._index:insert{s.id, 2, 's', 'rtree', utils.setmap({}), {{2, 'array'}}}
 box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false, dimension = 22}, {{2, 'array'}}}
 box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false, dimension = 'dimension'}, {{2, 'array'}}}
 box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false}, {{2, 'unsigned'}}}
@@ -216,9 +213,6 @@ box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false}, {{'no','time'}}
 box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false, distance = 'lobachevsky'}, {{2, 'array'}}}
 box.space._index:insert{s.id, 2, 's', 'rtee', {unique = false}, {{2, 'array'}}}
 box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false}, {{}}}
-box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'thing'}
-box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'array', 'wtf'}
-box.space._index:insert{s.id, 2, 's', 'rtree', 0, 0}
 
 -- unknown args checked
 f(box.space._index:insert{s.id, 2, 's', 'rtree', {unique = false, holy = 'cow'}, {{2, 'array'}}})
diff --git a/test/box/temp_spaces.result b/test/box/temp_spaces.result
index f0d9a684ef458fb816e59f9cdce5ef7c979143f7..d939f656395b09f44d326dba4e201d4bdc0f9c70 100644
--- a/test/box/temp_spaces.result
+++ b/test/box/temp_spaces.result
@@ -56,22 +56,6 @@ s:len()
 ---
 - 1
 ...
-_ = _space:update(s.id, {{'=', FLAGS, 'temporary'}})
----
-...
-s.temporary
----
-- true
-...
-_ = _space:update(s.id, {{'=', FLAGS, ''}})
----
-- error: 'Can''t modify space ''t'': can not switch temporary flag on a non-empty
-    space'
-...
-s.temporary
----
-- true
-...
 -- check that temporary space can be modified in read-only mode (gh-1378)
 box.cfg{read_only=true}
 ---
@@ -123,66 +107,6 @@ s.temporary
 ---
 - true
 ...
--- <!-- Tarantool < 1.7.0 compatibility
-_ = _space:update(s.id, {{'=', FLAGS, 'no-temporary'}})
----
-...
-s.temporary
----
-- false
-...
-_ = _space:update(s.id, {{'=', FLAGS, ',:asfda:temporary'}})
----
-...
-s.temporary
----
-- false
-...
-_ = _space:update(s.id, {{'=', FLAGS, 'a,b,c,d,e'}})
----
-...
-s.temporary
----
-- false
-...
-_ = _space:update(s.id, {{'=', FLAGS, 'temporary'}})
----
-...
-s.temporary
----
-- true
-...
-s:get{1}
----
-...
-s:insert{1, 2, 3}
----
-- [1, 2, 3]
-...
-_ = _space:update(s.id, {{'=', FLAGS, 'temporary'}})
----
-...
-s.temporary
----
-- true
-...
-_ = _space:update(s.id, {{'=', FLAGS, 'no-temporary'}})
----
-- error: 'Can''t modify space ''t'': can not switch temporary flag on a non-empty
-    space'
-...
-s.temporary
----
-- true
-...
-s:delete{1}
----
-- [1, 2, 3]
-...
-_ = _space:update(s.id, {{'=', FLAGS, 'no-temporary'}})
----
-...
--- Tarantool < 1.7.0 compatibility //-->
 s:drop()
 ---
 ...
diff --git a/test/box/temp_spaces.test.lua b/test/box/temp_spaces.test.lua
index c485b42f1f70cda00cbeb6699a22cc4730f0cefb..47d8b415bc5a6417260544fae64eb2622ccbde3a 100644
--- a/test/box/temp_spaces.test.lua
+++ b/test/box/temp_spaces.test.lua
@@ -23,11 +23,6 @@ s:insert{1, 2, 3}
 s:get{1}
 s:len()
 
-_ = _space:update(s.id, {{'=', FLAGS, 'temporary'}})
-s.temporary
-_ = _space:update(s.id, {{'=', FLAGS, ''}})
-s.temporary
-
 -- check that temporary space can be modified in read-only mode (gh-1378)
 box.cfg{read_only=true}
 box.cfg.read_only
@@ -48,27 +43,5 @@ s = box.space.t
 s:len()
 s.temporary
 
--- <!-- Tarantool < 1.7.0 compatibility
-_ = _space:update(s.id, {{'=', FLAGS, 'no-temporary'}})
-s.temporary
-_ = _space:update(s.id, {{'=', FLAGS, ',:asfda:temporary'}})
-s.temporary
-_ = _space:update(s.id, {{'=', FLAGS, 'a,b,c,d,e'}})
-s.temporary
-_ = _space:update(s.id, {{'=', FLAGS, 'temporary'}})
-s.temporary
-
-s:get{1}
-s:insert{1, 2, 3}
-
-_ = _space:update(s.id, {{'=', FLAGS, 'temporary'}})
-s.temporary
-_ = _space:update(s.id, {{'=', FLAGS, 'no-temporary'}})
-s.temporary
-
-s:delete{1}
-_ = _space:update(s.id, {{'=', FLAGS, 'no-temporary'}})
--- Tarantool < 1.7.0 compatibility //-->
-
 s:drop()
 s = nil
diff --git a/test/unit/vy_iterators_helper.c b/test/unit/vy_iterators_helper.c
index 1fee0474d2aadefb7d0637273b64a2c5ea8d31a7..ec24e3ae0e6a66af42592b71995a91eee75191b7 100644
--- a/test/unit/vy_iterators_helper.c
+++ b/test/unit/vy_iterators_helper.c
@@ -12,7 +12,8 @@ vy_iterator_C_test_init(size_t cache_size)
 	fiber_init(fiber_c_invoke);
 	tuple_init();
 	vy_cache_env_create(&cache_env, cord_slab_cache(), cache_size);
-	vy_key_format = tuple_format_new(&vy_tuple_format_vtab, NULL, 0, 0);
+	vy_key_format = tuple_format_new(&vy_tuple_format_vtab, NULL, 0, 0,
+					 NULL, 0);
 	tuple_format_ref(vy_key_format);
 }
 
@@ -189,9 +190,9 @@ create_test_mem(struct lsregion *region, struct key_def *def)
 {
 	/* Create format */
 	struct key_def *defs[] = { def };
-	struct tuple_format *format = tuple_format_new(&vy_tuple_format_vtab,
-						       defs, def->part_count,
-						       0);
+	struct tuple_format *format =
+		tuple_format_new(&vy_tuple_format_vtab, defs, def->part_count,
+				 0, NULL, 0);
 	fail_if(format == NULL);
 
 	/* Create format with column mask */
@@ -219,7 +220,7 @@ create_test_cache(uint32_t *fields, uint32_t *types,
 	*def = box_key_def_new(fields, types, key_cnt);
 	assert(*def != NULL);
 	vy_cache_create(cache, &cache_env, *def);
-	*format = tuple_format_new(&vy_tuple_format_vtab, def, 1, 0);
+	*format = tuple_format_new(&vy_tuple_format_vtab, def, 1, 0, NULL, 0);
 	tuple_format_ref(*format);
 }
 
diff --git a/test/unit/vy_mem.c b/test/unit/vy_mem.c
index 1fd01a1dc2c62fb660a4b377a8dab41de7cb802c..debac5cfa8cade7f399fe4db66689dc008d00483 100644
--- a/test/unit/vy_mem.c
+++ b/test/unit/vy_mem.c
@@ -85,7 +85,7 @@ test_iterator_restore_after_insertion()
 
 	/* Create format */
 	struct tuple_format *format = tuple_format_new(&vy_tuple_format_vtab,
-						       &key_def, 1, 0);
+						       &key_def, 1, 0, NULL, 0);
 	assert(format != NULL);
 	tuple_format_ref(format);
 
diff --git a/test/unit/vy_point_iterator.c b/test/unit/vy_point_iterator.c
index d9c763018e3894a5e8e24fbe2eac77cf1b2ba6ab..c10c8a1b6146821e0e5a8d49519ce55810bcc191 100644
--- a/test/unit/vy_point_iterator.c
+++ b/test/unit/vy_point_iterator.c
@@ -46,8 +46,8 @@ test_basic()
 
 	vy_cache_create(&cache, &cache_env, key_def);
 
-	struct tuple_format *format =
-		tuple_format_new(&vy_tuple_format_vtab, &key_def, 1, 0);
+	struct tuple_format *format = tuple_format_new(&vy_tuple_format_vtab,
+						       &key_def, 1, 0, NULL, 0);
 	isnt(format, NULL, "tuple_format_new is not NULL");
 	tuple_format_ref(format);
 
diff --git a/test/xlog/legacy.result b/test/xlog/legacy.result
new file mode 100644
index 0000000000000000000000000000000000000000..683c8b650bee80598e484aea4679efebb60f950b
--- /dev/null
+++ b/test/xlog/legacy.result
@@ -0,0 +1,177 @@
+test_run = require('test_run').new()
+---
+...
+version = test_run:get_cfg('version')
+---
+...
+-- Use 1.7.5 snapshot to check that space formats are not checked.
+-- It allows to use >= 1.6.5 format versions.
+test_run:cmd('create server legacy with script="xlog/upgrade.lua", workdir="xlog/upgrade/1.7.5"')
+---
+- true
+...
+test_run:cmd("start server legacy")
+---
+- true
+...
+test_run:switch('legacy')
+---
+- true
+...
+box.space._schema:get({'version'})
+---
+- ['version', 1, 7, 5]
+...
+_space = box.space._space
+---
+...
+--
+-- Check _space 1.7.5 format.
+--
+_space:replace{600, 1, 'test', 'memtx', 0}
+---
+- [600, 1, 'test', 'memtx', 0]
+...
+box.space.test:drop()
+---
+...
+--
+-- Check _index 1.6.5 format.
+--
+s = box.schema.space.create('s')
+---
+...
+pk = s:create_index('pk')
+---
+...
+sk = box.space._index:insert{s.id, 2, 'sk', 'rtree', 0, 1, 2, 'array'}
+---
+...
+s.index.sk.parts
+---
+- - type: array
+    fieldno: 3
+...
+s.index.sk:drop()
+---
+...
+box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'thing'}
+---
+- error: 'Wrong index parts: unknown field type; expected field1 id (number), field1
+    type (string), ...'
+...
+box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'array', 'wtf'}
+---
+- error: 'Wrong record in _index space: got {number, number, string, string, number,
+    number, number, string, string}, expected {space id (number), index id (number),
+    name (string), type (string), is_unique (number), part count (number) part0 field
+    no (number), part0 field type (string), ...}'
+...
+box.space._index:insert{s.id, 2, 's', 'rtree', 0, 0}
+---
+- error: 'Can''t create or modify index ''s'' in space ''s'': part count must be positive'
+...
+s:drop()
+---
+...
+--
+-- Check 1.6.5 space flags.
+--
+s = box.schema.space.create('t', { temporary = true })
+---
+...
+index = s:create_index('primary', { type = 'hash' })
+---
+...
+s:insert{1, 2, 3}
+---
+- [1, 2, 3]
+...
+_ = _space:update(s.id, {{'=', 6, 'temporary'}})
+---
+...
+s.temporary
+---
+- true
+...
+_ = _space:update(s.id, {{'=', 6, ''}})
+---
+- error: 'Can''t modify space ''t'': can not switch temporary flag on a non-empty
+    space'
+...
+s.temporary
+---
+- true
+...
+s:truncate()
+---
+...
+_ = _space:update(s.id, {{'=', 6, 'no-temporary'}})
+---
+...
+s.temporary
+---
+- false
+...
+_ = _space:update(s.id, {{'=', 6, ',:asfda:temporary'}})
+---
+...
+s.temporary
+---
+- false
+...
+_ = _space:update(s.id, {{'=', 6, 'a,b,c,d,e'}})
+---
+...
+s.temporary
+---
+- false
+...
+_ = _space:update(s.id, {{'=', 6, 'temporary'}})
+---
+...
+s.temporary
+---
+- true
+...
+s:get{1}
+---
+...
+s:insert{1, 2, 3}
+---
+- [1, 2, 3]
+...
+_ = _space:update(s.id, {{'=', 6, 'temporary'}})
+---
+...
+s.temporary
+---
+- true
+...
+_ = _space:update(s.id, {{'=', 6, 'no-temporary'}})
+---
+- error: 'Can''t modify space ''t'': can not switch temporary flag on a non-empty
+    space'
+...
+s.temporary
+---
+- true
+...
+s:delete{1}
+---
+- [1, 2, 3]
+...
+_ = _space:update(s.id, {{'=', 6, 'no-temporary'}})
+---
+...
+s:drop()
+---
+...
+test_run:switch('default')
+---
+- true
+...
+test_run:cmd('stop server legacy')
+---
+- true
+...
diff --git a/test/xlog/legacy.test.lua b/test/xlog/legacy.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..74569d3ce5f8d5cf4ca5104cdb7b06b060dcf3dd
--- /dev/null
+++ b/test/xlog/legacy.test.lua
@@ -0,0 +1,68 @@
+test_run = require('test_run').new()
+
+version = test_run:get_cfg('version')
+-- Use 1.7.5 snapshot to check that space formats are not checked.
+-- It allows to use >= 1.6.5 format versions.
+test_run:cmd('create server legacy with script="xlog/upgrade.lua", workdir="xlog/upgrade/1.7.5"')
+test_run:cmd("start server legacy")
+
+test_run:switch('legacy')
+
+box.space._schema:get({'version'})
+_space = box.space._space
+
+--
+-- Check _space 1.7.5 format.
+--
+_space:replace{600, 1, 'test', 'memtx', 0}
+box.space.test:drop()
+
+--
+-- Check _index 1.6.5 format.
+--
+s = box.schema.space.create('s')
+pk = s:create_index('pk')
+sk = box.space._index:insert{s.id, 2, 'sk', 'rtree', 0, 1, 2, 'array'}
+s.index.sk.parts
+s.index.sk:drop()
+box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'thing'}
+box.space._index:insert{s.id, 2, 's', 'rtree', 0, 1, 2, 'array', 'wtf'}
+box.space._index:insert{s.id, 2, 's', 'rtree', 0, 0}
+s:drop()
+
+--
+-- Check 1.6.5 space flags.
+--
+s = box.schema.space.create('t', { temporary = true })
+index = s:create_index('primary', { type = 'hash' })
+s:insert{1, 2, 3}
+_ = _space:update(s.id, {{'=', 6, 'temporary'}})
+s.temporary
+_ = _space:update(s.id, {{'=', 6, ''}})
+s.temporary
+s:truncate()
+
+_ = _space:update(s.id, {{'=', 6, 'no-temporary'}})
+s.temporary
+_ = _space:update(s.id, {{'=', 6, ',:asfda:temporary'}})
+s.temporary
+_ = _space:update(s.id, {{'=', 6, 'a,b,c,d,e'}})
+s.temporary
+_ = _space:update(s.id, {{'=', 6, 'temporary'}})
+s.temporary
+
+s:get{1}
+s:insert{1, 2, 3}
+
+_ = _space:update(s.id, {{'=', 6, 'temporary'}})
+s.temporary
+_ = _space:update(s.id, {{'=', 6, 'no-temporary'}})
+s.temporary
+
+s:delete{1}
+_ = _space:update(s.id, {{'=', 6, 'no-temporary'}})
+
+s:drop()
+
+test_run:switch('default')
+test_run:cmd('stop server legacy')