dotnet/runtime · error · Error
must be a Function but was
Error message
${function_name} must be a Function but was ${typeof fn} What it means
Thrown by mono_wasm_lookup_js_import in invoke-js.ts after successfully resolving the full dotted path: the final value (scope[fname]) must be typeof 'function', otherwise it throws with the actual typeof. So the path resolves to a non-function (object, string, undefined, number) and cannot be bound as a JSImport.
Solutions
- Make sure the last segment of the [JSImport] path refers to an actual function.
- Export the target as a named function in the JS module.
- Fix casing: JS is case-sensitive, a wrong-case name resolves to undefined.
Example fix
// before: export const parse = (s) => JSON.parse(s); imported as [JSImport("parse")]
// that works, but if imported a constant:
// export const DEFAULT = 42; // typeof 'number' -> error
// after: expose a function
export const getDefault = () => DEFAULT; Defensive patterns
Strategy: type-guard
Validate before calling
// verify the resolved value is callable
const v = scope[fname];
if (typeof v !== 'function') throw new TypeError(`${fname} is ${typeof v}, expected function`); Type guard
function isFunctionRef(value: unknown): value is Function {
return typeof value === 'function';
} Prevention
- Ensure the last segment of the [JSImport] path is a function export.
- Watch for case mismatches in function names (JS is case-sensitive).
- Re-run binding tests after any JS module refactor.
When it happens
Trigger: A [JSImport("MyApp.value")] where value is a property/constant, not a function; the final name resolves to undefined because of a typo; the JS module exports a class/namespace object instead of a method.
Common situations: Importing a getter, constant, or class instance by mistake; mismatched casing in the function name; the JS module default export is an object whose methods are one level deeper.
Related errors
- must be a Function but was
- not found while looking up
- JSObject proxy is not supported for
- not implemented
- NotImplementedException: ArraySegment. For more information…
AI-assisted analysis of dotnet/runtime@60108ba66e (2026-08-10).
Data as JSON: /api/errors/8ada58bb0c30519a.
Report an issue: GitHub.
Appendix: source
Thrown at src/mono/browser/runtime/invoke-js.ts:413
} else if (parts[0] === "globalThis") {
scope = globalThis;
parts.shift();
}
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
const newscope = scope[part];
if (!newscope) {
throw new Error(`${part} not found while looking up ${function_name}`);
}
scope = newscope;
}
const fname = parts[parts.length - 1];
const fn = scope[fname];
if (typeof (fn) !== "function") {
throw new Error(`${function_name} must be a Function but was ${typeof fn}`);
}
// if the function was already bound to some object it would stay bound to original object. That's good.
return fn.bind(scope);
}
export function set_property (self: any, name: string, value: any): void {
mono_check(self, "Null reference");
self[name] = value;
}
export function get_property (self: any, name: string): any {
mono_check(self, "Null reference");
return self[name];
}
export function has_property (self: any, name: string): boolean {
mono_check(self, "Null reference");View on GitHub (pinned to 60108ba66e)