getgrav/grav · error · RuntimeException

Bad form data for field collection '%s': %s used instead of

Error message

Bad form data for field collection '%s': %s used instead of an array

What it means

BlueprintSchema::processFormRecursive() walks nested blueprint definitions. Whenever a blueprint node is itself an array, the corresponding submitted value may be null or absent, but if it exists it must be an array. A string, integer, boolean, or other scalar at that node throws RuntimeException naming the collection key and the actual PHP type.

Source

Thrown at system/src/Grav/Common/Data/BlueprintSchema.php:370

                // TODO: Add support to collections.
                continue;
            }
            if (is_array($value)) {
                // Special toggle handling for all the nested data.
                $toggle = $toggles[$key] ?? [];
                if (!is_array($toggle)) {
                    if (!$toggle) {
                        $data[$key] = null;

                        continue;
                    }

                    $toggle = [];
                }
                // Recursively fetch the items.
                $childData = $data[$key] ?? null;
                if (null !== $childData && !is_array($childData)) {
                    throw new \RuntimeException(sprintf("Bad form data for field collection '%s': %s used instead of an array", $key, gettype($childData)));
                }
                $data[$key] = $this->processFormRecursive($data[$key] ?? null, $toggle, $value);
            } else {
                $field = $this->get($value);
                // Do not add the field if:
                if (
                    // Not an input field
                    !$field
                    // Field has been disabled
                    || !empty($field['disabled'])
                    // Field validation is set to be ignored
                    || !empty($field['validate']['ignore'])
                    // Field is overridable and the toggle is turned off
                    || (!empty($field['overridable']) && empty($toggles[$key]))
                ) {
                    continue;
                }
                if (!isset($data[$key])) {

View on GitHub (pinned to 6040efed04)

Solutions

  1. Fix the client or form field names so every nested collection submits an array, such as address[street] rather than address.
  2. Validate incoming request data against the blueprint shape and return HTTP 400 before processForm().
  3. If the field is genuinely scalar, change the blueprint to a flat field instead of a nested collection.
  4. Sanitize legacy data before processing it, but do not silently cast arbitrary text to an array when validation matters.

Example fix

// before
$data = ['address' => '123 Main Street'];
$processed = $blueprint->processForm($data); // Bad form data ... string used instead of an array

// after
$data = ['address' => ['street' => '123 Main Street']];
$processed = $blueprint->processForm($data);
Defensive patterns

Strategy: validation

Validate before calling

foreach (['address', 'contact'] as $collection) {
    if (array_key_exists($collection, $data) && !is_array($data[$collection]) && $data[$collection] !== null) {
        throw new InvalidArgumentException(sprintf('Field collection "%s" must be an array.', $collection));
    }
}
$processed = $blueprint->processForm($data);

Type guard

function isNullableFormFieldArray(mixed $value): bool
{
    return $value === null || is_array($value);
}

Try / catch

try {
    $processed = $blueprint->processForm($data);
} catch (RuntimeException $e) {
    return $response->withStatus(400)->withJson(['error' => $e->getMessage()]);
}

Prevention

When it happens

Trigger: Call Blueprint::processForm() or BlueprintSchema::processForm() with ['address' => 'Plain text'] while the address field contains nested child fields. The check at BlueprintSchema.php:368-371 sees a non-array child value and aborts processing.

Common situations: A browser submits a flattened field name, an API client sends a string where the schema defines an object, a form-building plugin collapses nested fields, or old data is passed to a blueprint that was later changed to a nested collection.

Related errors


AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17). Data as JSON: /api/errors/ecac2f9d885d1096. Report an issue: GitHub.