diff --git a/src/query/cypher_query_interpreter.cpp b/src/query/cypher_query_interpreter.cpp
index d3ddc22c4..30966119b 100644
--- a/src/query/cypher_query_interpreter.cpp
+++ b/src/query/cypher_query_interpreter.cpp
@@ -1,4 +1,4 @@
-// Copyright 2023 Memgraph Ltd.
+// Copyright 2024 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -12,7 +12,6 @@
 #include "query/cypher_query_interpreter.hpp"
 #include "query/frontend/ast/cypher_main_visitor.hpp"
 #include "query/frontend/opencypher/parser.hpp"
-#include "utils/synchronized.hpp"
 
 // NOLINTNEXTLINE (cppcoreguidelines-avoid-non-const-global-variables)
 DEFINE_bool(query_cost_planner, true, "Use the cost-estimating query planner.");
@@ -80,7 +79,7 @@ ParsedQuery ParseQuery(const std::string &query_string, const std::map<std::stri
     // Convert the ANTLR4 parse tree into an AST.
     AstStorage ast_storage;
     frontend::ParsingContext context{.is_query_cached = true};
-    frontend::CypherMainVisitor visitor(context, &ast_storage);
+    frontend::CypherMainVisitor visitor(context, &ast_storage, &parameters);
 
     visitor.visit(parser->tree());
 
diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp
index 7002ee4b9..b62c9e301 100644
--- a/src/query/frontend/ast/cypher_main_visitor.cpp
+++ b/src/query/frontend/ast/cypher_main_visitor.cpp
@@ -1825,7 +1825,15 @@ antlrcpp::Any CypherMainVisitor::visitNodePattern(MemgraphCypher::NodePatternCon
 antlrcpp::Any CypherMainVisitor::visitNodeLabels(MemgraphCypher::NodeLabelsContext *ctx) {
   std::vector<LabelIx> labels;
   for (auto *node_label : ctx->nodeLabel()) {
-    labels.push_back(AddLabel(std::any_cast<std::string>(node_label->accept(this))));
+    if (node_label->labelName()->symbolicName()) {
+      labels.emplace_back(AddLabel(std::any_cast<std::string>(node_label->accept(this))));
+    } else {
+      // If we have a parameter, we have to resolve it.
+      const auto *param_lookup = std::any_cast<ParameterLookup *>(node_label->accept(this));
+      const auto label_name = parameters_->AtTokenPosition(param_lookup->token_position_).ValueString();
+      labels.emplace_back(storage_->GetLabelIx(label_name));
+      query_info_.is_cacheable = false;  // We can't cache queries with label parameters.
+    }
   }
   return labels;
 }
diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp
index 9e828674f..7f75b0050 100644
--- a/src/query/frontend/ast/cypher_main_visitor.hpp
+++ b/src/query/frontend/ast/cypher_main_visitor.hpp
@@ -17,6 +17,7 @@
 
 #include "query/frontend/ast/ast.hpp"
 #include "query/frontend/opencypher/generated/MemgraphCypherBaseVisitor.h"
+#include "query/parameters.hpp"
 #include "utils/exceptions.hpp"
 #include "utils/logging.hpp"
 
@@ -30,7 +31,8 @@ struct ParsingContext {
 
 class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
  public:
-  explicit CypherMainVisitor(ParsingContext context, AstStorage *storage) : context_(context), storage_(storage) {}
+  explicit CypherMainVisitor(ParsingContext context, AstStorage *storage, Parameters *parameters)
+      : context_(context), storage_(storage), parameters_(parameters) {}
 
  private:
   Expression *CreateBinaryOperatorByToken(size_t token, Expression *e1, Expression *e2) {
@@ -1022,6 +1024,8 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
   // return.
   bool in_with_ = false;
 
+  Parameters *parameters_;
+
   QueryInfo query_info_;
 };
 }  // namespace memgraph::query::frontend
diff --git a/src/query/frontend/opencypher/grammar/Cypher.g4 b/src/query/frontend/opencypher/grammar/Cypher.g4
index d387002d8..bb435b85d 100644
--- a/src/query/frontend/opencypher/grammar/Cypher.g4
+++ b/src/query/frontend/opencypher/grammar/Cypher.g4
@@ -193,7 +193,7 @@ nodeLabels : nodeLabel ( nodeLabel )* ;
 
 nodeLabel : ':' labelName ;
 
-labelName : symbolicName ;
+labelName : symbolicName | parameter;
 
 relTypeName : symbolicName ;
 
diff --git a/tests/benchmark/query/execution.cpp b/tests/benchmark/query/execution.cpp
index aa72e21d7..750dd5564 100644
--- a/tests/benchmark/query/execution.cpp
+++ b/tests/benchmark/query/execution.cpp
@@ -23,6 +23,7 @@
 // variable of the same name, EOF.
 // This hides the definition of the macro which causes
 // the compilation to fail.
+#include "query/parameters.hpp"
 #include "query/plan/planner.hpp"
 //////////////////////////////////////////////////////
 #include "communication/result_stream_faker.hpp"
@@ -119,10 +120,11 @@ static void AddTree(memgraph::storage::Storage *db, int vertex_count) {
 static memgraph::query::CypherQuery *ParseCypherQuery(const std::string &query_string,
                                                       memgraph::query::AstStorage *ast) {
   memgraph::query::frontend::ParsingContext parsing_context;
+  memgraph::query::Parameters parameters;
   parsing_context.is_query_cached = false;
   memgraph::query::frontend::opencypher::Parser parser(query_string);
   // Convert antlr4 AST into Memgraph AST.
-  memgraph::query::frontend::CypherMainVisitor cypher_visitor(parsing_context, ast);
+  memgraph::query::frontend::CypherMainVisitor cypher_visitor(parsing_context, ast, &parameters);
   cypher_visitor.visit(parser.tree());
   return memgraph::utils::Downcast<memgraph::query::CypherQuery>(cypher_visitor.query());
 };
diff --git a/tests/gql_behave/tests/memgraph_V1/features/parameters.feature b/tests/gql_behave/tests/memgraph_V1/features/parameters.feature
index 908507e43..288f93206 100644
--- a/tests/gql_behave/tests/memgraph_V1/features/parameters.feature
+++ b/tests/gql_behave/tests/memgraph_V1/features/parameters.feature
@@ -52,7 +52,7 @@ Feature: Parameters
             |   [1, 2, 3]    |
 
     Scenario: Parameters in match:
-	Given an empty graph
+	    Given an empty graph
         And having executed:
             """
             CREATE (a {x : 10})
@@ -66,3 +66,90 @@ Feature: Parameters
         Then the result should be:
             | a.x |
             | 10  |
+
+    Scenario: Label parameters in match:
+        Given an empty graph
+        And having executed:
+            """
+            CREATE (a:Label1 {x : 10})
+            """
+        And parameters are:
+            | a     | 10     |
+            | label | Label1 |
+        When executing query:
+            """
+            MATCH (a:$label {x : $a}) RETURN a
+            """
+        Then the result should be:
+            | a                |
+            | (:Label1{x: 10}) |
+
+    Scenario: Label parameters in create and match
+        Given an empty graph
+        And parameters are:
+            | a     | 10     |
+            | label | Label1 |
+        When executing query:
+            """
+            CREATE (a:$label {x: $a})
+            """
+        When executing query:
+            """
+            MATCH (a:$label {x: $a}) RETURN a
+            """
+        Then the result should be:
+            | a                |
+            | (:Label1{x: 10}) |
+
+    Scenario: Label parameters in merge
+        Given an empty graph
+        And parameters are:
+            | a     | 10     |
+            | label | Label1 |
+        When executing query:
+            """
+            MERGE (a:$label {x: $a}) RETURN a
+            """
+        Then the result should be:
+            | a                |
+            | (:Label1{x: 10}) |
+
+    Scenario: Label parameters in set label
+        Given an empty graph
+        And having executed:
+            """
+            CREATE (a:Label1 {x : 10})
+            """
+        And parameters are:
+            | new_label | Label2 |
+        When executing query:
+            """
+            MATCH (a:Label1 {x: 10}) SET a:$new_label
+            """
+        When executing query:
+            """
+            MATCH (a:Label1:Label1 {x: 10}) RETURN a
+            """
+        Then the result should be:
+            | a                        |
+            | (:Label1:Label2 {x: 10}) |
+
+    Scenario: Label parameters in remove label
+        Given an empty graph
+        And having executed:
+            """
+            CREATE (a:Label1:LabelToRemove {x : 10})
+            """
+        And parameters are:
+            | label_to_remove | LabelToRemove |
+        When executing query:
+            """
+            MATCH (a {x: 10}) REMOVE a:$label_to_remove
+            """
+        When executing query:
+            """
+            MATCH (a {x: 10}) RETURN a
+            """
+        Then the result should be:
+            | a                 |
+            | (:Label1 {x: 10}) |
diff --git a/tests/manual/expression_pretty_printer.cpp b/tests/manual/expression_pretty_printer.cpp
index e2d58b350..20757b195 100644
--- a/tests/manual/expression_pretty_printer.cpp
+++ b/tests/manual/expression_pretty_printer.cpp
@@ -1,4 +1,4 @@
-// Copyright 2022 Memgraph Ltd.
+// Copyright 2023 Memgraph Ltd.
 //
 // Use of this software is governed by the Business Source License
 // included in the file licenses/BSL.txt; by using this file, you agree to be bound by the terms of the Business Source
@@ -17,6 +17,7 @@
 #include "query/frontend/ast/cypher_main_visitor.hpp"
 #include "query/frontend/ast/pretty_print.hpp"
 #include "query/frontend/opencypher/parser.hpp"
+#include "query/parameters.hpp"
 
 std::string AssembleQueryString(const std::string &expression_string) {
   return "return " + expression_string + " as expr";
@@ -24,8 +25,9 @@ std::string AssembleQueryString(const std::string &expression_string) {
 
 memgraph::query::Query *ParseQuery(const std::string &query_string, memgraph::query::AstStorage *ast_storage) {
   memgraph::query::frontend::ParsingContext context;
+  memgraph::query::Parameters parameters;
   memgraph::query::frontend::opencypher::Parser parser(query_string);
-  memgraph::query::frontend::CypherMainVisitor visitor(context, ast_storage);
+  memgraph::query::frontend::CypherMainVisitor visitor(context, ast_storage, &parameters);
 
   visitor.visit(parser.tree());
   return visitor.query();
diff --git a/tests/manual/interactive_planning.cpp b/tests/manual/interactive_planning.cpp
index d5da0ba2b..76ae4cc1a 100644
--- a/tests/manual/interactive_planning.cpp
+++ b/tests/manual/interactive_planning.cpp
@@ -434,11 +434,12 @@ void ExaminePlans(memgraph::query::DbAccessor *dba, const memgraph::query::Symbo
 
 memgraph::query::Query *MakeAst(const std::string &query, memgraph::query::AstStorage *storage) {
   memgraph::query::frontend::ParsingContext parsing_context;
+  memgraph::query::Parameters parameters;
   parsing_context.is_query_cached = false;
   // query -> AST
   auto parser = std::make_unique<memgraph::query::frontend::opencypher::Parser>(query);
   // AST -> high level tree
-  memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage);
+  memgraph::query::frontend::CypherMainVisitor visitor(parsing_context, storage, &parameters);
   visitor.visit(parser->tree());
   return visitor.query();
 }
diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp
index 31fd95c6c..9cb33589c 100644
--- a/tests/unit/cypher_main_visitor.cpp
+++ b/tests/unit/cypher_main_visitor.cpp
@@ -37,6 +37,7 @@
 #include "query/frontend/ast/cypher_main_visitor.hpp"
 #include "query/frontend/opencypher/parser.hpp"
 #include "query/frontend/stripped.hpp"
+#include "query/parameters.hpp"
 #include "query/procedure/cypher_types.hpp"
 #include "query/procedure/mg_procedure_impl.hpp"
 #include "query/procedure/module.hpp"
@@ -118,7 +119,8 @@ class AstGenerator : public Base {
  public:
   Query *ParseQuery(const std::string &query_string) override {
     ::frontend::opencypher::Parser parser(query_string);
-    CypherMainVisitor visitor(context_, &ast_storage_);
+    Parameters parameters;
+    CypherMainVisitor visitor(context_, &ast_storage_, &parameters);
     visitor.visit(parser.tree());
     return visitor.query();
   }
@@ -151,6 +153,7 @@ class ClonedAstGenerator : public Base {
  public:
   Query *ParseQuery(const std::string &query_string) override {
     ::frontend::opencypher::Parser parser(query_string);
+    Parameters parameters;
     AstStorage tmp_storage;
     {
       // Add a label, property and edge type into temporary storage so
@@ -159,7 +162,7 @@ class ClonedAstGenerator : public Base {
       tmp_storage.GetPropertyIx("fdjakfjdklfjdaslk");
       tmp_storage.GetEdgeTypeIx("fdjkalfjdlkajfdkla");
     }
-    CypherMainVisitor visitor(context_, &tmp_storage);
+    CypherMainVisitor visitor(context_, &tmp_storage, &parameters);
     visitor.visit(parser.tree());
     return visitor.query()->Clone(&ast_storage_);
   }
@@ -182,8 +185,9 @@ class CachedAstGenerator : public Base {
     StrippedQuery stripped(query_string);
     parameters_ = stripped.literals();
     ::frontend::opencypher::Parser parser(stripped.query());
+    Parameters parameters;
     AstStorage tmp_storage;
-    CypherMainVisitor visitor(context_, &tmp_storage);
+    CypherMainVisitor visitor(context_, &tmp_storage, &parameters);
     visitor.visit(parser.tree());
     return visitor.query()->Clone(&ast_storage_);
   }