babel/babel · error · Error
.visitor["${key}"] may only have .enter and/or .exit handler
Error message
.visitor["${key}"] may only have .enter and/or .exit handlers. What it means
Thrown by assertVisitorHandler when a per-node visitor object (e.g. visitor.Identifier) contains a property other than 'enter' or 'exit'. The per-node value may be either a single function or an object with exactly the enter/exit keys; any other key is rejected because Babel's traversal has no defined behaviour for it.
Source
Thrown at packages/babel-core/src/config/validation/plugins.ts:67
if (obj.enter || obj.exit) {
throw new Error(
`${msg(
loc,
)} cannot contain catch-all "enter" or "exit" handlers. Please target individual nodes.`,
);
}
}
return obj as Visitor;
}
function assertVisitorHandler(
key: string,
value: unknown,
): asserts value is VisitorHandler {
if (value && typeof value === "object") {
Object.keys(value).forEach((handler: string) => {
if (handler !== "enter" && handler !== "exit") {
throw new Error(
`.visitor["${key}"] may only have .enter and/or .exit handlers.`,
);
}
});
} else if (typeof value !== "function") {
throw new Error(`.visitor["${key}"] must be a function`);
}
}
type VisitorHandler =
| Function
| {
enter?: Function;
exit?: Function;
};
export type PluginObject<S extends PluginPass = PluginPass> = {
name?: string;View on GitHub (pinned to 06b6eae39d)
Solutions
- Keep only 'enter' and/or 'exit' inside per-node visitor objects.
- Store any extra metadata on the plugin's state object (PluginPass) or a closure variable, not on the visitor.
- Rename a 'visit' method to 'enter' if that was the intent.
Example fix
// before
visitor: { Identifier: { visit(path){...}, meta: true } }
// after
visitor: { Identifier: { enter(path){...} } } Defensive patterns
Strategy: type-guard
Validate before calling
function assertVisitorHandlers(visitor) {
for (const [node, handler] of Object.entries(visitor)) {
if (handler && typeof handler === 'object') {
for (const k of Object.keys(handler)) {
if (k !== 'enter' && k !== 'exit') {
throw new Error(`visitor.${node} has invalid handler '${k}'`);
}
}
}
}
} Type guard
import type { Visitor } from '@babel/traverse';
// Visitor<S> already restricts per-node objects to { enter?, exit? } Try / catch
try { transformSync(code, { plugins: [plugin] }); } catch (e) {
if (String(e.message).includes('may only have .enter and/or .exit')) {
throw new Error('Plugin visitor has an unknown handler key');
}
throw e;
} Prevention
- Type-check plugins with Visitor<S> from @babel/traverse.
- Store plugin metadata on state/closures, not on the visitor object.
- Remember Babel uses enter/exit, not visit.
When it happens
Trigger: Writing visitor: { Identifier: { enter(){}, shouldStop: true } } or visitor: { Identifier: { visit(){}, exit(){} } } where 'shouldStop'/'visit' are not valid handler names. The loop at plugins.ts:64-71 throws on the first invalid handler key.
Common situations: Trying to attach metadata to a visitor object; confusing Babel's visitor schema with another library's; using 'visit' instead of 'enter' (Babel uses enter/exit, not visit).
Related errors
- ${msg(loc)} cannot contain catch-all "enter" or "exit" handl
- .visitor["${key}"] must be a function
- You appear to be using an async parser plugin, which your cu
- You gave us a visitor for the node type ${nodeType} but it's
- You passed `traverse()` a visitor object with the property $
AI-assisted analysis of babel/babel@06b6eae39d (2026-08-03).
Data as JSON: /data/errors/6d8e1ae98f5004d1.json.
Report an issue: GitHub.