diff --git a/cmd/root.go b/cmd/root.go
index 105ff6d..e41c35d 100644
--- a/cmd/root.go
+++ b/cmd/root.go
@@ -3,6 +3,7 @@ package cmd
 import (
 	"fmt"
 	"github.com/packwiz/packwiz/core"
+	"github.com/spf13/pflag"
 	"os"
 	"path/filepath"
 
@@ -11,7 +12,6 @@ import (
 )
 
 var packFile string
-var modsFolder string
 var cfgFile string
 
 // rootCmd represents the base command when called without any subcommands
@@ -38,8 +38,22 @@ func init() {
 	rootCmd.PersistentFlags().StringVar(&packFile, "pack-file", "pack.toml", "The modpack metadata file to use")
 	_ = viper.BindPFlag("pack-file", rootCmd.PersistentFlags().Lookup("pack-file"))
 
-	rootCmd.PersistentFlags().StringVar(&modsFolder, "mods-folder", "mods", "The default folder to store mod metadata files in")
-	_ = viper.BindPFlag("mods-folder", rootCmd.PersistentFlags().Lookup("mods-folder"))
+	// Make mods-folder an alias for meta-folder
+	viper.RegisterAlias("mods-folder", "meta-folder")
+	rootCmd.SetGlobalNormalizationFunc(func(f *pflag.FlagSet, name string) pflag.NormalizedName {
+		if name == "mods-folder" {
+			return "meta-folder"
+		}
+		return pflag.NormalizedName(name)
+	})
+
+	var metaFolder string
+	rootCmd.PersistentFlags().StringVar(&metaFolder, "meta-folder", "", "The folder in which new metadata files will be added, defaulting to a folder based on the category (mods, resourcepacks, etc; if the category is unknown the current directory is used)")
+	_ = viper.BindPFlag("meta-folder", rootCmd.PersistentFlags().Lookup("meta-folder"))
+
+	var metaFolderBase string
+	rootCmd.PersistentFlags().StringVar(&metaFolderBase, "meta-folder-base", ".", "The base folder from which meta-folder will be resolved, defaulting to the current directory (so you can put all mods/etc in a subfolder while still using the default behaviour)")
+	_ = viper.BindPFlag("meta-folder-base", rootCmd.PersistentFlags().Lookup("meta-folder-base"))
 
 	file, err := core.GetPackwizLocalStore()
 	if err != nil {
diff --git a/core/index.go b/core/index.go
index 5777271..3095de3 100644
--- a/core/index.go
+++ b/core/index.go
@@ -150,16 +150,9 @@ func (in *Index) updateFile(path string) error {
 	}
 
 	mod := false
-	// If the file has an extension of toml and is in the mods folder, set mod to true
-	absFileDir, err := filepath.Abs(filepath.Dir(path))
-	if err == nil {
-		modsDir := filepath.Join(in.GetPackRoot(), viper.GetString("mods-folder"))
-		absModsDir, err := filepath.Abs(modsDir)
-		if err == nil {
-			if absFileDir == absModsDir && strings.HasSuffix(filepath.Base(path), ".toml") {
-				mod = true
-			}
-		}
+	// If the file has an extension of pw.toml, set mod to true
+	if strings.HasSuffix(filepath.Base(path), MetaExtension) {
+		mod = true
 	}
 
 	return in.updateFileHashGiven(path, "sha256", hashString, mod)
@@ -341,7 +334,7 @@ func (in Index) FindMod(modName string) (string, bool) {
 	for _, v := range in.Files {
 		if v.MetaFile {
 			_, file := filepath.Split(v.File)
-			fileTrimmed := strings.TrimSuffix(file, ModExtension)
+			fileTrimmed := strings.TrimSuffix(file, MetaExtension)
 			if fileTrimmed == modName {
 				return filepath.Join(filepath.Dir(in.indexFile), filepath.FromSlash(v.File)), true
 			}
diff --git a/core/mod.go b/core/mod.go
index 9afa4e3..73a0274 100644
--- a/core/mod.go
+++ b/core/mod.go
@@ -72,9 +72,9 @@ func LoadMod(modFile string) (Mod, error) {
 	return mod, nil
 }
 
-// SetMetaName sets the mod metadata file from a given file name (to be put in the mods folder)
-func (m *Mod) SetMetaName(metaName string, index Index) string {
-	m.metaFile = ResolveMod(metaName, index)
+// SetMetaPath sets the file path of a metadata file
+func (m *Mod) SetMetaPath(metaFile string) string {
+	m.metaFile = metaFile
 	return m.metaFile
 }
 
diff --git a/core/resolve.go b/core/resolve.go
index 571d343..19494f5 100644
--- a/core/resolve.go
+++ b/core/resolve.go
@@ -1,19 +1,5 @@
 package core
 
-import (
-	"path/filepath"
-	"strings"
-
-	"github.com/spf13/viper"
-)
-
-// ModExtension is the file extension of the mod metadata files
-const ModExtension = ".toml"
-
-// ResolveMod returns the path to a mod file from it's name
-func ResolveMod(modName string, index Index) string {
-	// TODO: should this work for any metadata file?
-	fileName := strings.ToLower(strings.TrimSuffix(modName, ModExtension)) + ModExtension
-	modsDir := filepath.Join(index.GetPackRoot(), viper.GetString("mods-folder"))
-	return filepath.Join(modsDir, fileName)
-}
+// MetaExtension is the file extension of the mod metadata files
+// Note that this is currently not required; it will only be used for new files.
+const MetaExtension = ".pw.toml"
diff --git a/curseforge/curseforge.go b/curseforge/curseforge.go
index 4843275..7972fef 100644
--- a/curseforge/curseforge.go
+++ b/curseforge/curseforge.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"github.com/spf13/viper"
 	"golang.org/x/exp/slices"
+	"path/filepath"
 	"regexp"
 	"strconv"
 	"strings"
@@ -136,8 +137,7 @@ func parseSlugOrUrl(url string) (game string, category string, slug string, file
 	return
 }
 
-// TODO: put projects into folders based on these
-var defaultFolders = map[int]map[int]string{
+var defaultFolders = map[uint32]map[uint32]string{
 	432: { // Minecraft
 		5:  "plugins", // Bukkit Plugins
 		12: "resourcepacks",
@@ -146,6 +146,21 @@ var defaultFolders = map[int]map[int]string{
 	},
 }
 
+func getPathForFile(gameID uint32, classID uint32, categoryID uint32, slug string) string {
+	metaFolder := viper.GetString("meta-folder")
+	if metaFolder == "" {
+		if m, ok := defaultFolders[gameID]; ok {
+			if folder, ok := m[classID]; ok {
+				return filepath.Join(viper.GetString("meta-folder-base"), folder, slug+core.MetaExtension)
+			} else if folder, ok := m[categoryID]; ok {
+				return filepath.Join(viper.GetString("meta-folder-base"), folder, slug+core.MetaExtension)
+			}
+		}
+		metaFolder = "."
+	}
+	return filepath.Join(viper.GetString("meta-folder-base"), metaFolder, slug+core.MetaExtension)
+}
+
 func createModFile(modInfo modInfo, fileInfo modFileInfo, index *core.Index, optionalDisabled bool) error {
 	updateMap := make(map[string]map[string]interface{})
 	var err error
@@ -179,7 +194,7 @@ func createModFile(modInfo modInfo, fileInfo modFileInfo, index *core.Index, opt
 		Option: optional,
 		Update: updateMap,
 	}
-	path := modMeta.SetMetaName(modInfo.Slug, *index)
+	path := modMeta.SetMetaPath(getPathForFile(modInfo.GameID, modInfo.ClassID, modInfo.PrimaryCategoryID, modInfo.Slug))
 
 	// If the file already exists, this will overwrite it!!!
 	// TODO: Should this be improved?
diff --git a/curseforge/import.go b/curseforge/import.go
index 8b8bb91..b20ebb1 100644
--- a/curseforge/import.go
+++ b/curseforge/import.go
@@ -266,8 +266,7 @@ var importCmd = &cobra.Command{
 				os.Exit(1)
 			}
 
-			// TODO: just use mods-folder directly? does texture pack importing affect this?
-			modFilePath := core.ResolveMod(modInfoValue.Slug, index)
+			modFilePath := getPathForFile(modInfoValue.GameID, modInfoValue.ClassID, modInfoValue.PrimaryCategoryID, modInfoValue.Slug)
 			ref, err := filepath.Abs(filepath.Join(filepath.Dir(modFilePath), modFileInfoValue.FileName))
 			if err == nil {
 				referencedModPaths = append(referencedModPaths, ref)
diff --git a/curseforge/request.go b/curseforge/request.go
index 5154f95..e8a3ba6 100644
--- a/curseforge/request.go
+++ b/curseforge/request.go
@@ -134,6 +134,9 @@ type modInfo struct {
 	Summary                string        `json:"summary"`
 	Slug                   string        `json:"slug"`
 	ID                     int           `json:"id"`
+	GameID                 uint32        `json:"gameId"`
+	PrimaryCategoryID      uint32        `json:"primaryCategoryId"`
+	ClassID                uint32        `json:"classId"`
 	LatestFiles            []modFileInfo `json:"latestFiles"`
 	GameVersionLatestFiles []struct {
 		// TODO: check how twitch launcher chooses which one to use, when you are on beta/alpha channel?!
diff --git a/modrinth/install.go b/modrinth/install.go
index b405d50..796fdb7 100644
--- a/modrinth/install.go
+++ b/modrinth/install.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"github.com/spf13/viper"
 	"os"
+	"path/filepath"
 	"regexp"
 	"strings"
 
@@ -207,10 +208,14 @@ func installVersion(mod Mod, version Version, pack core.Pack) error {
 		Update: updateMap,
 	}
 	var path string
+	folder := viper.GetString("meta-folder")
+	if folder == "" {
+		folder = "mods"
+	}
 	if mod.Slug != "" {
-		path = modMeta.SetMetaName(mod.Slug, index)
+		path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, mod.Slug+core.MetaExtension))
 	} else {
-		path = modMeta.SetMetaName(mod.Title, index)
+		path = modMeta.SetMetaPath(filepath.Join(viper.GetString("meta-folder-base"), folder, mod.Title+core.MetaExtension))
 	}
 
 	// If the file already exists, this will overwrite it!!!