diff --git a/Makefile b/Makefile
index 5c41cddb3979f628e9ae0ef94b01368d35fe0ba4..d78efbd7923e764829d2c3391eb5dd6a887fc78d 100644
--- a/Makefile
+++ b/Makefile
@@ -29,8 +29,12 @@ cashierd: generate
 clean:
 	rm -f cashier cashierd
 
+# usage: make migration name=whatever
+migration:
+	go run ./generate/migration/migration.go $(name)
+
 dep:
 	go get -u github.com/golang/lint/golint
 	go get -u golang.org/x/tools/cmd/goimports
 
-.PHONY: all build dep generate test cashier cashierd clean
+.PHONY: all build dep generate test cashier cashierd clean migration
diff --git a/generate/migration/migration.go b/generate/migration/migration.go
new file mode 100644
index 0000000000000000000000000000000000000000..37515bfa867a031c36e226a8e5d44b79c314a739
--- /dev/null
+++ b/generate/migration/migration.go
@@ -0,0 +1,53 @@
+package main
+
+import (
+	"flag"
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os/exec"
+	"path"
+	"strings"
+	"time"
+)
+
+const (
+	dateFormat     = "20060102150405"
+	migrationsPath = "server/store/migrations"
+)
+
+var (
+	contents = []byte(`-- +migrate Up
+
+
+-- +migrate Down`)
+)
+
+func main() {
+	flag.Usage = func() {
+		fmt.Println("Usage: migration <migration name>")
+	}
+	flag.Parse()
+	if len(flag.Args()) != 1 {
+		flag.Usage()
+	}
+	name := fmt.Sprintf("%s_%s.sql", time.Now().UTC().Format(dateFormat), flag.Arg(0))
+	gitRoot, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
+	if err != nil {
+		log.Fatal(err)
+	}
+	root := strings.TrimSpace(string(gitRoot))
+	ents, err := ioutil.ReadDir(path.Join(root, migrationsPath))
+	if err != nil {
+		log.Fatal(err)
+	}
+	for _, e := range ents {
+		if e.IsDir() {
+			filename := path.Join(migrationsPath, e.Name(), name)
+			fmt.Printf("Wrote empty migration file: %s\n", filename)
+			if err := ioutil.WriteFile(filename, contents, 0644); err != nil {
+				log.Fatal(err)
+			}
+		}
+	}
+}
diff --git a/generate/static.go b/generate/static/static.go
similarity index 100%
rename from generate/static.go
rename to generate/static/static.go