diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml
index e3ac97aaa3b3fa1c1b9c6a267a315a519e7034a7..14562bcf74185d212bd2f5e8113f6c9b258eceef 100644
--- a/.github/workflows/pull_request.yml
+++ b/.github/workflows/pull_request.yml
@@ -7,16 +7,17 @@ on:
       - master
       - 'v[0-9]+.*'
 
+defaults:
+  run:
+    shell: bash
+
 jobs:
   lint:
     name: lint
     if: ${{ contains(github.event.pull_request.labels.*.name, 'ready-for-testing') }}
     runs-on: ubuntu-latest
-    defaults:
-      run:
-        shell: bash
     outputs:
-      num_source_files: ${{ steps.filter-source-files.outputs.num_source_files }}
+      has_source_files: ${{ steps.filter-source-files.outputs.has_source_files }}
     steps:
       - uses: actions/checkout@v2
         with:
@@ -29,17 +30,14 @@ jobs:
         id: filter-source-files
         run: |
           diff_commits=$(git log --oneline -n 1 | cut -d' ' -f1,5)
-          num_source_files=$(git --no-pager diff --name-only $diff_commits | grep '^src\|^CMakeLists.txt\|^cmake\|^.github/workflows' | wc -l)
-          echo "::set-output name=num_source_files::${num_source_files}"
+          source_files=$(git --no-pager diff --name-only $diff_commits | grep '^src\|^tests\|^CMakeLists.txt\|^cmake\|^.github/workflows')
+          echo "::set-output name=has_source_files::$([[ "$source_files" == "" ]] && echo 0 || echo 1)"
 
   build:
     name: build
     needs: lint
-    if: ${{ needs.lint.outputs.num_source_files != 0 }}
+    if: ${{ needs.lint.outputs.has_source_files != 0 }}
     runs-on: self-hosted
-    defaults:
-      run:
-        shell: bash
     strategy:
       fail-fast: false
       matrix:
diff --git a/src/context/Result.h b/src/context/Result.h
index 01ccbb0bffdb560c99e3a97d60123394520ea5d4..9dd78c32e24ca4ca0c99af9e7a48f347c58c89a5 100644
--- a/src/context/Result.h
+++ b/src/context/Result.h
@@ -42,6 +42,10 @@ public:
         return core_.state;
     }
 
+    size_t size() const {
+        return core_.iter->size();
+    }
+
     std::unique_ptr<Iterator> iter() const {
         return core_.iter->copy();
     }
diff --git a/src/context/test/CMakeLists.txt b/src/context/test/CMakeLists.txt
index 4bc6f73fd5eca11340a87bba1661bbb7cc4e703f..bcbe73fd176908484f9b185497e9f015247b8108 100644
--- a/src/context/test/CMakeLists.txt
+++ b/src/context/test/CMakeLists.txt
@@ -5,12 +5,24 @@
 
 SET(CONTEXT_TEST_LIBS
     $<TARGET_OBJECTS:common_datatypes_obj>
+    $<TARGET_OBJECTS:common_expression_obj>
+    $<TARGET_OBJECTS:common_function_manager_obj>
+    $<TARGET_OBJECTS:common_fs_obj>
     $<TARGET_OBJECTS:common_time_obj>
     $<TARGET_OBJECTS:common_base_obj>
     $<TARGET_OBJECTS:common_thread_obj>
+    $<TARGET_OBJECTS:common_conf_obj>
+    $<TARGET_OBJECTS:common_file_based_cluster_id_man_obj>
+    $<TARGET_OBJECTS:common_meta_obj>
+    $<TARGET_OBJECTS:common_meta_client_obj>
+    $<TARGET_OBJECTS:common_meta_thrift_obj>
+    $<TARGET_OBJECTS:common_thrift_obj>
     $<TARGET_OBJECTS:common_common_thrift_obj>
     $<TARGET_OBJECTS:common_graph_thrift_obj>
+    $<TARGET_OBJECTS:common_storage_thrift_obj>
+    $<TARGET_OBJECTS:util_obj>
     $<TARGET_OBJECTS:context_obj>
+    $<TARGET_OBJECTS:parser_obj>
     $<TARGET_OBJECTS:graph_flags_obj>
     $<TARGET_OBJECTS:session_obj>
     $<TARGET_OBJECTS:planner_obj>
@@ -28,6 +40,9 @@ nebula_add_test(
     LIBRARIES
         ${THRIFT_LIBRARIES}
         gtest
+        wangle
+        proxygenhttpserver
+        proxygenlib
 )
 
 nebula_add_executable(
@@ -41,4 +56,7 @@ nebula_add_executable(
         follybenchmark
         boost_regex
         ${THRIFT_LIBRARIES}
+        wangle
+        proxygenhttpserver
+        proxygenlib
 )
diff --git a/src/exec/Executor.cpp b/src/exec/Executor.cpp
index a69641fcd0d287bdbea70cc8ec4a952c15207fe6..b37e60c1f6eaebe2a19cd39669923546f4de3ba0 100644
--- a/src/exec/Executor.cpp
+++ b/src/exec/Executor.cpp
@@ -24,8 +24,8 @@
 #include "exec/logic/StartExecutor.h"
 #include "exec/maintain/EdgeExecutor.h"
 #include "exec/maintain/TagExecutor.h"
-#include "exec/mutate/InsertExecutor.h"
 #include "exec/mutate/DeleteExecutor.h"
+#include "exec/mutate/InsertExecutor.h"
 #include "exec/mutate/UpdateExecutor.h"
 #include "exec/query/AggregateExecutor.h"
 #include "exec/query/DataCollectExecutor.h"
@@ -465,17 +465,13 @@ folly::Future<Status> Executor::error(Status status) const {
 }
 
 Status Executor::finish(Result &&result) {
+    numRows_ = result.size();
     ectx_->setResult(node()->varName(), std::move(result));
     return Status::OK();
 }
 
 Status Executor::finish(Value &&value) {
-    ectx_->setResult(node()->varName(),
-                     ResultBuilder()
-                        .value(std::move(value))
-                        .iter(Iterator::Kind::kDefault)
-                        .finish());
-    return Status::OK();
+    return finish(ResultBuilder().value(std::move(value)).iter(Iterator::Kind::kDefault).finish());
 }
 
 folly::Executor *Executor::runner() const {
diff --git a/src/exec/query/ProjectExecutor.cpp b/src/exec/query/ProjectExecutor.cpp
index 8c296cc41151d65466848a843b690fafe9ecb6e0..3b2caa1a23e270b494aa7dbb16952fe4232ff937 100644
--- a/src/exec/query/ProjectExecutor.cpp
+++ b/src/exec/query/ProjectExecutor.cpp
@@ -33,7 +33,6 @@ folly::Future<Status> ProjectExecutor::execute() {
         }
         ds.rows.emplace_back(std::move(row));
     }
-    numRows_ = ds.rows.size();
     return finish(ResultBuilder().value(Value(std::move(ds))).finish());
 }
 
diff --git a/src/parser/CMakeLists.txt b/src/parser/CMakeLists.txt
index 544cde7b2a35b86f3196a255184b95b0f83d32da..de3b634dd7c46169929911cfe8ff3ea24bd60cff 100644
--- a/src/parser/CMakeLists.txt
+++ b/src/parser/CMakeLists.txt
@@ -20,6 +20,7 @@ nebula_add_library(
     ${FLEX_Scanner_OUTPUTS}
     ${BISON_Parser_OUTPUTS}
     Clauses.cpp
+    EdgeKey.cpp
     SequentialSentences.cpp
     MaintainSentences.cpp
     TraverseSentences.cpp
diff --git a/src/parser/EdgeKey.cpp b/src/parser/EdgeKey.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..1596e7922f1787262506057b97f29901e03ffc37
--- /dev/null
+++ b/src/parser/EdgeKey.cpp
@@ -0,0 +1,49 @@
+/* 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 "parser/EdgeKey.h"
+
+#include "common/expression/Expression.h"
+
+namespace nebula {
+
+std::string EdgeKey::toString() const {
+    return folly::stringPrintf(
+        "%s->%s@%ld", srcid_->toString().c_str(), dstid_->toString().c_str(), rank_);
+}
+
+std::string EdgeKeys::toString() const {
+    std::string buf;
+    buf.reserve(256);
+    for (auto &key : keys_) {
+        buf += key->toString();
+        buf += ",";
+    }
+    if (!buf.empty()) {
+        buf.resize(buf.size() - 1);
+    }
+
+    return buf;
+}
+
+std::string EdgeKeyRef::toString() const {
+    std::string buf;
+    buf.reserve(256);
+    if (srcid_ != nullptr) {
+        buf += srcid_->toString();
+    }
+    if (dstid_ != nullptr) {
+        buf += "->";
+        buf += dstid_->toString();
+    }
+    if (rank_ != nullptr) {
+        buf += "@";
+        buf += rank_->toString();
+    }
+    return buf;
+}
+
+}   // namespace nebula
diff --git a/src/parser/EdgeKey.h b/src/parser/EdgeKey.h
new file mode 100644
index 0000000000000000000000000000000000000000..065275009f37079dc5da770ab160b986033d1173
--- /dev/null
+++ b/src/parser/EdgeKey.h
@@ -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.
+ */
+
+#ifndef PARSER_EDGEKEY_H_
+#define PARSER_EDGEKEY_H_
+
+#include <algorithm>
+#include <cstdint>
+#include <memory>
+#include <string>
+#include <unordered_set>
+#include <vector>
+
+#include "common/thrift/ThriftTypes.h"
+
+namespace nebula {
+
+class Expression;
+
+class EdgeKey final {
+public:
+    EdgeKey(Expression *srcid, Expression *dstid, int64_t rank) {
+        srcid_.reset(srcid);
+        dstid_.reset(dstid);
+        rank_ = rank;
+    }
+
+    Expression *srcid() const {
+        return srcid_.get();
+    }
+
+    Expression *dstid() const {
+        return dstid_.get();
+    }
+
+    int64_t rank() {
+        return rank_;
+    }
+
+    std::string toString() const;
+
+private:
+    std::unique_ptr<Expression> srcid_;
+    std::unique_ptr<Expression> dstid_;
+    EdgeRanking rank_;
+};
+
+class EdgeKeys final {
+public:
+    EdgeKeys() = default;
+
+    void addEdgeKey(EdgeKey *key) {
+        keys_.emplace_back(key);
+    }
+
+    std::vector<EdgeKey *> keys() const {
+        std::vector<EdgeKey *> result;
+        result.resize(keys_.size());
+        auto get = [](const auto &key) { return key.get(); };
+        std::transform(keys_.begin(), keys_.end(), result.begin(), get);
+        return result;
+    }
+
+    std::string toString() const;
+
+private:
+    std::vector<std::unique_ptr<EdgeKey>> keys_;
+};
+
+class EdgeKeyRef final {
+public:
+    EdgeKeyRef(Expression *srcid, Expression *dstid, Expression *rank, bool isInputExpr = true) {
+        srcid_.reset(srcid);
+        dstid_.reset(dstid);
+        rank_.reset(rank);
+        isInputExpr_ = isInputExpr;
+    }
+
+    Expression *srcid() const {
+        return srcid_.get();
+    }
+
+    Expression *dstid() const {
+        return dstid_.get();
+    }
+
+    Expression *rank() const {
+        return rank_.get();
+    }
+
+    Expression *type() const {
+        return type_.get();
+    }
+
+    void setType(Expression *type) {
+        type_.reset(type);
+    }
+
+    bool isInputExpr() const {
+        return isInputExpr_;
+    }
+
+    std::string toString() const;
+
+private:
+    std::unique_ptr<Expression> srcid_;
+    std::unique_ptr<Expression> dstid_;
+    std::unique_ptr<Expression> rank_;
+    std::unique_ptr<Expression> type_;
+    std::unordered_set<std::string> uniqVar_;
+    bool isInputExpr_;
+};
+
+}   // namespace nebula
+
+#endif   // PARSER_EDGEKEY_H_
diff --git a/src/parser/MutateSentences.h b/src/parser/MutateSentences.h
index 949412f12772224f564f27f79fbc353cd5bc2fe3..48fcc79703ee7caab5568197209e94e0e5126b43 100644
--- a/src/parser/MutateSentences.h
+++ b/src/parser/MutateSentences.h
@@ -9,6 +9,7 @@
 #include "common/base/Base.h"
 #include "common/base/StatusOr.h"
 #include "parser/Clauses.h"
+#include "parser/EdgeKey.h"
 #include "parser/Sentence.h"
 
 namespace nebula {
diff --git a/src/parser/Sentence.h b/src/parser/Sentence.h
index 3a40ad9192b139933a2a78bed62ce74ef59e3709..7e1f4b87cc7e828e8bfa3a980f63993eaedf1dc8 100644
--- a/src/parser/Sentence.h
+++ b/src/parser/Sentence.h
@@ -144,106 +144,6 @@ inline std::ostream& operator<<(std::ostream &os, Sentence::Kind kind) {
     return os << static_cast<uint32_t>(kind);
 }
 
-class EdgeKey final {
-public:
-    EdgeKey(Expression *srcid, Expression *dstid, int64_t rank) {
-        srcid_.reset(srcid);
-        dstid_.reset(dstid);
-        rank_ = rank;
-    }
-
-    Expression* srcid() const {
-        return srcid_.get();
-    }
-
-    Expression* dstid() const {
-        return dstid_.get();
-    }
-
-    int64_t rank() {
-        return rank_;
-    }
-
-    std::string toString() const;
-
-private:
-    std::unique_ptr<Expression>     srcid_;
-    std::unique_ptr<Expression>     dstid_;
-    EdgeRanking                     rank_;
-};
-
-class EdgeKeys final {
-public:
-    EdgeKeys() = default;
-
-    void addEdgeKey(EdgeKey *key) {
-        keys_.emplace_back(key);
-    }
-
-    std::vector<EdgeKey*> keys() const {
-        std::vector<EdgeKey*> result;
-        result.resize(keys_.size());
-        auto get = [](const auto&key) { return key.get(); };
-        std::transform(keys_.begin(), keys_.end(), result.begin(), get);
-        return result;
-    }
-
-    std::string toString() const;
-
-private:
-    std::vector<std::unique_ptr<EdgeKey>>   keys_;
-};
-
-class EdgeKeyRef final {
-public:
-    EdgeKeyRef(
-            Expression *srcid,
-            Expression *dstid,
-            Expression *rank,
-            bool isInputExpr = true) {
-        srcid_.reset(srcid);
-        dstid_.reset(dstid);
-        rank_.reset(rank);
-        isInputExpr_ = isInputExpr;
-    }
-
-    StatusOr<std::string> varname() const;
-
-    Expression* srcid() const {
-        return srcid_.get();
-    }
-
-    Expression* dstid() const {
-        return dstid_.get();
-    }
-
-    Expression* rank() const {
-        return rank_.get();
-    }
-
-    Expression* type() const {
-        return type_.get();
-    }
-
-    void setType(Expression *type) {
-        type_.reset(type);
-    }
-
-    bool isInputExpr() const {
-        return isInputExpr_;
-    }
-
-    std::string toString() const;
-
-private:
-    std::unique_ptr<Expression>             srcid_;
-    std::unique_ptr<Expression>             dstid_;
-    std::unique_ptr<Expression>             rank_;
-    std::unique_ptr<Expression>             type_;
-    std::unordered_set<std::string>         uniqVar_;
-    bool                                    isInputExpr_;
-};
-
 }   // namespace nebula
 
 #endif  // PARSER_SENTENCE_H_
diff --git a/src/parser/TraverseSentences.cpp b/src/parser/TraverseSentences.cpp
index 8041537773544187224a3eee522edf6163a7d6e4..fd51e2db613f20b8cc7a3b0f7d89c0e8397938ea 100644
--- a/src/parser/TraverseSentences.cpp
+++ b/src/parser/TraverseSentences.cpp
@@ -145,41 +145,6 @@ std::string FetchVerticesSentence::toString() const {
     return buf;
 }
 
-std::string EdgeKey::toString() const {
-    return folly::stringPrintf("%s->%s@%ld,",
-            srcid_->toString().c_str(), dstid_->toString().c_str(), rank_);
-}
-
-std::string EdgeKeys::toString() const {
-    std::string buf;
-    buf.reserve(256);
-    for (auto &key : keys_) {
-        buf += key->toString();
-    }
-    if (!buf.empty()) {
-        buf.resize(buf.size() - 1);
-    }
-
-    return buf;
-}
-
-std::string EdgeKeyRef::toString() const {
-    std::string buf;
-    buf.reserve(256);
-    if (srcid_ != nullptr) {
-        buf += srcid_->toString();
-    }
-    if (dstid_ != nullptr) {
-        buf += "->";
-        buf += dstid_->toString();
-    }
-    if (rank_ != nullptr) {
-        buf += "@";
-        buf += rank_->toString();
-    }
-    return buf;
-}
-
 std::string FetchEdgesSentence::toString() const {
     std::string buf;
     buf.reserve(256);
diff --git a/src/parser/TraverseSentences.h b/src/parser/TraverseSentences.h
index e9a06fad044dbfb13deebac916f66320b1927ae1..a9db4efc63a999c32499deb769ca358c14ea702d 100644
--- a/src/parser/TraverseSentences.h
+++ b/src/parser/TraverseSentences.h
@@ -7,13 +7,13 @@
 #define PARSER_TRAVERSESENTENCES_H_
 
 #include "common/base/Base.h"
-#include "parser/Sentence.h"
 #include "parser/Clauses.h"
+#include "parser/EdgeKey.h"
 #include "parser/MutateSentences.h"
+#include "parser/Sentence.h"
 
 namespace nebula {
 
-
 class GoSentence final : public Sentence {
 public:
     GoSentence() {
diff --git a/src/parser/test/CMakeLists.txt b/src/parser/test/CMakeLists.txt
index 203e067cdbf8482a07ead47c2cb7aa37a34d92da..ba1837af79fa1608d0c4bdb576a5ad9ffababec3 100644
--- a/src/parser/test/CMakeLists.txt
+++ b/src/parser/test/CMakeLists.txt
@@ -19,6 +19,7 @@ set(PARSER_TEST_LIBS
     $<TARGET_OBJECTS:common_function_manager_obj>
     $<TARGET_OBJECTS:common_meta_thrift_obj>
     $<TARGET_OBJECTS:common_graph_thrift_obj>
+    $<TARGET_OBJECTS:common_storage_thrift_obj>
     $<TARGET_OBJECTS:common_meta_obj>
     $<TARGET_OBJECTS:common_meta_client_obj>
     $<TARGET_OBJECTS:common_conf_obj>
diff --git a/src/planner/Admin.cpp b/src/planner/Admin.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..796415bd98bd6bd6d915ec91d68bce148729d5eb
--- /dev/null
+++ b/src/planner/Admin.cpp
@@ -0,0 +1,56 @@
+
+/* 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 "planner/Admin.h"
+
+#include "common/interface/gen-cpp2/graph_types.h"
+#include "util/ToJson.h"
+
+namespace nebula {
+namespace graph {
+
+std::unique_ptr<cpp2::PlanNodeDescription> CreateSpace::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("ifNotExists", folly::to<std::string>(ifNotExists_), desc.get());
+    addDescription("spaceDesc", folly::toJson(util::toJson(props_)), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> DropSpace::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("spaceName", spaceName_, desc.get());
+    addDescription("ifExists", folly::to<std::string>(ifExists_), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> DescSpace::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("spaceName", spaceName_, desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> ShowCreateSpace::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("spaceName", spaceName_, desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> DropSnapshot::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("snapshotName", snapshotName_, desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> ShowParts::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("spaceId", folly::to<std::string>(spaceId_), desc.get());
+    addDescription("partIds", folly::toJson(util::toJson(partIds_)), desc.get());
+    return desc;
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/planner/Admin.h b/src/planner/Admin.h
index 188b3b1b32194658e790a7c5c86493bc2e7891d2..1b950233c97f0684ca04fc1e42b42477f48fa9a9 100644
--- a/src/planner/Admin.h
+++ b/src/planner/Admin.h
@@ -19,6 +19,7 @@
  */
 namespace nebula {
 namespace graph {
+
 // TODO: All DDLs, DMLs and DQLs could be used in a single query
 // which would make them in a single and big execution plan
 
@@ -29,10 +30,6 @@ public:
         return new ShowHosts(plan, dep);
     }
 
-    std::string explain() const override {
-        return "ShowHosts";
-    }
-
 private:
     explicit ShowHosts(ExecutionPlan* plan, PlanNode* dep)
         : SingleDependencyNode(plan, Kind::kShowHosts, dep) {}
@@ -50,9 +47,7 @@ public:
                            ifNotExists);
     }
 
-    std::string explain() const override {
-        return "CreateSpace";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
 public:
     const meta::SpaceDesc& getSpaceDesc() const {
@@ -73,7 +68,6 @@ private:
         ifNotExists_ = ifNotExists;
     }
 
-
 private:
     meta::SpaceDesc               props_;
     bool                          ifNotExists_;
@@ -88,9 +82,7 @@ public:
         return new DropSpace(plan, input, std::move(spaceName), ifExists);
     }
 
-    std::string explain() const override {
-        return "DropSpace";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::string& getSpaceName() const {
         return spaceName_;
@@ -123,9 +115,7 @@ public:
     return new DescSpace(plan, input, std::move(spaceName));
     }
 
-    std::string explain() const override {
-        return "DescSpace";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::string& getSpaceName() const {
         return spaceName_;
@@ -149,10 +139,6 @@ public:
         return new ShowSpaces(plan, input);
     }
 
-    std::string explain() const override {
-        return "ShowSpaces";
-    }
-
 private:
     explicit ShowSpaces(ExecutionPlan* plan, PlanNode* input)
             : SingleInputNode(plan, Kind::kShowSpaces, input) {}
@@ -160,9 +146,6 @@ private:
 
 class Config final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "Config";
-    }
 };
 
 class ShowCreateSpace final : public SingleInputNode {
@@ -173,9 +156,7 @@ public:
         return new ShowCreateSpace(plan, input, std::move(spaceName));
     }
 
-    std::string explain() const override {
-        return "ShowCreateSpace";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::string& getSpaceName() const {
         return spaceName_;
@@ -195,9 +176,6 @@ private:
 
 class Balance final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "Balance";
-    }
 };
 
 class CreateSnapshot final : public SingleInputNode {
@@ -206,10 +184,6 @@ public:
         return new CreateSnapshot(plan, input);
     }
 
-    std::string explain() const override {
-        return "CreateSnapshot";
-    }
-
 private:
     explicit CreateSnapshot(ExecutionPlan* plan, PlanNode* input)
         : SingleInputNode(plan, Kind::kCreateSnapshot, input) {}
@@ -223,12 +197,10 @@ public:
         return new DropSnapshot(plan, input, std::move(snapshotName));
     }
 
-    std::string explain() const override {
-        return "DropSnapshot";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::string& getShapshotName() const {
-        return shapshotName_;
+        return snapshotName_;
     }
 
 private:
@@ -236,11 +208,11 @@ private:
                           PlanNode* input,
                           std::string snapshotName)
         : SingleInputNode(plan, Kind::kDropSnapshot, input) {
-        shapshotName_ = std::move(snapshotName);
+        snapshotName_ = std::move(snapshotName);
     }
 
 private:
-    std::string           shapshotName_;
+    std::string           snapshotName_;
 };
 
 class ShowSnapshots final : public SingleInputNode {
@@ -249,10 +221,6 @@ public:
         return new ShowSnapshots(plan, input);
     }
 
-    std::string explain() const override {
-        return "ShowSnapshots";
-    }
-
 private:
     explicit ShowSnapshots(ExecutionPlan* plan, PlanNode* input)
         : SingleInputNode(plan, Kind::kShowSnapshots, input) {}
@@ -260,16 +228,10 @@ private:
 
 class Download final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "Download";
-    }
 };
 
 class Ingest final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "Ingest";
-    }
 };
 
 class ShowParts final : public SingleInputNode {
@@ -281,9 +243,7 @@ public:
         return new ShowParts(plan, input, spaceId, std::move(partIds));
     }
 
-    std::string explain() const override {
-        return "ShowParts";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     GraphSpaceID getSpaceId() const {
         return spaceId_;
@@ -307,6 +267,7 @@ private:
     GraphSpaceID                       spaceId_{-1};
     std::vector<PartitionID>           partIds_;
 };
+
 }  // namespace graph
 }  // namespace nebula
 #endif  // PLANNER_ADMIN_H_
diff --git a/src/planner/CMakeLists.txt b/src/planner/CMakeLists.txt
index 923bcbdfd67627f3d9100383943de16ef6008728..1485a8ea6f72d6eb67467f1c7c6a2cc2f567074a 100644
--- a/src/planner/CMakeLists.txt
+++ b/src/planner/CMakeLists.txt
@@ -7,9 +7,11 @@ nebula_add_library(
     planner_obj OBJECT
     PlanNode.cpp
     ExecutionPlan.cpp
+    Admin.cpp
     Logic.cpp
     Query.cpp
-    Mutate.h
+    Mutate.cpp
+    Maintain.cpp
 )
 
 #nebula_add_subdirectory(test)
diff --git a/src/planner/ExecutionPlan.cpp b/src/planner/ExecutionPlan.cpp
index 90a3625fbae3dfef3776a6fb6afca6da0c9377c5..e2a5cbfc784c8c7edb643eceac92fbd51c4465d2 100644
--- a/src/planner/ExecutionPlan.cpp
+++ b/src/planner/ExecutionPlan.cpp
@@ -42,17 +42,9 @@ static size_t makePlanNodeDesc(const PlanNode* node, cpp2::PlanDescription* plan
 
     size_t planNodeDescPos = planDesc->plan_node_descs.size();
     planDesc->node_index_map.emplace(node->id(), planNodeDescPos);
-    planDesc->plan_node_descs.emplace_back(cpp2::PlanNodeDescription{});
+    planDesc->plan_node_descs.emplace_back(std::move(*node->explain()));
     auto& planNodeDesc = planDesc->plan_node_descs.back();
 
-    planNodeDesc.set_id(node->id());
-    planNodeDesc.set_name(PlanNode::toString(node->kind()));
-    planNodeDesc.set_output_var(node->varName());
-    cpp2::Pair p;
-    p.set_key("description");
-    p.set_value(node->explain());
-    planNodeDesc.set_description({std::move(p)});
-
     switch (node->kind()) {
         case PlanNode::Kind::kStart: {
             break;
@@ -61,7 +53,6 @@ static size_t makePlanNodeDesc(const PlanNode* node, cpp2::PlanDescription* plan
         case PlanNode::Kind::kIntersect:
         case PlanNode::Kind::kMinus: {
             auto bNode = static_cast<const BiInputNode*>(node);
-            planNodeDesc.set_dependencies({bNode->left()->id(), bNode->right()->id()});
             makePlanNodeDesc(bNode->left(), planDesc);
             makePlanNodeDesc(bNode->right(), planDesc);
             break;
@@ -96,7 +87,6 @@ static size_t makePlanNodeDesc(const PlanNode* node, cpp2::PlanDescription* plan
         default: {
             // Other plan nodes have single dependency
             auto singleDepNode = static_cast<const SingleDependencyNode*>(node);
-            planNodeDesc.set_dependencies({singleDepNode->dep()->id()});
             makePlanNodeDesc(singleDepNode->dep(), planDesc);
             break;
         }
diff --git a/src/planner/Logic.cpp b/src/planner/Logic.cpp
index c92d43c659d86fb6a541b2ac6176d45d0d94112d..dcae2387cfe45698682f115fdfb3e1cdad92ccd0 100644
--- a/src/planner/Logic.cpp
+++ b/src/planner/Logic.cpp
@@ -6,19 +6,19 @@
 
 #include "planner/Logic.h"
 
+#include "common/interface/gen-cpp2/graph_types.h"
+
 namespace nebula {
 namespace graph {
 
-std::string Select::explain() const {
-    return "Select";
+std::unique_ptr<cpp2::PlanNodeDescription> BinarySelect::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("condition", condition_ ? condition_->toString() : "", desc.get());
+    return desc;
 }
 
 Loop::Loop(ExecutionPlan* plan, PlanNode* input, PlanNode* body, Expression* condition)
     : BinarySelect(plan, Kind::kLoop, input, condition), body_(body) {}
 
-std::string Loop::explain() const {
-    return "Loop";
-}
-
 }   // namespace graph
 }   // namespace nebula
diff --git a/src/planner/Logic.h b/src/planner/Logic.h
index 4b19c7db9b800a296a2e0f44e945f7b180daaa83..c32c65203e16fdca1f7f1a6941e65fb40366ef47 100644
--- a/src/planner/Logic.h
+++ b/src/planner/Logic.h
@@ -18,10 +18,6 @@ public:
         return new StartNode(plan);
     }
 
-    std::string explain() const override {
-        return "Start";
-    }
-
 private:
     explicit StartNode(ExecutionPlan* plan) : PlanNode(plan, Kind::kStart) {}
 };
@@ -32,6 +28,8 @@ public:
         return condition_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     BinarySelect(ExecutionPlan* plan, Kind kind, PlanNode* input, Expression* condition)
         : SingleInputNode(plan, kind, input), condition_(condition) {}
@@ -57,8 +55,6 @@ public:
         else_ = elseBranch;
     }
 
-    std::string explain() const override;
-
     const PlanNode* then() const {
         return if_;
     }
@@ -90,8 +86,6 @@ public:
         body_ = body;
     }
 
-    std::string explain() const override;
-
     const PlanNode* body() const {
         return body_;
     }
@@ -111,10 +105,6 @@ public:
         return new MultiOutputsNode(input, plan);
     }
 
-    std::string explain() const override {
-        return "MultiOutputsNode";
-    }
-
 private:
     MultiOutputsNode(PlanNode* input, ExecutionPlan* plan)
         : SingleInputNode(plan, Kind::kMultiOutputs, input) {}
diff --git a/src/planner/Maintain.cpp b/src/planner/Maintain.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..744e5bc872e4b439c03c627e0cbfa3d5a6cd6f90
--- /dev/null
+++ b/src/planner/Maintain.cpp
@@ -0,0 +1,48 @@
+/* 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 "planner/Maintain.h"
+
+#include <sstream>
+
+#include "common/interface/gen-cpp2/graph_types.h"
+#include "util/ToJson.h"
+
+namespace nebula {
+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("schema", folly::toJson(util::toJson(schema_)), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> AlterSchemaNode::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("space", folly::to<std::string>(space_), desc.get());
+    addDescription("name", name_, desc.get());
+    addDescription("schemaItems", folly::toJson(util::toJson(schemaItems_)), desc.get());
+    addDescription("schemaProp", folly::toJson(util::toJson(schemaProp_)), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> DescSchema::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("name", name_, desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> DropSchema::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("name", name_, desc.get());
+    addDescription("ifExists", folly::to<std::string>(ifExists_), desc.get());
+    return desc;
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/planner/Maintain.h b/src/planner/Maintain.h
index 940b2347d4c30afb23e5ee484fa49ce63e7cc8ec..d07ddee13c3e4ab81b851f564dad61e9d4c9f71f 100644
--- a/src/planner/Maintain.h
+++ b/src/planner/Maintain.h
@@ -13,6 +13,7 @@
 
 namespace nebula {
 namespace graph {
+
 // which would make them in a single and big execution plan
 class CreateSchemaNode : public SingleInputNode {
 protected:
@@ -40,6 +41,8 @@ public:
         return ifNotExists_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     std::string            name_;
     meta::cpp2::Schema     schema_;
@@ -60,10 +63,6 @@ public:
                          ifNotExists);
     }
 
-    std::string explain() const override {
-        return "CreateTag";
-    }
-
 private:
     CreateTag(ExecutionPlan* plan,
               PlanNode* input,
@@ -93,10 +92,6 @@ public:
                           ifNotExists);
     }
 
-    std::string explain() const override {
-        return "CreateEdge";
-    }
-
 private:
     CreateEdge(ExecutionPlan* plan,
                PlanNode* input,
@@ -144,6 +139,8 @@ public:
         return space_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     GraphSpaceID                               space_;
     std::string                                name_;
@@ -167,10 +164,6 @@ public:
                             std::move(schemaProp));
     }
 
-    std::string explain() const override {
-        return "AlterTag";
-    }
-
 private:
     AlterTag(ExecutionPlan* plan,
              PlanNode* input,
@@ -204,10 +197,6 @@ public:
                              std::move(schemaProp));
     }
 
-    std::string explain() const override {
-        return "AlterEdge";
-    }
-
 private:
     AlterEdge(ExecutionPlan* plan,
               PlanNode* input,
@@ -240,6 +229,8 @@ public:
         return name_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     std::string            name_;
 };
@@ -252,10 +243,6 @@ public:
         return new DescTag(plan, input, std::move(tagName));
     }
 
-    std::string explain() const override {
-        return "DescTag";
-    }
-
 private:
     DescTag(ExecutionPlan* plan,
             PlanNode* input,
@@ -272,10 +259,6 @@ public:
         return new DescEdge(plan, input, std::move(edgeName));
     }
 
-    std::string explain() const override {
-        return "DescEdge";
-    }
-
 private:
     DescEdge(ExecutionPlan* plan,
              PlanNode* input,
@@ -292,10 +275,6 @@ public:
         return new ShowCreateTag(plan, input, std::move(name));
     }
 
-    std::string explain() const override {
-        return "ShowCreateTag";
-    }
-
 private:
     ShowCreateTag(ExecutionPlan* plan,
                   PlanNode* input,
@@ -312,10 +291,6 @@ public:
         return new ShowCreateEdge(plan, input, std::move(name));
     }
 
-    std::string explain() const override {
-        return "ShowCreateEdge";
-    }
-
 private:
     ShowCreateEdge(ExecutionPlan* plan,
                    PlanNode* input,
@@ -331,10 +306,6 @@ public:
         return new ShowTags(plan, input);
     }
 
-    std::string explain() const override {
-        return "ShowTags";
-    }
-
 private:
     ShowTags(ExecutionPlan* plan,
              PlanNode* input)
@@ -349,10 +320,6 @@ public:
         return new ShowEdges(plan, input);
     }
 
-    std::string explain() const override {
-        return "ShowEdges";
-    }
-
 private:
     ShowEdges(ExecutionPlan* plan,
               PlanNode* input)
@@ -380,6 +347,9 @@ public:
         return ifExists_;
     }
 
+
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     std::string            name_;
     bool                   ifExists_;
@@ -394,10 +364,6 @@ public:
         return new DropTag(plan, input, std::move(name), ifExists);
     }
 
-    std::string explain() const override {
-        return "DropTag";
-    }
-
 private:
     DropTag(ExecutionPlan* plan,
             PlanNode* input,
@@ -416,10 +382,6 @@ public:
         return new DropEdge(plan, input, std::move(name), ifExists);
     }
 
-    std::string explain() const override {
-        return "DropEdge";
-    }
-
 private:
     DropEdge(ExecutionPlan* plan,
              PlanNode* input,
@@ -431,59 +393,36 @@ private:
 
 class CreateTagIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "CreateTagIndex";
-    }
 };
 
 class CreateEdgeIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "CreateEdgeIndex";
-    }
 };
 
 class DescribeTagIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "DescribeTagIndex";
-    }
 };
 
 class DescribeEdgeIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "DescribeEdgeIndex";
-    }
 };
 
 class DropTagIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "DropTagIndex";
-    }
 };
 
 class DropEdgeIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "DropEdgeIndex";
-    }
 };
 
 class BuildTagIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "BuildTagIndex";
-    }
 };
 
 class BuildEdgeIndex final : public SingleInputNode {
 public:
-    std::string explain() const override {
-        return "BuildEdgeIndex";
-    }
 };
+
 }  // namespace graph
 }  // namespace nebula
 #endif  // PLANNER_MAINTAIN_H_
diff --git a/src/planner/Mutate.cpp b/src/planner/Mutate.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..2e18c04d88998e29e0ed29385cc9b849bcc8c42f
--- /dev/null
+++ b/src/planner/Mutate.cpp
@@ -0,0 +1,85 @@
+/* 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 "planner/Mutate.h"
+
+#include "common/interface/gen-cpp2/graph_types.h"
+#include "common/interface/gen-cpp2/storage_types.h"
+#include "util/ToJson.h"
+
+namespace nebula {
+namespace graph {
+
+std::unique_ptr<cpp2::PlanNodeDescription> InsertVertices::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("spaceId", folly::to<std::string>(spaceId_), desc.get());
+    addDescription("overwritable", folly::to<std::string>(overwritable_), desc.get());
+
+    folly::dynamic tagPropsArr = folly::dynamic::array();
+    for (const auto &p : tagPropNames_) {
+        folly::dynamic obj = folly::dynamic::object();
+        obj.insert("tagId", p.first);
+        obj.insert("props", util::toJson(p.second));
+        tagPropsArr.push_back(obj);
+    }
+    addDescription("tagPropNames", folly::toJson(tagPropsArr), desc.get());
+    addDescription("vertices", folly::toJson(util::toJson(vertices_)), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> InsertEdges::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("spaceId", folly::to<std::string>(spaceId_), desc.get());
+    addDescription("overwritable", folly::to<std::string>(overwritable_), desc.get());
+    addDescription("propNames", folly::toJson(util::toJson(propNames_)), desc.get());
+    addDescription("edges", folly::toJson(util::toJson(edges_)), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> Update::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("spaceId", folly::to<std::string>(spaceId_), desc.get());
+    addDescription("schemaName", schemaName_, desc.get());
+    addDescription("insertable", folly::to<std::string>(insertable_), desc.get());
+    addDescription("updatedProps", folly::toJson(util::toJson(updatedProps_)), desc.get());
+    addDescription("returnProps", folly::toJson(util::toJson(returnProps_)), desc.get());
+    addDescription("condition", condition_, desc.get());
+    addDescription("yieldNames", folly::toJson(util::toJson(yieldNames_)), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> UpdateVertex::explain() const {
+    auto desc = Update::explain();
+    addDescription("vid", folly::to<std::string>(vId_), desc.get());
+    addDescription("tagId", folly::to<std::string>(tagId_), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> UpdateEdge::explain() const {
+    auto desc = Update::explain();
+    addDescription("srcId", srcId_, desc.get());
+    addDescription("dstId", dstId_, desc.get());
+    addDescription("rank", folly::to<std::string>(rank_), desc.get());
+    addDescription("edgeType", folly::to<std::string>(edgeType_), desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> DeleteVertices::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("space", folly::to<std::string>(space_), desc.get());
+    addDescription("vidRef", vidRef_ ? vidRef_->toString() : "", desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> DeleteEdges::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("space", folly::to<std::string>(space_), desc.get());
+    addDescription("edgeKeyRefs", folly::toJson(util::toJson(edgeKeyRefs_)), desc.get());
+    return desc;
+}
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/planner/Mutate.h b/src/planner/Mutate.h
index 2de152ea1a3ae58becea9941aff67bf483545858..fc49b6bb43e1cdc6dfe2baf65a678db96ed5cd31 100644
--- a/src/planner/Mutate.h
+++ b/src/planner/Mutate.h
@@ -34,9 +34,7 @@ public:
                                   overwritable);
     }
 
-    std::string explain() const override {
-        return "InsertVertices";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::vector<storage::cpp2::NewVertex>& getVertices() const {
         return vertices_;
@@ -91,9 +89,7 @@ public:
                                overwritable);
     }
 
-    std::string explain() const override {
-        return "InsertEdges";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::vector<std::string>& getPropNames() const {
         return propNames_;
@@ -162,6 +158,8 @@ public:
         return schemaName_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     Update(Kind kind,
            ExecutionPlan* plan,
@@ -218,9 +216,7 @@ public:
                                 std::move(yieldNames));
     }
 
-    std::string explain() const override {
-        return "UpdateVertex";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::string& getVId() const {
         return vId_;
@@ -290,9 +286,7 @@ public:
                               std::move(yieldNames));
     }
 
-    std::string explain() const override {
-        return "UpdateEdge";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::string& getSrcId() const {
         return srcId_;
@@ -363,9 +357,7 @@ public:
                                   vidRef_);
     }
 
-    std::string explain() const override {
-        return "DeleteVertices";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     GraphSpaceID getSpace() const {
         return space_;
@@ -401,9 +393,7 @@ public:
                                std::move(edgeKeyRefs));
     }
 
-    std::string explain() const override {
-        return "DeleteEdges";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     GraphSpaceID getSpace() const {
         return space_;
@@ -423,9 +413,10 @@ private:
         , edgeKeyRefs_(std::move(edgeKeyRefs)) {}
 
 private:
-    GraphSpaceID                                   space_{-1};
-    std::vector<EdgeKeyRef*>  edgeKeyRefs_;
+    GraphSpaceID space_{-1};
+    std::vector<EdgeKeyRef*> edgeKeyRefs_;
 };
+
 }  // namespace graph
 }  // namespace nebula
 #endif  // PLANNER_MUTATE_H_
diff --git a/src/planner/PlanNode.cpp b/src/planner/PlanNode.cpp
index ac94a51a01da17d5ecc3d55473cabd1a7f8c04c6..b52cbd9fe8d412eaf7185d586bc0bc624760ece0 100644
--- a/src/planner/PlanNode.cpp
+++ b/src/planner/PlanNode.cpp
@@ -5,6 +5,8 @@
  */
 
 #include "planner/PlanNode.h"
+
+#include "common/interface/gen-cpp2/graph_types.h"
 #include "planner/ExecutionPlan.h"
 
 namespace nebula {
@@ -120,10 +122,53 @@ const char* PlanNode::toString(PlanNode::Kind kind) {
     LOG(FATAL) << "Impossible kind plan node " << static_cast<int>(kind);
 }
 
+// static
+void PlanNode::addDescription(std::string key, std::string value, cpp2::PlanNodeDescription* desc) {
+    if (!desc->__isset.description) {
+        desc->set_description({});
+    }
+    cpp2::Pair kv;
+    kv.set_key(std::move(key));
+    kv.set_value(std::move(value));
+    desc->get_description()->emplace_back(std::move(kv));
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> PlanNode::explain() const {
+    auto desc = std::make_unique<cpp2::PlanNodeDescription>();
+    desc->set_id(id_);
+    desc->set_name(toString(kind_));
+    desc->set_output_var(outputVar_);
+    return desc;
+}
+
 std::ostream& operator<<(std::ostream& os, PlanNode::Kind kind) {
     os << PlanNode::toString(kind);
     return os;
 }
 
+std::unique_ptr<cpp2::PlanNodeDescription> SingleDependencyNode::explain() const {
+    auto desc = PlanNode::explain();
+    DCHECK(!desc->__isset.dependencies);
+    desc->set_dependencies({dependency_->id()});
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> SingleInputNode::explain() const {
+    auto desc = SingleDependencyNode::explain();
+    DCHECK(!desc->__isset.description);
+    addDescription("inputVar", inputVar_, desc.get());
+    return desc;
+}
+
+std::unique_ptr<cpp2::PlanNodeDescription> BiInputNode::explain() const {
+    auto desc = PlanNode::explain();
+    DCHECK(!desc->__isset.dependencies);
+    desc->set_dependencies({left_->id(), right_->id()});
+    DCHECK(!desc->__isset.description);
+    addDescription("leftVar", leftVar_, desc.get());
+    addDescription("rightVar", rightVar_, desc.get());
+    return desc;
+}
+
 }   // namespace graph
 }   // namespace nebula
diff --git a/src/planner/PlanNode.h b/src/planner/PlanNode.h
index dd97153bea7db509b816b4739db9e1dd2dff98c8..94119d67efd94d9ec831749c994de30ae811fb87 100644
--- a/src/planner/PlanNode.h
+++ b/src/planner/PlanNode.h
@@ -14,6 +14,10 @@
 namespace nebula {
 namespace graph {
 
+namespace cpp2 {
+class PlanNodeDescription;
+}   // namespace cpp2
+
 class ExecutionPlan;
 
 /**
@@ -78,10 +82,8 @@ public:
 
     virtual ~PlanNode() = default;
 
-    /**
-     * To explain how a query would be executed
-     */
-    virtual std::string explain() const = 0;
+    // Describe plan node
+    virtual std::unique_ptr<cpp2::PlanNodeDescription> explain() const;
 
     Kind kind() const {
         return kind_;
@@ -135,6 +137,8 @@ public:
     }
 
 protected:
+    static void addDescription(std::string key, std::string value, cpp2::PlanNodeDescription* desc);
+
     Kind                                     kind_{Kind::kUnknown};
     int64_t                                  id_{IdGenerator::INVALID_ID};
     ExecutionPlan*                           plan_{nullptr};
@@ -163,6 +167,8 @@ protected:
     SingleDependencyNode(ExecutionPlan *plan, Kind kind, const PlanNode *dep)
         : PlanNode(plan, kind), dependency_(dep) {}
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
     const PlanNode *dependency_;
 };
 
@@ -176,6 +182,8 @@ public:
         return inputVar_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     SingleInputNode(ExecutionPlan* plan, Kind kind, const PlanNode* dep)
         : SingleDependencyNode(plan, kind, dep) {
@@ -219,9 +227,7 @@ public:
         return rightVar_;
     }
 
-    std::string explain() const override {
-        return "";
-    }
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
 protected:
     BiInputNode(ExecutionPlan* plan, Kind kind, PlanNode* left, PlanNode* right)
diff --git a/src/planner/Query.cpp b/src/planner/Query.cpp
index 5927c61f195a2ff7df141eaf602408e531fe85a9..bbeec0f4bb627014cb0e84339e2ef475c2920561 100644
--- a/src/planner/Query.cpp
+++ b/src/planner/Query.cpp
@@ -8,82 +8,129 @@
 
 #include <folly/String.h>
 
+#include "common/interface/gen-cpp2/graph_types.h"
+#include "util/ToJson.h"
+
 using folly::stringPrintf;
 
 namespace nebula {
 namespace graph {
-std::string GetNeighbors::explain() const {
-    // TODO:
-    return "GetNeighbors";
-}
 
-std::string GetVertices::explain() const {
-    // TODO:
-    return "GetVertices";
+std::unique_ptr<cpp2::PlanNodeDescription> Explore::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("space", folly::to<std::string>(space_), desc.get());
+    addDescription("dedup", folly::to<std::string>(dedup_), desc.get());
+    addDescription("limit", folly::to<std::string>(limit_), desc.get());
+    addDescription("filter", filter_, desc.get());
+    addDescription("orderBy", folly::toJson(util::toJson(orderBy_)), desc.get());
+    return desc;
 }
 
-std::string GetEdges::explain() const {
-    // TODO:
-    return "GetEdges";
+std::unique_ptr<cpp2::PlanNodeDescription> GetNeighbors::explain() const {
+    auto desc = Explore::explain();
+    addDescription("src", src_ ? src_->toString() : "", desc.get());
+    addDescription("edgeTypes", folly::toJson(util::toJson(edgeTypes_)), desc.get());
+    addDescription("edgeDirection",
+                   storage::cpp2::_EdgeDirection_VALUES_TO_NAMES.at(edgeDirection_),
+                   desc.get());
+    addDescription(
+        "vertexProps", vertexProps_ ? folly::toJson(util::toJson(*vertexProps_)) : "", desc.get());
+    addDescription(
+        "edgeProps", edgeProps_ ? folly::toJson(util::toJson(*edgeProps_)) : "", desc.get());
+    addDescription(
+        "statProps", statProps_ ? folly::toJson(util::toJson(*statProps_)) : "", desc.get());
+    addDescription("exprs", exprs_ ? folly::toJson(util::toJson(*exprs_)) : "", desc.get());
+    addDescription("random", folly::to<std::string>(random_), desc.get());
+    return desc;
 }
 
-std::string IndexScan::explain() const {
-    // TODO:
-    return "IndexScan";
+std::unique_ptr<cpp2::PlanNodeDescription> GetVertices::explain() const {
+    auto desc = Explore::explain();
+    addDescription("vertices", folly::toJson(util::toJson(vertices_)), desc.get());
+    addDescription("src", src_ ? src_->toString() : "", desc.get());
+    addDescription("props", folly::toJson(util::toJson(props_)), desc.get());
+    addDescription("exprs", folly::toJson(util::toJson(exprs_)), desc.get());
+    return desc;
 }
 
-std::string Filter::explain() const {
-    // TODO:
-    return "Filter";
+std::unique_ptr<cpp2::PlanNodeDescription> GetEdges::explain() const {
+    auto desc = Explore::explain();
+    addDescription("edges", folly::toJson(util::toJson(edges_)), desc.get());
+    addDescription("src", src_ ? src_->toString() : "", desc.get());
+    addDescription("type", util::toJson(type_), desc.get());
+    addDescription("ranking", ranking_ ? ranking_->toString() : "", desc.get());
+    addDescription("dst", dst_ ? dst_->toString() : "", desc.get());
+    addDescription("props", folly::toJson(util::toJson(props_)), desc.get());
+    addDescription("exprs", folly::toJson(util::toJson(exprs_)), desc.get());
+    return desc;
 }
 
-std::string Union::explain() const {
-    // TODO:
-    return "Union";
+std::unique_ptr<cpp2::PlanNodeDescription> IndexScan::explain() const {
+    auto desc = Explore::explain();
+    // TODO
+    return desc;
 }
 
-std::string Intersect::explain() const {
-    // TODO:
-    return "Intersect";
+std::unique_ptr<cpp2::PlanNodeDescription> Filter::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("condition", condition_ ? condition_->toString() : "", desc.get());
+    return desc;
 }
 
-std::string Minus::explain() const {
-    // TODO:
-    return "Minus";
+std::unique_ptr<cpp2::PlanNodeDescription> Project::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("columns", cols_ ? cols_->toString() : "", desc.get());
+    return desc;
 }
 
-std::string Project::explain() const {
-    // TODO:
-    return "Project";
+std::unique_ptr<cpp2::PlanNodeDescription> Sort::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("factors", folly::toJson(util::toJson(factors_)), desc.get());
+    return desc;
 }
 
-std::string Sort::explain() const {
-    // TODO:
-    return "Sort";
+std::unique_ptr<cpp2::PlanNodeDescription> Limit::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("offset", folly::to<std::string>(offset_), desc.get());
+    addDescription("count", folly::to<std::string>(count_), desc.get());
+    return desc;
 }
 
-std::string Limit::explain() const {
-    std::string buf;
-    buf.reserve(256);
-    buf += "Limit: ";
-    buf += folly::stringPrintf("offset %ld, count %ld", offset_, count_);
-    return buf;
+std::unique_ptr<cpp2::PlanNodeDescription> Aggregate::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("groupKeys", folly::toJson(util::toJson(groupKeys_)), desc.get());
+    folly::dynamic itemArr = folly::dynamic::array();
+    for (const auto &item : groupItems_) {
+        folly::dynamic itemObj = folly::dynamic::object();
+        itemObj.insert("distinct", item.distinct);
+        itemObj.insert("funcType", static_cast<uint8_t>(item.func));
+        itemObj.insert("expr", item.expr ? item.expr->toString() : "");
+        itemArr.push_back(itemObj);
+    }
+    addDescription("groupItems", folly::toJson(itemArr), desc.get());
+    return desc;
 }
-
-std::string Aggregate::explain() const {
-    // TODO:
-    return "Aggregate";
-}
-std::string SwitchSpace::explain() const {
-    return "SwitchSpace";
+std::unique_ptr<cpp2::PlanNodeDescription> SwitchSpace::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("space", spaceName_, desc.get());
+    return desc;
 }
 
-std::string Dedup::explain() const {
-    return "Dedup";
+std::unique_ptr<cpp2::PlanNodeDescription> DataCollect::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("vars", folly::toJson(util::toJson(vars_)), desc.get());
+    addDescription("kind", collectKind_ == CollectKind::kSubgraph ? "subgraph" : "row", desc.get());
+    return desc;
 }
 
-std::string DataCollect::explain() const {
-    return "DataCollect";
+std::unique_ptr<cpp2::PlanNodeDescription> DataJoin::explain() const {
+    auto desc = SingleInputNode::explain();
+    addDescription("leftVar", folly::toJson(util::toJson(leftVar_)), desc.get());
+    addDescription("rightVar", folly::toJson(util::toJson(rightVar_)), desc.get());
+    addDescription("hashKeys", folly::toJson(util::toJson(hashKeys_)), desc.get());
+    addDescription("probeKeys", folly::toJson(util::toJson(probeKeys_)), desc.get());
+    return desc;
 }
-}  // namespace graph
-}  // namespace nebula
+
+}   // namespace graph
+}   // namespace nebula
diff --git a/src/planner/Query.h b/src/planner/Query.h
index 2b25b805c2cc0d932a1c8802cacdadb43afae227..0d2c026c20daf3df7a10b955373ced2cb54ae318 100644
--- a/src/planner/Query.h
+++ b/src/planner/Query.h
@@ -20,6 +20,7 @@
  * All query-related nodes would be put in this file,
  * and they are derived from PlanNode.
  */
+
 namespace nebula {
 namespace graph {
 
@@ -68,6 +69,8 @@ public:
         orderBy_ = std::move(orderBy);
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 protected:
     Explore(ExecutionPlan* plan,
             Kind kind,
@@ -88,7 +91,7 @@ protected:
         : SingleInputNode(plan, kind, input), space_(space) {}
 
 protected:
-    GraphSpaceID        space_;
+    GraphSpaceID space_;
     bool dedup_{false};
     int64_t limit_{std::numeric_limits<int64_t>::max()};
     std::string filter_;
@@ -142,7 +145,7 @@ public:
                 std::move(filter));
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     Expression* src() const {
         return src_;
@@ -248,7 +251,7 @@ private:
 private:
     Expression*                                  src_{nullptr};
     std::vector<EdgeType>                        edgeTypes_;
-    storage::cpp2::EdgeDirection                 edgeDirection_;
+    storage::cpp2::EdgeDirection edgeDirection_{storage::cpp2::EdgeDirection::OUT_EDGE};
     VertexProps                                  vertexProps_;
     EdgeProps                                    edgeProps_;
     StatProps                                    statProps_;
@@ -286,7 +289,7 @@ public:
                 std::move(filter));
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::vector<Row>& vertices() const {
         return vertices_;
@@ -376,7 +379,7 @@ public:
                 std::move(filter));
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const std::vector<Row>& edges() const {
         return edges_;
@@ -459,7 +462,7 @@ public:
     IndexScan(ExecutionPlan* plan, PlanNode* input, GraphSpaceID space)
         : Explore(plan, Kind::kIndexScan, input, space) {}
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 };
 
 /**
@@ -477,7 +480,7 @@ public:
         return condition_;
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
 private:
     Filter(ExecutionPlan* plan, PlanNode* input, Expression* condition)
@@ -512,8 +515,6 @@ public:
         return new Union(plan, left, right);
     }
 
-    std::string explain() const override;
-
 private:
     Union(ExecutionPlan* plan, PlanNode* left, PlanNode* right)
         : SetOp(plan, Kind::kUnion, left, right) {}
@@ -528,8 +529,6 @@ public:
         return new Intersect(plan, left, right);
     }
 
-    std::string explain() const override;
-
 private:
     Intersect(ExecutionPlan* plan, PlanNode* left, PlanNode* right)
         : SetOp(plan, Kind::kIntersect, left, right) {}
@@ -544,7 +543,6 @@ public:
         return new Minus(plan, left, right);
     }
 
-    std::string explain() const override;
 
 private:
     Minus(ExecutionPlan* plan, PlanNode* left, PlanNode* right)
@@ -562,7 +560,7 @@ public:
         return new Project(plan, input, cols);
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
     const YieldColumns* columns() const {
         return cols_;
@@ -591,7 +589,7 @@ public:
         return factors_;
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
 private:
     Sort(ExecutionPlan* plan,
@@ -625,7 +623,7 @@ public:
         return count_;
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
 private:
     Limit(ExecutionPlan* plan, PlanNode* input, int64_t offset, int64_t count)
@@ -667,7 +665,7 @@ public:
         return groupItems_;
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
 private:
     Aggregate(ExecutionPlan* plan,
@@ -696,7 +694,7 @@ public:
         return spaceName_;
     }
 
-    std::string explain() const override;
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
 
 private:
     SwitchSpace(ExecutionPlan* plan,
@@ -717,8 +715,6 @@ public:
         return new Dedup(plan, input);
     }
 
-    std::string explain() const override;
-
 private:
     Dedup(ExecutionPlan* plan,
           PlanNode* input)
@@ -748,6 +744,8 @@ public:
         return vars_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 private:
     DataCollect(ExecutionPlan* plan,
                 PlanNode* input,
@@ -758,8 +756,6 @@ private:
         vars_ = std::move(vars);
     }
 
-    std::string explain() const override;
-
 private:
     CollectKind                 collectKind_;
     std::vector<std::string>    vars_;
@@ -781,10 +777,6 @@ public:
                             std::move(probeKeys));
     }
 
-    std::string explain() const override {
-        return "DataJoin";
-    }
-
     const std::pair<std::string, int64_t>& leftVar() const {
         return leftVar_;
     }
@@ -801,6 +793,8 @@ public:
         return probeKeys_;
     }
 
+    std::unique_ptr<cpp2::PlanNodeDescription> explain() const override;
+
 private:
     DataJoin(ExecutionPlan* plan, PlanNode* input,
              std::pair<std::string, int64_t> leftVar,
@@ -821,10 +815,13 @@ private:
 };
 
 class ProduceSemiShortestPath : public PlanNode {
+public:
 };
 
 class ConjunctPath : public PlanNode {
+public:
 };
+
 }  // namespace graph
 }  // namespace nebula
 #endif  // PLANNER_QUERY_H_
diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt
index e17d8e9e16d6c563eed5464d25df24f3ccfee68f..f333c400b835911d23f056624d3cd5b5e2c00e8b 100644
--- a/src/util/CMakeLists.txt
+++ b/src/util/CMakeLists.txt
@@ -7,6 +7,7 @@
 nebula_add_library(
     util_obj OBJECT
     SchemaUtil.cpp
+    ToJson.cpp
 )
 
 nebula_add_library(
diff --git a/src/util/ToJson.cpp b/src/util/ToJson.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cc0cfc7782965500c5e2c6e04315c611452f6fac
--- /dev/null
+++ b/src/util/ToJson.cpp
@@ -0,0 +1,223 @@
+/* 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 "util/ToJson.h"
+
+#include "common/clients/meta/MetaClient.h"
+#include "common/datatypes/Value.h"
+#include "common/expression/Expression.h"
+#include "parser/EdgeKey.h"
+
+#include "common/interface/gen-cpp2/meta_types.h"
+#include "common/interface/gen-cpp2/storage_types.h"
+
+namespace nebula {
+namespace util {
+
+std::string toJson(const std::string &str) {
+    return str;
+}
+
+std::string toJson(int32_t i) {
+    return folly::to<std::string>(i);
+}
+
+std::string toJson(int64_t i) {
+    return folly::to<std::string>(i);
+}
+
+std::string toJson(const List &list) {
+    return list.toString();
+}
+
+std::string toJson(const Value &value) {
+    return value.toString();
+}
+
+std::string toJson(const EdgeKeyRef *ref) {
+    return ref->toString();
+}
+
+std::string toJson(const Expression *expr) {
+    return expr->toString();
+}
+
+folly::dynamic toJson(const meta::SpaceDesc &desc) {
+    folly::dynamic obj = folly::dynamic::object();
+    obj.insert("name", desc.spaceName_);
+    obj.insert("partNum", desc.partNum_);
+    obj.insert("replicaFactor", desc.replicaFactor_);
+    obj.insert("charset", desc.charsetName_);
+    obj.insert("collate", desc.collationName_);
+    obj.insert("vidSize", desc.vidSize_);
+    return obj;
+}
+
+folly::dynamic toJson(const meta::cpp2::ColumnDef &column) {
+    folly::dynamic obj = folly::dynamic::object();
+    obj.insert("name", column.get_name());
+    obj.insert("type", meta::cpp2::_PropertyType_VALUES_TO_NAMES.at(column.get_type()));
+    if (column.__isset.type_length) {
+        obj.insert("typeLength", folly::to<std::string>(*column.get_type_length()));
+    }
+    if (column.__isset.nullable) {
+        obj.insert("nullable", folly::to<std::string>(*column.get_nullable()));
+    }
+    if (column.__isset.default_value) {
+        obj.insert("defaultValue", column.get_default_value()->toString());
+    }
+    return obj;
+}
+
+folly::dynamic toJson(const meta::cpp2::Schema &schema) {
+    folly::dynamic json = folly::dynamic::object();
+    if (schema.__isset.columns) {
+        json.insert("columns", toJson(schema.get_columns()));
+    }
+    if (schema.__isset.schema_prop) {
+        json.insert("prop", toJson(schema.get_schema_prop()));
+    }
+    return json;
+}
+
+folly::dynamic toJson(const meta::cpp2::SchemaProp &prop) {
+    folly::dynamic object = folly::dynamic::object();
+    if (prop.__isset.ttl_col) {
+        object.insert("ttlCol", *prop.get_ttl_col());
+    }
+    if (prop.__isset.ttl_duration) {
+        object.insert("ttlDuration", *prop.get_ttl_duration());
+    }
+    return object;
+}
+
+folly::dynamic toJson(const meta::cpp2::AlterSchemaItem &item) {
+    folly::dynamic json = folly::dynamic::object();
+    if (item.__isset.schema) {
+        json.insert("schema", toJson(item.get_schema()));
+    }
+    if (item.__isset.op) {
+        json.insert("op", meta::cpp2::_AlterSchemaOp_VALUES_TO_NAMES.at(item.get_op()));
+    }
+    return json;
+}
+
+folly::dynamic toJson(const storage::cpp2::EdgeKey &edgeKey) {
+    folly::dynamic edgeKeyObj = folly::dynamic::object();
+    if (edgeKey.__isset.src) {
+        edgeKeyObj.insert("src", edgeKey.get_src());
+    }
+    if (edgeKey.__isset.dst) {
+        edgeKeyObj.insert("dst", edgeKey.get_dst());
+    }
+    if (edgeKey.__isset.edge_type) {
+        edgeKeyObj.insert("edgeType", edgeKey.get_edge_type());
+    }
+    if (edgeKey.__isset.ranking) {
+        edgeKeyObj.insert("ranking", edgeKey.get_ranking());
+    }
+    return edgeKeyObj;
+}
+
+folly::dynamic toJson(const storage::cpp2::NewTag &tag) {
+    folly::dynamic tagObj = folly::dynamic::object();
+    if (tag.__isset.tag_id) {
+        tagObj.insert("tagId", tag.get_tag_id());
+    }
+    if (tag.__isset.props) {
+        tagObj.insert("props", toJson(tag.get_props()));
+    }
+    return tagObj;
+}
+
+folly::dynamic toJson(const storage::cpp2::NewVertex &vert) {
+    folly::dynamic vertObj = folly::dynamic::object();
+    if (vert.__isset.id) {
+        vertObj.insert("id", vert.get_id());
+    }
+    if (vert.__isset.tags) {
+        vertObj.insert("tags", util::toJson(vert.get_tags()));
+    }
+    return vertObj;
+}
+
+folly::dynamic toJson(const storage::cpp2::NewEdge &edge) {
+    folly::dynamic edgeObj = folly::dynamic::object();
+    if (edge.__isset.key) {
+        edgeObj.insert("key", toJson(edge.get_key()));
+    }
+    if (edge.__isset.props) {
+        edgeObj.insert("props", toJson(edge.get_props()));
+    }
+    return edgeObj;
+}
+
+folly::dynamic toJson(const storage::cpp2::UpdatedProp &prop) {
+    return folly::dynamic::object("name", prop.get_name())("value", prop.get_value());
+}
+
+folly::dynamic toJson(const storage::cpp2::OrderBy &orderBy) {
+    folly::dynamic obj = folly::dynamic::object();
+    if (orderBy.__isset.direction) {
+        auto dir = orderBy.get_direction();
+        obj.insert("direction", storage::cpp2::_OrderDirection_VALUES_TO_NAMES.at(dir));
+    }
+    if (orderBy.__isset.prop) {
+        obj.insert("prop", orderBy.get_prop());
+    }
+    return obj;
+}
+
+folly::dynamic toJson(const storage::cpp2::VertexProp &prop) {
+    folly::dynamic obj = folly::dynamic::object();
+    if (prop.__isset.tag) {
+        auto tag = prop.get_tag();
+        obj.insert("tagId", tag);
+    }
+    if (prop.__isset.props) {
+        obj.insert("props", toJson(prop.get_props()));
+    }
+    return obj;
+}
+
+folly::dynamic toJson(const storage::cpp2::EdgeProp &prop) {
+    folly::dynamic obj = folly::dynamic::object();
+    if (prop.__isset.type) {
+        obj.insert("type", toJson(prop.get_type()));
+    }
+    if (prop.__isset.props) {
+        obj.insert("props", toJson(prop.get_props()));
+    }
+    return obj;
+}
+
+folly::dynamic toJson(const storage::cpp2::StatProp &prop) {
+    folly::dynamic obj = folly::dynamic::object();
+    if (prop.__isset.alias) {
+        obj.insert("alias", prop.get_alias());
+    }
+    if (prop.__isset.prop) {
+        obj.insert("prop", prop.get_prop());
+    }
+    if (prop.__isset.stat) {
+        obj.insert("stat", storage::cpp2::_StatType_VALUES_TO_NAMES.at(prop.get_stat()));
+    }
+    return obj;
+}
+
+folly::dynamic toJson(const storage::cpp2::Expr &expr) {
+    folly::dynamic obj = folly::dynamic::object();
+    if (expr.__isset.alias) {
+        obj.insert("alias", expr.get_alias());
+    }
+    if (expr.__isset.expr) {
+        obj.insert("expr", expr.get_expr());
+    }
+    return obj;
+}
+
+}   // namespace util
+}   // namespace nebula
diff --git a/src/util/ToJson.h b/src/util/ToJson.h
new file mode 100644
index 0000000000000000000000000000000000000000..7c5ab017b697641164e7ee6f673b957863ac5918
--- /dev/null
+++ b/src/util/ToJson.h
@@ -0,0 +1,93 @@
+/* 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.
+ */
+#ifndef UTIL_TOJSON_H_
+#define UTIL_TOJSON_H_
+
+#include <iterator>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include <folly/dynamic.h>
+
+namespace nebula {
+
+class EdgeKeyRef;
+class Expression;
+struct List;
+struct Value;
+
+namespace meta {
+struct SpaceDesc;
+namespace cpp2 {
+class AlterSchemaItem;
+class ColumnDef;
+class Schema;
+class SchemaProp;
+}   // namespace cpp2
+}   // namespace meta
+
+namespace storage {
+namespace cpp2 {
+class EdgeKey;
+class NewTag;
+class NewEdge;
+class NewVertex;
+class UpdatedProp;
+class OrderBy;
+class VertexProp;
+class EdgeProp;
+class StatProp;
+class Expr;
+}   // namespace cpp2
+}   // namespace storage
+
+namespace util {
+
+template <typename T>
+folly::dynamic toJson(const std::vector<T> &arr);
+
+std::string toJson(const std::string &str);
+std::string toJson(int32_t i);
+std::string toJson(int64_t i);
+
+std::string toJson(const List &list);
+std::string toJson(const Value &value);
+std::string toJson(const EdgeKeyRef *ref);
+std::string toJson(const Expression *expr);
+folly::dynamic toJson(const meta::SpaceDesc &desc);
+folly::dynamic toJson(const meta::cpp2::ColumnDef &column);
+folly::dynamic toJson(const meta::cpp2::Schema &schema);
+folly::dynamic toJson(const meta::cpp2::SchemaProp &prop);
+folly::dynamic toJson(const meta::cpp2::AlterSchemaItem &item);
+folly::dynamic toJson(const storage::cpp2::EdgeKey &edgeKey);
+folly::dynamic toJson(const storage::cpp2::NewTag &tag);
+folly::dynamic toJson(const storage::cpp2::NewVertex &vert);
+folly::dynamic toJson(const storage::cpp2::NewEdge &edge);
+folly::dynamic toJson(const storage::cpp2::UpdatedProp &prop);
+folly::dynamic toJson(const storage::cpp2::OrderBy &orderBy);
+folly::dynamic toJson(const storage::cpp2::VertexProp &prop);
+folly::dynamic toJson(const storage::cpp2::EdgeProp &prop);
+folly::dynamic toJson(const storage::cpp2::StatProp &prop);
+folly::dynamic toJson(const storage::cpp2::Expr &expr);
+
+template <typename K, typename V>
+folly::dynamic toJson(const std::pair<K, V> &p) {
+    return folly::dynamic::object(toJson(p.first), toJson(p.second));
+}
+
+template <typename T>
+folly::dynamic toJson(const std::vector<T> &arr) {
+    auto farr = folly::dynamic::array();
+    std::transform(
+        arr.cbegin(), arr.cend(), std::back_inserter(farr), [](const T &t) { return toJson(t); });
+    return farr;
+}
+
+}   // namespace util
+}   // namespace nebula
+
+#endif   // UTIL_TOJSON_H_