diff --git a/backend/migrations/20220309170000_pgcrypto.ts b/backend/migrations/20220309170000_pgcrypto.ts
new file mode 100644
index 0000000000000000000000000000000000000000..4dc10e78a82ed305c9c9c6e3f7a354df5563ca4f
--- /dev/null
+++ b/backend/migrations/20220309170000_pgcrypto.ts
@@ -0,0 +1,9 @@
+import { Knex } from "knex";
+
+export async function up(knex: Knex): Promise<void> {
+  return knex.schema.raw('CREATE EXTENSION IF NOT EXISTS "pgcrypto"');
+}
+
+export async function down(knex: Knex): Promise<void> {
+  return knex.schema.raw('DROP EXTENSION IF EXISTS "pgcrypto"');
+}
diff --git a/backend/migrations/20220309170943_words.ts b/backend/migrations/20220309170943_words.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b8922aefed3c04c18ecfcaccd1b5aec106972aee
--- /dev/null
+++ b/backend/migrations/20220309170943_words.ts
@@ -0,0 +1,17 @@
+import { Knex } from "knex";
+
+export async function up(knex: Knex): Promise<void> {
+  return knex.schema.createTable("words", function (table) {
+    table.uuid("id").primary().defaultTo(knex.raw("gen_random_uuid()"));
+    table.string("page_id").index().notNullable();
+    table.integer("word_id").index().notNullable();
+    table.unique(["page_id", "word_id"]);
+    table.binary("image").notNullable();
+    table.string("ocr_transcript");
+    table.float("ocr_confidence");
+  });
+}
+
+export async function down(knex: Knex): Promise<void> {
+  return knex.schema.dropTable("words");
+}