diff --git a/AUTHORS b/AUTHORS
index e81431be7d..7b383d5d7d 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -685,6 +685,7 @@ answer newbie questions, and generally made Django that much better:
     scott@staplefish.com
     Sean Brant
     Sebastian Hillig <sebastian.hillig@gmail.com>
+    Sebastian Spiegel <http://www.tivix.com/>
     Selwin Ong <selwin@ui.co.id>
     Sengtha Chay <sengtha@e-khmer.com>
     Senko Rašić <senko.rasic@dobarkod.hr>
diff --git a/django/core/management/commands/showmigrations.py b/django/core/management/commands/showmigrations.py
index 767aca139d..890839b150 100644
--- a/django/core/management/commands/showmigrations.py
+++ b/django/core/management/commands/showmigrations.py
@@ -43,10 +43,18 @@ class Command(BaseCommand):
         connection = connections[db]
 
         if options['format'] == "plan":
-            return self.show_plan(connection)
+            return self.show_plan(connection, options['app_label'])
         else:
             return self.show_list(connection, options['app_label'])
 
+    def _validate_app_names(self, loader, app_names):
+        invalid_apps = []
+        for app_name in app_names:
+            if app_name not in loader.migrated_apps:
+                invalid_apps.append(app_name)
+        if invalid_apps:
+            raise CommandError('No migrations present for: %s' % (', '.join(sorted(invalid_apps))))
+
     def show_list(self, connection, app_names=None):
         """
         Shows a list of all migrations on the system, or only those of
@@ -57,12 +65,7 @@ class Command(BaseCommand):
         graph = loader.graph
         # If we were passed a list of apps, validate it
         if app_names:
-            invalid_apps = []
-            for app_name in app_names:
-                if app_name not in loader.migrated_apps:
-                    invalid_apps.append(app_name)
-            if invalid_apps:
-                raise CommandError("No migrations present for: %s" % (", ".join(invalid_apps)))
+            self._validate_app_names(loader, app_names)
         # Otherwise, show all apps in alphabetic order
         else:
             app_names = sorted(loader.migrated_apps)
@@ -88,14 +91,19 @@ class Command(BaseCommand):
             if not shown:
                 self.stdout.write(" (no migrations)", self.style.ERROR)
 
-    def show_plan(self, connection):
+    def show_plan(self, connection, app_names=None):
         """
-        Shows all known migrations in the order they will be applied
+        Shows all known migrations (or only those of the specified app_names)
+        in the order they will be applied.
         """
         # Load migrations from disk/DB
         loader = MigrationLoader(connection)
         graph = loader.graph
-        targets = graph.leaf_nodes()
+        if app_names:
+            self._validate_app_names(loader, app_names)
+            targets = [key for key in graph.leaf_nodes() if key[0] in app_names]
+        else:
+            targets = graph.leaf_nodes()
         plan = []
         seen = set()
 
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index 7a77f4d1e7..47cff8e8c6 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -1015,11 +1015,17 @@ This is the default output format.
 
 .. django-admin-option:: --plan, -p
 
-Shows the migration plan Django will follow to apply migrations. Any supplied
-app labels are ignored because the plan might go beyond those apps. Like
+Shows the migration plan Django will follow to apply migrations. Like
 ``--list``, applied migrations are marked by an ``[X]``. For a ``--verbosity``
 of 2 and above, all dependencies of a migration will also be shown.
 
+``app_label``\s arguments limit the output, however, dependencies of provided
+apps may also be included.
+
+.. versionchanged:: 1.11
+
+    In older versions, ``showmigrations --plan`` ignores app labels.
+
 .. django-admin-option:: --database DATABASE
 
 Specifies the database to examine. Defaults to ``default``.
diff --git a/docs/releases/1.11.txt b/docs/releases/1.11.txt
index abdfb08a27..a227b712ab 100644
--- a/docs/releases/1.11.txt
+++ b/docs/releases/1.11.txt
@@ -325,6 +325,9 @@ Management Commands
 * The new :option:`diffsettings --default` option allows specifying a settings
   module other than Django's default settings to compare against.
 
+* ``app_label``\s arguments now limit the :option:`showmigrations --plan`
+  output.
+
 Migrations
 ~~~~~~~~~~
 
diff --git a/tests/migrations/test_commands.py b/tests/migrations/test_commands.py
index 3ec3cfed1d..6477873e0d 100644
--- a/tests/migrations/test_commands.py
+++ b/tests/migrations/test_commands.py
@@ -339,6 +339,97 @@ class MigrateTests(MigrationTestBase):
             out.getvalue().lower()
         )
 
+    @override_settings(INSTALLED_APPS=[
+        'migrations.migrations_test_apps.mutate_state_b',
+        'migrations.migrations_test_apps.alter_fk.author_app',
+        'migrations.migrations_test_apps.alter_fk.book_app',
+    ])
+    def test_showmigrations_plan_single_app_label(self):
+        """
+        `showmigrations --plan app_label` output with a single app_label.
+        """
+        # Single app with no dependencies on other apps.
+        out = six.StringIO()
+        call_command('showmigrations', 'mutate_state_b', format='plan', stdout=out)
+        self.assertEqual(
+            '[ ]  mutate_state_b.0001_initial\n'
+            '[ ]  mutate_state_b.0002_add_field\n',
+            out.getvalue()
+        )
+        # Single app with dependencies.
+        out = six.StringIO()
+        call_command('showmigrations', 'author_app', format='plan', stdout=out)
+        self.assertEqual(
+            '[ ]  author_app.0001_initial\n'
+            '[ ]  book_app.0001_initial\n'
+            '[ ]  author_app.0002_alter_id\n',
+            out.getvalue()
+        )
+        # Some migrations already applied.
+        call_command('migrate', 'author_app', '0001', verbosity=0)
+        out = six.StringIO()
+        call_command('showmigrations', 'author_app', format='plan', stdout=out)
+        self.assertEqual(
+            '[X]  author_app.0001_initial\n'
+            '[ ]  book_app.0001_initial\n'
+            '[ ]  author_app.0002_alter_id\n',
+            out.getvalue()
+        )
+        # Cleanup by unmigrating author_app.
+        call_command('migrate', 'author_app', 'zero', verbosity=0)
+
+    @override_settings(INSTALLED_APPS=[
+        'migrations.migrations_test_apps.mutate_state_b',
+        'migrations.migrations_test_apps.alter_fk.author_app',
+        'migrations.migrations_test_apps.alter_fk.book_app',
+    ])
+    def test_showmigrations_plan_multiple_app_labels(self):
+        """
+        `showmigrations --plan app_label` output with multiple app_labels.
+        """
+        # Multiple apps: author_app depends on book_app; mutate_state_b doesn't
+        # depend on other apps.
+        out = six.StringIO()
+        call_command('showmigrations', 'mutate_state_b', 'author_app', format='plan', stdout=out)
+        self.assertEqual(
+            '[ ]  author_app.0001_initial\n'
+            '[ ]  book_app.0001_initial\n'
+            '[ ]  author_app.0002_alter_id\n'
+            '[ ]  mutate_state_b.0001_initial\n'
+            '[ ]  mutate_state_b.0002_add_field\n',
+            out.getvalue()
+        )
+        # Multiple apps: args order shouldn't matter (the same result is
+        # expected as above).
+        out = six.StringIO()
+        call_command('showmigrations', 'author_app', 'mutate_state_b', format='plan', stdout=out)
+        self.assertEqual(
+            '[ ]  author_app.0001_initial\n'
+            '[ ]  book_app.0001_initial\n'
+            '[ ]  author_app.0002_alter_id\n'
+            '[ ]  mutate_state_b.0001_initial\n'
+            '[ ]  mutate_state_b.0002_add_field\n',
+            out.getvalue()
+        )
+
+    @override_settings(INSTALLED_APPS=['migrations.migrations_test_apps.unmigrated_app'])
+    def test_showmigrations_plan_app_label_error(self):
+        """
+        `showmigrations --plan app_label` raises an error when no app or
+        no migrations are present in provided app labels.
+        """
+        # App with no migrations.
+        with self.assertRaisesMessage(CommandError, 'No migrations present for: unmigrated_app'):
+            call_command('showmigrations', 'unmigrated_app', format='plan')
+        # Nonexistent app (wrong app label).
+        with self.assertRaisesMessage(CommandError, 'No migrations present for: nonexistent_app'):
+            call_command('showmigrations', 'nonexistent_app', format='plan')
+        # Multiple nonexistent apps; input order shouldn't matter.
+        with self.assertRaisesMessage(CommandError, 'No migrations present for: nonexistent_app1, nonexistent_app2'):
+            call_command('showmigrations', 'nonexistent_app1', 'nonexistent_app2', format='plan')
+        with self.assertRaisesMessage(CommandError, 'No migrations present for: nonexistent_app1, nonexistent_app2'):
+            call_command('showmigrations', 'nonexistent_app2', 'nonexistent_app1', format='plan')
+
     @override_settings(MIGRATION_MODULES={"migrations": "migrations.test_migrations"})
     def test_sqlmigrate_forwards(self):
         """