diff --git a/AUTHORS b/AUTHORS
index 9a76e8879e..e3389edbf5 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -180,6 +180,7 @@ answer newbie questions, and generally made Django that much better:
Brian Fabian Crain
Brian Harring
Brian Helba
+ Brian Nettleton
Brian Ray
Brian Rosner
Bruce Kroeze
diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index de8fe9c339..e873e5ca86 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -1819,6 +1819,8 @@ class ManyToManyField(RelatedField):
kwargs["through"] = self.remote_field.through
elif not self.remote_field.through._meta.auto_created:
kwargs["through"] = self.remote_field.through._meta.label
+ if through_fields := getattr(self.remote_field, "through_fields", None):
+ kwargs["through_fields"] = through_fields
# If swappable is True, then see if we're actually pointing to the target
# of a swap.
swappable_setting = self.swappable_setting
diff --git a/tests/field_deconstruction/tests.py b/tests/field_deconstruction/tests.py
index 3b10ee0091..41353cbaaf 100644
--- a/tests/field_deconstruction/tests.py
+++ b/tests/field_deconstruction/tests.py
@@ -516,6 +516,23 @@ class FieldDeconstructionTests(SimpleTestCase):
self.assertEqual(path, "django.db.models.ManyToManyField")
self.assertEqual(args, [])
self.assertEqual(kwargs, {"to": "auth.permission", "through": "auth.Group"})
+ # Test through_fields
+ field = models.ManyToManyField(
+ "auth.Permission",
+ through="auth.Group",
+ through_fields=("foo", "permissions"),
+ )
+ name, path, args, kwargs = field.deconstruct()
+ self.assertEqual(path, "django.db.models.ManyToManyField")
+ self.assertEqual(args, [])
+ self.assertEqual(
+ kwargs,
+ {
+ "to": "auth.permission",
+ "through": "auth.Group",
+ "through_fields": ("foo", "permissions"),
+ },
+ )
# Test custom db_table
field = models.ManyToManyField("auth.Permission", db_table="custom_table")
name, path, args, kwargs = field.deconstruct()