{"record":{"id":"c07427cdebc02e4c","repo":"octobercms/october","slug":"the-inspectable-class-name-is-not-specified","errorCode":null,"errorMessage":"The inspectable class name is not specified.","messagePattern":"The inspectable class name is not specified\\.","errorType":"exception","errorClass":"ApplicationException","httpStatus":null,"severity":"error","filePath":"modules/backend/traits/InspectableContainer.php","lineNumber":30,"sourceCode":" */\ntrait InspectableContainer\n{\n    /**\n     * onInspectableGetOptions\n     */\n    public function onInspectableGetOptions()\n    {\n        // Disable asset broadcasting\n        $this->flushAssets();\n\n        $property = trim(post('inspectorProperty'));\n        if (!$property) {\n            throw new ApplicationException('The property name is not specified.');\n        }\n\n        $className = trim(post('inspectorClassName'));\n        if (!$className || $className === 'undefined') {\n            throw new ApplicationException('The inspectable class name is not specified.');\n        }\n\n        $traitFound = in_array(\\System\\Traits\\PropertyContainer::class, class_uses_recursive($className));\n        if (!$traitFound) {\n            throw new ApplicationException('Dynamic Inspector control options cannot be loaded for the specified class.');\n        }\n\n        $obj = new $className(null);\n        $obj->setProperties(post());\n\n        // Nested properties have names like object.property.\n        // Convert them to Object.Property.\n        $propertyNameParts = explode('.', $property);\n        $propertyMethodName = '';\n        foreach ($propertyNameParts as $part) {\n            $part = trim($part);\n\n            if (!strlen($part)) {","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/backend/traits/InspectableContainer.php#L12-L48","documentation":"The same onInspectableGetOptions handler rejects the request when the inspectorClassName POST field is missing, empty after trim(), or the literal string 'undefined'. That field tells the server which class (usually a component using PropertyContainer) computes the options; the inspector frontend fills it from the control's serverClassName (see control-base.js loadDynamicOptions). The 'undefined' special case exists because an unset JS variable serializes to the string 'undefined' in form data.","triggerScenarios":"POST to onInspectableGetOptions where inspectorClassName is absent, empty, or exactly 'undefined' — typically because this.serverClassName was never set on the inspector control/host, or the JS variable holding the class name was undefined and got stringified into the payload.","commonSituations":"A custom inspector control that forgets to set serverClassName; refactors that moved or renamed the inspectable class without updating the frontend reference; hand-built requests from tests that omit the field.","solutions":["Set the server class name on the inspector control/host, e.g. this.serverClassName = 'MyPlugin\\\\Components\\\\MyComponent' in a custom control, or fix the configured inspectorClassName of the host.","Verify the value is a fully-qualified, non-empty class name string — not a leaked JS undefined.","If invoking the handler from tests or custom JS, always include inspectorClassName in the POST data."],"exampleFix":"// before (custom control never sets serverClassName)\nloadDynamicOptions() { /* serverClassName is undefined */ }\n\n// after\nthis.serverClassName = 'MyPlugin\\\\Components\\\\MyComponent';","handlingStrategy":"validation","validationCode":"// Before sending the dynamic-options request\nconst className = this.serverClassName;\nif (typeof className !== 'string' || !className.trim() || className === 'undefined') {\n    console.error('serverClassName is not set on the inspector control');\n    return;\n}\nconst payload = {\n    inspectorProperty: control.property,\n    inspectorClassName: className\n};","typeGuard":"function hasServerClassName(control) {\n    return typeof control.serverClassName === 'string'\n        && control.serverClassName.trim().length > 0\n        && control.serverClassName !== 'undefined';\n}","tryCatchPattern":null,"preventionTips":["Always set this.serverClassName when writing custom inspector controls","Treat the literal string 'undefined' as a bug: a JS variable leaked into form data","Cover the dynamic-options request path in plugin tests"],"tags":["inspector","ajax","request-parameter","validation","undefined"],"backgroundTag":"missing-request-parameter","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}