Documents the 'can't access property main, n.status is undefined' crash in FilePond 4.32.12. Root cause: vendor code in filepond.min.js has a property name mismatch — createResponse objects use .code but the load-file-error handler reads .status. When action.status is undefined, the view writers crash. Proposes Option B (custom load function) as the cleanest fix.
10 KiB
FilePond crash analysis — TFE upload forms
Status: unresolved — analysis complete, root cause identified in vendor code.
Hand this doc (and the whole repo) to another agent for implementing the fix.
Errors observed (Firefox, dev server 127.0.0.1:8000)
Trigger: adding an image to "Image de couverture" (cover queue) or any TFE file upload form.
InstallTrigger is deprecated and will be removed in the future. content.js:1
Failed to execute 'postMessage' on 'DOMWindow': target origin mismatch 2 20260609-...
htmx:targetError htmx.min.js:1
Uncaught TypeError: can't access property "main", n.status is undefined filepond.min.js:9
Wt filepond.min.js:9
A filepond.min.js:9
A filepond.min.js:9
_write filepond.min.js:9 (×16)
<anonymous> filepond.min.js:9
<anonymous> filepond.min.js:9
e filepond.min.js:9
e filepond.min.js:9
u filepond.min.js:9 (× many — retry loop)
e filepond.min.js:9
u ...
[filepond:event] error Object { pond: {…}, error: null, file: {…} } file-upload-filepond.js:587
Rows 1–3 are noise: InstallTrigger/postMessage are Firefox internals; htmx:targetError is an unrelated HTMX issue. The real crash is rows 4+.
Root cause
The crash
At filepond.min.js:9:60852, FilePond 4.32.12 crashes inside its view system's _write method. Two view writers dereference action.status.main:
FilePond unminified (file-status view), line 7847:
var error = function error(_ref8) {
var root = _ref8.root,
action = _ref8.action;
text(root.ref.main, action.status.main); // ← crashes if action.status is undefined
text(root.ref.sub, action.status.sub);
};
FilePond unminified (assistant view), line 10735:
var itemError = function itemError(_ref6) {
var root = _ref6.root,
action = _ref6.action;
var item = root.query('GET_ITEM', action.id);
var filename = item.filename;
assist(root, action.status.main + ' ' + filename + ' ' + action.status.sub);
};
Neither function guards against action.status === undefined.
How action.status becomes undefined
FilePond's internal response objects use the property name code, not status:
// line 4700
var createResponse = function createResponse(type, code, body, headers) {
return {
type: type,
code: code, // ← "code", not "status"
body: body,
headers: headers,
};
};
But the load-file-error event handler accesses error.status:
// line 6777-6784
item.on('load-file-error', function(error) {
dispatch('DID_THROW_ITEM_INVALID', {
id: id,
error: error.status, // ← .status is undefined on createResponse objects!
status: error.status, // ← dispatches undefined as the status
});
failure({ error: error.status, file: createItemAPI(item) });
});
Because the error object has .code (not .status), both error: error.status and status: error.status are undefined. When the dispatched action reaches the view writer, action.status is undefined → crash.
When does load-file-error fire?
The load-file-error event is emitted in the item _load method when the LOAD_FILE filter chain rejects:
// line 5855-5863
loader.on('load', function(file) {
var error = function error(result) {
state.file = file;
fire('load-meta');
setStatus(ItemStatus.LOAD_ERROR);
fire('load-file-error', result); // ← fires when filter chain rejects
};
if (state.serverFileReference) {
success(file); // ← existing files take this safe path
return;
}
onload(file, success, error); // ← new files take this path
});
For existing DB files (edit mode), state.serverFileReference is set → success(file) is called directly → load-file-error never fires.
For newly added files (no serverId yet), onload(file, success, error) runs the LOAD_FILE filter chain. The FilePond FileValidateType plugin (v1.2.8) registers a LOAD_FILE filter:
// plugin line 132
addFilter('LOAD_FILE', function(file, _ref3) {
// ...
var handleRejection = function handleRejection() {
reject({
status: { main: '...', sub: '...' } // ← plugin rejects with proper status object
});
};
// ...
});
The plugin rejects with { status: { main, sub } }. This is CORRECT. The rejected value flows into the error(result) callback → result.status IS { main, sub }. So when load-file-error fires from a plugin rejection, error.status is actually a proper object, NOT undefined. This particular path is safe.
However, load-file-error can also fire from the DID_LOAD_ITEM filter chain catch handler at line 6878:
.catch(function(e) {
if (!e || !e.error || !e.status) return handleAdd(false);
dispatch('DID_THROW_ITEM_INVALID', {
id: id,
error: e.error,
status: e.status, // ← e.status could be anything
});
});
This dispatches directly to DID_THROW_ITEM_INVALID (bypassing load-file-error), but it still copies e.status into the action. If e.status is undefined or not an object with main/sub, same crash.
How raw createResponse objects reach load-file-error
There IS one path where a raw createResponse object (with .code, no .status) reaches load-file-error:
When the server returns an HTTP error for a load request but the XHR onload handler treats it as success:
// line 4652
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
api.onload(xhr); // → blob processed
} else {
api.onerror(xhr); // → error callback
}
};
If xhr.status is 0 (aborted XHR), it goes to api.onerror. But if the XHR is in some intermediate state, or if there's a race, it might not reach either path cleanly. Firefox's behavior with aborted XHRs and responseType: 'blob' can produce edge cases where xhr.response is a malformed blob and FilePond's blob processing triggers internal errors that propagate differently.
Summary of the bug
| Component | Issue |
|---|---|
createResponse() (line 4700) |
Uses .code, not .status |
load-file-error handler (line 6777) |
Reads .status on a createResponse object → undefined |
| Error view writer (line 7847) | No guard: crashes on undefined.status.main |
| Assistant view writer (line 10735) | Same: crashes on undefined.status.main |
The bug is in FilePond 4.32.12 vendor code. We cannot modify filepond.min.js.
Proposed fix
Option A: Patch the minified JS (risky but direct)
Find the load-file-error → DID_THROW_ITEM_INVALID dispatch in filepond.min.js and add a guard. Difficult because the code is minified and version-pinned via cache-busting query params.
Option B: Replace server.load with a custom function (cleanest)
In file-upload-filepond.js, replace the server.load URL string with a custom function that:
- Makes its own
fetch/XHR to load.php - On success: calls
load(blob)— safe becauseserverFileReferenceis set for existing files - On error: calls
error('message')— safe because this goes throughload-request-error(NOTload-file-error) which properly creates{ status: { main, sub } } - Never lets FilePond's internal
createFetchFunctioncreate a createResponse object with.code
This completely bypasses the buggy code path.
load: function(source, load, error, progress, abort, headers) {
var xhr = new XMLHttpRequest();
var url = base + '/load.php?id=' + encodeURIComponent(source);
xhr.open('GET', url);
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status >= 200 && xhr.status < 300) {
load(xhr.response);
} else {
error('Fichier introuvable (HTTP ' + xhr.status + ')');
}
};
xhr.onerror = function() {
error('Erreur réseau');
};
xhr.onabort = abort;
xhr.onprogress = function(e) {
if (e.lengthComputable) progress(e.lengthComputable, e.loaded, e.total);
};
xhr.send();
return { abort: function() { xhr.abort(); } };
},
Option C: Abort in-flight loads before destroying (defense in depth)
The destroyFilePondsIn() function in file-upload-filepond.js should abort in-flight loads/processing before calling pond.destroy(). Already partially attempted in commit znunoqpw but needs clean implementation.
Files involved
| File | Role |
|---|---|
app/public/assets/js/vendor/filepond.min.js |
FilePond 4.32.12 — contains the bug (unmodifiable) |
app/public/assets/js/app/file-upload-filepond.js |
Our FilePond wrapper — where the fix goes |
app/src/FilepondHandler.php |
Server-side FilePond endpoints (process, load, revert, remove) |
app/public/admin/actions/filepond/load.php |
Admin load endpoint |
app/public/admin/actions/filepond/process.php |
Admin process endpoint |
app/public/partage/actions/filepond/load.php |
Partage load endpoint |
app/public/partage/actions/filepond/process.php |
Partage process endpoint |
Reproduction
just dev(PHP dev server on 127.0.0.1:8000)- Open Firefox (Firefox triggers this more readily than Chromium due to different XHR abort behavior)
- Go to
/admin/edit.php?id=<any>or/admin/add.php - Click "Parcourir" on the "Image de couverture" FilePond input
- Select an image file → crash in console
- Or: drag a file to the "TFE" FilePond input → same crash if the load fails or races with HTMX swaps
What commit znunoqpw already did (insufficient)
- Added
Content-Type: text/plainheaders to all FilepondHandler error responses - Fixed
server.process.onerrorto not access.statuson a string - Converted
server.loadfrom a URL string to an object with onload/onerror - Added pre-destroy abort in
destroyFilePondsIn()
These changes address server response format and cleanup ordering, but do not bypass the buggy load-file-error → action.status path inside FilePond's internal code. The crash still reproduces.