Pro Forms Direct Upload reports success, then the file is not saved

Submitted by: HHH Consulting Component: Pro Forms (nestable) — File field, Direct Upload to Server
Severity: Low — the form still saves. Leave Direct Upload off (the default) and the file is stored on the record. The loss is the progress state that Direct Upload would have shown.
Type: File handoff / field name


Environment

Bricksforge 4.0.0
Bricks 2.4
WordPress 7.1.1
PHP 8.2.33
ACF PRO 6.8.10
Browser Chromium
Theme Bricks Child Theme 1.1

Observed on a photo add form that is not a Repeater. The same image was submitted twice: once with Direct Upload on, once with it off.


Summary

With Direct Upload to Server on, FilePond says the file has uploaded. The form then saves the post, but the ACF image field (and the featured image, when that is mapped to the same file field) stays empty.

With Direct Upload off, the same image is stored.

The file input is named form-field-{id}[]. A normal submit lets PHP turn that into form-field-{id}, and the save matches {id}. Direct Upload keeps the brackets, so the save looks for {id}[] and skips the file.


Steps to reproduce

  1. Add a Pro Form with a File field. Custom ID e.g. photo-front. Turn Direct Upload to Server on. Do not put the field inside a Repeater.
  2. Map that field to an ACF image field on Create Post (for example uploaded_image = {{photo-front}}).
  3. On the front end, choose an image. Wait until FilePond reports the upload complete. Submit the form.
  4. Open the new post. The ACF image is empty.
  5. Turn Direct Upload off. Submit the same image again.

Expected: both submissions store the image on the ACF field.

Actual: only the submission with Direct Upload off stores the image. FilePond still reported success on the first attempt.


Evidence

Input name

bricksforge/includes/elements/pro-forms/elements/File.php (4.0.0):

$this->set_attribute('field', 'name', 'form-field-' . $id . '[]');

For a field whose id is photo-front, the name attribute is form-field-photo-front[].

What Direct Upload stores

FilePond’s server.process receives that name as fieldName and keeps it:

window.bricksforgeData.temporaryFileUploads.push({
    file: uploadedFile,
    field: fieldName,
});

handle_temporary_file_uploads() in includes/elements/pro-forms/actions/init.php uses $file['field'] as the array key. Nothing removes the trailing [].

process_file_group() then copies that key onto the file record:

'field' => $input_name,

So the saved record is keyed form-field-photo-front[].

What the save compares

includes/elements/pro-forms/actions/base.php, when resolving {{photo-front}}:

// $field is form-field-my_field. Strip the "form-field-" part concretely
$field = substr($file['field'], strpos($file['field'], 'form-field-') + strlen('form-field-'));

if ($field === $id) {

$id is photo-front. After the strip, $field is photo-front[]. The strict compare fails, so handle_file() is never called for that field. The same strip is used in handle_file_field_ids().

get_allowed_file_types_from_settings() also strips only form-field-, so it looks up photo-front[], misses the field, and falls back to the default MIME list. The file is not rejected. It is accepted, then not attached.

Why Direct Upload off works

With the checkbox off, the browser posts the file in the form. PHP’s $_FILES key for an input named form-field-photo-front[] is form-field-photo-front (the brackets are not part of the key). The same compare then yields photo-front, which matches the field id, and the image is written.

Why FilePond still says it uploaded

form_file_upload() writes the file into the Bricksforge temp directory and returns that path. FilePond treats that response as success. No media attachment is created at that step. The attachment is supposed to be created on submit, from the temp file, and that step is the one that skips the field.


Cause

Direct Upload and a normal submit do not use the same field name.

  • Normal submit: form-field-photo-front
  • Direct Upload: form-field-photo-front[]

The save only removes the form-field- prefix. It never removes [], so the temp file is not applied to the field.


Suggested fix

Strip a trailing [] when the temp upload is recorded, and again when the field id is read back, so both paths produce photo-front:

$file_field = preg_replace('/\[\]$/', '', $file['field']);
$field = substr($file['field'], strpos($file['field'], 'form-field-') + strlen('form-field-'));
$field = preg_replace('/\[\]$/', '', $field);

Apply the second strip everywhere the prefix is removed (get_form_field_by_id, handle_file_field_ids, and the other file loop in base.php), and in get_allowed_file_types_from_settings().


What this is not

  • Not a bad image. The same file is stored when Direct Upload is off.
  • Not the known Repeater limit. Direct Upload is already unsupported inside a Repeater. This form is not a Repeater.
  • Not a permissions or ACF return-format problem. The post is created. Only the file mapped from that field is missing.

Happy to test a patch on a staging install.

Hi Pete,

thanks for the detailed write-up. I checked the code paths you listed, and the suggested cause does not hold up on the shipped code, so I want to be upfront about that before we dig further.

You are right that the input is named form-field-{id} and that FilePond hands that name to the temporary upload. But the brackets never reach the server on submit: the form’s submit handler walks the temporary uploads and strips a trailing from the field name before it appends temporaryFileUploads to the request (that has been in bricksforge_elements.js since late 2024 and is unchanged in 4.0.0). So on the server the temp file is keyed form-field-photo-front, exactly like a normal $_FILES upload, and the compare against the field ID in base.php matches. On top of that, the field lookup in get_form_field_by_id already accepts both id and id. So the bracket theory cannot be what loses the file.

So there is something else going on in your setup, and I would like to find it. Could you check two things:

  1. In the browser Network tab, open the form_submit request and look at the temporaryFileUploads value. It should contain “field”:“form-field-photo-front” (no brackets) and a “file” path that points into wp-content/uploads/bricksforge/tmp/. Please paste that JSON (you can mask the domain).

  2. 4.0.0 is stricter about which temporary files it accepts: the path in that JSON has to resolve inside uploads/bricksforge/tmp and the extension has to be one WordPress allows, otherwise the file is silently skipped. So please tell me: is this a multisite, is the uploads folder symlinked or moved by another plugin (custom upload path, offload plugins), and what is the exact file type of the image (jpg, png, heic, …)?

Also useful: does the same field work with Direct Upload on when you map it to something other than the ACF image, for example a plain Update Post Meta or an email attachment? That tells us whether the file is lost before the actions run or only in the ACF handoff.

With that info I can pin it down. Happy to send you a patch to test on staging once we know where it breaks.

Best,
Daniele