diff --git a/editor/plugins/tile_map_editor_plugin.cpp b/editor/plugins/tile_map_editor_plugin.cpp
index 72b3af5a096..ac2efe7acb7 100644
--- a/editor/plugins/tile_map_editor_plugin.cpp
+++ b/editor/plugins/tile_map_editor_plugin.cpp
@@ -132,16 +132,14 @@ void TileMapEditor::_menu_option(int p_option) {
 			if (!selection_active)
 				return;
 
-			undo_redo->create_action(TTR("Erase Selection"));
-			undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+			_start_undo(TTR("Erase Selection"));
 			for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
 				for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
 
 					_set_cell(Point2i(j, i), TileMap::INVALID_CELL, false, false, false);
 				}
 			}
-			undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
-			undo_redo->commit_action();
+			_finish_undo();
 
 			selection_active = false;
 			copydata.clear();
@@ -200,6 +198,46 @@ void TileMapEditor::set_selected_tile(int p_tile) {
 	}
 }
 
+void TileMapEditor::_create_set_cell_undo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new) {
+
+	Dictionary cell_old;
+	Dictionary cell_new;
+
+	cell_old["id"] = p_cell_old.idx;
+	cell_old["flip_h"] = p_cell_old.xf;
+	cell_old["flip_y"] = p_cell_old.yf;
+	cell_old["transpose"] = p_cell_old.tr;
+	cell_old["auto_coord"] = p_cell_old.ac;
+
+	cell_new["id"] = p_cell_new.idx;
+	cell_new["flip_h"] = p_cell_new.xf;
+	cell_new["flip_y"] = p_cell_new.yf;
+	cell_new["transpose"] = p_cell_new.tr;
+	cell_new["auto_coord"] = p_cell_new.ac;
+
+	undo_redo->add_undo_method(node, "set_celld", p_vec, cell_old);
+	undo_redo->add_do_method(node, "set_celld", p_vec, cell_new);
+}
+
+void TileMapEditor::_start_undo(const String &p_action) {
+
+	undo_data.clear();
+	undo_redo->create_action(p_action);
+}
+
+void TileMapEditor::_finish_undo() {
+
+	if (undo_data.size()) {
+		for (Map<Point2i, CellOp>::Element *E = undo_data.front(); E; E = E->next()) {
+			_create_set_cell_undo(E->key(), E->get(), _get_op_from_cell(E->key()));
+		}
+
+		undo_data.clear();
+	}
+
+	undo_redo->commit_action();
+}
+
 void TileMapEditor::_set_cell(const Point2i &p_pos, int p_value, bool p_flip_h, bool p_flip_v, bool p_transpose) {
 
 	ERR_FAIL_COND(!node);
@@ -213,6 +251,15 @@ void TileMapEditor::_set_cell(const Point2i &p_pos, int p_value, bool p_flip_h,
 	if (p_value == prev_val && p_flip_h == prev_flip_h && p_flip_v == prev_flip_v && p_transpose == prev_transpose)
 		return; //check that it's actually different
 
+	for (int y = p_pos.y - 1; y <= p_pos.y + 1; y++) {
+		for (int x = p_pos.x - 1; x <= p_pos.x + 1; x++) {
+			Point2i p = Point2i(x, y);
+			if (!undo_data.has(p)) {
+				undo_data[p] = _get_op_from_cell(p);
+			}
+		}
+	}
+
 	node->set_cell(p_pos.x, p_pos.y, p_value, p_flip_h, p_flip_v, p_transpose);
 	node->update_bitmask_area(Point2(p_pos));
 }
@@ -760,8 +807,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 						tool = TOOL_PAINTING;
 
-						undo_redo->create_action(TTR("Paint TileMap"));
-						undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+						_start_undo(TTR("Paint TileMap"));
 					}
 				} else if (tool == TOOL_PICKING) {
 
@@ -785,8 +831,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 						if (id != TileMap::INVALID_CELL) {
 
 							_set_cell(over_tile, id, flip_h, flip_v, transpose);
-							undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
-							undo_redo->commit_action();
+							_finish_undo();
 
 							paint_undo.clear();
 						}
@@ -796,14 +841,12 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 						if (id != TileMap::INVALID_CELL) {
 
-							undo_redo->create_action(TTR("Line Draw"));
-							undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+							_start_undo(TTR("Line Draw"));
 							for (Map<Point2i, CellOp>::Element *E = paint_undo.front(); E; E = E->next()) {
 
 								_set_cell(E->key(), id, flip_h, flip_v, transpose);
 							}
-							undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
-							undo_redo->commit_action();
+							_finish_undo();
 
 							paint_undo.clear();
 
@@ -815,16 +858,14 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 						if (id != TileMap::INVALID_CELL) {
 
-							undo_redo->create_action(TTR("Rectangle Paint"));
-							undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+							_start_undo(TTR("Rectangle Paint"));
 							for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
 								for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
 
 									_set_cell(Point2i(j, i), id, flip_h, flip_v, transpose);
 								}
 							}
-							undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
-							undo_redo->commit_action();
+							_finish_undo();
 
 							canvas_item_editor->update();
 						}
@@ -832,14 +873,12 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 						Point2 ofs = over_tile - rectangle.position;
 
-						undo_redo->create_action(TTR("Duplicate"));
-						undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+						_start_undo(TTR("Duplicate"));
 						for (List<TileData>::Element *E = copydata.front(); E; E = E->next()) {
 
 							_set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose);
 						}
-						undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
-						undo_redo->commit_action();
+						_finish_undo();
 
 						copydata.clear();
 
@@ -848,8 +887,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 						Point2 ofs = over_tile - rectangle.position;
 
-						undo_redo->create_action(TTR("Move"));
-						undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+						_start_undo(TTR("Move"));
 						for (int i = rectangle.position.y; i <= rectangle.position.y + rectangle.size.y; i++) {
 							for (int j = rectangle.position.x; j <= rectangle.position.x + rectangle.size.x; j++) {
 
@@ -860,8 +898,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 							_set_cell(E->get().pos + ofs, E->get().cell, E->get().flip_h, E->get().flip_v, E->get().transpose);
 						}
-						undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
-						undo_redo->commit_action();
+						_finish_undo();
 
 						copydata.clear();
 						selection_active = false;
@@ -880,7 +917,6 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 							return false;
 
 						undo_redo->create_action(TTR("Bucket Fill"));
-						undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
 
 						Dictionary op;
 						op["id"] = get_selected_tile();
@@ -890,7 +926,6 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 						_fill_points(points, op);
 
-						undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
 						undo_redo->commit_action();
 
 						// We want to keep the bucket-tool active
@@ -942,8 +977,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 
 					Point2 local = node->world_to_map(xform_inv.xform(mb->get_position()));
 
-					undo_redo->create_action(TTR("Erase TileMap"));
-					undo_redo->add_undo_method(node, "set", "tile_data", node->get("tile_data"));
+					_start_undo(TTR("Erase TileMap"));
 
 					if (mb->get_shift()) {
 #ifdef APPLE_STYLE_KEYS
@@ -970,8 +1004,7 @@ bool TileMapEditor::forward_gui_input(const Ref<InputEvent> &p_event) {
 			} else {
 				if (tool == TOOL_ERASING || tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) {
 
-					undo_redo->add_do_method(node, "set", "tile_data", node->get("tile_data"));
-					undo_redo->commit_action();
+					_finish_undo();
 
 					if (tool == TOOL_RECTANGLE_ERASE || tool == TOOL_LINE_ERASE) {
 						canvas_item_editor->update();
@@ -1534,6 +1567,7 @@ TileMapEditor::CellOp TileMapEditor::_get_op_from_cell(const Point2i &p_pos) {
 			op.yf = true;
 		if (node->is_cell_transposed(p_pos.x, p_pos.y))
 			op.tr = true;
+		op.ac = node->get_cell_autotile_coord(p_pos.x, p_pos.y);
 	}
 	return op;
 }
diff --git a/editor/plugins/tile_map_editor_plugin.h b/editor/plugins/tile_map_editor_plugin.h
index 642870aec09..292271c4f9e 100644
--- a/editor/plugins/tile_map_editor_plugin.h
+++ b/editor/plugins/tile_map_editor_plugin.h
@@ -124,6 +124,7 @@ class TileMapEditor : public VBoxContainer {
 		bool xf;
 		bool yf;
 		bool tr;
+		Vector2 ac;
 
 		CellOp() :
 				idx(TileMap::INVALID_CELL),
@@ -150,6 +151,8 @@ class TileMapEditor : public VBoxContainer {
 
 	List<TileData> copydata;
 
+	Map<Point2i, CellOp> undo_data;
+
 	void _pick_tile(const Point2 &p_pos);
 
 	PoolVector<Vector2> _bucket_fill(const Point2i &p_start, bool erase = false, bool preview = false);
@@ -174,6 +177,9 @@ class TileMapEditor : public VBoxContainer {
 	void _update_palette();
 	void _menu_option(int p_option);
 
+	void _start_undo(const String &p_action);
+	void _finish_undo();
+	void _create_set_cell_undo(const Vector2 &p_vec, const CellOp &p_cell_old, const CellOp &p_cell_new);
 	void _set_cell(const Point2i &p_pos, int p_value, bool p_flip_h = false, bool p_flip_v = false, bool p_transpose = false);
 
 	void _canvas_mouse_enter();
diff --git a/scene/2d/tile_map.cpp b/scene/2d/tile_map.cpp
index d88e148b2c0..a6283502cb4 100644
--- a/scene/2d/tile_map.cpp
+++ b/scene/2d/tile_map.cpp
@@ -725,6 +725,11 @@ void TileMap::set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x, bool p_
 	set_cell(p_pos.x, p_pos.y, p_tile, p_flip_x, p_flip_y, p_transpose);
 }
 
+void TileMap::set_celld(const Vector2 &p_pos, const Dictionary &p_data) {
+
+	set_cell(p_pos.x, p_pos.y, p_data["id"], p_data["flip_h"], p_data["flip_y"], p_data["transpose"], p_data["auto_coord"]);
+}
+
 void TileMap::set_cell(int p_x, int p_y, int p_tile, bool p_flip_x, bool p_flip_y, bool p_transpose, Vector2 p_autotile_coord) {
 
 	PosKey pk(p_x, p_y);
@@ -1602,6 +1607,7 @@ void TileMap::_bind_methods() {
 
 	ClassDB::bind_method(D_METHOD("set_cell", "x", "y", "tile", "flip_x", "flip_y", "transpose", "autotile_coord"), &TileMap::set_cell, DEFVAL(false), DEFVAL(false), DEFVAL(false), DEFVAL(Vector2()));
 	ClassDB::bind_method(D_METHOD("set_cellv", "position", "tile", "flip_x", "flip_y", "transpose"), &TileMap::set_cellv, DEFVAL(false), DEFVAL(false), DEFVAL(false));
+	ClassDB::bind_method(D_METHOD("set_celld", "data"), &TileMap::set_celld);
 	ClassDB::bind_method(D_METHOD("get_cell", "x", "y"), &TileMap::get_cell);
 	ClassDB::bind_method(D_METHOD("get_cellv", "position"), &TileMap::get_cellv);
 	ClassDB::bind_method(D_METHOD("is_cell_x_flipped", "x", "y"), &TileMap::is_cell_x_flipped);
diff --git a/scene/2d/tile_map.h b/scene/2d/tile_map.h
index 07947004b32..01c92c5c250 100644
--- a/scene/2d/tile_map.h
+++ b/scene/2d/tile_map.h
@@ -241,6 +241,7 @@ public:
 	void set_cell_autotile_coord(int p_x, int p_y, const Vector2 &p_coord);
 	Vector2 get_cell_autotile_coord(int p_x, int p_y) const;
 
+	void set_celld(const Vector2 &p_pos, const Dictionary &p_data);
 	void set_cellv(const Vector2 &p_pos, int p_tile, bool p_flip_x = false, bool p_flip_y = false, bool p_transpose = false);
 	int get_cellv(const Vector2 &p_pos) const;