{"record":{"id":"2841d8c458bcadcb","repo":"halo-dev/halo","slug":"pluginmodule-components-must-be-an-object","errorCode":null,"errorMessage":"PluginModule.components must be an object.","messagePattern":"PluginModule\\.components must be an object\\.","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ui/src/setup/setupModules.ts","lineNumber":668,"sourceCode":"      replacedRoute.route,\n      replacedRoute.owner,\n      routeOwners,\n      componentOwners\n    );\n  }\n}\n\nfunction preparePluginModule(\n  module: PluginModule,\n  registration: UiProviderRegistration,\n  report: (\n    registration: UiProviderRegistration,\n    stage: UiPluginFailureStage,\n    error: unknown\n  ) => void\n) {\n  if (module.components && !isRecord(module.components)) {\n    throw new TypeError(\"PluginModule.components must be an object.\");\n  }\n  if (module.routes && !Array.isArray(module.routes)) {\n    throw new TypeError(\"PluginModule.routes must be an array.\");\n  }\n  if (module.ucRoutes && !Array.isArray(module.ucRoutes)) {\n    throw new TypeError(\"PluginModule.ucRoutes must be an array.\");\n  }\n  if (module.formkit && !isRecord(module.formkit)) {\n    throw new TypeError(\"PluginModule.formkit must be an object.\");\n  }\n  if (module.extensionPoints && !isRecord(module.extensionPoints)) {\n    throw new TypeError(\"PluginModule.extensionPoints must be an object.\");\n  }\n  resetRouteMetaAndWrapChunks(module.routes, false, registration, report);\n  resetRouteMetaAndWrapChunks(module.ucRoutes, false, registration, report);\n}\n\nfunction resetRouteMetaAndWrapChunks(","sourceCodeStart":650,"sourceCodeEnd":686,"githubUrl":"https://github.com/halo-dev/halo/blob/d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8/ui/src/setup/setupModules.ts#L650-L686","documentation":"Thrown by preparePluginModule when a plugin's PluginModule.components field is present but not a plain object (isRecord returns false — i.e. it is null, an array, a primitive, or a function). components must be a Record<string, Component> so Halo can globally register each named component. Activating a plugin whose module has the wrong shape here would register garbage, so the host throws a TypeError during module preparation.","triggerScenarios":"A plugin's activated module returns { components: [...] } (array) or { components: () => Foo } (function) or { components: null }, processed at activation in preparePluginModule (line 393 call site).","commonSituations":"A plugin author mistakes components for an array; assigns a single component instead of a map; a build step mis-types the module; copying a snippet that returns an array of components.","solutions":["Return components as an object keyed by component name: { components: { MyWidget } } (using shorthand) or { components: { 'MyWidget': MyWidget } }.","Remove the components field entirely if the plugin registers no global components.","Run `pnpm -C ui typecheck` against the plugin to catch shape errors against the PluginModule type."],"exampleFix":"// before\nexport default definePluginModule({ components: [MyWidget, Other] });\n\n// after\nexport default definePluginModule({ components: { MyWidget, Other } });","handlingStrategy":"type-guard","validationCode":"function assertComponentsShape(module: { components?: unknown }) {\n  if (module.components && (typeof module.components !== \"object\" || module.components === null || Array.isArray(module.components))) {\n    throw new TypeError(\"PluginModule.components must be an object.\");\n  }\n}","typeGuard":"function isComponentRecord(value: unknown): value is Record<string, unknown> {\n  return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}","tryCatchPattern":null,"preventionTips":["Type the plugin module as PluginModule and run vue-tsc before release.","Key components by name inside an object, never an array.","Unit-test the module shape in the plugin's activation spec."],"tags":["runtime","plugin-module","typescript","console","validation"],"backgroundTag":null,"analyzedSha":"d2f5165f9c8f055ffcb3fa9c3f4032821a7b68c8","analyzedAt":"2026-08-14T00:18:38.915Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}