diff --git a/include/trigger.h b/include/trigger.h
index d3ebeb17e8f922ca2d57dabe5ad01c06f80102cc..49e799f7c6bc841408c6f0e89f375bcc9328fd8b 100644
--- a/include/trigger.h
+++ b/include/trigger.h
@@ -81,4 +81,12 @@ trigger_clear(struct trigger *trigger)
 	rlist_del_entry(trigger, link);
 }
 
+
+struct lua_trigger
+{
+	struct trigger trigger;
+	int ref;
+};
+
+
 #endif /* INCLUDES_TARANTOOL_TRIGGER_H */
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 62402d847f88201b4e0400e10b17f83e931e1254..d2a83179023e6a23dc73571131de4a7ebb4a4573 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -845,8 +845,7 @@ on_drop_space(struct trigger * /* trigger */, void *event)
 	space_delete(space);
 }
 
-static struct trigger drop_space_trigger =
-	{ rlist_nil, on_drop_space, NULL, NULL };
+static struct trigger drop_space_trigger =  { rlist_nil, on_drop_space, NULL };
 
 /**
  * A trigger which is invoked on replace in a data dictionary
@@ -1041,11 +1040,11 @@ on_replace_dd_index(struct trigger * /* trigger */, void *event)
 }
 
 struct trigger alter_space_on_replace_space = {
-	rlist_nil, on_replace_dd_space, NULL, NULL
+	rlist_nil, on_replace_dd_space, NULL
 };
 
 struct trigger alter_space_on_replace_index = {
-	rlist_nil, on_replace_dd_index, NULL, NULL
+	rlist_nil, on_replace_dd_index, NULL
 };
 
 /* vim: set foldmethod=marker */
diff --git a/src/box/box_lua.cc b/src/box/box_lua.cc
index 668ea8e739449ba6f5a9ab349d056f96adc75624..386bb8f3ee8a8707347a8065760130013ec57cc5 100644
--- a/src/box/box_lua.cc
+++ b/src/box/box_lua.cc
@@ -84,8 +84,6 @@ static const char *tuplelib_name = "box.tuple";
 static const char *tuple_iteratorlib_name = "box.tuple.iterator";
 static int tuple_totable_mt_ref = 0; /* a precreated metable for totable() */
 
-static void
-lbox_pushtuple(struct lua_State *L, struct tuple *tuple);
 
 static struct tuple *
 lua_totuple(struct lua_State *L, int first, int last);
@@ -524,7 +522,7 @@ lbox_tuple_tostring(struct lua_State *L)
 	return 1;
 }
 
-static void
+void
 lbox_pushtuple(struct lua_State *L, struct tuple *tuple)
 {
 	if (tuple) {
@@ -1262,6 +1260,31 @@ box_lua_execute(const struct request *request, struct txn *txn,
 	port_add_lua_multret(port, L);
 }
 
+/**
+ * Invoke C function with lua context
+ */
+void
+box_luactx(void (*f)(struct lua_State *L, va_list args), ...)
+{
+        lua_State *L = lua_newthread(root_L);
+        int coro_ref = luaL_ref(root_L, LUA_REGISTRYINDEX);
+
+        try {
+                auto scoped_guard = make_scoped_guard([=] {
+                        luaL_unref(root_L, LUA_REGISTRYINDEX, coro_ref);
+                });
+
+                va_list args;
+                va_start(args, f);
+                f(L, args);
+        } catch (const Exception& e) {
+                throw;
+        } catch (...) {
+                tnt_raise(ClientError, ER_PROC_LUA, lua_tostring(L, -1));
+        }
+}
+
+
 static void
 box_index_init_iterator_types(struct lua_State *L, int idx)
 {
diff --git a/src/box/box_lua.h b/src/box/box_lua.h
index a0572b6519b90e1ccbd268c7916edece1903bff8..d9c01d709ff062b44fc23546fd64bc88ee192d9e 100644
--- a/src/box/box_lua.h
+++ b/src/box/box_lua.h
@@ -32,6 +32,8 @@
 
 struct lua_State;
 struct txn;
+struct tuple;
+
 /**
  * Invoke a Lua stored procedure from the binary protocol
  * (implementation of 'CALL' command code).
@@ -40,5 +42,19 @@ void
 box_lua_execute(const struct request *request, struct txn *txn,
 		struct port *port);
 
+
+/**
+ * Invoke C function with lua context
+ */
+void
+box_luactx(void (*f)(struct lua_State *L, va_list args), ...);
+
+/**
+ * Push tuple on lua stack
+ */
+void
+lbox_pushtuple(struct lua_State *L, struct tuple *tuple);
+
+
 struct tuple *lua_istuple(struct lua_State *L, int narg);
 #endif /* INCLUDES_TARANTOOL_MOD_BOX_LUA_H */
diff --git a/src/box/box_lua_space.cc b/src/box/box_lua_space.cc
index f0d2908eeb6400984f43bec65f2defd534d32c4e..dee22eb6875211ba758b1892d5701f93ea9a1ebd 100644
--- a/src/box/box_lua_space.cc
+++ b/src/box/box_lua_space.cc
@@ -30,13 +30,174 @@
 #include "lua/utils.h"
 
 extern "C" {
-#include <lua.h>
-#include <lauxlib.h>
-#include <lualib.h>
+	#include <lua.h>
+	#include <lauxlib.h>
+	#include <lualib.h>
 } /* extern "C" */
 
 #include "space.h"
-#include <say.h>
+#include "schema.h"
+#include <trigger.h>
+#include <rlist.h>
+#include <scoped_guard.h>
+#include "box_lua.h"
+#include "txn.h"
+
+
+/**
+ * Run user trigger with lua context
+ */
+static void
+space_user_trigger_luactx(struct lua_State *L, va_list ap)
+{
+	struct lua_trigger *trigger = va_arg(ap, struct lua_trigger *);
+	struct txn *txn = va_arg(ap, struct txn *);
+
+	lua_rawgeti(L, LUA_REGISTRYINDEX, trigger->ref);
+
+	if (txn->old_tuple)
+		lbox_pushtuple(L, txn->old_tuple);
+	else
+		lua_pushnil(L);
+
+	if (txn->new_tuple)
+		lbox_pushtuple(L, txn->new_tuple);
+	else
+		lua_pushnil(L);
+
+	/* TODO: may me space object have to be here */
+	lua_pushstring(L, txn->space->def.name);
+
+	lua_call(L, 3, 0);
+}
+
+/**
+ * Trigger function for all spaces
+ */
+static void
+space_user_trigger(struct trigger *trigger, void *event)
+{
+	struct txn *txn = (struct txn *) event;
+	box_luactx(space_user_trigger_luactx, trigger, txn);
+}
+
+/**
+ * lua_trigger destroy method with lua context
+ */
+static void
+space_user_trigger_destroy_luaref(struct lua_State *L, va_list ap)
+{
+	int ref = va_arg(ap, int);
+	luaL_unref(L, LUA_REGISTRYINDEX, ref);
+}
+
+/**
+ * destroy trigger method (can be called from space_delete method)
+ */
+static void
+space_user_trigger_destroy(struct trigger *trigger)
+{
+	struct lua_trigger *lt = (struct lua_trigger *)trigger;
+	trigger_clear(trigger);
+	box_luactx(space_user_trigger_destroy_luaref, lt->ref);
+	free(trigger);
+}
+
+/**
+ * Set/Reset/Get space.on_replace trigger
+ */
+static int
+lbox_space_on_replace_trigger(struct lua_State *L)
+{
+	int top = lua_gettop(L);
+
+	if ( top == 0 || !lua_istable(L, 1))
+		luaL_error(L, "usage: space:on_replace "
+				"instead space.on_replace");
+
+	lua_pushstring(L, "n");
+	lua_rawget(L, 1);
+	if (lua_isnil(L, -1))
+		luaL_error(L, "Can't find space.n property");
+
+	int sno = lua_tointeger(L, -1);
+	lua_pop(L, 1);
+
+
+	struct space *space = space_find(sno);
+
+
+	struct trigger *trigger;
+	struct lua_trigger *current = NULL;
+	rlist_foreach_entry(trigger, &space->on_replace, link) {
+		if (trigger->run == space_user_trigger) {
+			current = (struct lua_trigger *)trigger;
+			break;
+		}
+	}
+
+
+	/* get current trigger function */
+	if (top == 1) {
+		if (!current) {
+			lua_pushnil(L);
+			return 1;
+		}
+		lua_rawgeti(L, LUA_REGISTRYINDEX, current->ref);
+		return 1;
+	}
+
+	/* set or re-set the trigger */
+	if (!lua_isfunction(L, 2) && !lua_isnil(L, 2)) {
+		luaL_error(L,
+				"usage: space:on_replace([ function | nil ])");
+	}
+
+	/* cleanup trigger */
+	if (lua_isnil(L, 2)) {
+		if (current) {
+			luaL_unref(L, LUA_REGISTRYINDEX, current->ref);
+			trigger_clear(&current->trigger);
+			free(current);
+		}
+		lua_pushnil(L);
+		return 1;
+	}
+
+
+
+	/* save ref on trigger function */
+	lua_pushvalue(L, 2);
+	int cb_ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+	if (current) {
+		luaL_unref(L, LUA_REGISTRYINDEX, current->ref);
+		current->ref = cb_ref;
+		lua_pushvalue(L, 2);
+		return 1;
+	}
+
+	auto scoped_guard = make_scoped_guard([&] {
+		luaL_unref(L, LUA_REGISTRYINDEX, cb_ref);
+	});
+
+
+	current = (struct lua_trigger *)malloc(sizeof(struct lua_trigger));
+
+	if (!current)
+		luaL_error(L, "Can't allocate memory for trigger");
+
+	current->trigger.run = space_user_trigger;
+	current->trigger.destroy = space_user_trigger_destroy;
+	current->ref = cb_ref;
+	trigger_set(&space->on_replace, &current->trigger);
+
+	scoped_guard.is_active = false;
+	lua_pushvalue(L, 2);
+	return 1;
+
+}
+
 
 /**
  * Make a single space available in Lua,
@@ -72,6 +233,12 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 	lua_pushboolean(L, space_index(space, 0) != 0);
 	lua_settable(L, i);
 
+
+        /* space:on_replace */
+        lua_pushstring(L, "on_replace");
+        lua_pushcfunction(L, lbox_space_on_replace_trigger);
+        lua_settable(L, i);
+
 	lua_getfield(L, i, "index");
 	if (lua_isnil(L, -1)) {
 		lua_pop(L, 1);
@@ -156,6 +323,8 @@ lbox_fillspace(struct lua_State *L, struct space *space, int i)
 	lua_pop(L, 3);	/* cleanup stack - box, schema, space */
 }
 
+
+
 /** Export a space to Lua */
 void
 box_lua_space_new(struct lua_State *L, struct space *space)
diff --git a/src/lua/session.cc b/src/lua/session.cc
index e7aeb8b243c46f646356660c930c10b6e3c43388..0887e3f4da5ddf6de2a3c52715b9eed68790dc27 100644
--- a/src/lua/session.cc
+++ b/src/lua/session.cc
@@ -38,6 +38,8 @@ extern "C" {
 #include "fiber.h"
 #include "session.h"
 #include "sio.h"
+#include <scoped_guard.h>
+#include "../box/box_lua.h"
 
 static const char *sessionlib_name = "box.session";
 
@@ -90,12 +92,21 @@ lbox_session_peer(struct lua_State *L)
 	return 1;
 }
 
-struct lbox_session_trigger
+
+/**
+ * run on_connect|on_disconnect trigger with lua context
+ */
+static void
+lbox_session_run_trigger_luactx(struct lua_State *L, va_list ap)
 {
-	struct trigger trigger;
-	int ref;
-};
+	struct lua_trigger *lt = va_arg(ap, struct lua_trigger *);
+	lua_rawgeti(L, LUA_REGISTRYINDEX, lt->ref);
+	lua_call(L, 0, 0);
+}
 
+/**
+ * run on_connect|on_disconnect trigger
+ */
 static void
 lbox_session_run_trigger(struct trigger *trg, void * /* event */)
 {
@@ -117,78 +128,95 @@ lbox_session_run_trigger(struct trigger *trg, void * /* event */)
 	}
 }
 
-static struct lbox_session_trigger on_connect =
-	{ { rlist_nil, lbox_session_run_trigger, NULL, NULL }, LUA_NOREF};
-static struct lbox_session_trigger on_disconnect =
-	{ { rlist_nil, lbox_session_run_trigger, NULL, NULL }, LUA_NOREF};
-
+/**
+ * set/reset/get trigger
+ */
 static int
-lbox_session_set_trigger(struct lua_State *L,
-			 struct rlist *list,
-			 struct lbox_session_trigger *trigger)
+lbox_session_set_trigger(struct lua_State *L, struct rlist *list)
 {
-	if (lua_gettop(L) != 1 ||
-	    (lua_type(L, -1) != LUA_TFUNCTION &&
-	     lua_type(L, -1) != LUA_TNIL)) {
+	int top = lua_gettop(L);
+	if (top > 1 || (top && !lua_isfunction(L, 1) && !lua_isnil(L, 1)))
 		luaL_error(L, "session.on_connect(chunk): bad arguments");
+
+	struct lua_trigger *current = 0;
+	struct trigger *trigger;
+	rlist_foreach_entry(trigger, list, link) {
+		if (trigger->run == lbox_session_run_trigger) {
+			current = (struct lua_trigger *)trigger;
+			break;
+		}
 	}
 
-	/* Pop the old trigger */
-	if (trigger->ref != LUA_NOREF) {
-		lua_rawgeti(L, LUA_REGISTRYINDEX, trigger->ref);
-		luaL_unref(L, LUA_REGISTRYINDEX, trigger->ref);
-		trigger_clear(&trigger->trigger);
-	} else {
+	/* get current trigger */
+	if (top == 0) {
+		if (current)
+			lua_rawgeti(L, LUA_REGISTRYINDEX, current->ref);
+		else
+			lua_pushnil(L);
+		return 1;
+	}
+
+	/* cleanup the trigger */
+	if (lua_isnil(L, 1)) {
+		if (current) {
+			trigger_clear(&current->trigger);
+			luaL_unref(L, LUA_REGISTRYINDEX, current->ref);
+			free(current);
+		}
 		lua_pushnil(L);
+		return 1;
 	}
 
-	/*
-	 * Set or clear the trigger. Return the old value of the
-	 * trigger.
-	 */
-	if (lua_type(L, -2) == LUA_TNIL) {
-		trigger->ref = LUA_NOREF;
+	/* set new trigger */
+	lua_pushvalue(L, 1);
+	int ref = luaL_ref(L, LUA_REGISTRYINDEX);
+
+
+	if (current) {
+		luaL_unref(L, LUA_REGISTRYINDEX, current->ref);
+		current->ref = ref;
 	} else {
-		/* Move the trigger to the top of the stack. */
-		lua_insert(L, -2);
-		/* Reference the new trigger. Pops it. */
-		trigger->ref = luaL_ref(L, LUA_REGISTRYINDEX);
-		trigger_set(list, &trigger->trigger);
+		current = (struct lua_trigger *)
+			malloc(sizeof(struct lua_trigger));
+		if (!current) {
+			luaL_unref(L, LUA_REGISTRYINDEX, ref);
+			luaL_error(L, "Can't allocate memory for trigger");
+		}
+		current->trigger.destroy = NULL;
+		current->trigger.run = lbox_session_run_trigger;
+		current->ref = ref;
+		trigger_set(list, &current->trigger);
 	}
-	/* Return the old trigger. */
+
+	lua_pushvalue(L, 1);
 	return 1;
+
 }
 
 static int
 lbox_session_on_connect(struct lua_State *L)
 {
-	return lbox_session_set_trigger(L, &session_on_connect, &on_connect);
+	return lbox_session_set_trigger(L, &session_on_connect);
 }
 
 static int
 lbox_session_on_disconnect(struct lua_State *L)
 {
-	return lbox_session_set_trigger(L, &session_on_disconnect,
-					&on_disconnect);
+	return lbox_session_set_trigger(L, &session_on_disconnect);
 }
 
-static const struct luaL_reg lbox_session_meta [] = {
-	{"id", lbox_session_id},
-	{NULL, NULL}
-};
-
-static const struct luaL_reg sessionlib[] = {
-	{"id", lbox_session_id},
-	{"exists", lbox_session_exists},
-	{"peer", lbox_session_peer},
-	{"on_connect", lbox_session_on_connect},
-	{"on_disconnect", lbox_session_on_disconnect},
-	{NULL, NULL}
-};
 
 void
 tarantool_lua_session_init(struct lua_State *L)
 {
+	static const struct luaL_reg sessionlib[] = {
+		{"id", lbox_session_id},
+		{"exists", lbox_session_exists},
+		{"peer", lbox_session_peer},
+		{"on_connect", lbox_session_on_connect},
+		{"on_disconnect", lbox_session_on_disconnect},
+		{NULL, NULL}
+	};
 	luaL_register(L, sessionlib_name, sessionlib);
 	lua_pop(L, 1);
 }
diff --git a/test/box/bad_trigger.result b/test/box/bad_trigger.result
index 58c553cdc8fe1ad4e47cded9827ad3d262760cee..362ef175bc45c3d5ca94784ecb88d34e8a38e700 100644
--- a/test/box/bad_trigger.result
+++ b/test/box/bad_trigger.result
@@ -5,7 +5,7 @@
  
 type(box.session.on_connect(function() nosuchfunction() end))
 ---
-- nil
+- function
 ...
 select * from t0 where k0=0
 ---
@@ -17,5 +17,5 @@ Connection is dead.
 
 type(box.session.on_connect(nil))
 ---
-- function
+- nil
 ...
diff --git a/test/box/lua.test.py b/test/box/lua.test.py
index 6f2bbe384570db2d0cc4d5241ecb425bbe468014..d0f2b3a3e8074a1c34eb328705e09e2dd9ed1a0a 100644
--- a/test/box/lua.test.py
+++ b/test/box/lua.test.py
@@ -126,14 +126,14 @@ admin("t = {} for k,v in pairs(box.cfg) do table.insert(t, k..': '..tostring(v))
 sys.stdout.push_filter("'reload: .*", "'reload: function_ptr'")
 admin("t")
 sys.stdout.pop_filter()
-admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' then table.insert(t, k..': '..tostring(v)) end end")
+admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end")
 admin("t")
 admin("box.cfg.reload()")
 admin("t = {} for k,v in pairs(box.cfg) do table.insert(t, k..': '..tostring(v)) end")
 sys.stdout.push_filter("'reload: .*", "'reload: function_ptr'")
 admin("t")
 sys.stdout.pop_filter()
-admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' then table.insert(t, k..': '..tostring(v)) end end")
+admin("t = {} for k,v in pairs(box.space[0]) do if type(v) ~= 'table' and type(v) ~= 'function' then table.insert(t, k..': '..tostring(v)) end end")
 admin("t")
 # must be read-only
 admin("box.cfg.nosuchoption = 1")
diff --git a/test/box/session.result b/test/box/session.result
index eeec64ab040e1264ce2b0e7d4f9fc35d13188d9d..37eaf1e6a32b293bd4f0252e1e22563280a26a9b 100644
--- a/test/box/session.result
+++ b/test/box/session.result
@@ -51,13 +51,13 @@ box.session.peer() == box.session.peer(box.session.id())
 - true
 ...
 -- check on_connect/on_disconnect triggers
-box.session.on_connect(function() end)
+type(box.session.on_connect(function() end))
 ---
-- null
+- function
 ...
-box.session.on_disconnect(function() end)
+type(box.session.on_disconnect(function() end))
 ---
-- null
+- function
 ...
 -- check it's possible to reset these triggers
 type(box.session.on_connect(function() error('hear') end))
@@ -69,13 +69,13 @@ type(box.session.on_disconnect(function() error('hear') end))
 - function
 ...
 -- check on_connect/on_disconnect argument count and type
-box.session.on_connect()
+type(box.session.on_connect())
 ---
-- error: 'session.on_connect(chunk): bad arguments'
+- function
 ...
-box.session.on_disconnect()
+type(box.session.on_disconnect())
 ---
-- error: 'session.on_connect(chunk): bad arguments'
+- function
 ...
 box.session.on_connect(function() end, function() end)
 ---
@@ -102,21 +102,13 @@ box.session.on_disconnect(1)
 - error: 'session.on_connect(chunk): bad arguments'
 ...
 -- use of nil to clear the trigger
-type(box.session.on_connect(nil))
+box.session.on_connect(nil)
 ---
-- function
-...
-type(box.session.on_disconnect(nil))
----
-- function
-...
-type(box.session.on_connect(nil))
----
-- nil
+- null
 ...
-type(box.session.on_disconnect(nil))
+box.session.on_disconnect(nil)
 ---
-- nil
+- null
 ...
 -- check how connect/disconnect triggers work
 function inc() active_connections = active_connections + 1 end
@@ -125,13 +117,13 @@ function inc() active_connections = active_connections + 1 end
 function dec() active_connections = active_connections - 1 end
 ---
 ...
-box.session.on_connect(inc)
+type(box.session.on_connect(inc))
 ---
-- null
+- function
 ...
-box.session.on_disconnect(dec)
+type(box.session.on_disconnect(dec))
 ---
-- null
+- function
 ...
 active_connections = 0
 ---
@@ -155,22 +147,22 @@ active_connections
 ---
 - 0
 ...
-type(box.session.on_connect(nil))
+box.session.on_connect(nil)
 ---
-- function
+- null
 ...
-type(box.session.on_disconnect(nil))
+box.session.on_disconnect(nil)
 ---
-- function
+- null
 ...
 -- write audit trail of connect/disconnect into a space
-box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end)
+type(box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end))
 ---
-- null
+- function
 ...
-box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end)
+type(box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end))
 ---
-- null
+- function
 ...
 --# create connection con_three to default
 --# set connection con_three
@@ -181,13 +173,13 @@ space:select(0, box.session.id())[0] == box.session.id()
 --# set connection default
 --# drop connection con_three
 -- cleanup
-type(box.session.on_connect(nil))
+box.session.on_connect(nil)
 ---
-- function
+- null
 ...
-type(box.session.on_disconnect(nil))
+box.session.on_disconnect(nil)
 ---
-- function
+- null
 ...
 active_connections
 ---
diff --git a/test/box/session.test.lua b/test/box/session.test.lua
index 16e79ffd7b1db90f25aec64b17ffacc9c07b9157..c3ff396c7311b5cbdb53b77edc1c2ed8d29c6fc1 100644
--- a/test/box/session.test.lua
+++ b/test/box/session.test.lua
@@ -17,16 +17,16 @@ failed
 box.session.peer() == box.session.peer(box.session.id())
 
 -- check on_connect/on_disconnect triggers
-box.session.on_connect(function() end)
-box.session.on_disconnect(function() end)
+type(box.session.on_connect(function() end))
+type(box.session.on_disconnect(function() end))
 
 -- check it's possible to reset these triggers
 type(box.session.on_connect(function() error('hear') end))
 type(box.session.on_disconnect(function() error('hear') end))
 
 -- check on_connect/on_disconnect argument count and type
-box.session.on_connect()
-box.session.on_disconnect()
+type(box.session.on_connect())
+type(box.session.on_disconnect())
 
 box.session.on_connect(function() end, function() end)
 box.session.on_disconnect(function() end, function() end)
@@ -38,16 +38,14 @@ box.session.on_connect(1)
 box.session.on_disconnect(1)
 
 -- use of nil to clear the trigger
-type(box.session.on_connect(nil))
-type(box.session.on_disconnect(nil))
-type(box.session.on_connect(nil))
-type(box.session.on_disconnect(nil))
+box.session.on_connect(nil)
+box.session.on_disconnect(nil)
 
 -- check how connect/disconnect triggers work
 function inc() active_connections = active_connections + 1 end
 function dec() active_connections = active_connections - 1 end
-box.session.on_connect(inc)
-box.session.on_disconnect(dec)
+type(box.session.on_connect(inc))
+type(box.session.on_disconnect(dec))
 active_connections = 0
 --# create connection con_one to default
 active_connections
@@ -58,12 +56,12 @@ active_connections
 box.fiber.sleep(0) -- yield
 active_connections
 
-type(box.session.on_connect(nil))
-type(box.session.on_disconnect(nil))
+box.session.on_connect(nil)
+box.session.on_disconnect(nil)
 
 -- write audit trail of connect/disconnect into a space
-box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end)
-box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end)
+type(box.session.on_connect(function() box.space['tweedledum']:insert(box.session.id()) end))
+type(box.session.on_disconnect(function() box.space['tweedledum']:delete(box.session.id()) end))
 
 --# create connection con_three to default
 --# set connection con_three
@@ -72,8 +70,8 @@ space:select(0, box.session.id())[0] == box.session.id()
 --# drop connection con_three
 
 -- cleanup
-type(box.session.on_connect(nil))
-type(box.session.on_disconnect(nil))
+box.session.on_connect(nil)
+box.session.on_disconnect(nil)
 active_connections
 
 space:drop()
diff --git a/test/box/space.on_replace.result b/test/box/space.on_replace.result
new file mode 100644
index 0000000000000000000000000000000000000000..c83871a21d3bf89f94b5a644fbfe350dcf9a42a1
--- /dev/null
+++ b/test/box/space.on_replace.result
@@ -0,0 +1,125 @@
+ts = box.schema.create_space('ttest_space')
+---
+...
+ts:create_index('primary', 'hash')
+---
+...
+type(ts.on_replace)
+---
+- function
+...
+ts.on_replace()
+---
+- error: 'usage: space:on_replace instead space.on_replace'
+...
+ts:on_replace()
+---
+- null
+...
+ts:on_replace(123)
+---
+- error: 'usage: space:on_replace([ function | nil ])'
+...
+type(ts:on_replace(function(old_tuple, new_tuple) error('test') end))
+---
+- function
+...
+type(ts:on_replace())
+---
+- function
+...
+ts:on_replace()()
+---
+- error: '[string "return type(ts:on_replace(function(old_tuple,..."]:1: test'
+...
+ts:insert(1, 'b', 'c')
+---
+- error: 'Lua error: [string "return type(ts:on_replace(function(old_tuple,..."]:1:
+    test'
+...
+ts:select(0, 1)
+---
+...
+ts:on_replace(nil)
+---
+- null
+...
+ts:insert(1, 'b', 'c')
+---
+- [1, 'b', 'c']
+...
+ts:select(0, 1)
+---
+- [1, 'b', 'c']
+...
+type(ts:on_replace(function(old_tuple, new_tuple) error('abc') end))
+---
+- function
+...
+ts:insert(2, 'b', 'c')
+---
+- error: 'Lua error: [string "return type(ts:on_replace(function(old_tuple,..."]:1:
+    abc'
+...
+ts:select(0, 2)
+---
+...
+type(ts:on_replace(function(told, tnew) o = told n = tnew end))
+---
+- function
+...
+ts:insert(2, 'a', 'b', 'c')
+---
+- [2, 'a', 'b', 'c']
+...
+o == nil
+---
+- true
+...
+n ~= nil
+---
+- true
+...
+n[1] == 'a'
+---
+- true
+...
+n[2] == 'b'
+---
+- true
+...
+n[3] == 'c'
+---
+- true
+...
+ts:replace(2, 'b', 'c', 'd')
+---
+- [2, 'b', 'c', 'd']
+...
+o ~= nil
+---
+- true
+...
+n ~= nil
+---
+- true
+...
+o[1] == 'a'
+---
+- true
+...
+n[1] == 'b'
+---
+- true
+...
+o[2] == 'b'
+---
+- true
+...
+n[2] == 'c'
+---
+- true
+...
+ts:drop()
+---
+...
diff --git a/test/box/space.on_replace.test.lua b/test/box/space.on_replace.test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..81b218ba1566b223796179d74bdb964ae9453105
--- /dev/null
+++ b/test/box/space.on_replace.test.lua
@@ -0,0 +1,51 @@
+ts = box.schema.create_space('ttest_space')
+ts:create_index('primary', 'hash')
+
+type(ts.on_replace)
+ts.on_replace()
+ts:on_replace()
+ts:on_replace(123)
+
+type(ts:on_replace(function(old_tuple, new_tuple) error('test') end))
+
+
+type(ts:on_replace())
+ts:on_replace()()
+
+ts:insert(1, 'b', 'c')
+ts:select(0, 1)
+
+
+ts:on_replace(nil)
+
+ts:insert(1, 'b', 'c')
+ts:select(0, 1)
+
+
+type(ts:on_replace(function(old_tuple, new_tuple) error('abc') end))
+
+
+ts:insert(2, 'b', 'c')
+ts:select(0, 2)
+
+
+
+type(ts:on_replace(function(told, tnew) o = told n = tnew end))
+
+ts:insert(2, 'a', 'b', 'c')
+o == nil
+n ~= nil
+n[1] == 'a'
+n[2] == 'b'
+n[3] == 'c'
+
+ts:replace(2, 'b', 'c', 'd')
+o ~= nil
+n ~= nil
+o[1] == 'a'
+n[1] == 'b'
+o[2] == 'b'
+n[2] == 'c'
+
+
+ts:drop()