halo-dev/halo · error · TypeError
PluginModule.components must be an object.
Error message
PluginModule.components must be an object.
What it means
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.
Source
Thrown at ui/src/setup/setupModules.ts:668
replacedRoute.route,
replacedRoute.owner,
routeOwners,
componentOwners
);
}
}
function preparePluginModule(
module: PluginModule,
registration: UiProviderRegistration,
report: (
registration: UiProviderRegistration,
stage: UiPluginFailureStage,
error: unknown
) => void
) {
if (module.components && !isRecord(module.components)) {
throw new TypeError("PluginModule.components must be an object.");
}
if (module.routes && !Array.isArray(module.routes)) {
throw new TypeError("PluginModule.routes must be an array.");
}
if (module.ucRoutes && !Array.isArray(module.ucRoutes)) {
throw new TypeError("PluginModule.ucRoutes must be an array.");
}
if (module.formkit && !isRecord(module.formkit)) {
throw new TypeError("PluginModule.formkit must be an object.");
}
if (module.extensionPoints && !isRecord(module.extensionPoints)) {
throw new TypeError("PluginModule.extensionPoints must be an object.");
}
resetRouteMetaAndWrapChunks(module.routes, false, registration, report);
resetRouteMetaAndWrapChunks(module.ucRoutes, false, registration, report);
}
function resetRouteMetaAndWrapChunks(View on GitHub (pinned to d2f5165f9c)
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.
Example fix
// before
export default definePluginModule({ components: [MyWidget, Other] });
// after
export default definePluginModule({ components: { MyWidget, Other } }); Defensive patterns
Strategy: type-guard
Validate before calling
function assertComponentsShape(module: { components?: unknown }) {
if (module.components && (typeof module.components !== "object" || module.components === null || Array.isArray(module.components))) {
throw new TypeError("PluginModule.components must be an object.");
}
} Type guard
function isComponentRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
} Prevention
- 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.
When it happens
Trigger: A plugin's activated module returns { components: [...] } (array) or { components: () => Foo } (function) or { components: null }, processed at activation in preparePluginModule (line 393 call site).
Common situations: 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.
Related errors
- PluginModule.formkit must be an object.
- PluginModule.routes must be an array.
- PluginModule.ucRoutes must be an array.
- Cannot restore a replaced route under an anonymous parent re
- Invalid attachment
AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14).
Data as JSON: /api/errors/2841d8c458bcadcb.
Report an issue: GitHub.