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
- Fix the client or form field names so every nested collection submits an array, such as address[street] rather than address.
- Validate incoming request data against the blueprint shape and return HTTP 400 before processForm().
- If the field is genuinely scalar, change the blueprint to a flat field instead of a nested collection.
- 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
- Generate form-field names from the blueprint so nested collections post arrays.
- Validate API request shapes before calling processForm().
- Return 400 for malformed submissions rather than coercing them.
- Add tests for scalar values at every nested collection node.
- Version blueprint structure changes and migrate legacy data explicitly.
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
- Bad data
- Cannot unserialize Block: %s
- Unsupported version %s
- Creating directory failed for {filepath}
- Opening file for writing failed on error {$message}
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/ecac2f9d885d1096.
Report an issue: GitHub.