diff --git a/lib/mgmt_fe_client.c b/lib/mgmt_fe_client.c
index a107582bea07..3345505213ff 100644
--- a/lib/mgmt_fe_client.c
+++ b/lib/mgmt_fe_client.c
@@ -329,6 +329,36 @@ int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client,
 	return ret;
 }
 
+int mgmt_fe_send_edit_req(struct mgmt_fe_client *client, uint64_t session_id,
+			  uint64_t req_id, uint8_t datastore,
+			  LYD_FORMAT request_type, uint8_t flags,
+			  uint8_t operation, const char *xpath, const char *data)
+{
+	struct mgmt_msg_edit *msg;
+	int ret;
+
+	msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit, 0,
+					MTYPE_MSG_NATIVE_EDIT);
+	msg->refer_id = session_id;
+	msg->req_id = req_id;
+	msg->code = MGMT_MSG_CODE_EDIT;
+	msg->request_type = request_type;
+	msg->flags = flags;
+	msg->datastore = datastore;
+	msg->operation = operation;
+
+	mgmt_msg_native_xpath_encode(msg, xpath);
+	if (data)
+		mgmt_msg_native_append(msg, data, strlen(data) + 1);
+
+	debug_fe_client("Sending EDIT_REQ session-id %" PRIu64
+			" req-id %" PRIu64 " xpath: %s",
+			session_id, req_id, xpath);
+
+	ret = mgmt_msg_native_send_msg(&client->client.conn, msg, false);
+	mgmt_msg_native_free_msg(msg);
+	return ret;
+}
 
 static int mgmt_fe_client_handle_msg(struct mgmt_fe_client *client,
 				     Mgmtd__FeMessage *fe_msg)
@@ -503,7 +533,9 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client,
 	struct mgmt_fe_client_session *session = NULL;
 	struct mgmt_msg_notify_data *notify_msg;
 	struct mgmt_msg_tree_data *tree_msg;
+	struct mgmt_msg_edit_reply *edit_msg;
 	struct mgmt_msg_error *err_msg;
+	const char *xpath = NULL;
 	const char *data = NULL;
 	size_t dlen;
 
@@ -554,6 +586,28 @@ static void fe_client_handle_native_msg(struct mgmt_fe_client *client,
 						     msg_len - sizeof(*tree_msg),
 						     tree_msg->partial_error);
 		break;
+	case MGMT_MSG_CODE_EDIT_REPLY:
+		if (!session->client->cbs.edit_notify)
+			return;
+
+		edit_msg = (typeof(edit_msg))msg;
+		if (msg_len < sizeof(*edit_msg)) {
+			log_err_fe_client("Corrupt edit-reply msg recv");
+			return;
+		}
+
+		xpath = mgmt_msg_native_xpath_decode(edit_msg, msg_len);
+		if (!xpath) {
+			log_err_fe_client("Corrupt edit-reply msg recv");
+			return;
+		}
+
+		session->client->cbs.edit_notify(client, client->user_data,
+						 session->client_id,
+						 msg->refer_id,
+						 session->user_ctx, msg->req_id,
+						 xpath);
+		break;
 	case MGMT_MSG_CODE_NOTIFY:
 		if (!session->client->cbs.async_notification)
 			return;
diff --git a/lib/mgmt_fe_client.h b/lib/mgmt_fe_client.h
index eee4594e17dc..9d569348ae58 100644
--- a/lib/mgmt_fe_client.h
+++ b/lib/mgmt_fe_client.h
@@ -114,6 +114,12 @@ struct mgmt_fe_client_cbs {
 			       LYD_FORMAT result_type, void *result, size_t len,
 			       int partial_error);
 
+	/* Called when edit result is returned */
+	int (*edit_notify)(struct mgmt_fe_client *client, uintptr_t user_data,
+			   uint64_t client_id, uint64_t session_id,
+			   uintptr_t session_ctx, uint64_t req_id,
+			   const char *xpath);
+
 	/* Called with asynchronous notifications from backends */
 	int (*async_notification)(struct mgmt_fe_client *client,
 				  uintptr_t user_data, uint64_t client_id,
@@ -409,6 +415,45 @@ extern int mgmt_fe_send_get_data_req(struct mgmt_fe_client *client,
 				     uint8_t flags, uint8_t defaults,
 				     const char *xpath);
 
+/*
+ * Send EDIT to MGMTD daemon.
+ *
+ * client
+ *    Client object.
+ *
+ * session_id
+ *    Client session ID.
+ *
+ * req_id
+ *    Client request ID.
+ *
+ * datastore
+ *    Datastore for editing.
+ *
+ * request_type
+ *    The LYD_FORMAT of the request.
+ *
+ * flags
+ *    Flags to control the behavior of the request.
+ *
+ * operation
+ *    NB_OP_* operation to perform.
+ *
+ * xpath
+ *    the xpath to edit.
+ *
+ * data
+ *    the data tree.
+ *
+ * Returns:
+ *    0 on success, otherwise msg_conn_send_msg() return values.
+ */
+extern int mgmt_fe_send_edit_req(struct mgmt_fe_client *client,
+				 uint64_t session_id, uint64_t req_id,
+				 uint8_t datastore, LYD_FORMAT request_type,
+				 uint8_t flags, uint8_t operation,
+				 const char *xpath, const char *data);
+
 /*
  * Destroy library and cleanup everything.
  */
diff --git a/lib/mgmt_msg_native.c b/lib/mgmt_msg_native.c
index 98b7da45ce8d..09ea43ece0bf 100644
--- a/lib/mgmt_msg_native.c
+++ b/lib/mgmt_msg_native.c
@@ -14,6 +14,8 @@ DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_TREE, "native get tree msg");
 DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_TREE_DATA, "native tree data msg");
 DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_GET_DATA, "native get data msg");
 DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_NOTIFY, "native get data msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT, "native edit msg");
+DEFINE_MTYPE(MSG_NATIVE, MSG_NATIVE_EDIT_REPLY, "native edit reply msg");
 
 int vmgmt_msg_native_send_error(struct msg_conn *conn, uint64_t sess_or_txn_id,
 				uint64_t req_id, bool short_circuit_ok,
diff --git a/lib/mgmt_msg_native.h b/lib/mgmt_msg_native.h
index 53bb81be2832..b7c29862aadd 100644
--- a/lib/mgmt_msg_native.h
+++ b/lib/mgmt_msg_native.h
@@ -21,6 +21,7 @@ extern "C" {
 #include "memory.h"
 #include "mgmt_msg.h"
 #include "mgmt_defines.h"
+#include "northbound.h"
 
 #include <stdalign.h>
 
@@ -149,6 +150,8 @@ DECLARE_MTYPE(MSG_NATIVE_GET_TREE);
 DECLARE_MTYPE(MSG_NATIVE_TREE_DATA);
 DECLARE_MTYPE(MSG_NATIVE_GET_DATA);
 DECLARE_MTYPE(MSG_NATIVE_NOTIFY);
+DECLARE_MTYPE(MSG_NATIVE_EDIT);
+DECLARE_MTYPE(MSG_NATIVE_EDIT_REPLY);
 
 /*
  * Native message codes
@@ -158,6 +161,8 @@ DECLARE_MTYPE(MSG_NATIVE_NOTIFY);
 #define MGMT_MSG_CODE_TREE_DATA 2
 #define MGMT_MSG_CODE_GET_DATA	3
 #define MGMT_MSG_CODE_NOTIFY	4
+#define MGMT_MSG_CODE_EDIT	 5
+#define MGMT_MSG_CODE_EDIT_REPLY 6
 
 /*
  * Datastores
@@ -318,6 +323,60 @@ _Static_assert(sizeof(struct mgmt_msg_notify_data) ==
 		       offsetof(struct mgmt_msg_notify_data, data),
 	       "Size mismatch");
 
+#define EDIT_FLAG_IMPLICIT_LOCK	  0x01
+#define EDIT_FLAG_IMPLICIT_COMMIT 0x02
+
+#define EDIT_OP_CREATE	0
+#define EDIT_OP_DELETE	4
+#define EDIT_OP_MERGE	2
+#define EDIT_OP_REPLACE 5
+#define EDIT_OP_REMOVE	3
+
+_Static_assert(EDIT_OP_CREATE == NB_OP_CREATE_EXCL, "Operation mismatch");
+_Static_assert(EDIT_OP_DELETE == NB_OP_DELETE, "Operation mismatch");
+_Static_assert(EDIT_OP_MERGE == NB_OP_MODIFY, "Operation mismatch");
+_Static_assert(EDIT_OP_REPLACE == NB_OP_REPLACE, "Operation mismatch");
+_Static_assert(EDIT_OP_REMOVE == NB_OP_DESTROY, "Operation mismatch");
+
+/**
+ * struct mgmt_msg_edit - frontend edit request.
+ *
+ * @request_type: ``LYD_FORMAT`` for the @data.
+ * @flags: combination of ``EDIT_FLAG_*`` flags.
+ * @datastore: the datastore to edit.
+ * @operation: one of ``EDIT_OP_*`` operations.
+ * @data: the xpath followed by the tree data for the operation.
+ *        for CREATE, xpath points to the parent node.
+ */
+struct mgmt_msg_edit {
+	struct mgmt_msg_header;
+	uint8_t request_type;
+	uint8_t flags;
+	uint8_t datastore;
+	uint8_t operation;
+	uint8_t resv2[4];
+
+	alignas(8) char data[];
+};
+_Static_assert(sizeof(struct mgmt_msg_edit) ==
+		       offsetof(struct mgmt_msg_edit, data),
+	       "Size mismatch");
+
+/**
+ * struct mgmt_msg_edit_reply - frontend edit reply.
+ *
+ * @data: the xpath of the data node that was created.
+ */
+struct mgmt_msg_edit_reply {
+	struct mgmt_msg_header;
+	uint8_t resv2[8];
+
+	alignas(8) char data[];
+};
+_Static_assert(sizeof(struct mgmt_msg_edit_reply) ==
+		       offsetof(struct mgmt_msg_edit_reply, data),
+	       "Size mismatch");
+
 /*
  * Validate that the message ends in a NUL terminating byte
  */
@@ -504,13 +563,13 @@ extern int vmgmt_msg_native_send_error(struct msg_conn *conn,
  *	The xpath string or NULL if there was an error decoding (i.e., the
  *	message is corrupt).
  */
-#define mgmt_msg_native_xpath_data_decode(msg, msglen, data)                   \
+#define mgmt_msg_native_xpath_data_decode(msg, msglen, __data)                 \
 	({                                                                     \
 		size_t __len = (msglen) - sizeof(*msg);                        \
 		const char *__s = NULL;                                        \
 		if (msg->vsplit && msg->vsplit <= __len &&                     \
 		    msg->data[msg->vsplit - 1] == 0) {                         \
-			(data) = msg->data + msg->vsplit;                      \
+			(__data) = msg->data + msg->vsplit;                    \
 			__s = msg->data;                                       \
 		}                                                              \
 		__s;                                                           \
diff --git a/lib/northbound.c b/lib/northbound.c
index 487f225913cf..37b655211879 100644
--- a/lib/northbound.c
+++ b/lib/northbound.c
@@ -813,6 +813,223 @@ int nb_candidate_edit(struct nb_config *candidate, const struct nb_node *nb_node
 	return NB_OK;
 }
 
+static int nb_candidate_edit_tree_add(struct nb_config *candidate,
+				      enum nb_operation operation,
+				      LYD_FORMAT format, const char *xpath,
+				      const char *data, char *xpath_created,
+				      char *errmsg, size_t errmsg_len)
+{
+	struct lyd_node *tree = NULL;
+	struct lyd_node *parent = NULL;
+	struct lyd_node *dnode = NULL;
+	struct lyd_node *existing = NULL;
+	struct lyd_node *ex_parent = NULL;
+	char *parent_xpath = NULL;
+	struct ly_in *in;
+	LY_ERR err;
+	bool root;
+	int ret;
+
+	ly_in_new_memory(data, &in);
+
+	root = xpath[0] == 0 || (xpath[0] == '/' && xpath[1] == 0);
+
+	/* get parent xpath if xpath is not root */
+	if (!root) {
+		/* NB_OP_CREATE_EXCT already expects parent xpath */
+		parent_xpath = XSTRDUP(MTYPE_TMP, xpath);
+
+		/* for other operations - pop one level */
+		if (operation != NB_OP_CREATE_EXCL) {
+			ret = yang_xpath_pop_node(parent_xpath);
+			if (ret) {
+				snprintf(errmsg, errmsg_len, "Invalid xpath");
+				goto done;
+			}
+
+			/* root is not actually a parent */
+			if (parent_xpath[0] == 0)
+				XFREE(MTYPE_TMP, parent_xpath);
+		}
+	}
+
+	/*
+	 * Create parent if it's not root. We're creating a new tree here to be
+	 * merged later with candidate.
+	 */
+	if (parent_xpath) {
+		err = lyd_new_path2(NULL, ly_native_ctx, parent_xpath, NULL, 0,
+				    0, 0, &tree, &parent);
+		if (err) {
+			yang_print_errors(ly_native_ctx, errmsg, errmsg_len);
+			ret = NB_ERR;
+			goto done;
+		}
+		assert(parent);
+	}
+
+	/* parse data */
+	err = yang_lyd_parse_data(ly_native_ctx, parent, in, format,
+				  LYD_PARSE_ONLY | LYD_PARSE_STRICT |
+					  LYD_PARSE_NO_STATE,
+				  0, &dnode);
+	if (err) {
+		yang_print_errors(ly_native_ctx, errmsg, errmsg_len);
+		ret = NB_ERR;
+		goto done;
+	}
+
+	/* set the tree if we created a top-level node */
+	if (!parent)
+		tree = dnode;
+
+	/* save xpath of the created node */
+	lyd_path(dnode, LYD_PATH_STD, xpath_created, XPATH_MAXLEN);
+
+	/* verify that list keys are the same in the xpath and the data tree */
+	if (!root && (operation == NB_OP_REPLACE || operation == NB_OP_MODIFY)) {
+		if (lyd_find_path(tree, xpath, 0, NULL)) {
+			snprintf(errmsg, errmsg_len,
+				 "List keys in xpath and data tree are different");
+			ret = NB_ERR;
+			goto done;
+		}
+	}
+
+	/* check if the node already exists in candidate */
+	if (operation == NB_OP_CREATE_EXCL || operation == NB_OP_REPLACE) {
+		existing = yang_dnode_get(candidate->dnode, xpath_created);
+
+		/* if the existing node is implicit default, ignore */
+		if (existing && (existing->flags & LYD_DEFAULT))
+			existing = NULL;
+
+		if (existing) {
+			if (operation == NB_OP_CREATE_EXCL) {
+				snprintf(errmsg, errmsg_len,
+					 "Data already exists");
+				ret = NB_ERR;
+				goto done;
+			}
+
+			if (root) {
+				candidate->dnode = NULL;
+			} else {
+				/* if it's the first top-level node, update candidate */
+				if (candidate->dnode == existing)
+					candidate->dnode =
+						candidate->dnode->next;
+
+				ex_parent = lyd_parent(existing);
+				lyd_unlink_tree(existing);
+			}
+		}
+	}
+
+	err = lyd_merge_siblings(&candidate->dnode, tree,
+				 LYD_MERGE_DESTRUCT | LYD_MERGE_WITH_FLAGS);
+	if (err) {
+		/* if replace failed, restore the original node */
+		if (existing) {
+			if (root) {
+				candidate->dnode = existing;
+			} else {
+				if (ex_parent)
+					lyd_insert_child(ex_parent, existing);
+				else
+					lyd_insert_sibling(candidate->dnode,
+							   existing,
+							   &candidate->dnode);
+			}
+		}
+		yang_print_errors(ly_native_ctx, errmsg, errmsg_len);
+		ret = NB_ERR;
+		goto done;
+	} else {
+		/*
+		 * Free existing node after replace.
+		 * We're using `lyd_free_siblings` here to free the whole
+		 * tree if we replaced the root node. It won't affect other
+		 * siblings if it wasn't root, because the existing node
+		 * was unlinked from the tree.
+		 */
+		if (existing)
+			lyd_free_siblings(existing);
+
+		tree = NULL; /* LYD_MERGE_DESTRUCT deleted the tree */
+	}
+
+	ret = NB_OK;
+done:
+	if (tree)
+		lyd_free_all(tree);
+	XFREE(MTYPE_TMP, parent_xpath);
+	ly_in_free(in, 0);
+
+	return ret;
+}
+
+static int nb_candidate_edit_tree_del(struct nb_config *candidate,
+				      enum nb_operation operation,
+				      const char *xpath, char *errmsg,
+				      size_t errmsg_len)
+{
+	struct lyd_node *dnode;
+
+	/* deleting root - remove the whole config */
+	if (xpath[0] == 0 || (xpath[0] == '/' && xpath[1] == 0)) {
+		lyd_free_all(candidate->dnode);
+		candidate->dnode = NULL;
+		return NB_OK;
+	}
+
+	dnode = yang_dnode_get(candidate->dnode, xpath);
+	if (!dnode || (dnode->flags & LYD_DEFAULT)) {
+		if (operation == NB_OP_DELETE) {
+			snprintf(errmsg, errmsg_len, "Data missing");
+			return NB_ERR;
+		} else
+			return NB_OK;
+	}
+
+	/* if it's the first top-level node, update candidate */
+	if (candidate->dnode == dnode)
+		candidate->dnode = candidate->dnode->next;
+
+	lyd_free_tree(dnode);
+
+	return NB_OK;
+}
+
+int nb_candidate_edit_tree(struct nb_config *candidate,
+			   enum nb_operation operation, LYD_FORMAT format,
+			   const char *xpath, const char *data,
+			   char *xpath_created, char *errmsg, size_t errmsg_len)
+{
+	int ret = NB_ERR;
+
+	switch (operation) {
+	case NB_OP_CREATE_EXCL:
+	case NB_OP_CREATE:
+	case NB_OP_MODIFY:
+	case NB_OP_REPLACE:
+		ret = nb_candidate_edit_tree_add(candidate, operation, format,
+						 xpath, data, xpath_created,
+						 errmsg, errmsg_len);
+		break;
+	case NB_OP_DESTROY:
+	case NB_OP_DELETE:
+		ret = nb_candidate_edit_tree_del(candidate, operation, xpath,
+						 errmsg, errmsg_len);
+		break;
+	case NB_OP_MOVE:
+		/* not supported yet */
+		break;
+	}
+
+	return ret;
+}
+
 const char *nb_operation_name(enum nb_operation operation)
 {
 	switch (operation) {
diff --git a/lib/northbound.h b/lib/northbound.h
index 5be111cf0a12..15a49999437e 100644
--- a/lib/northbound.h
+++ b/lib/northbound.h
@@ -1004,6 +1004,44 @@ extern int nb_candidate_edit(struct nb_config *candidate,
 			     const struct yang_data *previous,
 			     const struct yang_data *data);
 
+/*
+ * Edit a candidate configuration. Value is given as JSON/XML.
+ *
+ * candidate
+ *    Candidate configuration to edit.
+ *
+ * operation
+ *    Operation to apply.
+ *
+ * format
+ *    LYD_FORMAT of the value.
+ *
+ * xpath
+ *    XPath of the configuration node being edited.
+ *    For create, it must be the parent.
+ *
+ * data
+ *    New data tree for the node.
+ *
+ * xpath_created
+ *    XPath of the created node if operation is "create".
+ *
+ * errmsg
+ *    Buffer to store human-readable error message in case of error.
+ *
+ * errmsg_len
+ *    Size of errmsg.
+ *
+ * Returns:
+ *    - NB_OK on success.
+ *    - NB_ERR for other errors.
+ */
+extern int nb_candidate_edit_tree(struct nb_config *candidate,
+				  enum nb_operation operation,
+				  LYD_FORMAT format, const char *xpath,
+				  const char *data, char *xpath_created,
+				  char *errmsg, size_t errmsg_len);
+
 /*
  * Create diff for configuration.
  *
diff --git a/lib/northbound_oper.c b/lib/northbound_oper.c
index 2394b5e86581..7e7190f5a49e 100644
--- a/lib/northbound_oper.c
+++ b/lib/northbound_oper.c
@@ -341,38 +341,6 @@ static void nb_op_resume_data_tree(struct nb_op_yield_state *ys)
 /* Start of walk init code */
 /* ======================= */
 
-/**
- * __xpath_pop_node() - remove the last node from xpath string
- * @xpath: an xpath string
- *
- * Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop.
- */
-static int __xpath_pop_node(char *xpath)
-{
-	int len = strlen(xpath);
-	bool abs = xpath[0] == '/';
-	char *slash;
-
-	/* "//" or "/" => NULL */
-	if (abs && (len == 1 || (len == 2 && xpath[1] == '/')))
-		return NB_ERR_NOT_FOUND;
-
-	slash = (char *)frrstr_back_to_char(xpath, '/');
-	/* "/foo/bar/" or "/foo/bar//" => "/foo " */
-	if (slash && slash == &xpath[len - 1]) {
-		xpath[--len] = 0;
-		slash = (char *)frrstr_back_to_char(xpath, '/');
-		if (slash && slash == &xpath[len - 1]) {
-			xpath[--len] = 0;
-			slash = (char *)frrstr_back_to_char(xpath, '/');
-		}
-	}
-	if (!slash)
-		return NB_ERR_NOT_FOUND;
-	*slash = 0;
-	return NB_OK;
-}
-
 /**
  * nb_op_xpath_to_trunk() - generate a lyd_node tree (trunk) using an xpath.
  * @xpath_in: xpath query string to build trunk from.
@@ -398,7 +366,7 @@ static enum nb_error nb_op_xpath_to_trunk(const char *xpath_in,
 		if (err == LY_SUCCESS)
 			break;
 
-		ret = __xpath_pop_node(xpath);
+		ret = yang_xpath_pop_node(xpath);
 		if (ret != NB_OK)
 			break;
 	}
diff --git a/lib/vty.c b/lib/vty.c
index 912c89355610..d69b5eebd270 100644
--- a/lib/vty.c
+++ b/lib/vty.c
@@ -3826,6 +3826,23 @@ static int vty_mgmt_get_tree_result_notified(
 	return 0;
 }
 
+static int vty_mgmt_edit_result_notified(struct mgmt_fe_client *client,
+					 uintptr_t user_data,
+					 uint64_t client_id, uint64_t session_id,
+					 uintptr_t session_ctx, uint64_t req_id,
+					 const char *xpath)
+{
+	struct vty *vty = (struct vty *)session_ctx;
+
+	debug_fe_client("EDIT request for client 0x%" PRIx64 " req-id %" PRIu64
+			" was successful, xpath: %s",
+			client_id, req_id, xpath);
+
+	vty_mgmt_resume_response(vty, CMD_SUCCESS);
+
+	return 0;
+}
+
 static int vty_mgmt_error_notified(struct mgmt_fe_client *client,
 				   uintptr_t user_data, uint64_t client_id,
 				   uint64_t session_id, uintptr_t session_ctx,
@@ -3867,6 +3884,7 @@ static struct mgmt_fe_client_cbs mgmt_cbs = {
 	.commit_config_notify = vty_mgmt_commit_config_result_notified,
 	.get_data_notify = vty_mgmt_get_data_result_notified,
 	.get_tree_notify = vty_mgmt_get_tree_result_notified,
+	.edit_notify = vty_mgmt_edit_result_notified,
 	.error_notify = vty_mgmt_error_notified,
 
 };
@@ -4122,6 +4140,28 @@ int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore,
 	return 0;
 }
 
+int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore,
+			   LYD_FORMAT request_type, uint8_t flags,
+			   uint8_t operation, const char *xpath,
+			   const char *data)
+{
+	vty->mgmt_req_id++;
+
+	if (mgmt_fe_send_edit_req(mgmt_fe_client, vty->mgmt_session_id,
+				  vty->mgmt_req_id, datastore, request_type,
+				  flags, operation, xpath, data)) {
+		zlog_err("Failed to send EDIT to MGMTD session-id: %" PRIu64
+			 " req-id %" PRIu64 ".",
+			 vty->mgmt_session_id, vty->mgmt_req_id);
+		vty_out(vty, "Failed to send EDIT to MGMTD!\n");
+		return -1;
+	}
+
+	vty->mgmt_req_pending_cmd = "MESSAGE_EDIT_REQ";
+
+	return 0;
+}
+
 /* Install vty's own commands like `who' command. */
 void vty_init(struct event_loop *master_thread, bool do_command_logging)
 {
diff --git a/lib/vty.h b/lib/vty.h
index a59ac7a652ee..0e0c92c33650 100644
--- a/lib/vty.h
+++ b/lib/vty.h
@@ -419,6 +419,10 @@ extern int vty_mgmt_send_get_req(struct vty *vty, bool is_config,
 extern int vty_mgmt_send_get_data_req(struct vty *vty, uint8_t datastore,
 				      LYD_FORMAT result_type, uint8_t flags,
 				      uint8_t defaults, const char *xpath);
+extern int vty_mgmt_send_edit_req(struct vty *vty, uint8_t datastore,
+				  LYD_FORMAT request_type, uint8_t flags,
+				  uint8_t operation, const char *xpath,
+				  const char *data);
 extern int vty_mgmt_send_lockds_req(struct vty *vty, Mgmtd__DatastoreId ds_id,
 				    bool lock, bool scok);
 extern void vty_mgmt_resume_response(struct vty *vty, int ret);
diff --git a/lib/yang.c b/lib/yang.c
index d71cb2f498c7..013a7628424a 100644
--- a/lib/yang.c
+++ b/lib/yang.c
@@ -12,6 +12,7 @@
 #include "yang.h"
 #include "yang_translator.h"
 #include "northbound.h"
+#include "frrstr.h"
 
 #include "lib/config_paths.h"
 
@@ -1122,6 +1123,32 @@ int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys)
 	return NB_OK;
 }
 
+int yang_xpath_pop_node(char *xpath)
+{
+	int len = strlen(xpath);
+	bool abs = xpath[0] == '/';
+	char *slash;
+
+	/* "//" or "/" => NULL */
+	if (abs && (len == 1 || (len == 2 && xpath[1] == '/')))
+		return NB_ERR_NOT_FOUND;
+
+	slash = (char *)frrstr_back_to_char(xpath, '/');
+	/* "/foo/bar/" or "/foo/bar//" => "/foo " */
+	if (slash && slash == &xpath[len - 1]) {
+		xpath[--len] = 0;
+		slash = (char *)frrstr_back_to_char(xpath, '/');
+		if (slash && slash == &xpath[len - 1]) {
+			xpath[--len] = 0;
+			slash = (char *)frrstr_back_to_char(xpath, '/');
+		}
+	}
+	if (!slash)
+		return NB_ERR_NOT_FOUND;
+	*slash = 0;
+	return NB_OK;
+}
+
 /*
  * ------------------------
  * Libyang Future Functions
@@ -1275,6 +1302,42 @@ LY_ERR yang_lyd_trim_xpath(struct lyd_node **root, const char *xpath)
 #endif
 }
 
+/* Can be replaced by `lyd_parse_data` with libyang >= 2.1.156 */
+LY_ERR yang_lyd_parse_data(const struct ly_ctx *ctx, struct lyd_node *parent,
+			   struct ly_in *in, LYD_FORMAT format,
+			   uint32_t parse_options, uint32_t validate_options,
+			   struct lyd_node **tree)
+{
+	struct lyd_node *child;
+	LY_ERR err;
+
+	err = lyd_parse_data(ctx, parent, in, format, parse_options,
+			     validate_options, tree);
+	if (err)
+		return err;
+
+	if (!parent || !(parse_options & LYD_PARSE_ONLY))
+		return LY_SUCCESS;
+
+	/*
+	 * Versions prior to 2.1.156 don't return `tree` if `parent` is not NULL
+	 * and validation is disabled (`LYD_PARSE_ONLY`). To work around this,
+	 * go through the children and find the one with `LYD_NEW` flag set.
+	 */
+	*tree = NULL;
+
+	LY_LIST_FOR (lyd_child_no_keys(parent), child) {
+		if (child->flags & LYD_NEW) {
+			*tree = child;
+			break;
+		}
+	}
+
+	assert(tree);
+
+	return LY_SUCCESS;
+}
+
 /*
  * Safe to remove after libyang v2.1.128 is required
  */
diff --git a/lib/yang.h b/lib/yang.h
index 65f6a73e0b50..a831c9bfdfda 100644
--- a/lib/yang.h
+++ b/lib/yang.h
@@ -770,6 +770,14 @@ extern int yang_get_key_preds(char *s, const struct lysc_node *snode,
 /* Get YANG keys from an existing dnode */
 extern int yang_get_node_keys(struct lyd_node *node, struct yang_list_keys *keys);
 
+/**
+ * yang_xpath_pop_node() - remove the last node from xpath string
+ * @xpath: an xpath string
+ *
+ * Return: NB_OK or NB_ERR_NOT_FOUND if nothing left to pop.
+ */
+extern int yang_xpath_pop_node(char *xpath);
+
 /**
  * yang_resolve_snodes() - Resolve an XPath to matching schema nodes.
  * @ly_ctx: libyang context to operate on.
@@ -800,6 +808,11 @@ extern LY_ERR yang_lyd_new_list(struct lyd_node_inner *parent,
 				const struct yang_list_keys *keys,
 				struct lyd_node **nodes);
 extern LY_ERR yang_lyd_trim_xpath(struct lyd_node **rootp, const char *xpath);
+extern LY_ERR yang_lyd_parse_data(const struct ly_ctx *ctx,
+				  struct lyd_node *parent, struct ly_in *in,
+				  LYD_FORMAT format, uint32_t parse_options,
+				  uint32_t validate_options,
+				  struct lyd_node **tree);
 
 #ifdef __cplusplus
 }
diff --git a/mgmtd/mgmt_fe_adapter.c b/mgmtd/mgmt_fe_adapter.c
index 62d1a0109afc..ab0da64d8f88 100644
--- a/mgmtd/mgmt_fe_adapter.c
+++ b/mgmtd/mgmt_fe_adapter.c
@@ -898,11 +898,13 @@ static int mgmt_fe_session_handle_commit_config_req_msg(
 	/*
 	 * Create COMMITConfig request under the transaction
 	 */
-	if (mgmt_txn_send_commit_config_req(
-		    session->cfg_txn_id, commcfg_req->req_id,
-		    commcfg_req->src_ds_id, src_ds_ctx, commcfg_req->dst_ds_id,
-		    dst_ds_ctx, commcfg_req->validate_only, commcfg_req->abort,
-		    false) != 0) {
+	if (mgmt_txn_send_commit_config_req(session->cfg_txn_id,
+					    commcfg_req->req_id,
+					    commcfg_req->src_ds_id, src_ds_ctx,
+					    commcfg_req->dst_ds_id, dst_ds_ctx,
+					    commcfg_req->validate_only,
+					    commcfg_req->abort, false,
+					    NULL) != 0) {
 		fe_adapter_send_commit_cfg_reply(
 			session, commcfg_req->src_ds_id, commcfg_req->dst_ds_id,
 			commcfg_req->req_id, MGMTD_INTERNAL_ERROR,
@@ -1099,6 +1101,33 @@ static int fe_adapter_send_tree_data(struct mgmt_fe_session_ctx *session,
 	return ret;
 }
 
+static int fe_adapter_send_edit_reply(struct mgmt_fe_session_ctx *session,
+				      uint64_t req_id, const char *xpath)
+{
+	struct mgmt_msg_edit_reply *msg;
+	int ret;
+
+	msg = mgmt_msg_native_alloc_msg(struct mgmt_msg_edit_reply, 0,
+					MTYPE_MSG_NATIVE_EDIT_REPLY);
+	msg->refer_id = session->session_id;
+	msg->req_id = req_id;
+	msg->code = MGMT_MSG_CODE_EDIT_REPLY;
+
+	mgmt_msg_native_xpath_encode(msg, xpath);
+
+	__dbg("Sending edit-reply from adapter %s to session-id %" PRIu64
+	      " req-id %" PRIu64 " len %u",
+	      session->adapter->name, session->session_id, req_id,
+	      mgmt_msg_native_get_msg_len(msg));
+
+	ret = fe_adapter_send_native_msg(session->adapter, msg,
+					 mgmt_msg_native_get_msg_len(msg),
+					 false);
+	mgmt_msg_native_free_msg(msg);
+
+	return ret;
+}
+
 /**
  * fe_adapter_handle_get_data() - Handle a get-tree message from a FE client.
  * @session: the client session.
@@ -1224,6 +1253,112 @@ static void fe_adapter_handle_get_data(struct mgmt_fe_session_ctx *session,
 	darr_free(xpath_resolved);
 }
 
+static void fe_adapter_handle_edit(struct mgmt_fe_session_ctx *session,
+				   void *__msg, size_t msg_len)
+{
+	struct mgmt_msg_edit *msg = __msg;
+	Mgmtd__DatastoreId ds_id, rds_id;
+	struct mgmt_ds_ctx *ds_ctx, *rds_ctx;
+	const char *xpath, *data;
+	bool lock, commit;
+	int ret;
+
+	if (msg->datastore != MGMT_MSG_DATASTORE_CANDIDATE) {
+		fe_adapter_send_error(session, msg->req_id, false, -EINVAL,
+				      "Unsupported datastore");
+		return;
+	}
+
+	xpath = mgmt_msg_native_xpath_data_decode(msg, msg_len, data);
+	if (!xpath || !data) {
+		fe_adapter_send_error(session, msg->req_id, false, -EINVAL,
+				      "Invalid message");
+		return;
+	}
+
+	ds_id = MGMTD_DS_CANDIDATE;
+	ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id);
+	assert(ds_ctx);
+
+	rds_id = MGMTD_DS_RUNNING;
+	rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id);
+	assert(rds_ctx);
+
+	lock = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_LOCK);
+	commit = CHECK_FLAG(msg->flags, EDIT_FLAG_IMPLICIT_COMMIT);
+
+	if (lock) {
+		if (mgmt_fe_session_write_lock_ds(ds_id, ds_ctx, session)) {
+			fe_adapter_send_error(session, msg->req_id, false,
+					      -EBUSY,
+					      "Candidate DS is locked by another session");
+			return;
+		}
+
+		if (commit) {
+			if (mgmt_fe_session_write_lock_ds(rds_id, rds_ctx,
+							  session)) {
+				mgmt_fe_session_unlock_ds(ds_id, ds_ctx,
+							  session);
+				fe_adapter_send_error(
+					session, msg->req_id, false, -EBUSY,
+					"Running DS is locked by another session");
+				return;
+			}
+		}
+	} else {
+		if (!session->ds_locked[ds_id]) {
+			fe_adapter_send_error(session, msg->req_id, false,
+					      -EBUSY,
+					      "Candidate DS is not locked");
+			return;
+		}
+
+		if (commit) {
+			if (!session->ds_locked[rds_id]) {
+				fe_adapter_send_error(session, msg->req_id,
+						      false, -EBUSY,
+						      "Running DS is not locked");
+				return;
+			}
+		}
+	}
+
+	session->cfg_txn_id = mgmt_create_txn(session->session_id,
+					      MGMTD_TXN_TYPE_CONFIG);
+	if (session->cfg_txn_id == MGMTD_SESSION_ID_NONE) {
+		if (lock) {
+			mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session);
+			if (commit)
+				mgmt_fe_session_unlock_ds(rds_id, rds_ctx,
+							  session);
+		}
+		fe_adapter_send_error(session, msg->req_id, false, -EBUSY,
+				      "Failed to create a configuration transaction");
+		return;
+	}
+
+	__dbg("Created new config txn-id: %" PRIu64 " for session-id: %" PRIu64,
+	      session->cfg_txn_id, session->session_id);
+
+	ret = mgmt_txn_send_edit(session->cfg_txn_id, msg->req_id, ds_id,
+				 ds_ctx, rds_id, rds_ctx, lock, commit,
+				 msg->request_type, msg->flags, msg->operation,
+				 xpath, data);
+	if (ret) {
+		/* destroy the just created txn */
+		mgmt_destroy_txn(&session->cfg_txn_id);
+		if (lock) {
+			mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session);
+			if (commit)
+				mgmt_fe_session_unlock_ds(rds_id, rds_ctx,
+							  session);
+		}
+		fe_adapter_send_error(session, msg->req_id, false, -EBUSY,
+				      "Failed to create a configuration transaction");
+	}
+}
+
 /**
  * Handle a native encoded message from the FE client.
  */
@@ -1245,6 +1380,9 @@ static void fe_adapter_handle_native_msg(struct mgmt_fe_client_adapter *adapter,
 	case MGMT_MSG_CODE_GET_DATA:
 		fe_adapter_handle_get_data(session, msg, msg_len);
 		break;
+	case MGMT_MSG_CODE_EDIT:
+		fe_adapter_handle_edit(session, msg, msg_len);
+		break;
 	default:
 		__log_err("unknown native message session-id %" PRIu64
 			  " req-id %" PRIu64 " code %u to FE adapter %s",
@@ -1484,6 +1622,52 @@ int mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id,
 	return ret;
 }
 
+int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id,
+				    uint64_t req_id, bool unlock, bool commit,
+				    const char *xpath, int16_t error,
+				    const char *errstr)
+{
+	struct mgmt_fe_session_ctx *session;
+	Mgmtd__DatastoreId ds_id, rds_id;
+	struct mgmt_ds_ctx *ds_ctx, *rds_ctx;
+	int ret;
+
+	session = mgmt_session_id2ctx(session_id);
+	if (!session || session->cfg_txn_id != txn_id)
+		return -1;
+
+	if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && commit)
+		mgmt_fe_session_register_event(session,
+					       MGMTD_FE_SESSION_CFG_TXN_CLNUP);
+
+	if (unlock) {
+		ds_id = MGMTD_DS_CANDIDATE;
+		ds_ctx = mgmt_ds_get_ctx_by_id(mm, ds_id);
+		assert(ds_ctx);
+
+		mgmt_fe_session_unlock_ds(ds_id, ds_ctx, session);
+
+		if (commit) {
+			rds_id = MGMTD_DS_RUNNING;
+			rds_ctx = mgmt_ds_get_ctx_by_id(mm, rds_id);
+			assert(rds_ctx);
+
+			mgmt_fe_session_unlock_ds(rds_id, rds_ctx, session);
+		}
+	}
+
+	if (error)
+		ret = fe_adapter_send_error(session, req_id, false, error, "%s",
+					    errstr);
+	else
+		ret = fe_adapter_send_edit_reply(session, req_id, xpath);
+
+	if (session->cfg_txn_id != MGMTD_TXN_ID_NONE && !commit)
+		mgmt_destroy_txn(&session->cfg_txn_id);
+
+	return ret;
+}
+
 /**
  * Send an error back to the FE client and cleanup any in-progress txn.
  */
diff --git a/mgmtd/mgmt_fe_adapter.h b/mgmtd/mgmt_fe_adapter.h
index e768a3cca0b3..8d61ffe9106b 100644
--- a/mgmtd/mgmt_fe_adapter.h
+++ b/mgmtd/mgmt_fe_adapter.h
@@ -162,6 +162,26 @@ mgmt_fe_adapter_send_tree_data(uint64_t session_id, uint64_t txn_id,
 			       uint32_t wd_options, const struct lyd_node *tree,
 			       int partial_error, bool short_circuit_ok);
 
+/**
+ * Send edit reply back to client. If error is not 0, a native error is sent.
+ *
+ * This also cleans up and frees the transaction.
+ *
+ * Args:
+ *     session_id: the session.
+ *     txn_id: the txn_id this data pertains to
+ *     req_id: the req id for the edit message
+ *     unlock: implicit-lock flag was set in the request
+ *     commit: implicit-commit flag was set in the request
+ *     xpath: the xpath of the data node that was created
+ *     error: the error code, zero for successful request
+ *     errstr: the error string, if error is non-zero
+ */
+extern int mgmt_fe_adapter_send_edit_reply(uint64_t session_id, uint64_t txn_id,
+					   uint64_t req_id, bool unlock,
+					   bool commit, const char *xpath,
+					   int16_t error, const char *errstr);
+
 /**
  * Send an error back to the FE client using native messaging.
  *
diff --git a/mgmtd/mgmt_txn.c b/mgmtd/mgmt_txn.c
index a4e0fa21693c..fce805170866 100644
--- a/mgmtd/mgmt_txn.c
+++ b/mgmtd/mgmt_txn.c
@@ -92,6 +92,11 @@ DECLARE_LIST(mgmt_txn_batches, struct mgmt_txn_be_cfg_batch, list_linkage);
 #define FOREACH_TXN_CFG_BATCH_IN_LIST(list, batch)                             \
 	frr_each_safe (mgmt_txn_batches, list, batch)
 
+struct mgmt_edit_req {
+	char xpath_created[XPATH_MAXLEN];
+	bool unlock;
+};
+
 struct mgmt_commit_cfg_req {
 	Mgmtd__DatastoreId src_ds_id;
 	struct mgmt_ds_ctx *src_ds_ctx;
@@ -109,6 +114,12 @@ struct mgmt_commit_cfg_req {
 
 	enum mgmt_commit_phase be_phase[MGMTD_BE_CLIENT_ID_MAX];
 
+	/*
+	 * Additional information when the commit is triggered by native edit
+	 * request.
+	 */
+	struct mgmt_edit_req *edit;
+
 	/*
 	 * Set of config changes to commit. This is used only
 	 * when changes are NOT to be determined by comparing
@@ -446,6 +457,8 @@ static void mgmt_txn_req_free(struct mgmt_txn_req **txn_req)
 		cleanup = (ccreq->phase >= MGMTD_COMMIT_PHASE_TXN_CREATE &&
 			   ccreq->phase < MGMTD_COMMIT_PHASE_TXN_DELETE);
 
+		XFREE(MTYPE_MGMTD_TXN_REQ, ccreq->edit);
+
 		FOREACH_MGMTD_BE_CLIENT_ID (id) {
 			/*
 			 * Send TXN_DELETE to cleanup state for this
@@ -607,7 +620,8 @@ static void mgmt_txn_process_set_cfg(struct event *thread)
 								->dst_ds_id,
 							txn_req->req.set_cfg
 								->dst_ds_ctx,
-							false, false, true);
+							false, false, true,
+							NULL);
 
 			if (mm->perf_stats_en)
 				gettimeofday(&cmt_stats->last_start, NULL);
@@ -658,7 +672,8 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
 	 * b/c right now that is special cased.. that special casing should be
 	 * removed; however...
 	 */
-	if (!txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
+	if (!txn->commit_cfg_req->req.commit_cfg.edit &&
+	    !txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
 	    !txn->commit_cfg_req->req.commit_cfg.rollback &&
 	    mgmt_fe_send_commit_cfg_reply(txn->session_id, txn->txn_id,
 					  txn->commit_cfg_req->req.commit_cfg
@@ -674,7 +689,8 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
 			  txn->txn_id, txn->session_id);
 	}
 
-	if (txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
+	if (!txn->commit_cfg_req->req.commit_cfg.edit &&
+	    txn->commit_cfg_req->req.commit_cfg.implicit && txn->session_id &&
 	    !txn->commit_cfg_req->req.commit_cfg.rollback &&
 	    mgmt_fe_send_set_cfg_reply(txn->session_id, txn->txn_id,
 				       txn->commit_cfg_req->req.commit_cfg
@@ -688,6 +704,21 @@ static int mgmt_txn_send_commit_cfg_reply(struct mgmt_txn_ctx *txn,
 			  txn->txn_id, txn->session_id);
 	}
 
+	if (txn->commit_cfg_req->req.commit_cfg.edit &&
+	    mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id,
+					    txn->commit_cfg_req->req_id,
+					    txn->commit_cfg_req->req.commit_cfg
+						    .edit->unlock,
+					    true,
+					    txn->commit_cfg_req->req.commit_cfg
+						    .edit->xpath_created,
+					    success ? 0 : -1,
+					    error_if_any) != 0) {
+		__log_err("Failed to send EDIT-REPLY txn-id: %" PRIu64
+			  " session-id: %" PRIu64,
+			  txn->txn_id, txn->session_id);
+	}
+
 	if (success) {
 		/* Stop the commit-timeout timer */
 		/* XXX why only on success? */
@@ -2020,7 +2051,7 @@ int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id,
 				    Mgmtd__DatastoreId dst_ds_id,
 				    struct mgmt_ds_ctx *dst_ds_ctx,
 				    bool validate_only, bool abort,
-				    bool implicit)
+				    bool implicit, struct mgmt_edit_req *edit)
 {
 	struct mgmt_txn_ctx *txn;
 	struct mgmt_txn_req *txn_req;
@@ -2044,6 +2075,7 @@ int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id,
 	txn_req->req.commit_cfg.validate_only = validate_only;
 	txn_req->req.commit_cfg.abort = abort;
 	txn_req->req.commit_cfg.implicit = implicit;
+	txn_req->req.commit_cfg.edit = edit;
 	txn_req->req.commit_cfg.cmt_stats =
 		mgmt_fe_get_session_commit_stats(txn->session_id);
 
@@ -2427,6 +2459,52 @@ int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
 	return 0;
 }
 
+int mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id,
+		       Mgmtd__DatastoreId ds_id, struct mgmt_ds_ctx *ds_ctx,
+		       Mgmtd__DatastoreId commit_ds_id,
+		       struct mgmt_ds_ctx *commit_ds_ctx, bool unlock,
+		       bool commit, LYD_FORMAT request_type, uint8_t flags,
+		       uint8_t operation, const char *xpath, const char *data)
+{
+	struct mgmt_txn_ctx *txn;
+	struct mgmt_edit_req *edit;
+	struct nb_config *nb_config;
+	char errstr[BUFSIZ];
+	int ret;
+
+	txn = mgmt_txn_id2ctx(txn_id);
+	if (!txn)
+		return -1;
+
+	edit = XCALLOC(MTYPE_MGMTD_TXN_REQ, sizeof(struct mgmt_edit_req));
+
+	nb_config = mgmt_ds_get_nb_config(ds_ctx);
+	assert(nb_config);
+
+	ret = nb_candidate_edit_tree(nb_config, operation, request_type, xpath,
+				     data, edit->xpath_created, errstr,
+				     sizeof(errstr));
+	if (ret)
+		goto reply;
+
+	if (commit) {
+		edit->unlock = unlock;
+
+		mgmt_txn_send_commit_config_req(txn_id, req_id, ds_id, ds_ctx,
+						commit_ds_id, commit_ds_ctx,
+						false, false, true, edit);
+		return 0;
+	}
+reply:
+	mgmt_fe_adapter_send_edit_reply(txn->session_id, txn->txn_id, req_id,
+					unlock, commit, edit->xpath_created,
+					ret ? -1 : 0, errstr);
+
+	XFREE(MTYPE_MGMTD_TXN_REQ, edit);
+
+	return 0;
+}
+
 /*
  * Error reply from the backend client.
  */
diff --git a/mgmtd/mgmt_txn.h b/mgmtd/mgmt_txn.h
index b7198326da7e..aeb74469f141 100644
--- a/mgmtd/mgmt_txn.h
+++ b/mgmtd/mgmt_txn.h
@@ -43,6 +43,7 @@
 PREDECL_LIST(mgmt_txns);
 
 struct mgmt_master;
+struct mgmt_edit_req;
 
 enum mgmt_txn_type {
 	MGMTD_TXN_TYPE_NONE = 0,
@@ -171,16 +172,17 @@ extern int mgmt_txn_send_set_config_req(uint64_t txn_id, uint64_t req_id,
  * implicit
  *    TRUE if the commit is implicit, FALSE otherwise.
  *
+ * edit
+ *    Additional info when triggered from native edit request.
+ *
  * Returns:
  *    0 on success, -1 on failures.
  */
-extern int mgmt_txn_send_commit_config_req(uint64_t txn_id, uint64_t req_id,
-					   Mgmtd__DatastoreId src_ds_id,
-					   struct mgmt_ds_ctx *dst_ds_ctx,
-					   Mgmtd__DatastoreId dst_ds_id,
-					   struct mgmt_ds_ctx *src_ds_ctx,
-					   bool validate_only, bool abort,
-					   bool implicit);
+extern int mgmt_txn_send_commit_config_req(
+	uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId src_ds_id,
+	struct mgmt_ds_ctx *dst_ds_ctx, Mgmtd__DatastoreId dst_ds_id,
+	struct mgmt_ds_ctx *src_ds_ctx, bool validate_only, bool abort,
+	bool implicit, struct mgmt_edit_req *edit);
 
 /*
  * Send get-{cfg,data} request to be processed later in transaction.
@@ -219,6 +221,31 @@ extern int mgmt_txn_send_get_tree_oper(uint64_t txn_id, uint64_t req_id,
 				       uint32_t wd_options, bool simple_xpath,
 				       const char *xpath);
 
+/**
+ * Send edit request.
+ *
+ * Args:
+ *	txn_id: Transaction identifier.
+ *	req_id: FE client request identifier.
+ *	ds_id: Datastore ID.
+ *	ds_ctx: Datastore context.
+ *	commit_ds_id: Commit datastore ID.
+ *	commit_ds_ctx: Commit datastore context.
+ *	unlock: Unlock datastores after the edit.
+ *	commit: Commit the candidate datastore after the edit.
+ *	request_type: LYD_FORMAT request type.
+ *	flags: option flags for the request.
+ *	operation: The operation to perform.
+ *	xpath: The xpath of data node to edit.
+ *	data: The data tree.
+ */
+extern int
+mgmt_txn_send_edit(uint64_t txn_id, uint64_t req_id, Mgmtd__DatastoreId ds_id,
+		   struct mgmt_ds_ctx *ds_ctx, Mgmtd__DatastoreId commit_ds_id,
+		   struct mgmt_ds_ctx *commit_ds_ctx, bool unlock, bool commit,
+		   LYD_FORMAT request_type, uint8_t flags, uint8_t operation,
+		   const char *xpath, const char *data);
+
 /*
  * Notifiy backend adapter on connection.
  */
diff --git a/mgmtd/mgmt_vty.c b/mgmtd/mgmt_vty.c
index 2cd24719bc04..61d0760e05d6 100644
--- a/mgmtd/mgmt_vty.c
+++ b/mgmtd/mgmt_vty.c
@@ -238,6 +238,64 @@ DEFPY(mgmt_replace_config_data, mgmt_replace_config_data_cmd,
 	return CMD_SUCCESS;
 }
 
+DEFPY(mgmt_edit, mgmt_edit_cmd,
+      "mgmt edit {create|delete|merge|replace|remove}$op XPATH [json|xml]$fmt [lock$lock] [commit$commit] [DATA]",
+      MGMTD_STR
+      "Edit configuration data\n"
+      "Create data\n"
+      "Delete data\n"
+      "Merge data\n"
+      "Replace data\n"
+      "Remove data\n"
+      "XPath expression specifying the YANG data path\n"
+      "JSON input format (default)\n"
+      "XML input format\n"
+      "Lock the datastores automatically\n"
+      "Commit the changes automatically\n"
+      "Data tree\n")
+{
+	LYD_FORMAT format = (fmt && fmt[0] == 'x') ? LYD_XML : LYD_JSON;
+	uint8_t operation;
+	uint8_t flags = 0;
+
+	switch (op[2]) {
+	case 'e':
+		operation = NB_OP_CREATE_EXCL;
+		break;
+	case 'l':
+		operation = NB_OP_DELETE;
+		break;
+	case 'r':
+		operation = NB_OP_MODIFY;
+		break;
+	case 'p':
+		operation = NB_OP_REPLACE;
+		break;
+	case 'm':
+		operation = NB_OP_DESTROY;
+		break;
+	default:
+		vty_out(vty, "Invalid operation!\n");
+		return CMD_WARNING_CONFIG_FAILED;
+	}
+
+	if (!data && (operation == NB_OP_CREATE_EXCL ||
+		      operation == NB_OP_MODIFY || operation == NB_OP_REPLACE)) {
+		vty_out(vty, "Data tree is missing!\n");
+		return CMD_WARNING_CONFIG_FAILED;
+	}
+
+	if (lock)
+		flags |= EDIT_FLAG_IMPLICIT_LOCK;
+
+	if (commit)
+		flags |= EDIT_FLAG_IMPLICIT_COMMIT;
+
+	vty_mgmt_send_edit_req(vty, MGMT_MSG_DATASTORE_CANDIDATE, format, flags,
+			       operation, xpath, data);
+	return CMD_SUCCESS;
+}
+
 DEFPY(show_mgmt_get_config, show_mgmt_get_config_cmd,
       "show mgmt get-config [candidate|operational|running]$dsname WORD$path",
       SHOW_STR MGMTD_STR
@@ -643,6 +701,7 @@ void mgmt_vty_init(void)
 	install_element(CONFIG_NODE, &mgmt_delete_config_data_cmd);
 	install_element(CONFIG_NODE, &mgmt_remove_config_data_cmd);
 	install_element(CONFIG_NODE, &mgmt_replace_config_data_cmd);
+	install_element(CONFIG_NODE, &mgmt_edit_cmd);
 	install_element(CONFIG_NODE, &mgmt_load_config_cmd);
 	install_element(CONFIG_NODE, &mgmt_save_config_cmd);
 	install_element(CONFIG_NODE, &mgmt_rollback_cmd);