diff --git a/src/context/QueryContext.h b/src/context/QueryContext.h
index b809b92305f4c9d0ab7cf1e51412ad828983d3d9..b318bc80c477713f89c809c733993874a173a77f 100644
--- a/src/context/QueryContext.h
+++ b/src/context/QueryContext.h
@@ -106,6 +106,10 @@ public:
         return sm_;
     }
 
+    meta::IndexManager* indexMng() const {
+        return im_;
+    }
+
     storage::GraphStorageClient* getStorageClient() const {
         return storageClient_;
     }
diff --git a/src/mock/MetaCache.cpp b/src/mock/MetaCache.cpp
index 2ab26cde7a215c59554c8185cc5fa93679298b26..d04a2b2bc08f3bb126a935131782215314c79dcc 100644
--- a/src/mock/MetaCache.cpp
+++ b/src/mock/MetaCache.cpp
@@ -266,11 +266,98 @@ Status MetaCache::AlterEdge(const meta::cpp2::AlterEdgeReq &req) {
     return alterSchemaProp(schema, prop);
 }
 
-Status MetaCache::createTagIndex(const meta::cpp2::CreateTagIndexReq&) {
+Status MetaCache::createTagIndex(const meta::cpp2::CreateTagIndexReq& req, IndexID &indexId) {
+    folly::RWSpinLock::WriteHolder holder(lock_);
+    CHECK_SPACE_ID(req.get_space_id());
+    auto ifNotExists = req.get_if_not_exists();
+    auto tagName = req.get_tag_name();
+    auto indexName = req.get_index_name();
+    auto &tagIndexes = spaceIter->second.tagIndexes_;
+    auto findIndexIter = tagIndexes.find(indexName);
+    if (ifNotExists && findIndexIter != tagIndexes.end()) {
+        indexId = findIndexIter->second.get_index_id();
+        return Status::OK();
+    }
+    if (findIndexIter != tagIndexes.end()) {
+        LOG(INFO) << "\tIndex already exist";
+        return Status::Error("Index already exist");
+    }
+
+    if (req.get_fields().empty()) {
+        return Status::Error("column is empty");
+    }
+
+    auto &tagSchemas = spaceIter->second.tagSchemas_;
+    auto findTagIter = tagSchemas.find(tagName);
+    if (findTagIter == tagSchemas.end()) {
+        return Status::Error("Tag Not found");
+    }
+    auto tagId = findTagIter->second.get_tag_id();
+
+    indexId = ++id_;
+    meta::cpp2::IndexItem indexItem;
+    meta::cpp2::SchemaID schemaID;
+    schemaID.set_tag_id(tagId);
+    indexItem.set_schema_id(std::move(schemaID));
+    indexItem.set_schema_name(tagName);
+    indexItem.set_index_id(indexId);
+    indexItem.set_index_name(indexName);
+
+    std::vector<meta::cpp2::ColumnDef> columns;
+    for (auto &field : req.get_fields()) {
+        meta::cpp2::ColumnDef column;
+        column.set_name(std::move(field));
+        columns.emplace_back(std::move(column));
+    }
+    indexItem.set_fields(std::move(columns));
+    tagIndexes[indexName] = std::move(indexItem);
     return Status::OK();
 }
 
-Status MetaCache::createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq&) {
+Status MetaCache::createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq& req, IndexID &indexId) {
+    folly::RWSpinLock::WriteHolder holder(lock_);
+    CHECK_SPACE_ID(req.get_space_id());
+    auto ifNotExists = req.get_if_not_exists();
+    auto edgeName = req.get_edge_name();
+    auto indexName = req.get_index_name();
+    auto &edgeIndexes = spaceIter->second.edgeIndexes_;
+    auto findIndexIter = edgeIndexes.find(indexName);
+    if (ifNotExists && findIndexIter != edgeIndexes.end()) {
+        indexId = findIndexIter->second.get_index_id();
+        return Status::OK();
+    }
+    if (findIndexIter != edgeIndexes.end()) {
+        return Status::Error("Index already exist");
+    }
+
+    if (req.get_fields().empty()) {
+        return Status::Error("column is empty");
+    }
+
+    auto &edgeSchemas = spaceIter->second.edgeSchemas_;
+    auto findEdgeIter = edgeSchemas.find(edgeName);
+    if (findEdgeIter == edgeSchemas.end()) {
+        return Status::Error("Edge Not found");
+    }
+    auto edgeType = findEdgeIter->second.get_edge_type();
+
+    indexId = ++id_;
+    meta::cpp2::IndexItem indexItem;
+    meta::cpp2::SchemaID schemaID;
+    schemaID.set_edge_type(edgeType);
+    indexItem.set_schema_id(std::move(schemaID));
+    indexItem.set_schema_name(edgeName);
+    indexItem.set_index_id(indexId);
+    indexItem.set_index_name(indexName);
+
+    std::vector<meta::cpp2::ColumnDef> columns;
+    for (auto &field : req.get_fields()) {
+        meta::cpp2::ColumnDef column;
+        column.set_name(std::move(field));
+        columns.emplace_back(std::move(column));
+    }
+    indexItem.set_fields(std::move(columns));
+    edgeIndexes[indexName] = std::move(indexItem);
     return Status::OK();
 }
 
diff --git a/src/mock/MetaCache.h b/src/mock/MetaCache.h
index c71a4ce4b26baa2fb91ccbfec3f2c61781130e3d..b99c121f802cbd4f72e53e11f5a56f1ca88eeb3b 100644
--- a/src/mock/MetaCache.h
+++ b/src/mock/MetaCache.h
@@ -50,9 +50,9 @@ public:
 
     Status AlterEdge(const meta::cpp2::AlterEdgeReq &req);
 
-    Status createTagIndex(const meta::cpp2::CreateTagIndexReq &req);
+    Status createTagIndex(const meta::cpp2::CreateTagIndexReq &req, IndexID &indexId);
 
-    Status createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq &req);
+    Status createEdgeIndex(const meta::cpp2::CreateEdgeIndexReq &req, IndexID &indexId);
 
     Status dropTagIndex(const meta::cpp2::DropTagIndexReq &req);
 
diff --git a/src/mock/MockMetaServiceHandler.cpp b/src/mock/MockMetaServiceHandler.cpp
index 67d0e6532b2fc11bc3bef9eb7f6f043567c78702..74d4b4e055c891d9724559f4596a96ba6fb9b345 100644
--- a/src/mock/MockMetaServiceHandler.cpp
+++ b/src/mock/MockMetaServiceHandler.cpp
@@ -357,8 +357,23 @@ MockMetaServiceHandler::future_listEdges(const meta::cpp2::ListEdgesReq& req) {
 }
 
 folly::Future<meta::cpp2::ExecResp>
-MockMetaServiceHandler::future_createTagIndex(const meta::cpp2::CreateTagIndexReq&) {
-    RETURN_SUCCESSED();
+MockMetaServiceHandler::future_createTagIndex(const meta::cpp2::CreateTagIndexReq& req) {
+    folly::Promise<meta::cpp2::ExecResp> promise;
+    auto future = promise.getFuture();
+    meta::cpp2::ExecResp resp;
+    IndexID indexId = 0;
+    auto status = MetaCache::instance().createTagIndex(req, indexId);
+    if (!status.ok()) {
+        resp.set_code(meta::cpp2::ErrorCode::E_UNKNOWN);
+        promise.setValue(std::move(resp));
+        return future;
+    }
+    resp.set_code(meta::cpp2::ErrorCode::SUCCEEDED);
+    meta::cpp2::ID id;
+    id.set_index_id(indexId);
+    resp.set_id(std::move(id));
+    promise.setValue(std::move(resp));
+    return future;
 }
 
 folly::Future<meta::cpp2::ExecResp>
diff --git a/src/mock/test/CMakeLists.txt b/src/mock/test/CMakeLists.txt
index b2cf9f28225bfbe2df0ac689c6171819bff900c5..88f56a44dec75000dde3e5a274f647ddceb44d64 100644
--- a/src/mock/test/CMakeLists.txt
+++ b/src/mock/test/CMakeLists.txt
@@ -84,6 +84,22 @@ nebula_add_test(
         gtest_main
 )
 
+nebula_add_test(
+    NAME
+        index_test
+    SOURCES
+        IndexTest.cpp
+    OBJECTS
+        ${GRAPH_TEST_LIB}
+    LIBRARIES
+        ${THRIFT_LIBRARIES}
+        proxygenhttpserver
+        proxygenlib
+        wangle
+        gtest
+        gtest_main
+)
+
 nebula_add_test(
     NAME
         acl_test
diff --git a/src/mock/test/IndexTest.cpp b/src/mock/test/IndexTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c1cb0eb72e05f10c8013e0f024f976b692af434a
--- /dev/null
+++ b/src/mock/test/IndexTest.cpp
@@ -0,0 +1,119 @@
+/* Copyright (c) 2020 vesoft inc. All rights reserved.
+ *
+ * This source code is licensed under Apache 2.0 License,
+ * attached with Common Clause Condition 1.0, found in the LICENSES directory.
+ */
+
+#include "mock/test/TestEnv.h"
+#include "mock/test/TestBase.h"
+#include <gtest/gtest.h>
+
+DECLARE_int32(heartbeat_interval_secs);
+
+namespace nebula {
+namespace graph {
+
+class IndexTest : public TestBase {
+protected:
+    void SetUp() override {
+        TestBase::SetUp();
+    }
+
+    void TearDown() override {
+        TestBase::TearDown();
+    }
+
+    static void SetUpTestCase() {
+        client_ = gEnv->getGraphClient();
+        ASSERT_NE(nullptr, client_);
+    }
+
+    static void TearDownTestCase() {
+        client_.reset();
+    }
+
+protected:
+    static std::unique_ptr<GraphClient>     client_;
+};
+
+std::unique_ptr<GraphClient> IndexTest::client_{nullptr};
+
+TEST_F(IndexTest, TagIndex) {
+    ASSERT_NE(nullptr, client_);
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE SPACE tag_index_space(partition_num=1, replica_factor=1)";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    sleep(FLAGS_heartbeat_interval_secs + 1);
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "USE tag_index_space";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE TAG tag_1(col1 string, col2 int, col3 double, col4 timestamp)";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    sleep(FLAGS_heartbeat_interval_secs + 1);
+
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "SHOW TAGS;";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+        std::vector<std::string> colNames = {"Name"};
+        ASSERT_TRUE(verifyColNames(resp, colNames));
+        std::vector<std::vector<Value>> values = {{"tag_1"}};
+        ASSERT_TRUE(verifyValues(resp, values));
+    }
+
+    // Single Tag Single Field
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE TAG INDEX single_tag_index ON tag_1(col2)";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    // Property is empty
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE TAG INDEX single_tag_index ON tag_1()";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::E_SYNTAX_ERROR, code);
+    }
+    // Single Tag Multi Field
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE TAG INDEX multi_tag_index ON tag_1(col2, col3)";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "CREATE TAG INDEX disorder_tag_index ON tag_1(col3, col2)";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "DROP TAG INDEX IF EXISTS not_exists_tag_index";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+    {
+        cpp2::ExecutionResponse resp;
+        std::string query = "DROP SPACE tag_index_space";
+        auto code = client_->execute(query, resp);
+        ASSERT_EQ(cpp2::ErrorCode::SUCCEEDED, code);
+    }
+}
+
+
+}   // namespace graph
+}   // namespace nebula
+
diff --git a/src/planner/Maintain.cpp b/src/planner/Maintain.cpp
index d73d0eb27efbfbf231b53bda008749439aa3b6a9..14911fafdb0ac55ff415ed20d865a0fdbe8a57c1 100644
--- a/src/planner/Maintain.cpp
+++ b/src/planner/Maintain.cpp
@@ -17,7 +17,7 @@ namespace graph {
 std::unique_ptr<cpp2::PlanNodeDescription> CreateSchemaNode::explain() const {
     auto desc = SingleInputNode::explain();
     addDescription("name", name_, desc.get());
-    addDescription("ifNotExists", folly::to<std::string>(ifNotExists_), desc.get());
+    addDescription("ifNotExists", util::toJson(ifNotExists_), desc.get());
     addDescription("schema", folly::toJson(util::toJson(schema_)), desc.get());
     return desc;
 }
@@ -49,7 +49,7 @@ std::unique_ptr<cpp2::PlanNodeDescription> CreateIndexNode::explain() const {
     addDescription("schemaName", schemaName_, desc.get());
     addDescription("indexName", indexName_, desc.get());
     addDescription("fields", folly::toJson(util::toJson(fields_)), desc.get());
-    addDescription("ifNotExists", folly::to<std::string>(ifNotExists_), desc.get());
+    addDescription("ifNotExists", util::toJson(ifNotExists_), desc.get());
     return desc;
 }
 
diff --git a/tests/maintain/test_index.py b/tests/maintain/test_index.py
new file mode 100644
index 0000000000000000000000000000000000000000..bce5fb509ce30c1619f1edc4c22e4c1f4dad2749
--- /dev/null
+++ b/tests/maintain/test_index.py
@@ -0,0 +1,107 @@
+# --coding:utf-8--
+#
+# Copyright (c) 2020 vesoft inc. All rights reserved.
+#
+# This source code is licensed under Apache 2.0 License,
+# attached with Common Clause Condition 1.0, found in the LICENSES directory.
+
+
+import time
+
+from tests.common.nebula_test_suite import NebulaTestSuite
+from tests.common.nebula_test_suite import T_EMPTY, T_NULL
+
+class TestIndex(NebulaTestSuite):
+
+    @classmethod
+    def prepare(self):
+        resp = self.client.execute('CREATE SPACE index_space(partition_num=9)')
+        self.check_resp_succeeded(resp)
+
+        time.sleep(self.delay)
+        resp = self.client.execute('USE index_space')
+        self.check_resp_succeeded(resp)
+
+        resp = self.client.execute('CREATE TAG tag_1(col1 string, col2 int, col3 double, col4 timestamp)')
+        self.check_resp_succeeded(resp)
+
+        resp = self.client.execute('CREATE EDGE edge_1(col1 string, col2 int, col3 double, col4 timestamp)')
+        self.check_resp_succeeded(resp)
+        time.sleep(self.delay)
+    
+    @classmethod
+    def cleanup(self):
+        resp = self.execute('DROP SPACE index_space;')
+        self.check_resp_succeeded(resp)
+
+    def test_tag_index(self):
+        # Single Tag Single Field
+        resp = self.client.execute('CREATE TAG INDEX single_tag_index ON tag_1(col2)')
+        self.check_resp_succeeded(resp)
+
+        # Duplicate Index
+        resp = self.client.execute('CREATE TAG INDEX duplicate_tag_index_1 ON tag_1(col2)')
+        self.check_resp_failed(resp)
+
+        # Tag not exist
+        resp = self.client.execute('CREATE TAG INDEX single_person_index ON student(name)')
+        self.check_resp_failed(resp)
+
+        # Property not exist
+        resp = self.client.execute('CREATE TAG INDEX single_tag_index ON tag_1(col5)')
+        self.check_resp_failed(resp)
+
+        # Property is empty
+        resp = self.client.execute('CREATE TAG INDEX single_tag_index ON tag_1()')
+        self.check_resp_failed(resp)
+
+        # Single Tag Multi Field
+        resp = self.client.execute('CREATE TAG INDEX multi_tag_index ON tag_1(col2, col3)')
+        self.check_resp_succeeded(resp)
+
+        # Duplicate Index
+        resp = self.client.execute('CREATE TAG INDEX duplicate_person_index ON tag_1(col2, col3)')
+        self.check_resp_failed(resp)
+
+        # Duplicate Field
+        resp = self.client.execute('CREATE TAG INDEX duplicate_index ON tag_1(col2, col2)')
+        self.check_resp_failed(resp)
+
+        resp = self.client.execute('CREATE TAG INDEX disorder_tag_index ON tag_1(col3, col2)')
+        self.check_resp_succeeded(resp)
+
+    def test_edge_index(self):
+        # Single Tag Single Field
+        resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1(col2)')
+        self.check_resp_succeeded(resp)
+
+        # Duplicate Index
+        resp = self.client.execute('CREATE EDGE INDEX duplicate_edge_1_index ON edge_1(col2)')
+        self.check_resp_failed(resp)
+
+        # Edge not exist
+        resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1_ship(name)')
+        self.check_resp_failed(resp)
+
+        # Property not exist
+        resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1(startTime)')
+        self.check_resp_failed(resp)
+
+        # Property is empty
+        resp = self.client.execute('CREATE EDGE INDEX single_edge_index ON edge_1()')
+        self.check_resp_failed(resp)
+
+        # Single Edge Multi Field
+        resp = self.client.execute('CREATE EDGE INDEX multi_edge_1_index ON edge_1(col2, col3)')
+        self.check_resp_succeeded(resp)
+
+        # Duplicate Index
+        resp = self.client.execute('CREATE EDGE INDEX duplicate_edge_1_index ON edge_1(col2, col3)')
+        self.check_resp_failed(resp)
+
+        # Duplicate Field
+        resp = self.client.execute('CREATE EDGE INDEX duplicate_index ON edge_1(col2, col2)')
+        self.check_resp_failed(resp)
+
+        resp = self.client.execute('CREATE EDGE INDEX disorder_edge_1_index ON edge_1(col3, col2)')
+        self.check_resp_succeeded(resp)