Diagnosing PureScript FFI Runtime Errors: Foreign Import Failures
Step‑by‑step guide to diagnose and fix PureScript FFI runtime errors such as 'Foreign import failed' by checking annotations, exports, arity, and bundle inclusion.
05 Oct 2026, 00:14 UTC

Recognizable Condition
When running compiled PureScript code that uses a foreign import to call JavaScript, you may see runtime errors such as:
Foreign import failedUndefined function- TypeError:
foo is not a function
These errors occur after the code has type‑checked successfully, indicating a mismatch between the PureScript foreign import declaration and the actual JavaScript function it tries to invoke.
Cause/Diagnostic Table
| Symptom | Likely Cause | What to Look For |
|---|---|---|
Runtime error despite successful purs build or spago build |
Missing or incorrect @js annotation |
Foreign import lacks @js "name" or uses a different string |
| Error mentions a function name that does not exist in the generated JS | Export name mismatch | Generated JS calls exports.foo but the module defines bar |
| Error about wrong number of arguments | Arity mismatch between PureScript type and JS function | Foreign import declares Int -> Int -> Int but JS expects one argument |
| Error persists after fixing annotation and export | Module not included in bundle or conflicting global | Import path incorrect, or another script overwrites the export |
Ordered Checks
-
Verify the foreign import declaration
Open the PureScript source file and ensure the import includes the
@jsannotation with the exact string used in the JavaScript export, and that the function type matches the JavaScript arity and parameter types.-- Correct example foreign import add @\"add\" :: Int -> Int -> Int -
Inspect the generated JavaScript
Run the build command to produce the output and locate the call site.
# Using Spago spago build # Or using purs directly purs build -t javascript src/**/*.pursFind the emitted JavaScript (e.g.,
output/Main/index.js) and look for a line similar to:var add = $foreign.add;Confirm that the identifier after
$foreign.matches the string in the@jsannotation. -
Check the JavaScript module export
Ensure the JavaScript file exports a function with the exact name used in the annotation.
// correct export function add(a, b) { return a + b; } export { add };If you are using CommonJS, verify:
exports.add = function(a, b) { return a + b; }; -
Confirm the module is bundled
Make sure the JavaScript file is part of the build inputs. In a Spago project, place the file under
src/or add it topsc-packagedependencies. Verify that arequireorimportstatement for the file appears near the top of the generated output.// Example of a generated import line var add = require('./add.js');
Fixes Tied to Findings
- Missing/wrong
@jsannotation: Add or correct the annotation. foreign import foo @\"foo\" :: Int -> Int- If the JavaScript export uses a different name, change the string accordingly.
- Export name mismatch: Rename the export in the JavaScript file or adjust the annotation string to match the existing export.
- Arity mismatch: Update the PureScript type to reflect the actual number of arguments the JavaScript function expects (including any currying considerations).
- Module not bundled: Move the JavaScript file into the source tree or add its path to the build configuration (
spago.dhallsourceglobs orpsc-packagedependencies).
Escalation Criteria
If after performing the checks and applying the fixes the runtime error persists, consider the following deeper issues:
- Conflicting global definitions: another script may overwrite the exported function after it is loaded.
- Improper use of
unsafePerformEffectcausing the foreign import to be evaluated at an unexpected time. - Use of JavaScript language features not supported by the target runtime (e.g., optional chaining in an ES5 environment).
To isolate the problem:
- Create a minimal test module that only contains the problematic foreign import and a simple call to it.
- Rebuild with verbose logging:
spago build -vorpurs build -vto see the exact command lines and file paths. - Examine the generated JavaScript snippet for the test module; if the call looks correct, the issue lies outside the FFI (e.g., bundling or global interference).
- If needed, ask for help on the PureScript Discord or forum, providing the generated JavaScript and the foreign import declaration.
Limitations and Verification
This guide covers the most common sources of FFI runtime errors. It does not address:
- Issues arising from JavaScript exceptions thrown inside the foreign function (those will propagate as PureScript exceptions).
- Problems with data structure marshalling when using complex types (e.g., arrays, records) without proper
Foreignhelpers.
To verify that a fix works, run the compiled output in the target environment (Node.js or browser) and confirm that the function returns the expected value without throwing. For the example above, after correcting the annotation, the call Main.main() should print 42 (or whatever the test expects) and exit cleanly.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.