ruvnet/ruflo · error · Error
Module ${modulePath} does not export a plugin
Error message
Module ${modulePath} does not export a plugin What it means
loadPlugin(modulePath) dynamically imports the module and expects `export default` or a named `plugin` export containing an IPlugin instance or an async factory function. If neither export exists it throws before touching the registry. The lookup is exactly `module.default ?? module.plugin`, so any other export shape fails.
Source
Thrown at v3/@claude-flow/plugins/src/index.ts:342
}
/**
* Load and register a plugin module dynamically.
*
* @example
* ```typescript
* const plugin = await loadPlugin('./my-plugin.js');
* ```
*/
export async function loadPlugin(
modulePath: string,
config?: Partial<PluginConfig>
): Promise<IPlugin> {
const module = await import(modulePath);
const pluginOrFactory: IPlugin | PluginFactory = module.default ?? module.plugin;
if (!pluginOrFactory) {
throw new Error(`Module ${modulePath} does not export a plugin`);
}
const plugin = typeof pluginOrFactory === 'function'
? await pluginOrFactory()
: pluginOrFactory;
await getDefaultRegistry().register(plugin, config);
return plugin;
}
/**
* Initialize the plugin system.
*
* @example
* ```typescript
* await initializePlugins({
* coreVersion: '3.0.0',
* dataDir: './data',View on GitHub (pinned to fa13ee4ad6)
Solutions
- Ensure the target module has `export default plugin` (or `export const plugin = ...`) — instance or async factory both work
- Debug the resolution: `const m = await import(path); console.log(Object.keys(m), m.default)` and fix the path or export accordingly
- For CJS plugins use `module.exports = plugin` so interop exposes it as default, or re-export via a small ESM wrapper
Example fix
// before (my-plugin/index.js)
export class MyPlugin { /* ... */ } // no default, no `plugin` export
// after
export class MyPlugin { /* ... */ }
export default new MyPlugin();
// or: export const plugin = async () => new MyPlugin(); Defensive patterns
Strategy: type-guard
Validate before calling
// Inspect the module before handing it to loadPlugin:
const mod: any = await import(modulePath);
if (!mod?.default && !mod?.plugin) {
throw new Error(
`${modulePath} exports [${Object.keys(mod).join(', ')}] — need default or 'plugin'`
);
} Type guard
type PluginExport = { default?: unknown; plugin?: unknown };
function isPluginExport(mod: PluginExport): boolean {
const candidate = mod.default ?? mod.plugin;
return typeof candidate === 'object' || typeof candidate === 'function';
} Try / catch
try {
plugin = await loadPlugin(modulePath, config);
} catch (err) {
if (err instanceof Error && /does not export a plugin/.test(err.message)) {
// log Object.keys(await import(modulePath)) to see the real export shape
}
throw err;
} Prevention
- Standardize plugin packages on `export default` (instance or async factory)
- Verify dynamic-import paths resolve (extension, casing) in native ESM
- Write one contract test per plugin package asserting its default export exists
When it happens
Trigger: The plugin module exports only a named class or const other than `plugin`; a CommonJS module.exports without ESM interop so module.default is undefined; the path resolving to the wrong file (typo, missing extension in native ESM, barrel index re-export that drops the plugin); a package whose main entry is not the plugin module.
Common situations: Third-party plugin packages using non-standard export names; CJS/ESM interop mismatches under tsconfig `esModuleInterop` changes; path mistakes under bundler-less Node ESM where extensions matter; refactors that moved the default export into a named one.
Related errors
- Failed to import OpenAI
- "@agntcy/slim-bindings" is installed but does not export pub
- "@agntcy/slim-bindings" is installed but does not export joi
- "@agntcy/slim-bindings" is installed but does not export cre
- AIDefence failed to load: ${error.message}
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/76c04230837aaf7c.
Report an issue: GitHub.