diff --git a/NEWS b/NEWS
index 43aea97b5..12a895629 100644
--- a/NEWS
+++ b/NEWS
@@ -13,6 +13,8 @@ Post-v3.2.0
        a.k.a. 'configured' values, can be found in the 'status' column of
        the Interface table, i.e. with 'ovs-vsctl get interface <..> status'.
        Reported names adjusted accordingly.
+     * Added new option [-f|--format] to choose the output format, e.g. 'json'
+       or 'text' (by default).
 
 
 v3.2.0 - 17 Aug 2023
diff --git a/lib/command-line.c b/lib/command-line.c
index 967f4f5d5..775e0430a 100644
--- a/lib/command-line.c
+++ b/lib/command-line.c
@@ -24,6 +24,7 @@
 #include "ovs-thread.h"
 #include "util.h"
 #include "openvswitch/vlog.h"
+#include "openvswitch/json.h"
 
 VLOG_DEFINE_THIS_MODULE(command_line);
 
@@ -53,6 +54,41 @@ ovs_cmdl_long_options_to_short_options(const struct option options[])
     return xstrdup(short_options);
 }
 
+const char *
+ovs_output_fmt_to_string(enum ovs_output_fmt fmt)
+{
+    switch (fmt) {
+    case OVS_OUTPUT_FMT_TEXT:
+        return "text";
+
+    case OVS_OUTPUT_FMT_JSON:
+        return "json";
+
+    default:
+        return NULL;
+    }
+}
+
+struct json *
+ovs_output_fmt_to_json(enum ovs_output_fmt fmt)
+{
+    const char *string = ovs_output_fmt_to_string(fmt);
+    return string ? json_string_create(string) : NULL;
+}
+
+bool
+ovs_output_fmt_from_string(const char *string, enum ovs_output_fmt *fmt)
+{
+    if (!strcmp(string, "text")) {
+        *fmt = OVS_OUTPUT_FMT_TEXT;
+    } else if (!strcmp(string, "json")) {
+        *fmt = OVS_OUTPUT_FMT_JSON;
+    } else {
+        return false;
+    }
+    return true;
+}
+
 static char * OVS_WARN_UNUSED_RESULT
 build_short_options(const struct option *long_options)
 {
diff --git a/lib/command-line.h b/lib/command-line.h
index fc2a2690f..494274cec 100644
--- a/lib/command-line.h
+++ b/lib/command-line.h
@@ -20,6 +20,7 @@
 /* Utilities for command-line parsing. */
 
 #include "compiler.h"
+#include <openvswitch/list.h>
 
 struct option;
 
@@ -46,6 +47,15 @@ struct ovs_cmdl_command {
 
 char *ovs_cmdl_long_options_to_short_options(const struct option *options);
 
+enum ovs_output_fmt {
+    OVS_OUTPUT_FMT_TEXT = 1 << 0,
+    OVS_OUTPUT_FMT_JSON = 1 << 1
+};
+
+const char *ovs_output_fmt_to_string(enum ovs_output_fmt);
+struct json *ovs_output_fmt_to_json(enum ovs_output_fmt);
+bool ovs_output_fmt_from_string(const char *, enum ovs_output_fmt *);
+
 struct ovs_cmdl_parsed_option {
     const struct option *o;
     char *arg;
diff --git a/lib/dpctl.h b/lib/dpctl.h
index 9d0052152..1c9b2409f 100644
--- a/lib/dpctl.h
+++ b/lib/dpctl.h
@@ -19,6 +19,7 @@
 #include <stdbool.h>
 
 #include "compiler.h"
+#include "command-line.h"
 
 struct dpctl_params {
     /* True if it is called by ovs-appctl command. */
@@ -42,6 +43,9 @@ struct dpctl_params {
     /* --names: Use port names in output? */
     bool names;
 
+    /* Output format. */
+    enum ovs_output_fmt format;
+
     /* Callback for printing.  This function is called from dpctl_run_command()
      * to output data.  The 'aux' parameter is set to the 'aux'
      * member.  The 'error' parameter is true if 'string' is an error
diff --git a/lib/unixctl.c b/lib/unixctl.c
index 103357ee9..5de59f339 100644
--- a/lib/unixctl.c
+++ b/lib/unixctl.c
@@ -21,7 +21,6 @@
 #include "coverage.h"
 #include "dirs.h"
 #include "openvswitch/dynamic-string.h"
-#include "openvswitch/json.h"
 #include "jsonrpc.h"
 #include "openvswitch/list.h"
 #include "openvswitch/poll-loop.h"
@@ -39,6 +38,7 @@ COVERAGE_DEFINE(unixctl_replied);
 struct unixctl_command {
     const char *usage;
     int min_args, max_args;
+    int output_fmts;
     unixctl_cb_func *cb;
     void *aux;
 };
@@ -63,6 +63,8 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
 
 static struct shash commands = SHASH_INITIALIZER(&commands);
 
+static const char *rpc_marker = "execute/v1";
+
 static void
 unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED,
                       const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
@@ -109,6 +111,28 @@ void
 unixctl_command_register(const char *name, const char *usage,
                          int min_args, int max_args,
                          unixctl_cb_func *cb, void *aux)
+{
+    unixctl_command_register_fmt(name, usage, min_args, max_args,
+                                 OVS_OUTPUT_FMT_TEXT, cb, aux);
+}
+
+/* Registers a unixctl command with the given 'name'.  'usage' describes the
+ * arguments to the command; it is used only for presentation to the user in
+ * "list-commands" output.  (If 'usage' is NULL, then the command is hidden.)
+ * 'output_fmts' is a bitmap that defines what output formats a command
+ * supports, e.g. OVS_OUTPUT_FMT_TEXT | OVS_OUTPUT_FMT_JSON.
+ *
+ * 'cb' is called when the command is received.  It is passed an array
+ * containing the command name and arguments, plus a copy of 'aux'.  Normally
+ * 'cb' should reply by calling unixctl_command_reply() or
+ * unixctl_command_reply_error() before it returns, but if the command cannot
+ * be handled immediately then it can defer the reply until later.  A given
+ * connection can only process a single request at a time, so a reply must be
+ * made eventually to avoid blocking that connection. */
+void
+unixctl_command_register_fmt(const char *name, const char *usage,
+                             int min_args, int max_args, int output_fmts,
+                             unixctl_cb_func *cb, void *aux)
 {
     struct unixctl_command *command;
     struct unixctl_command *lookup = shash_find_data(&commands, name);
@@ -123,41 +147,48 @@ unixctl_command_register(const char *name, const char *usage,
     command->usage = usage;
     command->min_args = min_args;
     command->max_args = max_args;
+    command->output_fmts = output_fmts;
     command->cb = cb;
     command->aux = aux;
     shash_add(&commands, name, command);
 }
 
-static void
-unixctl_command_reply__(struct unixctl_conn *conn,
-                        bool success, const char *body)
+static struct json *
+json_string_create__(const char *body)
 {
-    struct json *body_json;
-    struct jsonrpc_msg *reply;
-
-    COVERAGE_INC(unixctl_replied);
-    ovs_assert(conn->request_id);
-
     if (!body) {
         body = "";
     }
 
     if (body[0] && body[strlen(body) - 1] != '\n') {
-        body_json = json_string_create_nocopy(xasprintf("%s\n", body));
+        return json_string_create_nocopy(xasprintf("%s\n", body));
     } else {
-        body_json = json_string_create(body);
+        return json_string_create(body);
     }
+}
+
+/* Takes ownership of 'body'. */
+static void
+unixctl_command_reply__(struct unixctl_conn *conn,
+                        bool success, struct json *body)
+{
+    struct jsonrpc_msg *reply;
+
+    COVERAGE_INC(unixctl_replied);
+    ovs_assert(conn->request_id);
 
     if (success) {
-        reply = jsonrpc_create_reply(body_json, conn->request_id);
+        reply = jsonrpc_create_reply(body, conn->request_id);
     } else {
-        reply = jsonrpc_create_error(body_json, conn->request_id);
+        reply = jsonrpc_create_error(body, conn->request_id);
     }
 
     if (VLOG_IS_DBG_ENABLED()) {
         char *id = json_to_string(conn->request_id, 0);
+        char *msg = json_to_string(body, 0);
         VLOG_DBG("replying with %s, id=%s: \"%s\"",
-                 success ? "success" : "error", id, body);
+                 success ? "success" : "error", id, msg);
+        free(msg);
         free(id);
     }
 
@@ -170,22 +201,34 @@ unixctl_command_reply__(struct unixctl_conn *conn,
 
 /* Replies to the active unixctl connection 'conn'.  'result' is sent to the
  * client indicating the command was processed successfully.  Only one call to
- * unixctl_command_reply() or unixctl_command_reply_error() may be made per
- * request. */
+ * unixctl_command_reply(), unixctl_command_reply_error() or
+ * unixctl_command_reply_json() may be made per request. */
 void
 unixctl_command_reply(struct unixctl_conn *conn, const char *result)
 {
-    unixctl_command_reply__(conn, true, result);
+    unixctl_command_reply__(conn, true, json_string_create__(result));
 }
 
 /* Replies to the active unixctl connection 'conn'. 'error' is sent to the
- * client indicating an error occurred processing the command.  Only one call to
- * unixctl_command_reply() or unixctl_command_reply_error() may be made per
- * request. */
+ * client indicating an error occurred processing the command.  Only one call
+ * to unixctl_command_reply(), unixctl_command_reply_error() or
+ * unixctl_command_reply_json() may be made per request. */
 void
 unixctl_command_reply_error(struct unixctl_conn *conn, const char *error)
 {
-    unixctl_command_reply__(conn, false, error);
+    unixctl_command_reply__(conn, false, json_string_create__(error));
+}
+
+/* Replies to the active unixctl connection 'conn'.  'result' is sent to the
+ * client indicating the command was processed successfully.  Only one call to
+ * unixctl_command_reply(), unixctl_command_reply_error() or
+ * unixctl_command_reply_json() may be made per request.
+ *
+ * Takes ownership of 'body'. */
+void
+unixctl_command_reply_json(struct unixctl_conn *conn, struct json *body)
+{
+    unixctl_command_reply__(conn, true, body);
 }
 
 /* Creates a unixctl server listening on 'path', which for POSIX may be:
@@ -266,6 +309,11 @@ process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
 
     struct unixctl_command *command;
     struct json_array *params;
+    const char *method;
+    enum ovs_output_fmt fmt;
+    struct svec argv = SVEC_EMPTY_INITIALIZER;
+    int args_offset;
+    bool plain_rpc;
 
     COVERAGE_INC(unixctl_received);
     conn->request_id = json_clone(request->id);
@@ -279,45 +327,95 @@ process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
         free(id_s);
     }
 
+    /* The JSON-RPC API requires an indirection in order to allow transporting
+     * additional data like the output format besides command and args.  For
+     * backward compatibility with older clients the plain RPC is still
+     * supported. */
+    plain_rpc = strcmp(request->method, rpc_marker);
+    args_offset = plain_rpc ? 0 : 2;
+
     params = json_array(request->params);
-    command = shash_find_data(&commands, request->method);
+    if (!plain_rpc && (params->n < 2)) {
+        error = xasprintf("JSON-RPC API mismatch: Unexpected # of params:"\
+                          " %"PRIuSIZE, params->n);
+        goto error;
+    }
+
+    for (size_t i = 0; i < params->n; i++) {
+        if (params->elems[i]->type != JSON_STRING) {
+            error = xasprintf("command has non-string argument: %s",
+                              json_to_string(params->elems[i], 0));
+            goto error;
+        }
+    }
+
+    /* Extract method name. */
+    method = plain_rpc ? request->method : json_string(params->elems[0]);
+
+    /* Extract output format. */
+    if (plain_rpc) {
+        fmt = OVS_OUTPUT_FMT_TEXT;
+    } else {
+        if (!ovs_output_fmt_from_string(json_string(params->elems[1]), &fmt)) {
+            error = xasprintf("invalid output format: %s",
+                              json_string(params->elems[1]));
+            goto error;
+        }
+    }
+
+    /* Find command with method name. */
+    command = shash_find_data(&commands, method);
+
+    /* Verify that method call complies with command requirements. */
     if (!command) {
         error = xasprintf("\"%s\" is not a valid command (use "
                           "\"list-commands\" to see a list of valid commands)",
-                          request->method);
-    } else if (params->n < command->min_args) {
+                          method);
+        goto error;
+    } else if ((params->n - args_offset) < command->min_args) {
         error = xasprintf("\"%s\" command requires at least %d arguments",
-                          request->method, command->min_args);
-    } else if (params->n > command->max_args) {
+                          method, command->min_args);
+        goto error;
+    } else if ((params->n - args_offset) > command->max_args) {
         error = xasprintf("\"%s\" command takes at most %d arguments",
-                          request->method, command->max_args);
-    } else {
-        struct svec argv = SVEC_EMPTY_INITIALIZER;
-        int  i;
-
-        svec_add(&argv, request->method);
-        for (i = 0; i < params->n; i++) {
-            if (params->elems[i]->type != JSON_STRING) {
-                error = xasprintf("\"%s\" command has non-string argument",
-                                  request->method);
-                break;
-            }
-            svec_add(&argv, json_string(params->elems[i]));
-        }
-        svec_terminate(&argv);
-
-        if (!error) {
-            command->cb(conn, argv.n, (const char **) argv.names,
-                        command->aux);
-        }
+                          method, command->max_args);
+        goto error;
+    } else if ((!command->output_fmts && fmt != OVS_OUTPUT_FMT_TEXT) ||
+               (command->output_fmts && !(fmt & command->output_fmts)))
+    {
+        error = xasprintf("\"%s\" command does not support output format"\
+                          " \"%s\" %d %d", method,
+                          ovs_output_fmt_to_string(fmt), command->output_fmts,
+                          fmt);
+        goto error;
+    }
 
-        svec_destroy(&argv);
+    /* FIXME: Remove this check once output format will be passed to the
+     *        command handler below. */
+    if (fmt != OVS_OUTPUT_FMT_TEXT) {
+        error = xasprintf("output format \"%s\" has not been implemented yet",
+                          ovs_output_fmt_to_string(fmt));
+        goto error;
     }
 
-    if (error) {
-        unixctl_command_reply_error(conn, error);
-        free(error);
+    /* Extract command args. */
+    svec_add(&argv, method);
+    for (size_t i = args_offset; i < params->n; i++) {
+        svec_add(&argv, json_string(params->elems[i]));
     }
+    svec_terminate(&argv);
+
+    /* FIXME: Output format will be passed as 'fmt' to the command in later
+     *        patch. */
+    command->cb(conn, argv.n, (const char **) argv.names, /* fmt, */
+                command->aux);
+
+    svec_destroy(&argv);
+
+    return;
+error:
+    unixctl_command_reply_error(conn, error);
+    free(error);
 }
 
 static int
@@ -483,21 +581,43 @@ unixctl_client_create(const char *path, struct jsonrpc **client)
  * '*err' if not NULL. */
 int
 unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
-                        char *argv[], char **result, char **err)
+                        char *argv[], enum ovs_output_fmt fmt,
+                        char **result, char **err)
 {
     struct jsonrpc_msg *request, *reply;
     struct json **json_args, *params;
     int error, i;
+    /* The JSON-RPC API requires an indirection in order to allow transporting
+     * additional data like the output format besides command and args.  For
+     * backward compatibility with older servers the plain RPC is still
+     * supported. */
+    bool plain_rpc = (fmt == OVS_OUTPUT_FMT_TEXT);
 
     *result = NULL;
     *err = NULL;
 
-    json_args = xmalloc(argc * sizeof *json_args);
-    for (i = 0; i < argc; i++) {
-        json_args[i] = json_string_create(argv[i]);
+    if (plain_rpc) {
+        json_args = xmalloc(argc * sizeof *json_args);
+        for (i = 0; i < argc; i++) {
+            json_args[i] = json_string_create(argv[i]);
+        }
+
+        params = json_array_create(json_args, argc);
+        request = jsonrpc_create_request(command, params, NULL);
+    } else {
+        json_args = xmalloc((argc + 2) * sizeof *json_args);
+        json_args[0] = json_string_create(command);
+        json_args[1] = ovs_output_fmt_to_json(fmt);
+        for (i = 0; i < argc; i++) {
+            json_args[i + 2] = json_string_create(argv[i]);
+        }
+
+        params = json_array_create(json_args, argc + 2);
+
+        /* Use a versioned command to ensure that both server and client
+         * use the same JSON-RPC API. */
+        request = jsonrpc_create_request(rpc_marker, params, NULL);
     }
-    params = json_array_create(json_args, argc);
-    request = jsonrpc_create_request(command, params, NULL);
 
     error = jsonrpc_transact_block(client, request, &reply);
     if (error) {
@@ -508,7 +628,17 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
 
     if (reply->error) {
         if (reply->error->type == JSON_STRING) {
-            *err = xstrdup(json_string(reply->error));
+            /* Catch incompatible server and return helpful error msg. */
+            char *plain_rpc_error = xasprintf("\"%s\" is not a valid command",
+                                              rpc_marker);
+            if (!strncmp(plain_rpc_error, json_string(reply->error),
+                         strlen(plain_rpc_error))) {
+                *err = xstrdup("JSON RPC reply indicates incompatible server. "
+                               "Please upgrade server side to newer version.");
+            } else {
+                *err = xstrdup(json_string(reply->error));
+            }
+            free(plain_rpc_error);
         } else {
             VLOG_WARN("%s: unexpected error type in JSON RPC reply: %s",
                       jsonrpc_get_name(client),
@@ -518,6 +648,9 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
     } else if (reply->result) {
         if (reply->result->type == JSON_STRING) {
             *result = xstrdup(json_string(reply->result));
+        } else if (reply->result->type == JSON_OBJECT ||
+                   reply->result->type == JSON_ARRAY) {
+            *result = json_to_string(reply->result, 0);
         } else {
             VLOG_WARN("%s: unexpected result type in JSON rpc reply: %s",
                       jsonrpc_get_name(client),
diff --git a/lib/unixctl.h b/lib/unixctl.h
index 4562dbc49..896ecb062 100644
--- a/lib/unixctl.h
+++ b/lib/unixctl.h
@@ -17,6 +17,9 @@
 #ifndef UNIXCTL_H
 #define UNIXCTL_H 1
 
+#include "openvswitch/json.h"
+#include "command-line.h"
+
 #ifdef  __cplusplus
 extern "C" {
 #endif
@@ -36,17 +39,29 @@ int unixctl_client_create(const char *path, struct jsonrpc **client);
 int unixctl_client_transact(struct jsonrpc *client,
                             const char *command,
                             int argc, char *argv[],
+                            enum ovs_output_fmt fmt,
                             char **result, char **error);
 
 /* Command registration. */
 struct unixctl_conn;
+/* FIXME: Output format will be passed as 'fmt' to the command in later patch.
+ */
 typedef void unixctl_cb_func(struct unixctl_conn *,
-                             int argc, const char *argv[], void *aux);
+                             int argc, const char *argv[],
+                             /* enum ovs_output_fmt fmt, */ void *aux);
+/* FIXME: unixctl_command_register() will be replaced with
+ *        unixctl_command_register_fmt() in a later patch of this series.  It
+ *        is kept temporarily to reduce the amount of changes in this patch. */
 void unixctl_command_register(const char *name, const char *usage,
                               int min_args, int max_args,
                               unixctl_cb_func *cb, void *aux);
+void unixctl_command_register_fmt(const char *name, const char *usage,
+                                  int min_args, int max_args, int output_fmts,
+                                  unixctl_cb_func *cb, void *aux);
 void unixctl_command_reply_error(struct unixctl_conn *, const char *error);
 void unixctl_command_reply(struct unixctl_conn *, const char *body);
+void unixctl_command_reply_json(struct unixctl_conn *,
+                                struct json *body);
 
 #ifdef  __cplusplus
 }
diff --git a/tests/pmd.at b/tests/pmd.at
index 06cc90477..3fcda13fd 100644
--- a/tests/pmd.at
+++ b/tests/pmd.at
@@ -93,6 +93,11 @@ pmd thread numa_id <cleared> core_id <cleared>:
   overhead: NOT AVAIL
 ])
 
+AT_CHECK([ovs-appctl --format json dpif-netdev/pmd-rxq-show], [2], [], [dnl
+"dpif-netdev/pmd-rxq-show" command does not support output format "json" 1 2
+ovs-appctl: ovs-vswitchd: server returned an error
+])
+
 AT_CHECK([ovs-appctl dpif/show], [0], [dnl
 dummy@ovs-dummy: hit:0 missed:0
   br0:
diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c
index ba0c172e6..feff1be0b 100644
--- a/utilities/ovs-appctl.c
+++ b/utilities/ovs-appctl.c
@@ -34,7 +34,16 @@
 #include "openvswitch/vlog.h"
 
 static void usage(void);
-static const char *parse_command_line(int argc, char *argv[]);
+
+/* Parsed command line args. */
+struct cmdl_args {
+    enum ovs_output_fmt format;
+    char *target;
+};
+
+static struct cmdl_args *cmdl_args_create(void);
+static void cmdl_args_destroy(struct cmdl_args *);
+static struct cmdl_args *parse_command_line(int argc, char *argv[]);
 static struct jsonrpc *connect_to_target(const char *target);
 
 int
@@ -43,30 +52,30 @@ main(int argc, char *argv[])
     char *cmd_result, *cmd_error;
     struct jsonrpc *client;
     char *cmd, **cmd_argv;
-    const char *target;
+    struct cmdl_args *args;
     int cmd_argc;
     int error;
 
     set_program_name(argv[0]);
 
     /* Parse command line and connect to target. */
-    target = parse_command_line(argc, argv);
-    client = connect_to_target(target);
+    args = parse_command_line(argc, argv);
+    client = connect_to_target(args->target);
 
     /* Transact request and process reply. */
     cmd = argv[optind++];
     cmd_argc = argc - optind;
     cmd_argv = cmd_argc ? argv + optind : NULL;
     error = unixctl_client_transact(client, cmd, cmd_argc, cmd_argv,
-                                    &cmd_result, &cmd_error);
+                                    args->format, &cmd_result, &cmd_error);
     if (error) {
-        ovs_fatal(error, "%s: transaction error", target);
+        ovs_fatal(error, "%s: transaction error", args->target);
     }
 
     if (cmd_error) {
         jsonrpc_close(client);
         fputs(cmd_error, stderr);
-        ovs_error(0, "%s: server returned an error", target);
+        ovs_error(0, "%s: server returned an error", args->target);
         exit(2);
     } else if (cmd_result) {
         fputs(cmd_result, stdout);
@@ -74,6 +83,7 @@ main(int argc, char *argv[])
         OVS_NOT_REACHED();
     }
 
+    cmdl_args_destroy(args);
     jsonrpc_close(client);
     free(cmd_result);
     free(cmd_error);
@@ -101,13 +111,34 @@ Common commands:\n\
   vlog/reopen        Make the program reopen its log file\n\
 Other options:\n\
   --timeout=SECS     wait at most SECS seconds for a response\n\
+  -f, --format=FMT   Output format. One of: 'json', or 'text'\n\
+                     ('text', by default)\n\
   -h, --help         Print this helpful information\n\
   -V, --version      Display ovs-appctl version information\n",
            program_name, program_name);
     exit(EXIT_SUCCESS);
 }
 
-static const char *
+static struct cmdl_args *
+cmdl_args_create(void) {
+    struct cmdl_args *args = xmalloc(sizeof *args);
+
+    args->format = OVS_OUTPUT_FMT_TEXT;
+    args->target = NULL;
+
+    return args;
+}
+
+static void
+cmdl_args_destroy(struct cmdl_args *args) {
+    if (args->target) {
+        free(args->target);
+    }
+
+    free(args);
+}
+
+static struct cmdl_args *
 parse_command_line(int argc, char *argv[])
 {
     enum {
@@ -117,6 +148,7 @@ parse_command_line(int argc, char *argv[])
     static const struct option long_options[] = {
         {"target", required_argument, NULL, 't'},
         {"execute", no_argument, NULL, 'e'},
+        {"format", required_argument, NULL, 'f'},
         {"help", no_argument, NULL, 'h'},
         {"option", no_argument, NULL, 'o'},
         {"version", no_argument, NULL, 'V'},
@@ -126,11 +158,11 @@ parse_command_line(int argc, char *argv[])
     };
     char *short_options_ = ovs_cmdl_long_options_to_short_options(long_options);
     char *short_options = xasprintf("+%s", short_options_);
-    const char *target;
+
+    struct cmdl_args *args = cmdl_args_create();
     int e_options;
     unsigned int timeout = 0;
 
-    target = NULL;
     e_options = 0;
     for (;;) {
         int option;
@@ -141,10 +173,10 @@ parse_command_line(int argc, char *argv[])
         }
         switch (option) {
         case 't':
-            if (target) {
+            if (args->target) {
                 ovs_fatal(0, "-t or --target may be specified only once");
             }
-            target = optarg;
+            args->target = xstrdup(optarg);
             break;
 
         case 'e':
@@ -157,6 +189,12 @@ parse_command_line(int argc, char *argv[])
             }
             break;
 
+        case 'f':
+            if (!ovs_output_fmt_from_string(optarg, &args->format)) {
+                ovs_fatal(0, "value %s on -f or --format is invalid", optarg);
+            }
+            break;
+
         case 'h':
             usage();
             break;
@@ -194,7 +232,10 @@ parse_command_line(int argc, char *argv[])
                   "(use --help for help)");
     }
 
-    return target ? target : "ovs-vswitchd";
+    if (!args->target) {
+        args->target = xstrdup("ovs-vswitchd");
+    }
+    return args;
 }
 
 static struct jsonrpc *
diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c
index 56d7a942b..30104ddb2 100644
--- a/utilities/ovs-dpctl.c
+++ b/utilities/ovs-dpctl.c
@@ -63,6 +63,7 @@ main(int argc, char *argv[])
     fatal_ignore_sigpipe();
 
     dpctl_p.is_appctl = false;
+    dpctl_p.format = OVS_OUTPUT_FMT_TEXT;
     dpctl_p.output = dpctl_print;
     dpctl_p.usage = usage;
 
