halo-dev/halo · error · TypeError

PluginModule.formkit must be an object.

Error message

PluginModule.formkit must be an object.

What it means

Thrown by preparePluginModule when module.formkit is present but not a plain object (isRecord fails — array, primitive, function, null). formkit must be a PluginFormKit record mapping input names to FormKit definitions so Halo can register custom FormKit inputs. A wrong shape would corrupt FormKit config, so activation throws a TypeError.

Source

Thrown at ui/src/setup/setupModules.ts:677

  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(
  routes: RouteRecordRaw[] | RouteRecordAppend[] | undefined,
  core: boolean,
  registration?: UiProviderRegistration,
  report?: (
    registration: UiProviderRegistration,
    stage: UiPluginFailureStage,
    error: unknown
  ) => void
) {

View on GitHub (pinned to d2f5165f9c)

Solutions

  1. Return formkit as an object keyed by input name: { formkit: { myInput: { type: 'input' } } }.
  2. Omit the field if the plugin ships no custom FormKit inputs.
  3. Typecheck the plugin module against PluginModule.

Example fix

// before
export default definePluginModule({ formkit: [{ type: "input", name: "myInput" }] });

// after
export default definePluginModule({ formkit: { myInput: { type: "input" } } });
Defensive patterns

Strategy: type-guard

Validate before calling

function assertFormkitShape(module: { formkit?: unknown }) {
  if (module.formkit && (typeof module.formkit !== "object" || module.formkit === null || Array.isArray(module.formkit))) {
    throw new TypeError("PluginModule.formkit must be an object.");
  }
}

Type guard

function isFormkitRecord(value: unknown): value is Record<string, unknown> {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}

Prevention

When it happens

Trigger: A plugin module returns { formkit: [...] } (array) or { formkit: null } or a function during activation; preparePluginModule's isRecord(module.formkit) check fails.

Common situations: Author mistakes formkit for an array of definitions; assigns a single schema; a generator returns the wrong shape.

Related errors


AI-assisted analysis of halo-dev/halo@d2f5165f9c (2026-08-14). Data as JSON: /api/errors/241eff7faae2016b. Report an issue: GitHub.