diff --git a/src/auth/models.cpp b/src/auth/models.cpp
index 54bf24da6..32b2ca291 100644
--- a/src/auth/models.cpp
+++ b/src/auth/models.cpp
@@ -37,7 +37,7 @@ const std::vector<Permission> kPermissionsAll = {
     Permission::CONSTRAINT, Permission::DUMP,      Permission::AUTH,        Permission::REPLICATION,
     Permission::DURABILITY, Permission::READ_FILE, Permission::FREE_MEMORY, Permission::TRIGGER,
     Permission::CONFIG,     Permission::STREAM,    Permission::MODULE_READ, Permission::MODULE_WRITE,
-    Permission::WEBSOCKET};
+    Permission::WEBSOCKET,  Permission::SCHEMA};
 }  // namespace
 
 std::string PermissionToString(Permission permission) {
@@ -84,6 +84,8 @@ std::string PermissionToString(Permission permission) {
       return "MODULE_WRITE";
     case Permission::WEBSOCKET:
       return "WEBSOCKET";
+    case Permission::SCHEMA:
+      return "SCHEMA";
   }
 }
 
diff --git a/src/auth/models.hpp b/src/auth/models.hpp
index 0f01c0a39..00c26464b 100644
--- a/src/auth/models.hpp
+++ b/src/auth/models.hpp
@@ -38,7 +38,8 @@ enum class Permission : uint64_t {
   STREAM       = 1U << 17U,
   MODULE_READ  = 1U << 18U,
   MODULE_WRITE = 1U << 19U,
-  WEBSOCKET    = 1U << 20U
+  WEBSOCKET    = 1U << 20U,
+  SCHEMA       = 1U << 21U
 };
 // clang-format on
 
diff --git a/src/common/types.hpp b/src/common/types.hpp
new file mode 100644
index 000000000..09a0aecf5
--- /dev/null
+++ b/src/common/types.hpp
@@ -0,0 +1,19 @@
+// Copyright 2022 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
+// License, and you may not use this file except in compliance with the Business Source License.
+//
+// As of the Change Date specified in that file, in accordance with
+// the Business Source License, use of this software will be governed
+// by the Apache License, Version 2.0, included in the file
+// licenses/APL.txt.
+
+#pragma once
+
+#include <cstdint>
+
+namespace memgraph::common {
+enum class SchemaType : uint8_t { BOOL, INT, STRING, DATE, LOCALTIME, LOCALDATETIME, DURATION };
+
+}  // namespace memgraph::common
diff --git a/src/glue/auth.cpp b/src/glue/auth.cpp
index 7f05d8045..5d9ffbb84 100644
--- a/src/glue/auth.cpp
+++ b/src/glue/auth.cpp
@@ -57,6 +57,8 @@ auth::Permission PrivilegeToPermission(query::AuthQuery::Privilege privilege) {
       return auth::Permission::MODULE_WRITE;
     case query::AuthQuery::Privilege::WEBSOCKET:
       return auth::Permission::WEBSOCKET;
+    case query::AuthQuery::Privilege::SCHEMA:
+      return auth::Permission::SCHEMA;
   }
 }
 }  // namespace memgraph::glue
diff --git a/src/query/frontend/ast/ast.lcp b/src/query/frontend/ast/ast.lcp
index 33d397754..de24ae37a 100644
--- a/src/query/frontend/ast/ast.lcp
+++ b/src/query/frontend/ast/ast.lcp
@@ -17,6 +17,7 @@
 #include <variant>
 #include <vector>
 
+#include "common/types.hpp"
 #include "query/frontend/ast/ast_visitor.hpp"
 #include "query/frontend/semantic/symbol.hpp"
 #include "query/interpret/awesome_memgraph_functions.hpp"
@@ -133,6 +134,15 @@ cpp<#
     }
     cpp<#))
 
+
+(defun clone-schema-property-vector (source dest)
+    #>cpp
+    ${dest}.reserve(${source}.size());
+    for (const auto &[property_ix, property_type]: ${source}) {
+      ${dest}.emplace_back(storage->GetPropertyIx(property_ix.name), property_type);
+    }
+    cpp<#)
+
 ;; The following index structs serve as a decoupling point of AST from
 ;; concrete database types. All the names are collected in AstStorage, and can
 ;; be indexed through these instances. This means that we can create a vector
@@ -2253,7 +2263,7 @@ cpp<#
     (lcp:define-enum privilege
         (create delete match merge set remove index stats auth constraint
          dump replication durability read_file free_memory trigger config stream module_read module_write
-         websocket)
+         websocket schema)
       (:serialize))
     #>cpp
     AuthQuery() = default;
@@ -2295,7 +2305,7 @@ const std::vector<AuthQuery::Privilege> kPrivilegesAll = {
     AuthQuery::Privilege::FREE_MEMORY, AuthQuery::Privilege::TRIGGER,
     AuthQuery::Privilege::CONFIG,      AuthQuery::Privilege::STREAM,
     AuthQuery::Privilege::MODULE_READ, AuthQuery::Privilege::MODULE_WRITE,
-    AuthQuery::Privilege::WEBSOCKET};
+    AuthQuery::Privilege::WEBSOCKET,   AuthQuery::Privilege::SCHEMA};
 cpp<#
 
 (lcp:define-class info-query (query)
@@ -2668,5 +2678,38 @@ cpp<#
   (:serialize (:slk))
   (:clone))
 
+(lcp:define-class schema-query (query)
+  ((action "Action" :scope :public)
+   (label "LabelIx" :scope :public
+          :slk-load (lambda (member)
+                     #>cpp
+                     slk::Load(&self->${member}, reader, storage);
+                     cpp<#)
+          :clone (lambda (source dest)
+                   #>cpp
+                   ${dest} = storage->GetLabelIx(${source}.name);
+                   cpp<#))
+    (schema_type_map "std::vector<std::pair<PropertyIx, common::SchemaType>>"
+      :slk-save #'slk-save-property-map
+      :slk-load #'slk-load-property-map
+      :clone #'clone-schema-property-vector
+      :scope :public))
+
+  (:public
+    (lcp:define-enum action
+        (create-schema drop-schema show-schema show-schemas)
+      (:serialize))
+    #>cpp
+    SchemaQuery() = default;
+
+    DEFVISITABLE(QueryVisitor<void>);
+    cpp<#)
+  (:private
+    #>cpp
+    friend class AstStorage;
+    cpp<#)
+  (:serialize (:slk))
+  (:clone))
+
 (lcp:pop-namespace) ;; namespace query
 (lcp:pop-namespace) ;; namespace memgraph
diff --git a/src/query/frontend/ast/ast_visitor.hpp b/src/query/frontend/ast/ast_visitor.hpp
index 0e4a6012c..307b96907 100644
--- a/src/query/frontend/ast/ast_visitor.hpp
+++ b/src/query/frontend/ast/ast_visitor.hpp
@@ -94,6 +94,7 @@ class StreamQuery;
 class SettingQuery;
 class VersionQuery;
 class Foreach;
+class SchemaQuery;
 
 using TreeCompositeVisitor = utils::CompositeVisitor<
     SingleQuery, CypherUnion, NamedExpression, OrOperator, XorOperator, AndOperator, NotOperator, AdditionOperator,
@@ -125,9 +126,9 @@ class ExpressionVisitor
           None, ParameterLookup, Identifier, PrimitiveLiteral, RegexMatch> {};
 
 template <class TResult>
-class QueryVisitor
-    : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery, InfoQuery,
-                            ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery, FreeMemoryQuery, TriggerQuery,
-                            IsolationLevelQuery, CreateSnapshotQuery, StreamQuery, SettingQuery, VersionQuery> {};
+class QueryVisitor : public utils::Visitor<TResult, CypherQuery, ExplainQuery, ProfileQuery, IndexQuery, AuthQuery,
+                                           InfoQuery, ConstraintQuery, DumpQuery, ReplicationQuery, LockPathQuery,
+                                           FreeMemoryQuery, TriggerQuery, IsolationLevelQuery, CreateSnapshotQuery,
+                                           StreamQuery, SettingQuery, VersionQuery, SchemaQuery> {};
 
 }  // namespace memgraph::query
diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp
index f4a269dfd..2e73b2fbb 100644
--- a/src/query/frontend/ast/cypher_main_visitor.cpp
+++ b/src/query/frontend/ast/cypher_main_visitor.cpp
@@ -17,6 +17,7 @@
 #include <cstring>
 #include <iterator>
 #include <limits>
+#include <ranges>
 #include <string>
 #include <tuple>
 #include <type_traits>
@@ -27,6 +28,7 @@
 
 #include <boost/preprocessor/cat.hpp>
 
+#include "common/types.hpp"
 #include "query/exceptions.hpp"
 #include "query/frontend/ast/ast.hpp"
 #include "query/frontend/ast/ast_visitor.hpp"
@@ -1355,6 +1357,7 @@ antlrcpp::Any CypherMainVisitor::visitPrivilege(MemgraphCypher::PrivilegeContext
   if (ctx->MODULE_READ()) return AuthQuery::Privilege::MODULE_READ;
   if (ctx->MODULE_WRITE()) return AuthQuery::Privilege::MODULE_WRITE;
   if (ctx->WEBSOCKET()) return AuthQuery::Privilege::WEBSOCKET;
+  if (ctx->SCHEMA()) return AuthQuery::Privilege::SCHEMA;
   LOG_FATAL("Should not get here - unknown privilege!");
 }
 
@@ -2353,6 +2356,93 @@ antlrcpp::Any CypherMainVisitor::visitForeach(MemgraphCypher::ForeachContext *ct
   return for_each;
 }
 
+antlrcpp::Any CypherMainVisitor::visitSchemaQuery(MemgraphCypher::SchemaQueryContext *ctx) {
+  MG_ASSERT(ctx->children.size() == 1, "SchemaQuery should have exactly one child!");
+  auto *schema_query = ctx->children[0]->accept(this).as<SchemaQuery *>();
+  query_ = schema_query;
+  return schema_query;
+}
+
+antlrcpp::Any CypherMainVisitor::visitShowSchema(MemgraphCypher::ShowSchemaContext *ctx) {
+  auto *schema_query = storage_->Create<SchemaQuery>();
+  schema_query->action_ = SchemaQuery::Action::SHOW_SCHEMA;
+  schema_query->label_ = AddLabel(ctx->labelName()->accept(this));
+  query_ = schema_query;
+  return schema_query;
+}
+
+antlrcpp::Any CypherMainVisitor::visitShowSchemas(MemgraphCypher::ShowSchemasContext * /*ctx*/) {
+  auto *schema_query = storage_->Create<SchemaQuery>();
+  schema_query->action_ = SchemaQuery::Action::SHOW_SCHEMAS;
+  query_ = schema_query;
+  return schema_query;
+}
+
+antlrcpp::Any CypherMainVisitor::visitPropertyType(MemgraphCypher::PropertyTypeContext *ctx) {
+  MG_ASSERT(ctx->symbolicName());
+  const auto property_type = utils::ToLowerCase(ctx->symbolicName()->accept(this).as<std::string>());
+  if (property_type == "bool") {
+    return common::SchemaType::BOOL;
+  }
+  if (property_type == "string") {
+    return common::SchemaType::STRING;
+  }
+  if (property_type == "integer") {
+    return common::SchemaType::INT;
+  }
+  if (property_type == "date") {
+    return common::SchemaType::DATE;
+  }
+  if (property_type == "duration") {
+    return common::SchemaType::DURATION;
+  }
+  if (property_type == "localdatetime") {
+    return common::SchemaType::LOCALDATETIME;
+  }
+  if (property_type == "localtime") {
+    return common::SchemaType::LOCALTIME;
+  }
+  throw SyntaxException("Property type must be one of the supported types!");
+}
+
+/**
+ * @return Schema*
+ */
+antlrcpp::Any CypherMainVisitor::visitSchemaPropertyMap(MemgraphCypher::SchemaPropertyMapContext *ctx) {
+  std::vector<std::pair<PropertyIx, common::SchemaType>> schema_property_map;
+  for (auto *property_key_pair : ctx->propertyKeyTypePair()) {
+    PropertyIx key = property_key_pair->propertyKeyName()->accept(this);
+    common::SchemaType type = property_key_pair->propertyType()->accept(this);
+    if (std::ranges::find_if(schema_property_map, [&key](const auto &elem) { return elem.first == key; }) !=
+        schema_property_map.end()) {
+      throw SemanticException("Same property name can't appear twice in a schema map.");
+    }
+    schema_property_map.emplace_back(key, type);
+  }
+  return schema_property_map;
+}
+
+antlrcpp::Any CypherMainVisitor::visitCreateSchema(MemgraphCypher::CreateSchemaContext *ctx) {
+  auto *schema_query = storage_->Create<SchemaQuery>();
+  schema_query->action_ = SchemaQuery::Action::CREATE_SCHEMA;
+  schema_query->label_ = AddLabel(ctx->labelName()->accept(this));
+  schema_query->schema_type_map_ =
+      ctx->schemaPropertyMap()->accept(this).as<std::vector<std::pair<PropertyIx, common::SchemaType>>>();
+  query_ = schema_query;
+  return schema_query;
+}
+
+/**
+ * @return Schema*
+ */
+antlrcpp::Any CypherMainVisitor::visitDropSchema(MemgraphCypher::DropSchemaContext *ctx) {
+  auto *schema_query = storage_->Create<SchemaQuery>();
+  schema_query->action_ = SchemaQuery::Action::DROP_SCHEMA;
+  schema_query->label_ = AddLabel(ctx->labelName()->accept(this));
+  query_ = schema_query;
+  return schema_query;
+}
+
 LabelIx CypherMainVisitor::AddLabel(const std::string &name) { return storage_->GetLabelIx(name); }
 
 PropertyIx CypherMainVisitor::AddProperty(const std::string &name) { return storage_->GetPropertyIx(name); }
diff --git a/src/query/frontend/ast/cypher_main_visitor.hpp b/src/query/frontend/ast/cypher_main_visitor.hpp
index 2a6b8ff5e..a4216e702 100644
--- a/src/query/frontend/ast/cypher_main_visitor.hpp
+++ b/src/query/frontend/ast/cypher_main_visitor.hpp
@@ -849,6 +849,41 @@ class CypherMainVisitor : public antlropencypher::MemgraphCypherBaseVisitor {
    */
   antlrcpp::Any visitForeach(MemgraphCypher::ForeachContext *ctx) override;
 
+  /**
+   * @return Schema*
+   */
+  antlrcpp::Any visitPropertyType(MemgraphCypher::PropertyTypeContext *ctx) override;
+
+  /**
+   * @return Schema*
+   */
+  antlrcpp::Any visitSchemaPropertyMap(MemgraphCypher::SchemaPropertyMapContext *ctx) override;
+
+  /**
+   * @return Schema*
+   */
+  antlrcpp::Any visitSchemaQuery(MemgraphCypher::SchemaQueryContext *ctx) override;
+
+  /**
+   * @return Schema*
+   */
+  antlrcpp::Any visitShowSchema(MemgraphCypher::ShowSchemaContext *ctx) override;
+
+  /**
+   * @return Schema*
+   */
+  antlrcpp::Any visitShowSchemas(MemgraphCypher::ShowSchemasContext *ctx) override;
+
+  /**
+   * @return Schema*
+   */
+  antlrcpp::Any visitCreateSchema(MemgraphCypher::CreateSchemaContext *ctx) override;
+
+  /**
+   * @return Schema*
+   */
+  antlrcpp::Any visitDropSchema(MemgraphCypher::DropSchemaContext *ctx) override;
+
  public:
   Query *query() { return query_; }
   const static std::string kAnonPrefix;
diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
index b412a474a..df51de704 100644
--- a/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
+++ b/src/query/frontend/opencypher/grammar/MemgraphCypher.g4
@@ -46,10 +46,10 @@ memgraphCypherKeyword : cypherKeyword
                       | DROP
                       | DUMP
                       | EXECUTE
-                      | FOR
-                      | FOREACH
                       | FREE
                       | FROM
+                      | FOR
+                      | FOREACH
                       | GLOBAL
                       | GRANT
                       | HEADER
@@ -76,6 +76,8 @@ memgraphCypherKeyword : cypherKeyword
                       | ROLE
                       | ROLES
                       | QUOTE
+                      | SCHEMA
+                      | SCHEMAS
                       | SESSION
                       | SETTING
                       | SETTINGS
@@ -122,6 +124,7 @@ query : cypherQuery
       | streamQuery
       | settingQuery
       | versionQuery
+      | schemaQuery
       ;
 
 authQuery : createRole
@@ -192,6 +195,12 @@ settingQuery : setSetting
              | showSettings
              ;
 
+schemaQuery : showSchema
+            | showSchemas
+            | createSchema
+            | dropSchema
+            ;
+
 loadCsv : LOAD CSV FROM csvFile ( WITH | NO ) HEADER
          ( IGNORE BAD ) ?
          ( DELIMITER delimiter ) ?
@@ -254,6 +263,7 @@ privilege : CREATE
           | MODULE_READ
           | MODULE_WRITE
           | WEBSOCKET
+          | SCHEMA
           ;
 
 privilegeList : privilege ( ',' privilege )* ;
@@ -374,3 +384,17 @@ showSetting : SHOW DATABASE SETTING settingName ;
 showSettings : SHOW DATABASE SETTINGS ;
 
 versionQuery : SHOW VERSION ;
+
+showSchema : SHOW SCHEMA ON ':' labelName ;
+
+showSchemas : SHOW SCHEMAS ;
+
+propertyType : symbolicName ;
+
+propertyKeyTypePair : propertyKeyName propertyType ;
+
+schemaPropertyMap : '(' propertyKeyTypePair ( ',' propertyKeyTypePair )* ')' ;
+
+createSchema : CREATE SCHEMA ON ':' labelName schemaPropertyMap ;
+
+dropSchema : DROP SCHEMA ON ':' labelName ;
diff --git a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4 b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
index 55e5d53a2..869141033 100644
--- a/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
+++ b/src/query/frontend/opencypher/grammar/MemgraphCypherLexer.g4
@@ -89,6 +89,8 @@ REVOKE              : R E V O K E ;
 ROLE                : R O L E ;
 ROLES               : R O L E S ;
 QUOTE               : Q U O T E ;
+SCHEMA              : S C H E M A ;
+SCHEMAS             : S C H E M A S ;
 SERVICE_URL         : S E R V I C E UNDERSCORE U R L ;
 SESSION             : S E S S I O N ;
 SETTING             : S E T T I N G ;
diff --git a/src/query/frontend/semantic/required_privileges.cpp b/src/query/frontend/semantic/required_privileges.cpp
index e8dbd21e5..160004ac2 100644
--- a/src/query/frontend/semantic/required_privileges.cpp
+++ b/src/query/frontend/semantic/required_privileges.cpp
@@ -80,6 +80,8 @@ class PrivilegeExtractor : public QueryVisitor<void>, public HierarchicalTreeVis
 
   void Visit(VersionQuery & /*version_query*/) override { AddPrivilege(AuthQuery::Privilege::STATS); }
 
+  void Visit(SchemaQuery & /*schema_query*/) override { AddPrivilege(AuthQuery::Privilege::SCHEMA); }
+
   bool PreVisit(Create & /*unused*/) override {
     AddPrivilege(AuthQuery::Privilege::CREATE);
     return false;
diff --git a/src/query/frontend/stripped_lexer_constants.hpp b/src/query/frontend/stripped_lexer_constants.hpp
index 42b7b4aeb..be516aee6 100644
--- a/src/query/frontend/stripped_lexer_constants.hpp
+++ b/src/query/frontend/stripped_lexer_constants.hpp
@@ -204,8 +204,9 @@ const trie::Trie kKeywords = {"union",
                               "pulsar",
                               "service_url",
                               "version",
-                              "websocket"
-                              "foreach"};
+                              "websocket",
+                              "foreach",
+                              "schema"};
 
 // Unicode codepoints that are allowed at the start of the unescaped name.
 const std::bitset<kBitsetSize> kUnescapedNameAllowedStarts(
diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp
index a8bb42dc9..fbb1cbc55 100644
--- a/src/query/interpreter.cpp
+++ b/src/query/interpreter.cpp
@@ -44,6 +44,7 @@
 #include "query/trigger.hpp"
 #include "query/typed_value.hpp"
 #include "storage/v2/property_value.hpp"
+#include "storage/v2/schemas.hpp"
 #include "utils/algorithm.hpp"
 #include "utils/csv_parsing.hpp"
 #include "utils/event_counter.hpp"
@@ -891,6 +892,102 @@ Callback HandleSettingQuery(SettingQuery *setting_query, const Parameters &param
   }
 }
 
+Callback HandleSchemaQuery(SchemaQuery *schema_query, InterpreterContext *interpreter_context,
+                           std::vector<Notification> *notifications) {
+  Callback callback;
+  switch (schema_query->action_) {
+    case SchemaQuery::Action::SHOW_SCHEMAS: {
+      callback.header = {"label", "primary_key"};
+      callback.fn = [interpreter_context]() {
+        auto *db = interpreter_context->db;
+        auto schemas_info = db->ListAllSchemas();
+        std::vector<std::vector<TypedValue>> results;
+        results.reserve(schemas_info.schemas.size());
+
+        for (const auto &[label_id, schema_types] : schemas_info.schemas) {
+          std::vector<TypedValue> schema_info_row;
+          schema_info_row.reserve(3);
+
+          schema_info_row.emplace_back(db->LabelToName(label_id));
+          std::vector<std::string> primary_key_properties;
+          primary_key_properties.reserve(schema_types.size());
+          std::transform(schema_types.begin(), schema_types.end(), std::back_inserter(primary_key_properties),
+                         [&db](const auto &schema_type) {
+                           return db->PropertyToName(schema_type.property_id) +
+                                  "::" + storage::SchemaTypeToString(schema_type.type);
+                         });
+
+          schema_info_row.emplace_back(utils::Join(primary_key_properties, ", "));
+          results.push_back(std::move(schema_info_row));
+        }
+        return results;
+      };
+      return callback;
+    }
+    case SchemaQuery::Action::SHOW_SCHEMA: {
+      callback.header = {"property_name", "property_type"};
+      callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
+        auto *db = interpreter_context->db;
+        const auto label = db->NameToLabel(primary_label.name);
+        const auto schema = db->GetSchema(label);
+        std::vector<std::vector<TypedValue>> results;
+        if (schema) {
+          for (const auto &schema_property : schema->second) {
+            std::vector<TypedValue> schema_info_row;
+            schema_info_row.reserve(2);
+            schema_info_row.emplace_back(db->PropertyToName(schema_property.property_id));
+            schema_info_row.emplace_back(storage::SchemaTypeToString(schema_property.type));
+            results.push_back(std::move(schema_info_row));
+          }
+          return results;
+        }
+        throw QueryException(fmt::format("Schema on label :{} not found!", primary_label.name));
+      };
+      return callback;
+    }
+    case SchemaQuery::Action::CREATE_SCHEMA: {
+      auto schema_type_map = schema_query->schema_type_map_;
+      if (schema_query->schema_type_map_.empty()) {
+        throw SyntaxException("One or more types have to be defined in schema definition.");
+      }
+      callback.fn = [interpreter_context, primary_label = schema_query->label_,
+                     schema_type_map = std::move(schema_type_map)]() {
+        auto *db = interpreter_context->db;
+        const auto label = db->NameToLabel(primary_label.name);
+        std::vector<storage::SchemaProperty> schemas_types;
+        schemas_types.reserve(schema_type_map.size());
+        for (const auto &schema_type : schema_type_map) {
+          auto property_id = db->NameToProperty(schema_type.first.name);
+          schemas_types.push_back({property_id, schema_type.second});
+        }
+        if (!db->CreateSchema(label, schemas_types)) {
+          throw QueryException(fmt::format("Schema on label :{} already exists!", primary_label.name));
+        }
+        return std::vector<std::vector<TypedValue>>{};
+      };
+      notifications->emplace_back(SeverityLevel::INFO, NotificationCode::CREATE_SCHEMA,
+                                  fmt::format("Create schema on label :{}", schema_query->label_.name));
+      return callback;
+    }
+    case SchemaQuery::Action::DROP_SCHEMA: {
+      callback.fn = [interpreter_context, primary_label = schema_query->label_]() {
+        auto *db = interpreter_context->db;
+        const auto label = db->NameToLabel(primary_label.name);
+
+        if (!db->DropSchema(label)) {
+          throw QueryException(fmt::format("Schema on label :{} does not exist!", primary_label.name));
+        }
+
+        return std::vector<std::vector<TypedValue>>{};
+      };
+      notifications->emplace_back(SeverityLevel::INFO, NotificationCode::DROP_SCHEMA,
+                                  fmt::format("Dropped schema on label :{}", schema_query->label_.name));
+      return callback;
+    }
+  }
+  return callback;
+}
+
 // Struct for lazy pulling from a vector
 struct PullPlanVector {
   explicit PullPlanVector(std::vector<std::vector<TypedValue>> values) : values_(std::move(values)) {}
@@ -2086,6 +2183,32 @@ PreparedQuery PrepareConstraintQuery(ParsedQuery parsed_query, bool in_explicit_
                        RWType::NONE};
 }
 
+PreparedQuery PrepareSchemaQuery(ParsedQuery parsed_query, bool in_explicit_transaction,
+                                 InterpreterContext *interpreter_context, std::vector<Notification> *notifications) {
+  if (in_explicit_transaction) {
+    throw ConstraintInMulticommandTxException();
+  }
+  auto *schema_query = utils::Downcast<SchemaQuery>(parsed_query.query);
+  MG_ASSERT(schema_query);
+  auto callback = HandleSchemaQuery(schema_query, interpreter_context, notifications);
+
+  return PreparedQuery{std::move(callback.header), std::move(parsed_query.required_privileges),
+                       [handler = std::move(callback.fn), action = QueryHandlerResult::NOTHING,
+                        pull_plan = std::shared_ptr<PullPlanVector>(nullptr)](
+                           AnyStream *stream, std::optional<int> n) mutable -> std::optional<QueryHandlerResult> {
+                         if (!pull_plan) {
+                           auto results = handler();
+                           pull_plan = std::make_shared<PullPlanVector>(std::move(results));
+                         }
+
+                         if (pull_plan->Pull(stream, n)) {
+                           return action;
+                         }
+                         return std::nullopt;
+                       },
+                       RWType::NONE};
+}
+
 void Interpreter::BeginTransaction() {
   const auto prepared_query = PrepareTransactionQuery("BEGIN");
   prepared_query.query_handler(nullptr, {});
@@ -2219,6 +2342,9 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string,
       prepared_query = PrepareSettingQuery(std::move(parsed_query), in_explicit_transaction_, &*execution_db_accessor_);
     } else if (utils::Downcast<VersionQuery>(parsed_query.query)) {
       prepared_query = PrepareVersionQuery(std::move(parsed_query), in_explicit_transaction_);
+    } else if (utils::Downcast<SchemaQuery>(parsed_query.query)) {
+      prepared_query = PrepareSchemaQuery(std::move(parsed_query), in_explicit_transaction_, interpreter_context_,
+                                          &query_execution->notifications);
     } else {
       LOG_FATAL("Should not get here -- unknown query type!");
     }
diff --git a/src/query/metadata.cpp b/src/query/metadata.cpp
index f4e8512fd..2e25ce8a4 100644
--- a/src/query/metadata.cpp
+++ b/src/query/metadata.cpp
@@ -38,6 +38,8 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
       return "CreateIndex"sv;
     case NotificationCode::CREATE_STREAM:
       return "CreateStream"sv;
+    case NotificationCode::CREATE_SCHEMA:
+      return "CreateSchema"sv;
     case NotificationCode::CHECK_STREAM:
       return "CheckStream"sv;
     case NotificationCode::CREATE_TRIGGER:
@@ -48,6 +50,8 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
       return "DropReplica"sv;
     case NotificationCode::DROP_INDEX:
       return "DropIndex"sv;
+    case NotificationCode::DROP_SCHEMA:
+      return "DropSchema"sv;
     case NotificationCode::DROP_STREAM:
       return "DropStream"sv;
     case NotificationCode::DROP_TRIGGER:
@@ -68,6 +72,10 @@ constexpr std::string_view GetCodeString(const NotificationCode code) {
       return "ReplicaPortWarning"sv;
     case NotificationCode::SET_REPLICA:
       return "SetReplica"sv;
+    case NotificationCode::SHOW_SCHEMA:
+      return "ShowSchema"sv;
+    case NotificationCode::SHOW_SCHEMAS:
+      return "ShowSchemas"sv;
     case NotificationCode::START_STREAM:
       return "StartStream"sv;
     case NotificationCode::START_ALL_STREAMS:
@@ -114,4 +122,4 @@ std::string ExecutionStatsKeyToString(const ExecutionStats::Key key) {
   }
 }
 
-}  // namespace memgraph::query
\ No newline at end of file
+}  // namespace memgraph::query
diff --git a/src/query/metadata.hpp b/src/query/metadata.hpp
index 67f784fa8..e557ca72e 100644
--- a/src/query/metadata.hpp
+++ b/src/query/metadata.hpp
@@ -26,12 +26,14 @@ enum class SeverityLevel : uint8_t { INFO, WARNING };
 enum class NotificationCode : uint8_t {
   CREATE_CONSTRAINT,
   CREATE_INDEX,
+  CREATE_SCHEMA,
   CHECK_STREAM,
   CREATE_STREAM,
   CREATE_TRIGGER,
   DROP_CONSTRAINT,
   DROP_INDEX,
   DROP_REPLICA,
+  DROP_SCHEMA,
   DROP_STREAM,
   DROP_TRIGGER,
   EXISTANT_INDEX,
@@ -42,6 +44,8 @@ enum class NotificationCode : uint8_t {
   REPLICA_PORT_WARNING,
   REGISTER_REPLICA,
   SET_REPLICA,
+  SHOW_SCHEMA,
+  SHOW_SCHEMAS,
   START_STREAM,
   START_ALL_STREAMS,
   STOP_STREAM,
diff --git a/src/storage/v2/schemas.cpp b/src/storage/v2/schemas.cpp
index 2e4fbbe3b..1bec8455a 100644
--- a/src/storage/v2/schemas.cpp
+++ b/src/storage/v2/schemas.cpp
@@ -26,14 +26,31 @@ SchemaViolation::SchemaViolation(ValidationStatus status, LabelId label, SchemaP
                                  PropertyValue violated_property_value)
     : status{status}, label{label}, violated_type{violated_type}, violated_property_value{violated_property_value} {}
 
-bool Schemas::CreateSchema(const LabelId primary_label, const std::vector<SchemaProperty> &schemas_types) {
-  return schemas_.insert({primary_label, schemas_types}).second;
+Schemas::SchemasList Schemas::ListSchemas() const {
+  Schemas::SchemasList ret;
+  ret.reserve(schemas_.size());
+  std::transform(schemas_.begin(), schemas_.end(), std::back_inserter(ret),
+                 [](const auto &schema_property_type) { return schema_property_type; });
+  return ret;
 }
 
-bool Schemas::DeleteSchema(const LabelId primary_label) {
-  return schemas_.erase(primary_label);
+std::optional<Schemas::Schema> Schemas::GetSchema(const LabelId primary_label) const {
+  if (auto schema_map = schemas_.find(primary_label); schema_map != schemas_.end()) {
+    return Schema{schema_map->first, schema_map->second};
+  }
+  return std::nullopt;
 }
 
+bool Schemas::CreateSchema(const LabelId primary_label, const std::vector<SchemaProperty> &schemas_types) {
+  if (schemas_.contains(primary_label)) {
+    return false;
+  }
+  schemas_.emplace(primary_label, schemas_types);
+  return true;
+}
+
+bool Schemas::DropSchema(const LabelId primary_label) { return schemas_.erase(primary_label); }
+
 std::optional<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_label, const Vertex &vertex) {
   // TODO Check for multiple defined primary labels
   const auto schema = schemas_.find(primary_label);
@@ -51,7 +68,7 @@ std::optional<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_lab
     // Property type check
     //  TODO Can this be replaced with just property id check?
     if (auto vertex_property = vertex.properties.GetProperty(schema_type.property_id);
-        PropertyValueTypeToSchemaProperty(vertex_property) != schema_type.type) {
+        PropertyTypeToSchemaType(vertex_property) != schema_type.type) {
       return SchemaViolation(SchemaViolation::ValidationStatus::VERTEX_PROPERTY_WRONG_TYPE, primary_label, schema_type,
                              vertex_property);
     }
@@ -62,13 +79,66 @@ std::optional<SchemaViolation> Schemas::ValidateVertex(const LabelId primary_lab
   return std::nullopt;
 }
 
-Schemas::SchemasList Schemas::ListSchemas() const {
-  Schemas::SchemasList ret;
-  ret.reserve(schemas_.size());
-  for (const auto &[label_props, schema_property] : schemas_) {
-    ret.emplace_back(label_props, schema_property);
+std::optional<common::SchemaType> PropertyTypeToSchemaType(const PropertyValue &property_value) {
+  switch (property_value.type()) {
+    case PropertyValue::Type::Bool: {
+      return common::SchemaType::BOOL;
+    }
+    case PropertyValue::Type::Int: {
+      return common::SchemaType::INT;
+    }
+    case PropertyValue::Type::String: {
+      return common::SchemaType::STRING;
+    }
+    case PropertyValue::Type::TemporalData: {
+      switch (property_value.ValueTemporalData().type) {
+        case TemporalType::Date: {
+          return common::SchemaType::DATE;
+        }
+        case TemporalType::LocalDateTime: {
+          return common::SchemaType::LOCALDATETIME;
+        }
+        case TemporalType::LocalTime: {
+          return common::SchemaType::LOCALTIME;
+        }
+        case TemporalType::Duration: {
+          return common::SchemaType::DURATION;
+        }
+      }
+    }
+    case PropertyValue::Type::Double:
+    case PropertyValue::Type::Null:
+    case PropertyValue::Type::Map:
+    case PropertyValue::Type::List: {
+      return std::nullopt;
+    }
+  }
+}
+
+std::string SchemaTypeToString(const common::SchemaType type) {
+  switch (type) {
+    case common::SchemaType::BOOL: {
+      return "Bool";
+    }
+    case common::SchemaType::INT: {
+      return "Integer";
+    }
+    case common::SchemaType::STRING: {
+      return "String";
+    }
+    case common::SchemaType::DATE: {
+      return "Date";
+    }
+    case common::SchemaType::LOCALTIME: {
+      return "LocalTime";
+    }
+    case common::SchemaType::LOCALDATETIME: {
+      return "LocalDateTime";
+    }
+    case common::SchemaType::DURATION: {
+      return "Duration";
+    }
   }
-  return ret;
 }
 
 }  // namespace memgraph::storage
diff --git a/src/storage/v2/schemas.hpp b/src/storage/v2/schemas.hpp
index 113707069..898288b6b 100644
--- a/src/storage/v2/schemas.hpp
+++ b/src/storage/v2/schemas.hpp
@@ -17,6 +17,7 @@
 #include <utility>
 #include <vector>
 
+#include "common/types.hpp"
 #include "storage/v2/id_types.hpp"
 #include "storage/v2/indices.hpp"
 #include "storage/v2/property_value.hpp"
@@ -32,10 +33,8 @@ class SchemaViolationException : public utils::BasicException {
 };
 
 struct SchemaProperty {
-  enum class Type : uint8_t { Bool, Int, Double, String, Date, LocalTime, LocalDateTime, Duration };
-
-  Type type;
   PropertyId property_id;
+  common::SchemaType type;
 };
 
 struct SchemaViolation {
@@ -63,8 +62,9 @@ struct SchemaViolation {
 /// Schema can be mapped under only one label => primary label
 class Schemas {
  public:
+  using Schema = std::pair<LabelId, std::vector<SchemaProperty>>;
   using SchemasMap = std::unordered_map<LabelId, std::vector<SchemaProperty>>;
-  using SchemasList = std::vector<std::pair<LabelId, std::vector<SchemaProperty>>>;
+  using SchemasList = std::vector<Schema>;
 
   Schemas() = default;
   Schemas(const Schemas &) = delete;
@@ -73,83 +73,26 @@ class Schemas {
   Schemas &operator=(Schemas &&) = delete;
   ~Schemas() = default;
 
+  [[nodiscard]] SchemasList ListSchemas() const;
+
+  [[nodiscard]] std::optional<Schemas::Schema> GetSchema(LabelId primary_label) const;
+
+  // Returns true if it was successfully created or false if the schema
+  // already exists
   [[nodiscard]] bool CreateSchema(LabelId label, const std::vector<SchemaProperty> &schemas_types);
 
-  [[nodiscard]] bool DeleteSchema(LabelId label);
+  // Returns true if it was successfully dropped or false if the schema
+  // does not exist
+  [[nodiscard]] bool DropSchema(LabelId label);
 
   [[nodiscard]] std::optional<SchemaViolation> ValidateVertex(LabelId primary_label, const Vertex &vertex);
 
-  [[nodiscard]] SchemasList ListSchemas() const;
-
  private:
   SchemasMap schemas_;
 };
 
-inline std::optional<SchemaProperty::Type> PropertyValueTypeToSchemaProperty(const PropertyValue &property_value) {
-  switch (property_value.type()) {
-    case PropertyValue::Type::Bool: {
-      return SchemaProperty::Type::Bool;
-    }
-    case PropertyValue::Type::Int: {
-      return SchemaProperty::Type::Int;
-    }
-    case PropertyValue::Type::Double: {
-      return SchemaProperty::Type::Double;
-    }
-    case PropertyValue::Type::String: {
-      return SchemaProperty::Type::String;
-    }
-    case PropertyValue::Type::TemporalData: {
-      switch (property_value.ValueTemporalData().type) {
-        case TemporalType::Date: {
-          return SchemaProperty::Type::Date;
-        }
-        case TemporalType::LocalDateTime: {
-          return SchemaProperty::Type::LocalDateTime;
-        }
-        case TemporalType::LocalTime: {
-          return SchemaProperty::Type::LocalTime;
-        }
-        case TemporalType::Duration: {
-          return SchemaProperty::Type::Duration;
-        }
-      }
-    }
-    case PropertyValue::Type::Null:
-    case PropertyValue::Type::Map:
-    case PropertyValue::Type::List: {
-      return std::nullopt;
-    }
-  }
-}
+std::optional<common::SchemaType> PropertyTypeToSchemaType(const PropertyValue &property_value);
 
-inline std::string SchemaPropertyToString(const SchemaProperty::Type type) {
-  switch (type) {
-    case SchemaProperty::Type::Bool: {
-      return "Bool";
-    }
-    case SchemaProperty::Type::Int: {
-      return "Integer";
-    }
-    case SchemaProperty::Type::Double: {
-      return "Double";
-    }
-    case SchemaProperty::Type::String: {
-      return "String";
-    }
-    case SchemaProperty::Type::Date: {
-      return "Date";
-    }
-    case SchemaProperty::Type::LocalTime: {
-      return "LocalTime";
-    }
-    case SchemaProperty::Type::LocalDateTime: {
-      return "LocalDateTime";
-    }
-    case SchemaProperty::Type::Duration: {
-      return "Duration";
-    }
-  }
-}
+std::string SchemaTypeToString(common::SchemaType type);
 
 }  // namespace memgraph::storage
diff --git a/src/storage/v2/storage.cpp b/src/storage/v2/storage.cpp
index 5b1f3084b..36f306a62 100644
--- a/src/storage/v2/storage.cpp
+++ b/src/storage/v2/storage.cpp
@@ -1241,6 +1241,17 @@ SchemasInfo Storage::ListAllSchemas() const {
   return {schemas_.ListSchemas()};
 }
 
+std::optional<Schemas::Schema> Storage::GetSchema(const LabelId primary_label) const {
+  std::shared_lock<utils::RWLock> storage_guard_(main_lock_);
+  return schemas_.GetSchema(primary_label);
+}
+
+bool Storage::CreateSchema(const LabelId primary_label, const std::vector<SchemaProperty> &schemas_types) {
+  return schemas_.CreateSchema(primary_label, schemas_types);
+}
+
+bool Storage::DropSchema(const LabelId primary_label) { return schemas_.DropSchema(primary_label); }
+
 StorageInfo Storage::GetInfo() const {
   auto vertex_count = vertices_.size();
   auto edge_count = edge_count_.load(std::memory_order_acquire);
diff --git a/src/storage/v2/storage.hpp b/src/storage/v2/storage.hpp
index 4839ff3ca..7f8ac6ddc 100644
--- a/src/storage/v2/storage.hpp
+++ b/src/storage/v2/storage.hpp
@@ -414,12 +414,14 @@ class Storage final {
 
   ConstraintsInfo ListAllConstraints() const;
 
-  bool CreateSchema(LabelId primary_label, std::vector<SchemaProperty> &schemas_types);
-
-  bool DeleteSchema(LabelId primary_label);
-
   SchemasInfo ListAllSchemas() const;
 
+  std::optional<Schemas::Schema> GetSchema(LabelId primary_label) const;
+
+  bool CreateSchema(LabelId primary_label, const std::vector<SchemaProperty> &schemas_types);
+
+  bool DropSchema(LabelId primary_label);
+
   StorageInfo GetInfo() const;
 
   bool LockPath();
diff --git a/tests/unit/cypher_main_visitor.cpp b/tests/unit/cypher_main_visitor.cpp
index 39663e2ea..f0d5a6cef 100644
--- a/tests/unit/cypher_main_visitor.cpp
+++ b/tests/unit/cypher_main_visitor.cpp
@@ -32,6 +32,7 @@
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
+#include "common/types.hpp"
 #include "query/exceptions.hpp"
 #include "query/frontend/ast/ast.hpp"
 #include "query/frontend/ast/cypher_main_visitor.hpp"
@@ -2213,6 +2214,8 @@ TEST_P(CypherMainVisitorTest, GrantPrivilege) {
                    {AuthQuery::Privilege::MODULE_READ});
   check_auth_query(&ast_generator, "GRANT MODULE_WRITE TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
                    {AuthQuery::Privilege::MODULE_WRITE});
+  check_auth_query(&ast_generator, "GRANT SCHEMA TO user", AuthQuery::Action::GRANT_PRIVILEGE, "", "", "user", {},
+                   {AuthQuery::Privilege::SCHEMA});
 }
 
 TEST_P(CypherMainVisitorTest, DenyPrivilege) {
@@ -2253,6 +2256,8 @@ TEST_P(CypherMainVisitorTest, DenyPrivilege) {
                    {AuthQuery::Privilege::MODULE_READ});
   check_auth_query(&ast_generator, "DENY MODULE_WRITE TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
                    {AuthQuery::Privilege::MODULE_WRITE});
+  check_auth_query(&ast_generator, "DENY SCHEMA TO user", AuthQuery::Action::DENY_PRIVILEGE, "", "", "user", {},
+                   {AuthQuery::Privilege::SCHEMA});
 }
 
 TEST_P(CypherMainVisitorTest, RevokePrivilege) {
@@ -2295,6 +2300,8 @@ TEST_P(CypherMainVisitorTest, RevokePrivilege) {
                    {}, {AuthQuery::Privilege::MODULE_READ});
   check_auth_query(&ast_generator, "REVOKE MODULE_WRITE FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user",
                    {}, {AuthQuery::Privilege::MODULE_WRITE});
+  check_auth_query(&ast_generator, "REVOKE SCHEMA FROM user", AuthQuery::Action::REVOKE_PRIVILEGE, "", "", "user", {},
+                   {AuthQuery::Privilege::SCHEMA});
 }
 
 TEST_P(CypherMainVisitorTest, ShowPrivileges) {
@@ -4211,3 +4218,110 @@ TEST_P(CypherMainVisitorTest, Foreach) {
     ASSERT_TRUE(dynamic_cast<RemoveProperty *>(*++clauses.begin()));
   }
 }
+
+TEST_P(CypherMainVisitorTest, TestShowSchemas) {
+  auto &ast_generator = *GetParam();
+  auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("SHOW SCHEMAS"));
+  ASSERT_TRUE(query);
+  EXPECT_EQ(query->action_, SchemaQuery::Action::SHOW_SCHEMAS);
+}
+
+TEST_P(CypherMainVisitorTest, TestShowSchema) {
+  auto &ast_generator = *GetParam();
+  EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA ON label"), SyntaxException);
+  EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA :label"), SyntaxException);
+  EXPECT_THROW(ast_generator.ParseQuery("SHOW SCHEMA label"), SyntaxException);
+
+  auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("SHOW SCHEMA ON :label"));
+  ASSERT_TRUE(query);
+  EXPECT_EQ(query->action_, SchemaQuery::Action::SHOW_SCHEMA);
+  EXPECT_EQ(query->label_, ast_generator.Label("label"));
+}
+
+void AssertSchemaPropertyMap(auto &schema_property_map,
+                             std::vector<std::pair<std::string, memgraph::common::SchemaType>> properties_type,
+                             auto &ast_generator) {
+  EXPECT_EQ(schema_property_map.size(), properties_type.size());
+  for (size_t i{0}; i < schema_property_map.size(); ++i) {
+    // Assert PropertyId
+    EXPECT_EQ(schema_property_map[i].first, ast_generator.Prop(properties_type[i].first));
+    // Assert Property Type
+    EXPECT_EQ(schema_property_map[i].second, properties_type[i].second);
+  }
+}
+
+TEST_P(CypherMainVisitorTest, TestCreateSchema) {
+  {
+    auto &ast_generator = *GetParam();
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label"), SyntaxException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label()"), SyntaxException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(123 INTEGER)"), SyntaxException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name TYPE)"), SyntaxException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name, age)"), SyntaxException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name, DURATION)"), SyntaxException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON label(name INTEGER)"), SyntaxException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name INTEGER, name INTEGER)"), SemanticException);
+    EXPECT_THROW(ast_generator.ParseQuery("CREATE SCHEMA ON :label(name INTEGER, name STRING)"), SemanticException);
+  }
+  {
+    auto &ast_generator = *GetParam();
+    auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("CREATE SCHEMA ON :label1(name STRING)"));
+    ASSERT_TRUE(query);
+    EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
+    EXPECT_EQ(query->label_, ast_generator.Label("label1"));
+    AssertSchemaPropertyMap(query->schema_type_map_, {{"name", memgraph::common::SchemaType::STRING}}, ast_generator);
+  }
+  {
+    auto &ast_generator = *GetParam();
+    auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("CREATE SCHEMA ON :label2(name string)"));
+    ASSERT_TRUE(query);
+    EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
+    EXPECT_EQ(query->label_, ast_generator.Label("label2"));
+    AssertSchemaPropertyMap(query->schema_type_map_, {{"name", memgraph::common::SchemaType::STRING}}, ast_generator);
+  }
+  {
+    auto &ast_generator = *GetParam();
+    auto *query = dynamic_cast<SchemaQuery *>(
+        ast_generator.ParseQuery("CREATE SCHEMA ON :label3(first_name STRING, last_name STRING)"));
+    ASSERT_TRUE(query);
+    EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
+    EXPECT_EQ(query->label_, ast_generator.Label("label3"));
+    AssertSchemaPropertyMap(
+        query->schema_type_map_,
+        {{"first_name", memgraph::common::SchemaType::STRING}, {"last_name", memgraph::common::SchemaType::STRING}},
+        ast_generator);
+  }
+  {
+    auto &ast_generator = *GetParam();
+    auto *query = dynamic_cast<SchemaQuery *>(
+        ast_generator.ParseQuery("CREATE SCHEMA ON :label4(name STRING, age INTEGER, dur DURATION, birthday1 "
+                                 "LOCALDATETIME, birthday2 DATE, some_time LOCALTIME, speaks_truth BOOL)"));
+    ASSERT_TRUE(query);
+    EXPECT_EQ(query->action_, SchemaQuery::Action::CREATE_SCHEMA);
+    EXPECT_EQ(query->label_, ast_generator.Label("label4"));
+    AssertSchemaPropertyMap(query->schema_type_map_,
+                            {
+                                {"name", memgraph::common::SchemaType::STRING},
+                                {"age", memgraph::common::SchemaType::INT},
+                                {"dur", memgraph::common::SchemaType::DURATION},
+                                {"birthday1", memgraph::common::SchemaType::LOCALDATETIME},
+                                {"birthday2", memgraph::common::SchemaType::DATE},
+                                {"some_time", memgraph::common::SchemaType::LOCALTIME},
+                                {"speaks_truth", memgraph::common::SchemaType::BOOL},
+                            },
+                            ast_generator);
+  }
+}
+
+TEST_P(CypherMainVisitorTest, TestDropSchema) {
+  auto &ast_generator = *GetParam();
+  EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA"), SyntaxException);
+  EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA ON label"), SyntaxException);
+  EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA :label"), SyntaxException);
+  EXPECT_THROW(ast_generator.ParseQuery("DROP SCHEMA ON :label()"), SyntaxException);
+
+  auto *query = dynamic_cast<SchemaQuery *>(ast_generator.ParseQuery("DROP SCHEMA ON :label"));
+  ASSERT_TRUE(query);
+  EXPECT_EQ(query->action_, SchemaQuery::Action::DROP_SCHEMA);
+  EXPECT_EQ(query->label_, ast_generator.Label("label"));
+}
diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp
index f5a3e03b3..466079578 100644
--- a/tests/unit/interpreter.cpp
+++ b/tests/unit/interpreter.cpp
@@ -10,8 +10,10 @@
 // licenses/APL.txt.
 
 #include <algorithm>
+#include <cstddef>
 #include <cstdlib>
 #include <filesystem>
+#include <unordered_set>
 
 #include "communication/bolt/v1/value.hpp"
 #include "communication/result_stream_faker.hpp"
@@ -38,6 +40,11 @@ auto ToEdgeList(const memgraph::communication::bolt::Value &v) {
     list.push_back(x.ValueEdge());
   }
   return list;
+}
+
+auto StringToUnorderedSet(const std::string &element) {
+  const auto element_split = memgraph::utils::Split(element, ", ");
+  return std::unordered_set<std::string>(element_split.begin(), element_split.end());
 };
 
 struct InterpreterFaker {
@@ -1465,3 +1472,145 @@ TEST_F(InterpreterTest, LoadCsvClauseNotification) {
             "conversion functions such as ToInteger, ToFloat, ToBoolean etc.");
   ASSERT_EQ(notification["description"].ValueString(), "");
 }
+
+TEST_F(InterpreterTest, CreateSchemaMulticommandTransaction) {
+  Interpret("BEGIN");
+  ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)"),
+               memgraph::query::ConstraintInMulticommandTxException);
+  Interpret("ROLLBACK");
+}
+
+TEST_F(InterpreterTest, ShowSchemasMulticommandTransaction) {
+  Interpret("BEGIN");
+  ASSERT_THROW(Interpret("SHOW SCHEMAS"), memgraph::query::ConstraintInMulticommandTxException);
+  Interpret("ROLLBACK");
+}
+
+TEST_F(InterpreterTest, ShowSchemaMulticommandTransaction) {
+  Interpret("BEGIN");
+  ASSERT_THROW(Interpret("SHOW SCHEMA ON :label"), memgraph::query::ConstraintInMulticommandTxException);
+  Interpret("ROLLBACK");
+}
+
+TEST_F(InterpreterTest, DropSchemaMulticommandTransaction) {
+  Interpret("BEGIN");
+  ASSERT_THROW(Interpret("DROP SCHEMA ON :label"), memgraph::query::ConstraintInMulticommandTxException);
+  Interpret("ROLLBACK");
+}
+
+TEST_F(InterpreterTest, SchemaTestCreateAndShow) {
+  // Empty schema type map should result with syntax exception.
+  ASSERT_THROW(Interpret("CREATE SCHEMA ON :label();"), memgraph::query::SyntaxException);
+
+  // Duplicate properties are should also cause an exception
+  ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name STRING);"), memgraph::query::SemanticException);
+  ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING, name INTEGER);"), memgraph::query::SemanticException);
+
+  {
+    // Cannot create same schema twice
+    Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
+    ASSERT_THROW(Interpret("CREATE SCHEMA ON :label(name STRING);"), memgraph::query::QueryException);
+  }
+  // Show schema
+  {
+    auto stream = Interpret("SHOW SCHEMA ON :label");
+    ASSERT_EQ(stream.GetHeader().size(), 2U);
+    const auto &header = stream.GetHeader();
+    ASSERT_EQ(header[0], "property_name");
+    ASSERT_EQ(header[1], "property_type");
+    ASSERT_EQ(stream.GetResults().size(), 2U);
+    std::unordered_map<std::string, std::string> result_table{{"age", "Integer"}, {"name", "String"}};
+
+    const auto &result = stream.GetResults().front();
+    ASSERT_EQ(result.size(), 2U);
+    const auto key1 = result[0].ValueString();
+    ASSERT_TRUE(result_table.contains(key1));
+    ASSERT_EQ(result[1].ValueString(), result_table[key1]);
+
+    const auto &result2 = stream.GetResults().front();
+    ASSERT_EQ(result2.size(), 2U);
+    const auto key2 = result2[0].ValueString();
+    ASSERT_TRUE(result_table.contains(key2));
+    ASSERT_EQ(result[1].ValueString(), result_table[key2]);
+  }
+  // Create Another Schema
+  Interpret("CREATE SCHEMA ON :label2(place STRING, dur DURATION)");
+
+  // Show schemas
+  {
+    auto stream = Interpret("SHOW SCHEMAS");
+    ASSERT_EQ(stream.GetHeader().size(), 2U);
+    const auto &header = stream.GetHeader();
+    ASSERT_EQ(header[0], "label");
+    ASSERT_EQ(header[1], "primary_key");
+    ASSERT_EQ(stream.GetResults().size(), 2U);
+    std::unordered_map<std::string, std::unordered_set<std::string>> result_table{
+        {"label", {"name::String", "age::Integer"}}, {"label2", {"place::String", "dur::Duration"}}};
+
+    const auto &result = stream.GetResults().front();
+    ASSERT_EQ(result.size(), 2U);
+    const auto key1 = result[0].ValueString();
+    ASSERT_TRUE(result_table.contains(key1));
+    const auto primary_key_split = StringToUnorderedSet(result[1].ValueString());
+    ASSERT_EQ(primary_key_split.size(), 2);
+    ASSERT_TRUE(primary_key_split == result_table[key1]) << "actual value is: " << result[1].ValueString();
+
+    const auto &result2 = stream.GetResults().front();
+    ASSERT_EQ(result2.size(), 2U);
+    const auto key2 = result2[0].ValueString();
+    ASSERT_TRUE(result_table.contains(key2));
+    const auto primary_key_split2 = StringToUnorderedSet(result2[1].ValueString());
+    ASSERT_EQ(primary_key_split2.size(), 2);
+    ASSERT_TRUE(primary_key_split2 == result_table[key2]) << "Real value is: " << result[1].ValueString();
+  }
+}
+
+TEST_F(InterpreterTest, SchemaTestCreateDropAndShow) {
+  Interpret("CREATE SCHEMA ON :label(name STRING, age INTEGER)");
+  // Wrong syntax for dropping schema.
+  ASSERT_THROW(Interpret("DROP SCHEMA ON :label();"), memgraph::query::SyntaxException);
+  // Cannot drop non existant schema.
+  ASSERT_THROW(Interpret("DROP SCHEMA ON :label1;"), memgraph::query::QueryException);
+
+  // Create Schema and Drop
+  auto get_number_of_schemas = [this]() {
+    auto stream = Interpret("SHOW SCHEMAS");
+    return stream.GetResults().size();
+  };
+
+  ASSERT_EQ(get_number_of_schemas(), 1);
+  Interpret("CREATE SCHEMA ON :label1(name STRING, age INTEGER)");
+  ASSERT_EQ(get_number_of_schemas(), 2);
+  Interpret("CREATE SCHEMA ON :label2(name STRING, sex BOOL)");
+  ASSERT_EQ(get_number_of_schemas(), 3);
+  Interpret("DROP SCHEMA ON :label1");
+  ASSERT_EQ(get_number_of_schemas(), 2);
+  Interpret("CREATE SCHEMA ON :label3(name STRING, birthday LOCALDATETIME)");
+  ASSERT_EQ(get_number_of_schemas(), 3);
+  Interpret("DROP SCHEMA ON :label2");
+  ASSERT_EQ(get_number_of_schemas(), 2);
+  Interpret("CREATE SCHEMA ON :label4(name STRING, age DURATION)");
+  ASSERT_EQ(get_number_of_schemas(), 3);
+  Interpret("DROP SCHEMA ON :label3");
+  ASSERT_EQ(get_number_of_schemas(), 2);
+  Interpret("DROP SCHEMA ON :label");
+  ASSERT_EQ(get_number_of_schemas(), 1);
+
+  // Show schemas
+  auto stream = Interpret("SHOW SCHEMAS");
+  ASSERT_EQ(stream.GetHeader().size(), 2U);
+  const auto &header = stream.GetHeader();
+  ASSERT_EQ(header[0], "label");
+  ASSERT_EQ(header[1], "primary_key");
+  ASSERT_EQ(stream.GetResults().size(), 1U);
+  std::unordered_map<std::string, std::unordered_set<std::string>> result_table{
+      {"label4", {"name::String", "age::Duration"}}};
+
+  const auto &result = stream.GetResults().front();
+  ASSERT_EQ(result.size(), 2U);
+  const auto key1 = result[0].ValueString();
+  ASSERT_TRUE(result_table.contains(key1));
+  const auto primary_key_split = StringToUnorderedSet(result[1].ValueString());
+  ASSERT_EQ(primary_key_split.size(), 2);
+  ASSERT_TRUE(primary_key_split == result_table[key1]);
+}
diff --git a/tests/unit/query_required_privileges.cpp b/tests/unit/query_required_privileges.cpp
index ad21b10c4..4aab492e1 100644
--- a/tests/unit/query_required_privileges.cpp
+++ b/tests/unit/query_required_privileges.cpp
@@ -192,6 +192,11 @@ TEST_F(TestPrivilegeExtractor, ShowVersion) {
   EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::STATS));
 }
 
+TEST_F(TestPrivilegeExtractor, SchemaQuery) {
+  auto *query = storage.Create<SchemaQuery>();
+  EXPECT_THAT(GetRequiredPrivileges(query), UnorderedElementsAre(AuthQuery::Privilege::SCHEMA));
+}
+
 TEST_F(TestPrivilegeExtractor, CallProcedureQuery) {
   {
     auto *query = QUERY(SINGLE_QUERY(CALL_PROCEDURE("mg.get_module_files")));