getgrav/grav · error · InvalidArgumentException

400

400

Error message

%s: Relationship %s cannot be modified

What it means

During save, UserObject::updateRelationships() (line 745) iterates modified relationships and only knows how to persist 'avatar' (via updateAvatarRelationship). Any other modified relationship hits the default branch and throws InvalidArgumentException with HTTP code 400. Relationships like 'media' are readable but read-only through this API, so the error means your payload tried to write a relation the type does not support.

Source

Thrown at system/src/Grav/Common/Flex/Types/Users/UserObject.php:745

        throw new \InvalidArgumentException(sprintf('%s: Relationship %s does not exist', $this->getFlexType(), $name));
    }

    /**
     * @return bool Return true if relationships were updated.
     */
    protected function updateRelationships(): bool
    {
        $modified = $this->getRelationships()->getModified();
        if ($modified) {
            foreach ($modified as $relationship) {
                $name = $relationship->getName();
                switch ($name) {
                    case 'avatar':
                        \assert($relationship instanceof ToOneRelationshipInterface);
                        $this->updateAvatarRelationship($relationship);
                        break;
                    default:
                        throw new \InvalidArgumentException(sprintf('%s: Relationship %s cannot be modified', $this->getFlexType(), $name), 400);
                }
            }

            $this->resetRelationships();

            return true;
        }

        return false;
    }

    /**
     * @param ToOneRelationshipInterface $relationship
     */
    protected function updateAvatarRelationship(ToOneRelationshipInterface $relationship): void
    {
        $files = [];
        $avatar = $this->getAvatarImage();

View on GitHub (pinned to 6040efed04)

Solutions

  1. Drop the non-avatar relationship change from the modification set before save() — user media is managed through media uploads/fields, not the relationship API.
  2. Validate submitted relationship names against ['avatar'] and reject others with your own 400/422 before save() is reached.
  3. Catch InvalidArgumentException and map it to an HTTP 400 naming the read-only relationship so clients get an actionable message.

Example fix

// before
foreach ($payload['relationships'] ?? [] as $name => $data) {
    $user->getRelationships()->get($name)->update($data);
}
$user->save(); // 400: Relationship media cannot be modified

// after
foreach ($payload['relationships'] ?? [] as $name => $data) {
    if ('avatar' !== $name) {
        continue; // or reject with 422
    }
    $user->getRelationships()->get('avatar')->update($data);
}
$user->save();
Defensive patterns

Strategy: validation

Validate before calling

foreach ($payload['relationships'] ?? [] as $name => $_) {
    if ('avatar' !== $name) {
        unset($payload['relationships'][$name]); // or reject with 422
    }
}

Type guard

function isWritableUserRelationship(string $name): bool
{
    return 'avatar' === $name;
}

Try / catch

try {
    $user->save();
} catch (\InvalidArgumentException $e) {
    if (str_contains($e->getMessage(), 'cannot be modified')) {
        // return 400 listing the read-only relationship
    }
    throw $e;
}

Prevention

When it happens

Trigger: Saving a user while a relationship other than avatar is marked modified in the relationship collection — e.g. mutating the 'media' relationship object before save(), or a PATCH/POST to an endpoint that maps client-submitted relationship names onto the object; programmatic attempts to attach media files as relationships.

Common situations: REST integrations that try to modify user media through the generic flex relationship endpoint; plugins ported from flex types that do support writable relationships; stale client payloads carrying relationship changes the server cannot apply.

Related errors


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