diff --git a/docs/src/content/docs/reference/migration-types.mdx b/docs/src/content/docs/reference/migration-types.mdx
new file mode 100644
index 0000000..b47f5c0
--- /dev/null
+++ b/docs/src/content/docs/reference/migration-types.mdx
@@ -0,0 +1,93 @@
+---
+title: Migration Types
+---
+
+import Link from '@components/Link.astro';
+
+These are common TypeScript types for migration files used by plugins.
+
+## `MigrationMetadata`
+
+```ts
+type MigrationMetadata = {
+ /**
+ * The name of the migration file
+ *
+ * @example 20210901123456000_create_users_table.js
+ */
+ name: string;
+ /**
+ * The directory where the migration file is located, relative to the current working directory
+ *
+ * @example migrations
+ */
+ directory: string;
+ /**
+ * The full absolute path to the migration file
+ *
+ * @example /home/user/project/migrations/20210901123456000_create_users_table.js
+ */
+ filePath: string;
+ /**
+ * The relative path to the migration file, relative to the current working directory
+ *
+ * @example migrations/20210901123456000_create_users_table.js
+ */
+ relativeFilePath: string;
+ /**
+ * The current working directory (the same as process.cwd())
+ */
+ cwd: string;
+ /**
+ * The extension of the migration file, with a leading period
+ *
+ * @example .js
+ */
+ extension: string;
+};
+```
+
+## `MigrationMetadataFinished`
+
+```ts
+type MigrationMetadataFinished = MigrationMetadata & {
+ status: 'done' | 'failed' | 'skipped' | 'pending';
+ /**
+ * The duration of the migration in milliseconds
+ */
+ duration: number;
+ /**
+ * The error that occurred during the migration, if `status` is `"failed"`
+ */
+ error?: Error;
+};
+```
+
+## `MigrationHistoryEntry`
+
+```ts
+type MigrationHistoryEntry = {
+ /**
+ * The name of the migration.
+ *
+ * @example 20210901123456000_create_users_table.js
+ */
+ name: string;
+ /**
+ * The date when the migration was executed.
+ */
+ date: Date;
+ /**
+ * The status of the migration.
+ *
+ * As an entry is only added to the history after the migration has finished, this will always be either `"done"` or `"failed"`.
+ */
+ status: 'done' | 'failed';
+ /**
+ * The error that occurred during the migration, if `status` is `"failed"`
+ *
+ * This should be a plain object, as it is serialized when passed to the storage plugin's `onError` method.
+ */
+ error?: Record;
+};
+```
diff --git a/docs/src/content/docs/reference/storage-plugin-api.mdx b/docs/src/content/docs/reference/storage-plugin-api.mdx
new file mode 100644
index 0000000..5840fe9
--- /dev/null
+++ b/docs/src/content/docs/reference/storage-plugin-api.mdx
@@ -0,0 +1,86 @@
+---
+title: Storage Plugin API
+---
+
+import Link from '@components/Link.astro';
+
+When writing a storage plugin, you will need to implement the following interface:
+
+```ts
+type EmigrateStorage = {
+ initializeStorage(): Promise;
+};
+```
+
+Where `Storage` is the following interface:
+
+```ts
+type Storage = {
+ /**
+ * Acquire a lock on the given migrations.
+ *
+ * To best support concurrent migrations (e.g. when multiple services are deployed at the same time and want to migrate the same database)
+ * the plugin should try to lock all migrations at once (i.e. in a transaction) and ignore migrations that are already locked (or done).
+ * The successfully locked migrations should be returned and are the migrations that will be executed.
+ *
+ * If one of the migrations to lock is in a failed state, the plugin should throw an error to abort the migration attempt.
+ *
+ * @returns The migrations that were successfully locked.
+ */
+ lock(migrations: MigrationMetadata[]): Promise;
+ /**
+ * The unlock method is called after all migrations have been executed or when the process is interrupted (e.g. by a SIGTERM or SIGINT signal).
+ *
+ * Depending on the plugin implementation, the unlock method is usually a no-op for already succeeded or failed migrations.
+ *
+ * @param migrations The previously successfully locked migrations that should now be unlocked.
+ */
+ unlock(migrations: MigrationMetadata[]): Promise;
+
+ /**
+ * Remove a migration from the history.
+ *
+ * This is used to remove a migration from the history which is needed for failed migrations to be re-executed.
+ *
+ * @param migration The migration that should be removed from the history.
+ */
+ remove(migration: MigrationMetadata): Promise;
+ /**
+ * Get the history of previously executed migrations.
+ *
+ * For failed migrations, the error property should be set.
+ * Emigrate will not sort the history entries, so the plugin should return the entries in the order they were executed.
+ * The order doesn't affect the execution of migrations, but it does affect the order in which the history is displayed in the CLI.
+ * Migrations that have not yet been executed will always be run in alphabetical order.
+ *
+ * The history has two purposes:
+ * 1. To determine which migrations have already been executed.
+ * 2. To list the migration history in the CLI.
+ */
+ getHistory(): AsyncIterable;
+ /**
+ * Called when a migration has been successfully executed.
+ *
+ * @param migration The name of the migration that should be marked as done.
+ */
+ onSuccess(migration: MigrationMetadataFinished): Promise;
+ /**
+ * Called when a migration has failed.
+ *
+ * The passed error will be serialized so it's easily storable it in the history.
+ * If the original Error instance is needed it's available as the `error` property on the finished migration.
+ *
+ * @param migration The name of the migration that should be marked as failed.
+ * @param error The error that caused the migration to fail. Serialized for easy storage.
+ */
+ onError(migration: MigrationMetadataFinished, error: SerializedError): Promise;
+ /**
+ * Called when the command is finished or aborted (e.g. by a SIGTERM or SIGINT signal).
+ *
+ * Use this to clean up any resources like database connections or file handles.
+ */
+ end(): Promise;
+};
+```
+
+See the Migration Types page for more information about the `MigrationMetadata`, `MigrationMetadataFinished`, and `MigrationHistoryEntry` types.