{"record":{"id":"34a101472e6a0a19","repo":"octobercms/october","slug":"unable-to-remove-settings-item-before-items-are-lo","errorCode":null,"errorMessage":"Unable to remove settings item before items are loaded.","messagePattern":"Unable to remove settings item before items are loaded\\.","errorType":"exception","errorClass":"SystemException","httpStatus":null,"severity":"error","filePath":"modules/system/classes/SettingsManager.php","lineNumber":315,"sourceCode":"    }\n\n    /**\n     * defineSettingsMenuItem\n     */\n    protected function defineSettingsMenuItem(array $config): SettingsMenuItem\n    {\n        return (new SettingsMenuItem)->useConfig($config);\n    }\n\n    /**\n     * removeSettingItem using its owner and code\n     * @param string $owner\n     * @param string $code\n     */\n    public function removeSettingItem($owner, $code)\n    {\n        if (!$this->items) {\n            throw new SystemException('Unable to remove settings item before items are loaded.');\n        }\n\n        $itemKey = $this->makeItemKey($owner, $code);\n        unset($this->items[$itemKey]);\n\n        if ($this->groupedItems) {\n            foreach ($this->groupedItems as $category => $items) {\n                if (isset($items[$itemKey])) {\n                    unset($this->groupedItems[$category][$itemKey]);\n                }\n            }\n        }\n    }\n\n    /**\n     * setContext sets the navigation context. The owner specifies the setting items owner\n     * plugin or module in the format Vendor.Module. The code specifies the settings item code.\n     * @param string $owner","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/system/classes/SettingsManager.php#L297-L333","documentation":"SettingsManager::removeSettingItem($owner, $code) removes a registered settings item from the internal $items/$groupedItems maps. Those maps are populated lazily by the protected loadItems() on first real use, and removeSettingItem refuses to run against a not-yet-loaded state: if $this->items is falsy it throws a SystemException. So this is a lifecycle misuse — the removal was requested before anything caused settings items to be loaded (i.e. before plugins' registerSettings() output was collected).","triggerScenarios":"Calling SettingsManager::instance()->removeSettingItem('Author.Plugin', 'code') very early — typically from a plugin's register() or boot() on every request — before the settings manager has loaded its items (which normally happens when the backend settings UI or another consumer first asks for them).","commonSituations":"A plugin tries to hide another plugin's settings page and does it too early in the boot cycle; code ported from an older version where items happened to be preloaded; an artisan command that manipulates settings without ever triggering the backend load path.","solutions":["Move the removeSettingItem() call to a later stage, after items are guaranteed loaded — e.g. inside a backend request lifecycle event such as backend.page.beforeDisplay, not in register()/boot()","Or trigger any code path that loads settings items first (render/extend the settings nav) before removing","If you control the target plugin, deregister the item at its source (registerSettings()) instead of removing it from a third party","Verify you are using the correct owner ('Author.Plugin') and code, since a wrong key on a loaded manager would silently no-op anyway"],"exampleFix":"// before — runs on every request, before settings are loaded\npublic function register()\n{\n    SettingsManager::instance()->removeSettingItem('Author.Other', 'settings');\n}\n// after — defer until the backend has actually loaded settings items\npublic function boot(){\n    Event::listen('backend.page.beforeDisplay', function () {\n        SettingsManager::instance()->removeSettingItem('Author.Other', 'settings');\n    });\n}","handlingStrategy":"validation","validationCode":"// Only remove once items exist — probe by requesting the item first\n$manager = \\System\\Classes\\SettingsManager::instance();\n$itemKey = 'Author.Plugin' . '.' . 'setting';\n// items are protected; defer via a backend event so the manager has loaded them\nEvent::listen('backend.page.beforeDisplay', function () use ($manager) {\n    $manager->removeSettingItem('Author.Plugin', 'setting');\n});","typeGuard":null,"tryCatchPattern":"try {\n    \\System\\Classes\\SettingsManager::instance()->removeSettingItem('Author.Plugin', 'setting');\n} catch (\\System\\Classes\\SystemException $ex) {\n    // items not loaded yet — retry from a later lifecycle point instead of register()\n}","preventionTips":["Never call manager mutation methods from register(); defer to boot() or a backend request event","Prefer extending/deregistering at the source (registerSettings of the owning plugin) over third-party removal","Wrap settings-manager calls in code paths that already render the backend"],"tags":["octobercms","settings","lifecycle","ordering","invalid-state"],"backgroundTag":"call-before-init","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}