diff --git a/main.go b/main.go
index a06120d..7638a14 100644
--- a/main.go
+++ b/main.go
@@ -4,6 +4,7 @@ import (
 	// Modules of packwiz
 	"github.com/comp500/packwiz/cmd"
 	_ "github.com/comp500/packwiz/curseforge"
+	_ "github.com/comp500/packwiz/utils"
 )
 
 func main() {
diff --git a/utils/completion.go b/utils/completion.go
new file mode 100644
index 0000000..bcd20ed
--- /dev/null
+++ b/utils/completion.go
@@ -0,0 +1,186 @@
+package utils
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"strings"
+
+	"github.com/spf13/cobra"
+	"github.com/spf13/viper"
+)
+
+// completionCmd represents the completion command
+var completionCmd = &cobra.Command{
+	Use:   "completion [bash/powershell/zsh]",
+	Short: "Installs bash/powershell/zsh completion commands",
+	Long: `Installs bash/powershell/zsh completion commands.
+Please note that the completions may be incomplete or broken, see https://github.com/spf13/cobra`,
+	Args:      cobra.ExactValidArgs(1),
+	ValidArgs: []string{"bash", "powershell", "zsh"},
+	Run: func(cmd *cobra.Command, args []string) {
+		if args[0] == "bash" {
+			if viper.GetBool("utils.completion.source") {
+				cmd.Root().GenBashCompletion(os.Stdout)
+			} else {
+				file, err := getConfigPath("completion.sh")
+				if err != nil {
+					fmt.Printf("Error saving completion file: %s\n", err)
+					os.Exit(1)
+				}
+				err = cmd.Root().GenBashCompletionFile(file)
+				if err != nil {
+					fmt.Printf("Error saving completion file: %s\n", err)
+					os.Exit(1)
+				}
+
+				// Get the value of $HOME
+				home, err := os.UserHomeDir()
+				if err != nil {
+					fmt.Printf("Failed to get $HOME location: %s\n", err)
+					os.Exit(1)
+				}
+				bashrc := filepath.Join(home, ".bashrc")
+
+				absFile, err := filepath.Abs(file)
+				if err != nil {
+					fmt.Printf("Failed to resolve path: %s\n", err)
+					os.Exit(1)
+				}
+				command := ". " + absFile
+				commandFound := false
+				// Check for existing text in bashrc
+				data, err := ioutil.ReadFile(bashrc)
+				if err == nil {
+					commandFound = strings.Contains(string(data), command)
+				}
+				if !commandFound {
+					// Append to bashrc
+					os.MkdirAll(filepath.Dir(bashrc), os.ModePerm)
+					f, err := os.OpenFile(bashrc, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+					if err != nil {
+						fmt.Printf("Failed to open bashrc: %s\n", err)
+						os.Exit(1)
+					}
+					_, err = f.WriteString("\r\n" + command + "\r\n")
+					if err != nil {
+						fmt.Printf("Failed to write to bashrc: %s\n", err)
+						f.Close()
+						os.Exit(1)
+					}
+					f.Close()
+				}
+				fmt.Println("Completions installed! Restart your shell to load them.")
+			}
+		} else if args[0] == "powershell" {
+			if viper.GetBool("utils.completion.source") {
+				cmd.Root().GenPowerShellCompletion(os.Stdout)
+			} else {
+				file, err := getConfigPath("completion.ps1")
+				if err != nil {
+					fmt.Printf("Error saving completion file: %s\n", err)
+					os.Exit(1)
+				}
+				err = cmd.Root().GenPowerShellCompletionFile(file)
+				if err != nil {
+					fmt.Printf("Error saving completion file: %s\n", err)
+					os.Exit(1)
+				}
+
+				// Get the value of $PROFILE (not an environment variable!!)
+				profile, err := getPowershellProfile()
+				if err != nil {
+					fmt.Printf("Failed to get profile location: %s\n", err)
+					os.Exit(1)
+				}
+
+				absFile, err := filepath.Abs(file)
+				if err != nil {
+					fmt.Printf("Failed to resolve path: %s\n", err)
+					os.Exit(1)
+				}
+				command := ". " + absFile
+				commandFound := false
+				// Check for existing text in profile
+				data, err := ioutil.ReadFile(profile)
+				if err == nil {
+					commandFound = strings.Contains(string(data), command)
+				}
+				if !commandFound {
+					// Append to profile
+					os.MkdirAll(filepath.Dir(profile), os.ModePerm)
+					f, err := os.OpenFile(profile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
+					if err != nil {
+						fmt.Printf("Failed to open profile: %s\n", err)
+						os.Exit(1)
+					}
+					_, err = f.WriteString("\r\n" + command + "\r\n")
+					if err != nil {
+						fmt.Printf("Failed to write to profile: %s\n", err)
+						f.Close()
+						os.Exit(1)
+					}
+					f.Close()
+				}
+				fmt.Println("Completions installed! Restart your shell to load them.")
+			}
+		} else if args[0] == "zsh" {
+			if viper.GetBool("utils.completion.source") {
+				cmd.Root().GenZshCompletion(os.Stdout)
+			} else {
+				file, err := getConfigPath("completion.zsh")
+				if err != nil {
+					fmt.Printf("Error saving completion file: %s\n", err)
+					os.Exit(1)
+				}
+				err = cmd.Root().GenZshCompletionFile(file)
+				if err != nil {
+					fmt.Printf("Error saving completion file: %s\n", err)
+					os.Exit(1)
+				}
+
+				// If someone knows how to do this automagically, please PR it!!
+				fmt.Println("Completions saved to " + file)
+				fmt.Println("You need to put this file in your $fpath manually!")
+			}
+		}
+	},
+}
+
+func getConfigPath(fileName string) (string, error) {
+	dir, err := os.UserConfigDir()
+	if err != nil {
+		return "", err
+	}
+	dir = filepath.Join(dir, "packwiz")
+	err = os.MkdirAll(dir, os.ModePerm)
+	if err != nil {
+		return "", err
+	}
+	return filepath.Join(dir, fileName), nil
+}
+
+func getPowershellProfile() (string, error) {
+	ps, err := exec.LookPath("powershell.exe")
+	if err != nil {
+		return "", err
+	}
+	cmd := exec.Command(ps, "-NoProfile", "-NonInteractive", "-Command", "$PROFILE")
+	var stdout bytes.Buffer
+	cmd.Stdout = &stdout
+	err = cmd.Run()
+	if err != nil {
+		return "", err
+	}
+	return strings.TrimSpace(stdout.String()), nil
+}
+
+func init() {
+	utilsCmd.AddCommand(completionCmd)
+
+	completionCmd.Flags().Bool("source", false, "Output the source of the commands to be installed, rather than installing them")
+	viper.BindPFlag("utils.completion.source", completionCmd.Flags().Lookup("source"))
+}
diff --git a/utils/utils.go b/utils/utils.go
new file mode 100644
index 0000000..eb8436c
--- /dev/null
+++ b/utils/utils.go
@@ -0,0 +1,16 @@
+package utils
+
+import (
+	"github.com/comp500/packwiz/cmd"
+	"github.com/spf13/cobra"
+)
+
+// utilsCmd represents the base command when called without any subcommands
+var utilsCmd = &cobra.Command{
+	Use:   "utils",
+	Short: "Utilities for managing packwiz itself",
+}
+
+func init() {
+	cmd.Add(utilsCmd)
+}