diff --git a/src/bin/spin.rs b/src/bin/spin.rs
index 16038aed9d..a08815e57e 100644
--- a/src/bin/spin.rs
+++ b/src/bin/spin.rs
@@ -8,6 +8,7 @@ use spin_cli::commands::{
     cloud::{DeployCommand, LoginCommand},
     doctor::DoctorCommand,
     external::execute_external_subcommand,
+    generate_completions::GenerateCompletionsCommand,
     new::{AddCommand, NewCommand},
     plugins::PluginCommands,
     registry::RegistryCommands,
@@ -129,8 +130,8 @@ enum SpinApp {
     Plugins(PluginCommands),
     #[clap(subcommand, hide = true)]
     Trigger(TriggerCommands),
-    #[clap(hide = true, name = "generate-bash-completions")]
-    GenerateBashCompletions,
+    #[clap(hide = true, name = "generate-completions")]
+    GenerateCompletions(GenerateCompletionsCommand),
     #[clap(external_subcommand)]
     External(Vec<String>),
     Watch(WatchCommand),
@@ -164,11 +165,7 @@ impl SpinApp {
             Self::External(cmd) => execute_external_subcommand(cmd, app).await,
             Self::Watch(cmd) => cmd.run().await,
             Self::Doctor(cmd) => cmd.run().await,
-            Self::GenerateBashCompletions => {
-                let mut cmd: clap::Command = SpinApp::into_app();
-                print_completions(clap_complete::Shell::Bash, &mut cmd);
-                Ok(())
-            }
+            Self::GenerateCompletions(cmd) => cmd.run(SpinApp::into_app()).await,
         }
     }
 }
@@ -227,7 +224,3 @@ fn installed_plugin_help_entries() -> Vec<PluginHelpEntry> {
 fn hide_plugin_in_help(plugin: &spin_plugins::manifest::PluginManifest) -> bool {
     plugin.name().starts_with("trigger-")
 }
-
-fn print_completions<G: clap_complete::Generator>(gen: G, cmd: &mut clap::Command) {
-    clap_complete::generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout())
-}
diff --git a/src/commands.rs b/src/commands.rs
index ad8f039728..bf99aaaf98 100644
--- a/src/commands.rs
+++ b/src/commands.rs
@@ -8,6 +8,8 @@ pub mod cloud;
 pub mod doctor;
 /// Commands for external subcommands (i.e. plugins)
 pub mod external;
+/// Command to generate shell completions
+pub mod generate_completions;
 /// Command for creating a new application.
 pub mod new;
 /// Command for adding a plugin to Spin
diff --git a/src/commands/generate_completions.rs b/src/commands/generate_completions.rs
new file mode 100644
index 0000000000..7fe37827b3
--- /dev/null
+++ b/src/commands/generate_completions.rs
@@ -0,0 +1,22 @@
+use anyhow::Result;
+use clap::Parser;
+
+/// Generate shell completions.
+#[derive(Parser, Debug)]
+#[clap(about = "Generate completions")]
+pub struct GenerateCompletionsCommand {
+    #[clap(value_parser = clap::value_parser!(clap_complete::Shell))]
+    pub shell: clap_complete::Shell,
+}
+
+impl GenerateCompletionsCommand {
+    pub async fn run(&self, mut cmd: clap::Command<'_>) -> Result<()> {
+        // let mut cmd: clap::Command = SpinApp::into_app();
+        print_completions(self.shell, &mut cmd);
+        Ok(())
+    }
+}
+
+fn print_completions<G: clap_complete::Generator>(gen: G, cmd: &mut clap::Command) {
+    clap_complete::generate(gen, cmd, cmd.get_name().to_string(), &mut std::io::stdout())
+}