diff --git a/changelogs/unreleased/space-type.md b/changelogs/unreleased/space-type.md
new file mode 100644
index 0000000000000000000000000000000000000000..2f2b4bae8977a736541d81855ffc3cf6ba9901f8
--- /dev/null
+++ b/changelogs/unreleased/space-type.md
@@ -0,0 +1,3 @@
+## feature/space
+
+* Introduces space type: a new space definition field.
diff --git a/src/box/alter.cc b/src/box/alter.cc
index a1780983c47abddbb59b700a006d17833e8f7bf2..a09102be3cee9b99f590e3ebdc3f5390ddb40aa6 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -416,11 +416,18 @@ space_opts_decode(struct space_opts *opts, const char *map,
 		  struct region *region)
 {
 	space_opts_create(opts);
+	opts->type = SPACE_TYPE_DEFAULT;
 	if (opts_decode(opts, space_opts_reg, &map, region) != 0) {
 		diag_set(ClientError, ER_WRONG_SPACE_OPTIONS,
 			 diag_last_error(diag_get())->errmsg);
 		return -1;
 	}
+	/*
+	 * This can only be SPACE_TYPE_DEFAULT if neither 'type' nor 'temporary'
+	 * was specified, which means the space type is normal.
+	 */
+	if (opts->type == SPACE_TYPE_DEFAULT)
+		opts->type = SPACE_TYPE_NORMAL;
 	return 0;
 }
 
@@ -2072,8 +2079,8 @@ space_check_alter(struct space *old_space, struct space_def *new_space_def)
 	 */
 	assert(old_space->def->opts.group_id == new_space_def->opts.group_id);
 	/* Only alter from non-temporary to temporary can cause problems. */
-	if (old_space->def->opts.is_temporary ||
-	    !new_space_def->opts.is_temporary)
+	if (space_is_temporary(old_space) ||
+	    !space_opts_is_temporary(&new_space_def->opts))
 		return 0;
 	/* Check for foreign keys that refers to this space. */
 	struct space_cache_holder *h;
@@ -2090,7 +2097,7 @@ space_check_alter(struct space *old_space, struct space_def *new_space_def)
 		 * If the referring space is temporary too then the alter
 		 * can't break foreign key consistency after restart.
 		 */
-		if (other_space->def->opts.is_temporary)
+		if (space_opts_is_temporary(&other_space->def->opts))
 			continue;
 		diag_set(ClientError, ER_ALTER_SPACE,
 			 space_name(old_space),
diff --git a/src/box/lua/schema.lua b/src/box/lua/schema.lua
index 12a439ad3cca86b4740f14d4e0aea364d6089dae..2097128f55d03a3d9301492bba736db6235eed51 100644
--- a/src/box/lua/schema.lua
+++ b/src/box/lua/schema.lua
@@ -674,6 +674,24 @@ local function denormalize_format(format)
     return result
 end
 
+local space_types = {
+    'normal',
+    'data-temporary',
+}
+local function check_space_type(space_type)
+    if space_type == nil then
+        return
+    end
+    for _, t in ipairs(space_types) do
+        if t == space_type then
+            return
+        end
+    end
+    box.error(box.error.ILLEGAL_PARAMS,
+              "unknown space type, must be one of: '" ..
+              table.concat(space_types, "', '") .. "'.")
+end
+
 box.schema.space = {}
 box.schema.space.create = function(name, options)
     check_param(name, 'name', 'string')
@@ -684,6 +702,7 @@ box.schema.space.create = function(name, options)
         field_count = 'number',
         user = 'string, number',
         format = 'table',
+        type = 'string',
         is_local = 'boolean',
         temporary = 'boolean',
         is_sync = 'boolean',
@@ -694,10 +713,14 @@ box.schema.space.create = function(name, options)
     local options_defaults = {
         engine = 'memtx',
         field_count = 0,
-        temporary = false,
     }
     check_param_table(options, options_template)
     options = update_param_table(options, options_defaults)
+    check_space_type(options.type)
+    if options.type ~= nil and options.temporary ~= nil then
+        box.error(box.error.ILLEGAL_PARAMS,
+                  "only one of 'type' or 'temporary' may be specified")
+    end
     if options.engine == 'vinyl' then
         options = update_param_table(options, {
             defer_deletes = box.cfg.vinyl_defer_deletes,
@@ -732,7 +755,8 @@ box.schema.space.create = function(name, options)
     -- filter out global parameters from the options array
     local space_options = setmap({
         group_id = options.is_local and 1 or nil,
-        temporary = options.temporary and true or nil,
+        temporary = options.temporary,
+        type = options.type,
         is_sync = options.is_sync,
         defer_deletes = options.defer_deletes and true or nil,
         constraint = constraint,
@@ -825,6 +849,7 @@ local alter_space_template = {
     field_count = 'number',
     user = 'string, number',
     format = 'table',
+    type = 'string',
     temporary = 'boolean',
     is_sync = 'boolean',
     defer_deletes = 'boolean',
@@ -858,6 +883,11 @@ box.schema.space.alter = function(space_id, options)
     local field_count = options.field_count or tuple.field_count
     local flags = tuple.flags
 
+    if options.type ~= nil then
+        check_space_type(options.type)
+        flags.type = options.type
+    end
+
     if options.temporary ~= nil then
         flags.temporary = options.temporary
     end
diff --git a/src/box/lua/space.cc b/src/box/lua/space.cc
index 923cabd41b14bb19a310a7f0700fcf6b6e67791d..e6bb82859adbbdfb17b1911d83790672315cb4d8 100644
--- a/src/box/lua/space.cc
+++ b/src/box/lua/space.cc
@@ -304,11 +304,16 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 	lua_pushboolean(L, space_is_local(space));
 	lua_settable(L, i);
 
-	/* space.is_temp */
+	/* space.temporary */
 	lua_pushstring(L, "temporary");
 	lua_pushboolean(L, space_is_temporary(space));
 	lua_settable(L, i);
 
+	/* space.type */
+	lua_pushstring(L, "type");
+	lua_pushstring(L, space_type_name(space->def->opts.type));
+	lua_settable(L, i);
+
 	/* space.name */
 	lua_pushstring(L, "name");
 	lua_pushstring(L, space_name(space));
diff --git a/src/box/space.c b/src/box/space.c
index 58dfd41726cdf1c56cab2fa82b2fe2bbe56705d2..1a2bbe68e8c51e42751776160dc58a3fe17b9f1a 100644
--- a/src/box/space.c
+++ b/src/box/space.c
@@ -429,7 +429,7 @@ space_new(struct space_def *def, struct rlist *key_list)
 struct space *
 space_new_ephemeral(struct space_def *def, struct rlist *key_list)
 {
-	assert(def->opts.is_temporary);
+	assert(space_opts_is_temporary(&def->opts));
 	assert(def->opts.is_ephemeral);
 	struct space *space = space_new(def, key_list);
 	if (space == NULL)
diff --git a/src/box/space.h b/src/box/space.h
index 8941a469497600407ada61013a8f9289a4ec671b..d60daa3fa0c059ee2cf2a18b3b67267e3b22d5ea 100644
--- a/src/box/space.h
+++ b/src/box/space.h
@@ -344,7 +344,7 @@ space_name(const struct space *space)
 static inline bool
 space_is_temporary(const struct space *space)
 {
-	return space->def->opts.is_temporary;
+	return space_opts_is_temporary(&space->def->opts);
 }
 
 /** Return true if space is synchronous. */
diff --git a/src/box/space_def.c b/src/box/space_def.c
index 8f9b1c8df2d702ec08f6086e904d29541790456d..48bc26813a793757cd56e0c0bc31ac3067bba560 100644
--- a/src/box/space_def.c
+++ b/src/box/space_def.c
@@ -40,7 +40,7 @@
 
 const struct space_opts space_opts_default = {
 	/* .group_id = */ 0,
-	/* .is_temporary = */ false,
+	/* .type = */ SPACE_TYPE_NORMAL,
 	/* .is_ephemeral = */ false,
 	/* .view = */ false,
 	/* .is_sync = */ false,
@@ -75,9 +75,25 @@ static int
 space_opts_parse_upgrade(const char **data, void *vopts,
 			 struct region *region);
 
+/**
+ * Callback to parse a value with 'temporary' key in msgpack space opts
+ * definition. See function definition below.
+ */
+static int
+space_opts_parse_temporary(const char **data, void *vopts,
+			   struct region *region);
+
+/**
+ * Callback to parse a value with 'type' key in msgpack space opts
+ * definition. See function definition below.
+ */
+static int
+space_opts_parse_type(const char **data, void *vopts, struct region *region);
+
 const struct opt_def space_opts_reg[] = {
+	OPT_DEF_CUSTOM("type", space_opts_parse_type),
 	OPT_DEF("group_id", OPT_UINT32, struct space_opts, group_id),
-	OPT_DEF("temporary", OPT_BOOL, struct space_opts, is_temporary),
+	OPT_DEF_CUSTOM("temporary", space_opts_parse_temporary),
 	OPT_DEF("view", OPT_BOOL, struct space_opts, is_view),
 	OPT_DEF("is_sync", OPT_BOOL, struct space_opts, is_sync),
 	OPT_DEF("defer_deletes", OPT_BOOL, struct space_opts, defer_deletes),
@@ -97,7 +113,8 @@ space_tuple_format_new(struct tuple_format_vtab *vtab, void *engine,
 	return tuple_format_new(vtab, engine, keys, key_count,
 				def->fields, def->field_count,
 				def->exact_field_count, def->dict,
-				def->opts.is_temporary, def->opts.is_ephemeral,
+				space_opts_is_temporary(&def->opts),
+				def->opts.is_ephemeral,
 				def->opts.constraint_def,
 				def->opts.constraint_count);
 }
@@ -168,7 +185,7 @@ struct space_def*
 space_def_new_ephemeral(uint32_t exact_field_count, struct field_def *fields)
 {
 	struct space_opts opts = space_opts_default;
-	opts.is_temporary = true;
+	opts.type = SPACE_TYPE_DATA_TEMPORARY;
 	opts.is_ephemeral = true;
 	uint32_t field_count = exact_field_count;
 	if (fields == NULL) {
@@ -242,3 +259,56 @@ space_opts_parse_upgrade(const char **data, void *vopts,
 	opts->upgrade_def = space_upgrade_def_decode(data, region);
 	return opts->upgrade_def == NULL ? -1 : 0;
 }
+
+static int
+space_opts_parse_temporary(const char **data, void *vopts,
+			   struct region *region)
+{
+	(void)region;
+	if (mp_typeof(**data) != MP_BOOL) {
+		diag_set(IllegalParams, "'temporary' must be boolean");
+		return -1;
+	}
+	struct space_opts *opts = vopts;
+	if (opts->type != SPACE_TYPE_DEFAULT) {
+		/* This means 'type' was specified. */
+		diag_set(IllegalParams,
+			 "only one of 'type' or 'temporary' may be specified");
+		return -1;
+	}
+	bool is_temporary = mp_decode_bool(data);
+	opts->type = is_temporary ?
+		SPACE_TYPE_DATA_TEMPORARY : SPACE_TYPE_NORMAL;
+	return 0;
+}
+
+const char *space_type_strs[] = {
+	/* [SPACE_TYPE_NORMAL]         = */ "normal",
+	/* [SPACE_TYPE_DATA_TEMPORARY] = */ "data-temporary",
+};
+
+static int
+space_opts_parse_type(const char **data, void *vopts, struct region *region)
+{
+	(void)region;
+	if (mp_typeof(**data) != MP_STR) {
+		diag_set(IllegalParams, "'type' must be a string");
+		return -1;
+	}
+	uint32_t str_len = 0;
+	const char *str = mp_decode_str(data, &str_len);
+	enum space_type space_type = STRN2ENUM(space_type, str, str_len);
+	if (space_type == SPACE_TYPE_DEFAULT) {
+		diag_set(IllegalParams, "unknown space type");
+		return -1;
+	}
+	struct space_opts *opts = vopts;
+	if (opts->type != SPACE_TYPE_DEFAULT) {
+		/* This means 'temporary' was specified. */
+		diag_set(IllegalParams,
+			 "only one of 'type' or 'temporary' may be specified");
+		return -1;
+	}
+	opts->type = space_type;
+	return 0;
+}
diff --git a/src/box/space_def.h b/src/box/space_def.h
index 5db266e2ad96dc1e40a4a338cbca928e0c054d10..2b40585eac5774f78daddc4a0373c61c69d6bac9 100644
--- a/src/box/space_def.h
+++ b/src/box/space_def.h
@@ -42,6 +42,30 @@ extern "C" {
 
 struct space_upgrade_def;
 
+/** Space type names. */
+extern const char *space_type_strs[];
+
+/** See space_opts::type. */
+enum space_type {
+	/**
+	 * SPACE_TYPE_DEFAULT is a special value which is used when decoding
+	 * space options from a tuple. After the options have been parsed
+	 * SPACE_TYPE_DEFAULT will be replaced with SPACE_TYPE_NORMAL.
+	 * No live space should ever have this type.
+	 */
+	SPACE_TYPE_DEFAULT = -1,
+	SPACE_TYPE_NORMAL = 0,
+	SPACE_TYPE_DATA_TEMPORARY = 1,
+	space_type_MAX,
+};
+
+static inline const char *
+space_type_name(enum space_type space_type)
+{
+	assert(space_type != SPACE_TYPE_DEFAULT);
+	return space_type_strs[space_type];
+}
+
 /** Space options */
 struct space_opts {
 	/**
@@ -49,15 +73,15 @@ struct space_opts {
 	 * made to a space are replicated.
 	 */
 	uint32_t group_id;
-        /**
-	 * The space is a temporary:
+	/**
+	 * If set to SPACE_TYPE_DATA_TEMPORARY:
 	 * - it is empty at server start
 	 * - changes are not written to WAL
 	 * - changes are not part of a snapshot
-         * - in SQL: space_def memory is allocated on region and
-         *   does not require manual release.
+	 * - in SQL: space_def memory is allocated on region and
+	 *   does not require manual release.
 	 */
-	bool is_temporary;
+	enum space_type type;
 	/**
 	 * This flag is set if space is ephemeral and hence
 	 * its format might be re-used.
@@ -104,6 +128,16 @@ space_opts_create(struct space_opts *opts)
 	*opts = space_opts_default;
 }
 
+/**
+ * Check if the space is temporary.
+ */
+static inline bool
+space_opts_is_temporary(const struct space_opts *opts)
+{
+	assert(opts->type != SPACE_TYPE_DEFAULT);
+	return opts->type != SPACE_TYPE_NORMAL;
+}
+
 /** Space metadata. */
 struct space_def {
 	/** Space id. */
diff --git a/src/box/sql/build.c b/src/box/sql/build.c
index f3b48bbfa18ccad2306838a26d7a419f047b650a..38c7e08cba43020b2680fbd061542b80f5ac56d9 100644
--- a/src/box/sql/build.c
+++ b/src/box/sql/build.c
@@ -285,7 +285,7 @@ sql_shallow_space_copy(struct Parse *parse, struct space *space)
 	memcpy(ret->index, space->index,
 	       sizeof(struct index *) * space->index_count);
 	memcpy(ret->def, space->def, sizeof(struct space_def));
-	ret->def->opts.is_temporary = true;
+	ret->def->opts.type = SPACE_TYPE_DATA_TEMPORARY;
 	ret->def->opts.is_ephemeral = true;
 	if (ret->def->field_count != 0) {
 		ret->def->fields =
diff --git a/src/box/vinyl.c b/src/box/vinyl.c
index 018f2a409b9845ea3a1d5ef3598180313f46fe0d..ed8118b29d7284090719070896b83953d3da5b94 100644
--- a/src/box/vinyl.c
+++ b/src/box/vinyl.c
@@ -586,7 +586,7 @@ vinyl_engine_check_space_def(struct space_def *def)
 			return -1;
 		}
 	}
-	if (def->opts.is_temporary) {
+	if (space_opts_is_temporary(&def->opts)) {
 		diag_set(ClientError, ER_ALTER_SPACE,
 			 def->name, "engine does not support temporary flag");
 		return -1;