diff --git a/qapi/block-export.json b/qapi/block-export.json
index d4e9290210..ebdfa978e0 100644
--- a/qapi/block-export.json
+++ b/qapi/block-export.json
@@ -74,9 +74,6 @@
 # @description: Free-form description of the export, up to 4096 bytes.
 #               (Since 5.0)
 #
-# @writable: Whether clients should be able to write to the device via the
-#            NBD connection (default false).
-#
 # @bitmap: Also export the dirty bitmap reachable from @device, so the
 #          NBD client can use NBD_OPT_SET_META_CONTEXT with
 #          "qemu:dirty-bitmap:NAME" to inspect the bitmap. (since 4.0)
@@ -85,7 +82,7 @@
 ##
 { 'struct': 'BlockExportOptionsNbd',
   'data': { '*name': 'str', '*description': 'str',
-            '*writable': 'bool', '*bitmap': 'str' } }
+            '*bitmap': 'str' } }
 
 ##
 # @NbdServerAddOptions:
@@ -94,11 +91,15 @@
 #
 # @device: The device name or node name of the node to be exported
 #
+# @writable: Whether clients should be able to write to the device via the
+#            NBD connection (default false).
+#
 # Since: 5.0
 ##
 { 'struct': 'NbdServerAddOptions',
   'base': 'BlockExportOptionsNbd',
-  'data': { 'device': 'str' } }
+  'data': { 'device': 'str',
+            '*writable': 'bool' } }
 
 ##
 # @nbd-server-add:
@@ -188,6 +189,9 @@
 #
 # @node-name: The node name of the block node to be exported (since: 5.2)
 #
+# @writable: True if clients should be able to write to the export
+#            (default false)
+#
 # @writethrough: If true, caches are flushed after every write request to the
 #                export before completion is signalled. (since: 5.2;
 #                default: false)
@@ -198,6 +202,7 @@
   'base': { 'type': 'BlockExportType',
             'id': 'str',
             'node-name': 'str',
+            '*writable': 'bool',
             '*writethrough': 'bool' },
   'discriminator': 'type',
   'data': {
diff --git a/block/export/export.c b/block/export/export.c
index bbdb256267..87516d1e05 100644
--- a/block/export/export.c
+++ b/block/export/export.c
@@ -62,6 +62,7 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
     BlockDriverState *bs;
     BlockBackend *blk;
     AioContext *ctx;
+    uint64_t perm;
     int ret;
 
     if (!id_wellformed(export->id)) {
@@ -84,6 +85,14 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
         return NULL;
     }
 
+    if (!export->has_writable) {
+        export->writable = false;
+    }
+    if (bdrv_is_read_only(bs) && export->writable) {
+        error_setg(errp, "Cannot export read-only node as writable");
+        return NULL;
+    }
+
     ctx = bdrv_get_aio_context(bs);
     aio_context_acquire(ctx);
 
@@ -95,7 +104,12 @@ BlockExport *blk_exp_add(BlockExportOptions *export, Error **errp)
      */
     bdrv_invalidate_cache(bs, NULL);
 
-    blk = blk_new(ctx, BLK_PERM_CONSISTENT_READ, BLK_PERM_ALL);
+    perm = BLK_PERM_CONSISTENT_READ;
+    if (export->writable) {
+        perm |= BLK_PERM_WRITE;
+    }
+
+    blk = blk_new(ctx, perm, BLK_PERM_ALL);
     ret = blk_insert_bs(blk, bs, errp);
     if (ret < 0) {
         goto fail;
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
index cdbbcdb958..30e165c23f 100644
--- a/blockdev-nbd.c
+++ b/blockdev-nbd.c
@@ -204,16 +204,8 @@ int nbd_export_create(BlockExport *exp, BlockExportOptions *exp_args,
         return -EEXIST;
     }
 
-    if (!arg->has_writable) {
-        arg->writable = false;
-    }
-    if (blk_is_read_only(exp->blk) && arg->writable) {
-        error_setg(errp, "Cannot export read-only node as writable");
-        return -EINVAL;
-    }
-
     return nbd_export_new(exp, arg->name, arg->description, arg->bitmap,
-                          !arg->writable, !arg->writable, errp);
+                          !exp_args->writable, !exp_args->writable, errp);
 }
 
 void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
@@ -241,13 +233,13 @@ void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
         .type                   = BLOCK_EXPORT_TYPE_NBD,
         .id                     = g_strdup(arg->name),
         .node_name              = g_strdup(bdrv_get_node_name(bs)),
+        .has_writable           = arg->has_writable,
+        .writable               = arg->writable,
         .u.nbd = {
             .has_name           = true,
             .name               = g_strdup(arg->name),
             .has_description    = arg->has_description,
             .description        = g_strdup(arg->description),
-            .has_writable       = arg->has_writable,
-            .writable           = arg->writable,
             .has_bitmap         = arg->has_bitmap,
             .bitmap             = g_strdup(arg->bitmap),
         },
@@ -259,8 +251,8 @@ void qmp_nbd_server_add(NbdServerAddOptions *arg, Error **errp)
      * block-export-add.
      */
     if (bdrv_is_read_only(bs)) {
-        export_opts->u.nbd.has_writable = true;
-        export_opts->u.nbd.writable = false;
+        export_opts->has_writable = true;
+        export_opts->writable = false;
     }
 
     export = blk_exp_add(export_opts, errp);
diff --git a/nbd/server.c b/nbd/server.c
index 6c8532de23..465ec9e762 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -1530,11 +1530,6 @@ int nbd_export_new(BlockExport *blk_exp,
     /* Don't allow resize while the NBD server is running, otherwise we don't
      * care what happens with the node. */
     blk_get_perm(blk, &perm, &shared_perm);
-
-    if (!readonly) {
-        perm |= BLK_PERM_WRITE;
-    }
-
     ret = blk_set_perm(blk, perm, shared_perm & ~BLK_PERM_RESIZE, errp);
     if (ret < 0) {
         return ret;
diff --git a/qemu-nbd.c b/qemu-nbd.c
index ac82acb4ac..6d7ac7490f 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -1068,13 +1068,13 @@ int main(int argc, char **argv)
         .node_name          = g_strdup(bdrv_get_node_name(bs)),
         .has_writethrough   = true,
         .writethrough       = writethrough,
+        .has_writable       = true,
+        .writable           = !readonly,
         .u.nbd = {
             .has_name           = true,
             .name               = g_strdup(export_name),
             .has_description    = !!export_description,
             .description        = g_strdup(export_description),
-            .has_writable       = true,
-            .writable           = !readonly,
             .has_bitmap         = !!bitmap,
             .bitmap             = g_strdup(bitmap),
         },
