diff --git a/changelogs/unreleased/gh-7652-complex-fkey-new-field-name.md b/changelogs/unreleased/gh-7652-complex-fkey-new-field-name.md
new file mode 100644
index 0000000000000000000000000000000000000000..93a751eb31b1e30bde1d2cb71984f357190dca89
--- /dev/null
+++ b/changelogs/unreleased/gh-7652-complex-fkey-new-field-name.md
@@ -0,0 +1,4 @@
+## bugfix/box
+
+* Fixed a bug in foreign key creation together with fields that participate
+  in that foreign key (gh-7652).
diff --git a/src/box/alter.cc b/src/box/alter.cc
index 05af6521b2fea1cf3a616a8d48181f3008fff830..febedb9c3e078e652e16bdb50bd95adf8bb666f7 100644
--- a/src/box/alter.cc
+++ b/src/box/alter.cc
@@ -1058,44 +1058,43 @@ CheckSpaceFormat::prepare(struct alter_space *alter)
 /** Change non-essential properties of a space. */
 class ModifySpace: public AlterSpaceOp
 {
+	void space_swap_dictionaries(struct space_def *old_def,
+				     struct space_def *new_def,
+				     struct tuple_format *new_format);
 public:
 	ModifySpace(struct alter_space *alter, struct space_def *def)
-		:AlterSpaceOp(alter), new_def(def), new_dict(NULL) {}
+		: AlterSpaceOp(alter), new_def(def) {}
 	/* New space definition. */
 	struct space_def *new_def;
-	/**
-	 * Newely created field dictionary. When new space_def is
-	 * created, it allocates new dictionary. Alter moves new
-	 * names into an old dictionary and deletes new one.
-	 */
-	struct tuple_dictionary *new_dict;
 	virtual void alter_def(struct alter_space *alter);
 	virtual void alter(struct alter_space *alter);
 	virtual void rollback(struct alter_space *alter);
 	virtual ~ModifySpace();
 };
 
-/** Amend the definition of the new space. */
 void
-ModifySpace::alter_def(struct alter_space *alter)
+ModifySpace::space_swap_dictionaries(struct space_def *old_def,
+				     struct space_def *new_def,
+				     struct tuple_format *new_format)
 {
 	/*
-	 * Unless it's online space upgrade, Use the old dictionary for the new
-	 * space, because it's already referenced by existing tuple formats.
-	 * We will update it in place in ModifySpace::alter.
-	 *
-	 * For online space upgrade, all tuples fetched from the space will be
-	 * upgraded to the new format before returning to the user so it isn't
-	 * necessary. Moreover, the old tuples may be incompatible with the new
-	 * format so using the new dictionary for them would be wrong and could
-	 * result in error accessing fields by name from the space upgrade
-	 * function.
+	 * When new space_def is created, it allocates new dictionary.
+	 * Move new names into an old dictionary, which already is referenced
+	 * by existing tuple formats. New dictionary object is deleted later,
+	 * in alter_space_delete.
 	 */
-	if (new_def->opts.upgrade_def == NULL) {
-		new_dict = new_def->dict;
-		new_def->dict = alter->old_space->def->dict;
-		tuple_dictionary_ref(new_def->dict);
-	}
+	tuple_dictionary_unref(new_format->dict);
+	new_format->dict = old_def->dict;
+	tuple_dictionary_ref(new_format->dict);
+
+	SWAP(old_def->dict, new_def->dict);
+	tuple_dictionary_swap(old_def->dict, new_def->dict);
+}
+
+/** Amend the definition of the new space. */
+void
+ModifySpace::alter_def(struct alter_space *alter)
+{
 	new_def->view_ref_count = alter->old_space->def->view_ref_count;
 	space_def_delete(alter->space_def);
 	alter->space_def = new_def;
@@ -1107,25 +1106,35 @@ void
 ModifySpace::alter(struct alter_space *alter)
 {
 	/*
-	 * Move new names into an old dictionary, which already is
-	 * referenced by existing tuple formats. New dictionary
-	 * object is deleted later, in destructor.
+	 * Unless it's online space upgrade, use the old dictionary for the new
+	 * space, because it's already referenced by existing tuple formats.
+	 *
+	 * For online space upgrade, all tuples fetched from the space will be
+	 * upgraded to the new format before returning to the user so it isn't
+	 * necessary. Moreover, the old tuples may be incompatible with the new
+	 * format so using the new dictionary for them would be wrong and could
+	 * result in error accessing fields by name from the space upgrade
+	 * function.
 	 */
-	if (new_dict != NULL)
-		tuple_dictionary_swap(alter->new_space->def->dict, new_dict);
+	if (alter->space_def->opts.upgrade_def == NULL) {
+		space_swap_dictionaries(alter->old_space->def,
+					alter->new_space->def,
+					alter->new_space->format);
+	}
 }
 
 void
 ModifySpace::rollback(struct alter_space *alter)
 {
-	if (new_dict != NULL)
-		tuple_dictionary_swap(alter->new_space->def->dict, new_dict);
+	if (alter->space_def->opts.upgrade_def == NULL) {
+		space_swap_dictionaries(alter->new_space->def,
+					alter->old_space->def,
+					alter->new_space->format);
+	}
 }
 
 ModifySpace::~ModifySpace()
 {
-	if (new_dict != NULL)
-		tuple_dictionary_unref(new_dict);
 	if (new_def != NULL)
 		space_def_delete(new_def);
 }
diff --git a/test/engine-luatest/gh_6436_field_constraint_test.lua b/test/engine-luatest/gh_6436_field_constraint_test.lua
index f1eac82ee15ccbfefe383203dfb7fe85863d162a..54251e2a6de8b86d2cfb1f06bb31c7a55c4993a2 100644
--- a/test/engine-luatest/gh_6436_field_constraint_test.lua
+++ b/test/engine-luatest/gh_6436_field_constraint_test.lua
@@ -536,7 +536,7 @@ g.test_field_constraint_integrity = function(cg)
         t.assert_equals(s:replace{1, 2, 300}, {1, 2, 300})
 
         t.assert_error_msg_content_equals(
-            "Check constraint 'field_constr3' failed for field '3'",
+            "Check constraint 'field_constr3' failed for field '3 (id3)'",
             function() s:format{{"id1"},
                                 {"id2", constraint='field_constr2'},
                                 {"id3", constraint='field_constr3'}} end
diff --git a/test/engine-luatest/gh_7652_complex_fkey_new_field_name_test.lua b/test/engine-luatest/gh_7652_complex_fkey_new_field_name_test.lua
new file mode 100644
index 0000000000000000000000000000000000000000..d100dd15bf18420af42df50a99ec7589a8a93849
--- /dev/null
+++ b/test/engine-luatest/gh_7652_complex_fkey_new_field_name_test.lua
@@ -0,0 +1,92 @@
+local t = require('luatest')
+local g = t.group('gh-7652', {{engine = 'memtx'}, {engine = 'vinyl'}})
+
+g.before_all(function(cg)
+    local server = require('test.luatest_helpers.server')
+    cg.server = server:new({alias = 'master'})
+    cg.server:start()
+end)
+
+g.after_all(function(cg)
+    cg.server:stop()
+    cg.server = nil
+end)
+
+g.after_each(function(cg)
+    cg.server:exec(function()
+        if box.space.space2 then box.space.space2:drop() end
+        if box.space.space1 then box.space.space1:drop() end
+    end)
+end)
+
+-- Check that s2:insert{} doesn't fail with:
+-- "Foreign key constraint 'one' failed: wrong local field name"
+g.test_space_replace = function(cg)
+    local engine = cg.params.engine
+
+    cg.server:exec(function(engine)
+        local fmt = {{'id', 'integer'}}
+        local opts = {engine = engine, format = fmt}
+        local s1 = box.schema.space.create('space1', opts)
+        s1:create_index('i1')
+        s1:insert{1}
+
+        opts = {engine = engine}
+        local s2 = box.schema.space.create('space2', opts)
+        s2:create_index('i2')
+        opts = {foreign_key = {one = {space = s1.id,
+                                      field = {ext_id = 'id'}}}}
+        fmt = {{name = 'id', type = 'integer'},
+               {name = 'ext_id', type = 'integer'}}
+        box.space._space:replace({s2.id, 1, 'space2', engine, 0, opts, fmt})
+        s2:insert{11, 1}
+    end, {engine})
+end
+
+-- Check that s2:insert{} doesn't fail with:
+-- "Foreign key constraint 'one' failed: wrong local field name"
+g.test_space_update = function(cg)
+    local engine = cg.params.engine
+
+    cg.server:exec(function(engine)
+        local fmt = {{'id1', 'integer'}, {'id2', 'integer'}}
+        local opts = {engine = engine, format = fmt}
+        local s1 = box.schema.space.create('space1', opts)
+        s1:create_index('i1', {parts={{1}, {2}}})
+        s1:insert{1, 1}
+
+        fmt = {{name = 'id', type = 'integer'},
+               {name = 'ext_id1', type = 'integer'}}
+        opts = {engine = engine, format = fmt}
+        local s2 = box.schema.space.create('space2', opts)
+        s2:create_index('i2')
+        opts = {foreign_key = {one = {space = s1.id,
+                                      field = {ext_id2 = 'id2',
+                                               ext_id1 = 'id1'}}}}
+        fmt = {{name = 'id', type = 'integer'},
+               {name = 'ext_id1', type = 'integer'},
+               {name = 'ext_id2', type = 'integer'}}
+        box.space._space:update(s2.id, {{'=', 7, fmt}, {'=', 6, opts}})
+        s2:insert{11, 1, 1}
+    end, {engine})
+end
+
+-- Check that new_name doesn't leak through box.rollback()
+g.test_dict_rollback = function(cg)
+    local engine = cg.params.engine
+
+    cg.server:exec(function(engine)
+        local fmt = {{'id'}, {'old_name'}}
+        local opts = {engine = engine, format = fmt}
+        local s1 = box.schema.space.create('space1', opts)
+        s1:create_index('pk')
+
+        box.begin()
+        s1:format({{'id'}, {'new_name'}})
+        box.rollback()
+        local t = require('luatest')
+        t.assert_error_msg_content_equals(
+            "Tuple field 2 (old_name) required by space format is missing",
+            function() s1:insert{1} end)
+    end, {engine})
+end
diff --git a/test/engine/ddl.result b/test/engine/ddl.result
index b1dc950bae8b51e324e8bd2c5a399fda4fc5bb7b..b857a63b9b87ac32447011977cde18ba1d5da4da 100644
--- a/test/engine/ddl.result
+++ b/test/engine/ddl.result
@@ -1567,7 +1567,7 @@ format[7] = {name = 'field7', type = 'unsigned'}
 -- Fail, the tuple {1, ... 1} is invalid for a new format.
 s:format(format)
 ---
-- error: Tuple field 7 required by space format is missing
+- error: Tuple field 7 (field7) required by space format is missing
 ...
 s:drop()
 ---
diff --git a/test/engine/json.result b/test/engine/json.result
index fac53d3736ef633a085080d52586522f72aa8d32..6f3034ea548e9179a37981b630c68cd86113e47a 100644
--- a/test/engine/json.result
+++ b/test/engine/json.result
@@ -59,7 +59,7 @@ format = {{'id', 'unsigned'}, {'meta', 'unsigned'}, {'data', 'array'}, {'age', '
 ...
 s:format(format)
 ---
-- error: Field 3 has type 'array' in one index, but type 'map' in another
+- error: Field 3 (data) has type 'array' in one index, but type 'map' in another
 ...
 format = {{'id', 'unsigned'}, {'meta', 'unsigned'}, {'data', 'map'}, {'age', 'unsigned'}, {'level', 'unsigned'}}
 ---
diff --git a/test/vinyl/errinj_ddl.result b/test/vinyl/errinj_ddl.result
index c803ba7e5fb3c54af03772c3e80ccdb57854a25d..1dff175b2ff0475272e3a23cd026a1699a810cdd 100644
--- a/test/vinyl/errinj_ddl.result
+++ b/test/vinyl/errinj_ddl.result
@@ -380,7 +380,7 @@ fiber.sleep(0)
 ...
 s:format{{'key', 'unsigned'}, {'value', 'unsigned'}} -- must fail
 ---
-- error: Tuple field 2 required by space format is missing
+- error: Tuple field 2 (value) required by space format is missing
 ...
 s:select()
 ---