diff --git a/lib/bb/tests/setup.py b/lib/bb/tests/setup.py
index 638d56d3b..7b259cc97 100644
--- a/lib/bb/tests/setup.py
+++ b/lib/bb/tests/setup.py
@@ -726,3 +726,110 @@ print("BBPATH is {{}}".format(os.environ["BBPATH"]))
             self.assertEqual(f.read(), 'conflicting-upstream\n',
                              "re-cloned layer must contain the upstream content after conflict backup")
         del os.environ['BBPATH']
+
+    def test_extra_remotes(self):
+        """
+        Test that extra-remotes are added and filtered correctly during init and update.
+
+        Covers:
+          1. Default 'non-optional' filter: non-optional remote added, optional skipped.
+          2. --extra-remotes-filter=all: both optional and non-optional remotes added.
+          3. --extra-remotes-filter=none: no extra remotes added.
+          4. --extra-remotes-filter=<name>: only the explicitly named remote added.
+          5. update re-applies extra remotes and updates the URI of an existing remote.
+        """
+        if 'BBPATH' in os.environ:
+            del os.environ['BBPATH']
+        os.chdir(self.tempdir)
+
+        self.runbbsetup("settings set default registry 'git://{};protocol=file;branch=master;rev=master'".format(self.registrypath))
+        self.add_file_to_testrepo('test-file', 'initial\n')
+
+        # Two local repos used as extra remote targets (remote_a = initial URI, remote_b = updated URI)
+        remote_a = os.path.join(self.tempdir, 'extra-remote-a')
+        remote_b = os.path.join(self.tempdir, 'extra-remote-b')
+        for remote in (remote_a, remote_b):
+            os.makedirs(remote)
+            self.git_init(cwd=remote)
+            self.git('commit --allow-empty -m "Initial commit"', cwd=remote)
+
+        # Config with one non-optional and one optional extra-remote
+        sources_both = '''
+        "test-repo": {
+            "git-remote": {
+                "remotes": {"origin": {"uri": "file://%s"}},
+                "branch": "master",
+                "rev": "master",
+                "extra-remotes": {
+                    "required-remote": {"uri": "file://%s"},
+                    "optional-remote": {"uri": "file://%s", "optional": true}
+                }
+            }
+        }
+        ''' % (self.testrepopath, remote_a, remote_b)
+        self._add_json_config_to_registry_helper('er-test.conf.json', sources_both)
+
+        def get_remotes(layer_path):
+            return self.git('remote', cwd=layer_path).split()
+
+        # 1. Default filter 'non-optional': required-remote added, optional-remote skipped
+        out = self.runbbsetup("init --non-interactive er-test gadget")
+        setuppath = self.get_setup_path('er-test', 'gadget')
+        layer_path = os.path.join(setuppath, 'layers', 'test-repo')
+        remotes = get_remotes(layer_path)
+        self.assertIn('required-remote', remotes,
+                      "non-optional extra remote must be added with the default 'non-optional' filter")
+        self.assertNotIn('optional-remote', remotes,
+                         "optional extra remote must be skipped by the default 'non-optional' filter")
+        self.assertEqual(
+            self.git('remote get-url required-remote', cwd=layer_path).strip(),
+            'file://' + remote_a)
+        self.assertIn("Added extra remote 'required-remote'", out[0])
+
+        # 2. --extra-remotes-filter=all: both optional and non-optional remotes added
+        self.runbbsetup("init --non-interactive --extra-remotes-filter=all --setup-dir-name er-filter-all er-test gadget")
+        all_layer = os.path.join(self.tempdir, 'bitbake-builds', 'er-filter-all', 'layers', 'test-repo')
+        remotes = get_remotes(all_layer)
+        self.assertIn('required-remote', remotes)
+        self.assertIn('optional-remote', remotes)
+
+        # 3. --extra-remotes-filter=none: no extra remotes added
+        self.runbbsetup("init --non-interactive --extra-remotes-filter=none --setup-dir-name er-filter-none er-test gadget")
+        none_layer = os.path.join(self.tempdir, 'bitbake-builds', 'er-filter-none', 'layers', 'test-repo')
+        remotes = get_remotes(none_layer)
+        self.assertNotIn('required-remote', remotes)
+        self.assertNotIn('optional-remote', remotes)
+
+        # 4. --extra-remotes-filter=<name>: only the explicitly named remote added
+        self.runbbsetup("init --non-interactive --extra-remotes-filter=optional-remote --setup-dir-name er-filter-named er-test gadget")
+        named_layer = os.path.join(self.tempdir, 'bitbake-builds', 'er-filter-named', 'layers', 'test-repo')
+        remotes = get_remotes(named_layer)
+        self.assertNotIn('required-remote', remotes)
+        self.assertIn('optional-remote', remotes)
+
+        # 5. update re-applies extra remotes and updates the URI of an existing remote
+        sources_updated = '''
+        "test-repo": {
+            "git-remote": {
+                "remotes": {"origin": {"uri": "file://%s"}},
+                "branch": "master",
+                "rev": "master",
+                "extra-remotes": {
+                    "required-remote": {"uri": "file://%s"}
+                }
+            }
+        }
+        ''' % (self.testrepopath, remote_b)
+        config_path = os.path.join(self.registrypath, 'er-test.conf.json')
+        os.remove(config_path)
+        self._add_json_config_to_registry_helper('er-test.conf.json', sources_updated)
+
+        os.environ['BBPATH'] = os.path.join(setuppath, 'build')
+        out = self.runbbsetup("update --update-bb-conf='no'")
+        del os.environ['BBPATH']
+
+        self.assertEqual(
+            self.git('remote get-url required-remote', cwd=layer_path).strip(),
+            'file://' + remote_b,
+            "update must call 'git remote set-url' to update the existing remote to the new URI")
+        self.assertIn("Updated extra remote 'required-remote'", out[0])
