dotnet/runtime · error · Error

${function_name} must be a Function but was ${typeof fn}

Error message

${function_name} must be a Function but was ${typeof fn}

What it means

Thrown by mono_wasm_lookup_js_import after successfully resolving the final property of a [JSImport] name, when that resolved value is not a function (typeof !== 'function'). It signals the attribute points to a non-callable export such as a constant, object, or undefined.

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 290d5ab72c)

Solutions

  1. Confirm the final segment of the JSImport path resolves to a function (typeof check in the JS module).
  2. Update the JSImport attribute to the correct function export name.
  3. If interop needs a non-function value, use a wrapper JS function that returns it instead of importing the value directly.
  4. Ensure the module is imported via JSHost.ImportAsync so the export is populated.

Example fix

// before: importing a non-function export
// JS: export const config = { url: '...' };
// C#: [JSImport("myMod.config")] static partial string GetUrl();

// after: expose a function
// JS: export function getUrl() { return config.url; }
// C#: [JSImport("myMod.getUrl")] static partial string GetUrl();
Defensive patterns

Strategy: type-guard

Validate before calling

const fn = scope[fname];
if (typeof fn !== 'function') {
  // the export is missing or not callable; fix the JS export or the attribute
}

Type guard

function isCallable(scope: any, name: string): boolean {
  return typeof scope?.[name] === 'function';
}

Prevention

When it happens

Trigger: Produced when the last segment of a [JSImport] name resolves to a non-function value. E.g. [JSImport("myMod.config")] where `config` is an object/string, or the export does not exist on the module (resolves to undefined).

Common situations: Attribute points to a property/constant instead of a function; the export was renamed; the default export is referenced as if it were a named function; the module exports an object whose member is not a function.

Related errors


AI-assisted analysis of dotnet/runtime@290d5ab72c (2026-08-06). Data as JSON: /api/errors/8ada58bb0c30519a. Report an issue: GitHub.