diff --git a/mux/src/tab.rs b/mux/src/tab.rs
index 1b46ff170bb1..f7c178b64ae0 100644
--- a/mux/src/tab.rs
+++ b/mux/src/tab.rs
@@ -745,6 +745,10 @@ impl Tab {
             .lock()
             .split_and_insert(pane_index, request, pane)
     }
+
+    pub fn get_zoomed_pane(&self) -> Option<Arc<dyn Pane>> {
+        self.inner.lock().get_zoomed_pane()
+    }
 }
 
 impl TabInner {
@@ -2051,6 +2055,10 @@ impl TabInner {
             pane_index
         })
     }
+
+    fn get_zoomed_pane(&self) -> Option<Arc<dyn Pane>> {
+        self.zoomed.clone()
+    }
 }
 
 /// This type is used directly by the codec, take care to bump
diff --git a/wezterm-mux-server-impl/src/sessionhandler.rs b/wezterm-mux-server-impl/src/sessionhandler.rs
index 31df12dad8c2..977c6a7b7fa6 100644
--- a/wezterm-mux-server-impl/src/sessionhandler.rs
+++ b/wezterm-mux-server-impl/src/sessionhandler.rs
@@ -532,8 +532,11 @@ impl SessionHandler {
                             let tab = mux
                                 .get_tab(containing_tab_id)
                                 .ok_or_else(|| anyhow!("no such tab {}", containing_tab_id))?;
-                            tab.set_active_pane(&pane);
-                            tab.set_zoomed(zoomed);
+                            tab.set_zoomed(false);
+                            if zoomed {
+                                tab.set_active_pane(&pane);
+                                tab.set_zoomed(zoomed);
+                            }
                             Ok(Pdu::UnitResponse(UnitResponse {}))
                         },
                         send_response,
@@ -1021,7 +1024,7 @@ async fn split_pane(split: SplitPane, client_id: Option<Arc<ClientId>>) -> anyho
 
     Ok::<Pdu, anyhow::Error>(Pdu::SpawnResponse(SpawnResponse {
         pane_id: pane.pane_id(),
-        tab_id: tab_id,
+        tab_id,
         window_id,
         size,
     }))
diff --git a/wezterm/src/cli/mod.rs b/wezterm/src/cli/mod.rs
index 0dbd5bd09cfa..95e90bf89cb1 100644
--- a/wezterm/src/cli/mod.rs
+++ b/wezterm/src/cli/mod.rs
@@ -22,6 +22,7 @@ mod set_window_title;
 mod spawn_command;
 mod split_pane;
 mod tls_creds;
+mod zoom_pane;
 
 #[derive(Debug, Parser, Clone, Copy)]
 enum CliOutputFormatKind {
@@ -159,6 +160,10 @@ Outputs the pane-id for the newly created pane on success"
     /// Rename a workspace
     #[command(name = "rename-workspace", rename_all = "kebab")]
     RenameWorkspace(rename_workspace::RenameWorkspace),
+
+    /// Zoom, unzoom, or toggle zoom state
+    #[command(name = "zoom-pane", rename_all = "kebab")]
+    ZoomPane(zoom_pane::ZoomPane),
 }
 
 async fn run_cli_async(opts: &crate::Opt, cli: CliCommand) -> anyhow::Result<()> {
@@ -194,6 +199,7 @@ async fn run_cli_async(opts: &crate::Opt, cli: CliCommand) -> anyhow::Result<()>
         CliSubCommand::SetTabTitle(cmd) => cmd.run(client).await,
         CliSubCommand::SetWindowTitle(cmd) => cmd.run(client).await,
         CliSubCommand::RenameWorkspace(cmd) => cmd.run(client).await,
+        CliSubCommand::ZoomPane(cmd) => cmd.run(client).await,
     }
 }
 
diff --git a/wezterm/src/cli/zoom_pane.rs b/wezterm/src/cli/zoom_pane.rs
new file mode 100644
index 000000000000..f1ce57f75a56
--- /dev/null
+++ b/wezterm/src/cli/zoom_pane.rs
@@ -0,0 +1,109 @@
+use crate::cli::resolve_pane_id;
+use anyhow::{anyhow, Result};
+use clap::Parser;
+use codec::SetPaneZoomed;
+use mux::pane::PaneId;
+use std::collections::HashMap;
+use wezterm_client::client::Client;
+
+#[derive(Debug, Parser, Clone)]
+pub struct ZoomPane {
+    /// Specify the target pane.
+    /// The default is to use the current pane based on the
+    /// environment variable WEZTERM_PANE.
+    #[arg(long)]
+    pane_id: Option<PaneId>,
+
+    /// Zooms the pane if it wasn't already zoomed
+    #[arg(long, default_value = "true")]
+    zoom: bool,
+
+    /// Unzooms the pane if it was zoomed
+    #[arg(long)]
+    unzoom: bool,
+
+    /// Toggles the zoom state of the pane
+    #[arg(long)]
+    toggle: bool,
+}
+
+impl ZoomPane {
+    pub async fn run(&self, client: Client) -> Result<()> {
+        let panes = client.list_panes().await?;
+
+        let mut pane_id_to_tab_id = HashMap::new();
+        let mut tab_id_to_active_zoomed_pane_id = HashMap::new();
+
+        for tabroot in panes.tabs {
+            let mut cursor = tabroot.into_tree().cursor();
+
+            loop {
+                if let Some(entry) = cursor.leaf_mut() {
+                    pane_id_to_tab_id.insert(entry.pane_id, entry.tab_id);
+                    if entry.is_active_pane && entry.is_zoomed_pane {
+                        tab_id_to_active_zoomed_pane_id.insert(entry.tab_id, entry.pane_id);
+                    }
+                }
+                match cursor.preorder_next() {
+                    Ok(c) => cursor = c,
+                    Err(_) => break,
+                }
+            }
+        }
+
+        let pane_id = resolve_pane_id(&client, self.pane_id).await?;
+        let containing_tab_id = pane_id_to_tab_id
+            .get(&pane_id)
+            .copied()
+            .ok_or_else(|| anyhow!("unable to resolve current tab"))?;
+
+        if self.zoom {
+            client
+                .set_zoomed(SetPaneZoomed {
+                    containing_tab_id,
+                    pane_id,
+                    zoomed: true,
+                })
+                .await?;
+        }
+
+        if self.unzoom {
+            client
+                .set_zoomed(SetPaneZoomed {
+                    containing_tab_id,
+                    pane_id,
+                    zoomed: false,
+                })
+                .await?;
+        }
+
+        if self.toggle {
+            if tab_id_to_active_zoomed_pane_id.contains_key(&containing_tab_id) {
+                let target_pane = tab_id_to_active_zoomed_pane_id
+            .get(&containing_tab_id)
+            .copied()
+            .ok_or_else(|| {
+                anyhow!("could not determine which pane should be active for tab {containing_tab_id}")
+            })?;
+                if target_pane == pane_id {
+                    client
+                        .set_zoomed(SetPaneZoomed {
+                            containing_tab_id,
+                            pane_id,
+                            zoomed: false,
+                        })
+                        .await?;
+                }
+            } else {
+                client
+                    .set_zoomed(SetPaneZoomed {
+                        containing_tab_id,
+                        pane_id,
+                        zoomed: true,
+                    })
+                    .await?;
+            }
+        }
+        Ok(())
+    }
+}