diff --git a/docs/advanced/migrations.md b/docs/advanced/migrations.md
new file mode 100644
index 0000000000..acf2a6fd6e
--- /dev/null
+++ b/docs/advanced/migrations.md
@@ -0,0 +1,321 @@
+# Migrations
+
+We will use `Alembic` to handle database schema changes.
+
+`SQLModel` is compatible with `Alembic`.
+
+## Initial example
+
+We'll continue from another example that has the creation of database and tables, and other essentials features.
+
+
+π Full file example
+
+```Python
+{!./docs_src/advanced/migrations/tutorial001.py!}
+```
+
+
+
+## First step
+
+Add `Alembic` to your project.
+
+Example using pip.
+
+
+
+```console
+$ pip install alembic
+
+Installing collected packages: alembic
+Successfully installed alembic-1.8.1
+```
+
+
+
+## Clean your code
+
+We need to clean our step that create the database and tables.
+
+```Python hl_lines="3-4"
+# Code above omitted π
+
+{!./docs_src/advanced/migrations/tutorial001.py[ln:19-20]!}
+
+# Code below omitted π
+```
+
+```Python hl_lines="4-4"
+# Code above omitted π
+
+{!./docs_src/advanced/migrations/tutorial001.py[ln:44-47]!}
+
+# Code below omitted π
+```
+
+
+π Full file example
+
+```Python
+{!./docs_src/advanced/migrations/main.py!}
+```
+
+
+
+## Alembic configuration
+
+In this step we need initialize alembic.
+
+
+
+```console
+$ alembic init migrations
+
+Creating directory migrations ... done
+Creating directory migrations\versions ... done
+Generating alembic.ini ... done
+Generating migrations\env.py ... done
+Generating migrations\README ... done
+Generating migrations\script.py.mako ... done
+Please edit configuration/connection/logging settings in 'alembic.ini' before proceeding.
+
+```
+
+
+
+!!! info
+ We can also use `alembic init alembic` to create `alembic` folder instead of `migrations` folder.
+
+Then go to `migrations\script.py.mako` to add sqlmodel module.
+
+```Python hl_lines="5-5"
+# Code above omitted π
+
+{!./docs_src/advanced/migrations/tutorial003.mako[ln:8-10]!}
+
+# Code below omitted π
+```
+
+!!! info
+ In new migrations alembic will add SQLModel automatically.
+
+
+π Full script.py.mako example
+
+```Python
+{!./docs_src/advanced/migrations/tutorial003.mako!}
+```
+
+
+
+Then go to `migrations\env.py` to finish the alembic configuration.
+
+- Import your models (in this case `Hero`) and `SQLModel`
+
+```Python hl_lines="5-6"
+# Code above omitted π
+
+{!./docs_src/advanced/migrations/tutorial004.py[ln:1-6]!}
+
+# Code below omitted π
+```
+
+!!! warning
+ First import your models and then import SQLModel otherwise sqlmodel doesnΒ΄t recognize all models.
+
+- Then set your database url
+
+```Python hl_lines="4-4"
+# Code above omitted π
+
+{!./docs_src/advanced/migrations/tutorial004.py[ln:13-14]!}
+
+# Code below omitted π
+```
+
+!!! tip
+ This step can be replaced setting the same `sqlalchemy.url` variable in `alembic.ini` file.
+
+- Finally set `target_metadata` with your `SQLModel.metada`
+
+```Python hl_lines="3-3"
+# Code above omitted π
+
+{!./docs_src/advanced/migrations/tutorial004.py[ln:25-25]!}
+
+# Code below omitted π
+```
+
+
+π Full env.py example
+
+```Python
+{!./docs_src/advanced/migrations/tutorial004.py!}
+```
+
+
+
+## Run migrations
+
+In this step we need to generate the initial version of the database.
+
+
+
+```console
+$ alembic revision --autogenerate -m "init_db"
+
+INFO [alembic.runtime.migration] Context impl SQLiteImpl.
+INFO [alembic.runtime.migration] Will assume non-transactional DDL.
+INFO [alembic.autogenerate.compare] Detected added table 'hero'
+Generating migrations\versions\34abfb7ac266_init_db.py ... done
+```
+
+
+
+Now in `versions` folder we have a new file called `34abfb7ac266_init_db.py`
+
+!!! info
+ This file has a revision id and the message part from our revision command.
+
+```{ .python .annotate }
+{!./docs_src/advanced/migrations/tutorial005.py!}
+```
+
+{!./docs_src/advanced/migrations/annotations/en/tutorial005.md!}
+
+!!! success
+ At this moment we have all the files to create our new database model.
+
+Initialize the database:
+
+
+
+```console
+$ alembic upgrade head
+
+INFO [alembic.runtime.migration] Context impl SQLiteImpl.
+INFO [alembic.runtime.migration] Will assume non-transactional DDL.
+INFO [alembic.runtime.migration] Running upgrade -> 34abfb7ac266, init_db
+```
+
+
+
+Now we have two tables:
+
+- `alembic_version`: with the version_num asociate with the revision id
+- `hero`: the new table from our model
+
+
+
+`Hero` table is empty.
+
+
+
+Then run `main.py` script
+
+
+
+```console
+$ python main.py
+
+INFO sqlalchemy.engine.Engine BEGIN (implicit)
+INFO sqlalchemy.engine.Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
+INFO sqlalchemy.engine.Engine [generated in 0.00035s] ('Deadpond', 'Dive Wilson', None)
+INFO sqlalchemy.engine.Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
+INFO sqlalchemy.engine.Engine [cached since 0.002439s ago] ('Spider-Boy', 'Pedro Parqueador', None)
+INFO sqlalchemy.engine.Engine INSERT INTO hero (name, secret_name, age) VALUES (?, ?, ?)
+INFO sqlalchemy.engine.Engine [cached since 0.003134s ago] ('Rusty-Man', 'Tommy Sharp', 48)
+INFO sqlalchemy.engine.Engine COMMIT
+INFO sqlalchemy.engine.Engine BEGIN (implicit)
+INFO sqlalchemy.engine.Engine SELECT hero.id, hero.name, hero.secret_name, hero.age
+FROM hero
+INFO sqlalchemy.engine.Engine [generated in 0.00038s] ()
+age=None id=1 name='Deadpond' secret_name='Dive Wilson'
+age=None id=2 name='Spider-Boy' secret_name='Pedro Parqueador'
+age=48 id=3 name='Rusty-Man' secret_name='Tommy Sharp'
+INFO sqlalchemy.engine.Engine ROLLBACK
+```
+
+
+
+Now the `hero` table has new rows:
+
+
+
+## Next steps
+
+If we edit our model changing the database schema we can run again alembic to generate a new revision.
+
+Example: adding a new field named `power`
+
+```Python hl_lines="4-4"
+# Code above omitted π
+
+{!./docs_src/advanced/migrations/tutorial006.py[ln:10-11]!}
+
+# Code below omitted π
+```
+
+
+π Full file example
+
+```Python
+{!./docs_src/advanced/migrations/tutorial006.py!}
+```
+
+
+
+
+
+```console
+$ alembic revision --autogenerate -m "new field power"
+
+INFO [alembic.runtime.migration] Context impl SQLiteImpl.
+INFO [alembic.runtime.migration] Will assume non-transactional DDL.
+INFO [alembic.autogenerate.compare] Detected added column 'hero.power'
+Generating migrations\versions\b39b8d3c77f0_new_field_power.py ... done
+```
+
+
+
+The new file `b39b8d3c77f0_new_field_power.py`:
+
+```{ .python .annotate }
+{!./docs_src/advanced/migrations/tutorial007.py!}
+```
+
+{!./docs_src/advanced/migrations/annotations/en/tutorial007.md!}
+
+!!! note
+ Run `alembic upgrade head` to add the new field named power
+
+
+
+```console
+$ alembic upgrade head
+
+INFO [alembic.runtime.migration] Context impl SQLiteImpl.
+INFO [alembic.runtime.migration] Will assume non-transactional DDL.
+INFO [alembic.runtime.migration] Running upgrade 357d6ebcfadf -> b39b8d3c77f0, new field power
+```
+
+
+
+!!! note
+ After you can downgrade the database to the previous version, run `alembic downgrade -1`
+
+
+
+```console
+$ alembic downgrade -1
+
+INFO [alembic.runtime.migration] Context impl SQLiteImpl.
+INFO [alembic.runtime.migration] Will assume non-transactional DDL.
+INFO [alembic.runtime.migration] Running downgrade b39b8d3c77f0 -> 357d6ebcfadf, new field power
+```
+
+
+
+!!! success
+ Migrations complete!!! Try adding new tables and relationship.
diff --git a/docs/img/advanced/migrations/migrations001.png b/docs/img/advanced/migrations/migrations001.png
new file mode 100644
index 0000000000..d439f416c9
Binary files /dev/null and b/docs/img/advanced/migrations/migrations001.png differ
diff --git a/docs/img/advanced/migrations/migrations002.png b/docs/img/advanced/migrations/migrations002.png
new file mode 100644
index 0000000000..e3eb43155e
Binary files /dev/null and b/docs/img/advanced/migrations/migrations002.png differ
diff --git a/docs/img/advanced/migrations/migrations003.png b/docs/img/advanced/migrations/migrations003.png
new file mode 100644
index 0000000000..f86db2735d
Binary files /dev/null and b/docs/img/advanced/migrations/migrations003.png differ
diff --git a/docs_src/advanced/migrations/__init__.py b/docs_src/advanced/migrations/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/docs_src/advanced/migrations/annotations/en/tutorial005.md b/docs_src/advanced/migrations/annotations/en/tutorial005.md
new file mode 100644
index 0000000000..a3408469f2
--- /dev/null
+++ b/docs_src/advanced/migrations/annotations/en/tutorial005.md
@@ -0,0 +1,10 @@
+1. SQLmodel import
+2. Upgrade function to add the new schema in the database
+3. Create a new table named `hero`
+4. `id` field
+5. `name` field
+6. `secret_name` field
+7. `age` field
+8. Setting `id` field as primary key
+9. Downgrade function to rollback our changes
+10. Delete the table named `hero`
diff --git a/docs_src/advanced/migrations/annotations/en/tutorial007.md b/docs_src/advanced/migrations/annotations/en/tutorial007.md
new file mode 100644
index 0000000000..21970a79bd
--- /dev/null
+++ b/docs_src/advanced/migrations/annotations/en/tutorial007.md
@@ -0,0 +1,4 @@
+1. New revision id
+2. Previous revision id, if downgrade go to this revision id
+3. `power` new field
+4. Drop column if downgrade
diff --git a/docs_src/advanced/migrations/main.py b/docs_src/advanced/migrations/main.py
new file mode 100644
index 0000000000..067a8292f9
--- /dev/null
+++ b/docs_src/advanced/migrations/main.py
@@ -0,0 +1,46 @@
+from typing import Optional
+
+from sqlmodel import Field, Session, SQLModel, create_engine, select
+
+
+class Hero(SQLModel, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+ name: str
+ secret_name: str
+ age: Optional[int] = None
+
+
+sqlite_file_name = "database.db"
+sqlite_url = f"sqlite:///{sqlite_file_name}"
+
+engine = create_engine(sqlite_url, echo=True)
+
+
+def create_heroes():
+ hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
+ hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
+ hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
+
+ with Session(engine) as session:
+ session.add(hero_1)
+ session.add(hero_2)
+ session.add(hero_3)
+
+ session.commit()
+
+
+def select_heroes():
+ with Session(engine) as session:
+ statement = select(Hero)
+ results = session.exec(statement)
+ for hero in results:
+ print(hero)
+
+
+def main():
+ create_heroes()
+ select_heroes()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs_src/advanced/migrations/tutorial001.py b/docs_src/advanced/migrations/tutorial001.py
new file mode 100644
index 0000000000..285f711221
--- /dev/null
+++ b/docs_src/advanced/migrations/tutorial001.py
@@ -0,0 +1,51 @@
+from typing import Optional
+
+from sqlmodel import Field, Session, SQLModel, create_engine, select
+
+
+class Hero(SQLModel, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+ name: str
+ secret_name: str
+ age: Optional[int] = None
+
+
+sqlite_file_name = "database.db"
+sqlite_url = f"sqlite:///{sqlite_file_name}"
+
+engine = create_engine(sqlite_url, echo=True)
+
+
+def create_db_and_tables():
+ SQLModel.metadata.create_all(engine)
+
+
+def create_heroes():
+ hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
+ hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
+ hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
+
+ with Session(engine) as session:
+ session.add(hero_1)
+ session.add(hero_2)
+ session.add(hero_3)
+
+ session.commit()
+
+
+def select_heroes():
+ with Session(engine) as session:
+ statement = select(Hero)
+ results = session.exec(statement)
+ for hero in results:
+ print(hero)
+
+
+def main():
+ create_db_and_tables()
+ create_heroes()
+ select_heroes()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs_src/advanced/migrations/tutorial002.py b/docs_src/advanced/migrations/tutorial002.py
new file mode 100644
index 0000000000..3aa2e2b09f
--- /dev/null
+++ b/docs_src/advanced/migrations/tutorial002.py
@@ -0,0 +1,51 @@
+from typing import Optional
+
+from sqlmodel import Field, Session, SQLModel, create_engine, select
+
+
+class Hero(SQLModel, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+ name: str
+ secret_name: str
+ age: Optional[int] = None
+
+
+sqlite_file_name = "database.db"
+sqlite_url = f"sqlite:///{sqlite_file_name}"
+
+engine = create_engine(sqlite_url, echo=True)
+
+
+# def create_db_and_tables():
+# SQLModel.metadata.create_all(engine)
+
+
+def create_heroes():
+ hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
+ hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
+ hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
+
+ with Session(engine) as session:
+ session.add(hero_1)
+ session.add(hero_2)
+ session.add(hero_3)
+
+ session.commit()
+
+
+def select_heroes():
+ with Session(engine) as session:
+ statement = select(Hero)
+ results = session.exec(statement)
+ for hero in results:
+ print(hero)
+
+
+def main():
+ # create_db_and_tables()
+ create_heroes()
+ select_heroes()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs_src/advanced/migrations/tutorial003.mako b/docs_src/advanced/migrations/tutorial003.mako
new file mode 100644
index 0000000000..3124b62c2a
--- /dev/null
+++ b/docs_src/advanced/migrations/tutorial003.mako
@@ -0,0 +1,25 @@
+"""${message}
+
+Revision ID: ${up_revision}
+Revises: ${down_revision | comma,n}
+Create Date: ${create_date}
+
+"""
+from alembic import op
+import sqlalchemy as sa
+import sqlmodel
+${imports if imports else ""}
+
+# revision identifiers, used by Alembic.
+revision = ${repr(up_revision)}
+down_revision = ${repr(down_revision)}
+branch_labels = ${repr(branch_labels)}
+depends_on = ${repr(depends_on)}
+
+
+def upgrade() -> None:
+ ${upgrades if upgrades else "pass"}
+
+
+def downgrade() -> None:
+ ${downgrades if downgrades else "pass"}
diff --git a/docs_src/advanced/migrations/tutorial004.py b/docs_src/advanced/migrations/tutorial004.py
new file mode 100644
index 0000000000..e113d5c650
--- /dev/null
+++ b/docs_src/advanced/migrations/tutorial004.py
@@ -0,0 +1,75 @@
+from logging.config import fileConfig # isort:skip
+
+from main import Hero # noqa: F401, isort:skip
+from sqlmodel import SQLModel # isort:skip
+
+from sqlalchemy import engine_from_config # isort:skip
+from sqlalchemy import pool # isort:skip
+
+from alembic import context # isort:skip
+
+# this is the Alembic Config object, which provides
+# access to the values within the .ini file in use.
+config = context.config
+config.set_main_option("sqlalchemy.url", "sqlite:///database.db")
+
+# Interpret the config file for Python logging.
+# This line sets up loggers basically.
+if config.config_file_name is not None:
+ fileConfig(config.config_file_name)
+
+# add your model's MetaData object here
+# for 'autogenerate' support
+# from myapp import mymodel
+# target_metadata = mymodel.Base.metadata
+target_metadata = SQLModel.metadata
+
+# other values from the config, defined by the needs of env.py,
+# can be acquired:
+# my_important_option = config.get_main_option("my_important_option")
+# ... etc.
+
+
+def run_migrations_offline() -> None:
+ """Run migrations in 'offline' mode.
+ This configures the context with just a URL
+ and not an Engine, though an Engine is acceptable
+ here as well. By skipping the Engine creation
+ we don't even need a DBAPI to be available.
+ Calls to context.execute() here emit the given string to the
+ script output.
+ """
+ url = config.get_main_option("sqlalchemy.url")
+ context.configure(
+ url=url,
+ target_metadata=target_metadata,
+ literal_binds=True,
+ dialect_opts={"paramstyle": "named"},
+ )
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+def run_migrations_online() -> None:
+ """Run migrations in 'online' mode.
+ In this scenario we need to create an Engine
+ and associate a connection with the context.
+ """
+ connectable = engine_from_config(
+ config.get_section(config.config_ini_section),
+ prefix="sqlalchemy.",
+ poolclass=pool.NullPool,
+ )
+
+ with connectable.connect() as connection:
+ context.configure(connection=connection, target_metadata=target_metadata)
+
+ with context.begin_transaction():
+ context.run_migrations()
+
+
+if context.is_offline_mode():
+ run_migrations_offline()
+else:
+ run_migrations_online()
diff --git a/docs_src/advanced/migrations/tutorial005.py b/docs_src/advanced/migrations/tutorial005.py
new file mode 100644
index 0000000000..bd5d529bd9
--- /dev/null
+++ b/docs_src/advanced/migrations/tutorial005.py
@@ -0,0 +1,38 @@
+"""init_db
+
+Revision ID: 34abfb7ac266
+Revises:
+Create Date:
+
+"""
+from alembic import op # isort:skip
+import sqlalchemy as sa # isort:skip
+import sqlmodel # (1), # isort:skip
+
+
+# revision identifiers, used by Alembic.
+revision = "34abfb7ac266"
+down_revision = None
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None: # (2)
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.create_table(
+ "hero", # (3)
+ sa.Column("id", sa.Integer(), nullable=False), # (4)
+ sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), # (5)
+ sa.Column(
+ "secret_name", sqlmodel.sql.sqltypes.AutoString(), nullable=False
+ ), # (6)
+ sa.Column("age", sa.Integer(), nullable=True), # (7)
+ sa.PrimaryKeyConstraint("id"), # (8)
+ )
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None: # (9)
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_table("hero") # (10)
+ # ### end Alembic commands ###
diff --git a/docs_src/advanced/migrations/tutorial006.py b/docs_src/advanced/migrations/tutorial006.py
new file mode 100644
index 0000000000..8470140a52
--- /dev/null
+++ b/docs_src/advanced/migrations/tutorial006.py
@@ -0,0 +1,47 @@
+from typing import Optional
+
+from sqlmodel import Field, Session, SQLModel, create_engine, select
+
+
+class Hero(SQLModel, table=True):
+ id: Optional[int] = Field(default=None, primary_key=True)
+ name: str
+ secret_name: str
+ age: Optional[int] = None
+ power: int = None
+
+
+sqlite_file_name = "database.db"
+sqlite_url = f"sqlite:///{sqlite_file_name}"
+
+engine = create_engine(sqlite_url, echo=True)
+
+
+def create_heroes():
+ hero_1 = Hero(name="Deadpond", secret_name="Dive Wilson")
+ hero_2 = Hero(name="Spider-Boy", secret_name="Pedro Parqueador")
+ hero_3 = Hero(name="Rusty-Man", secret_name="Tommy Sharp", age=48)
+
+ with Session(engine) as session:
+ session.add(hero_1)
+ session.add(hero_2)
+ session.add(hero_3)
+
+ session.commit()
+
+
+def select_heroes():
+ with Session(engine) as session:
+ statement = select(Hero)
+ results = session.exec(statement)
+ for hero in results:
+ print(hero)
+
+
+def main():
+ create_heroes()
+ select_heroes()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/docs_src/advanced/migrations/tutorial007.py b/docs_src/advanced/migrations/tutorial007.py
new file mode 100644
index 0000000000..0f332b9000
--- /dev/null
+++ b/docs_src/advanced/migrations/tutorial007.py
@@ -0,0 +1,29 @@
+"""new field power
+
+Revision ID: b39b8d3c77f0
+Revises: 357d6ebcfadf
+Create Date:
+
+"""
+from alembic import op # isort:skip
+import sqlalchemy as sa # isort:skip
+import sqlmodel # noqa: F401, isort:skip
+
+
+# revision identifiers, used by Alembic.
+revision = "b39b8d3c77f0" # (1)
+down_revision = "357d6ebcfadf" # (2)
+branch_labels = None
+depends_on = None
+
+
+def upgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column("hero", sa.Column("power", sa.Integer(), nullable=True)) # (3)
+ # ### end Alembic commands ###
+
+
+def downgrade() -> None:
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column("hero", "power") # (4)
+ # ### end Alembic commands ###
diff --git a/mkdocs.yml b/mkdocs.yml
index a27bbde8a1..237fa3aa69 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -85,6 +85,7 @@ nav:
- Advanced User Guide:
- advanced/index.md
- advanced/decimal.md
+ - advanced/migrations.md
- alternatives.md
- help.md
- contributing.md