diff --git a/src/query/plan/operator.cpp b/src/query/plan/operator.cpp
index aef907d73..73128fa57 100644
--- a/src/query/plan/operator.cpp
+++ b/src/query/plan/operator.cpp
@@ -576,7 +576,10 @@ bool Delete::DeleteCursor::Pull(Frame &frame, const SymbolTable &symbol_table) {
               "connections. Consider using DETACH DELETE.");
         break;
       }
+
+      // skip Edges (already deleted) and Nulls (can occur in optional match)
       case TypedValue::Type::Edge:
+      case TypedValue::Type::Null:
         break;
       // check we're not trying to delete anything except vertices and edges
       default:
diff --git a/tests/unit/query_plan_create_set_remove_delete.cpp b/tests/unit/query_plan_create_set_remove_delete.cpp
index 59781d090..49a414a11 100644
--- a/tests/unit/query_plan_create_set_remove_delete.cpp
+++ b/tests/unit/query_plan_create_set_remove_delete.cpp
@@ -412,6 +412,19 @@ TEST(QueryPlan, DeleteReturn) {
   EXPECT_EQ(0, CountIterable(dba->vertices()));
 }
 
+TEST(QueryPlan, DeleteNull) {
+  // test (simplified) WITH Null as x delete x
+  Dbms dbms;
+  auto dba = dbms.active();
+  AstTreeStorage storage;
+  SymbolTable symbol_table;
+
+  auto once = std::make_shared<Once>();
+  auto delete_op = std::make_shared<plan::Delete>(
+      once, std::vector<Expression *>{LITERAL(TypedValue::Null)}, false);
+  EXPECT_EQ(1, PullAll(delete_op, *dba, symbol_table));
+}
+
 TEST(QueryPlan, DeleteAdvance) {
   // test queries on empty DB:
   // CREATE (n)