{"record":{"id":"fda1f906977a563b","repo":"getgrav/grav","slug":"400-fda1f9","errorCode":"400","errorMessage":"%s: Relationship %s cannot be modified","messagePattern":"(.+?): Relationship (.+?) cannot be modified","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":400,"severity":"error","filePath":"system/src/Grav/Common/Flex/Types/Users/UserObject.php","lineNumber":745,"sourceCode":"        throw new \\InvalidArgumentException(sprintf('%s: Relationship %s does not exist', $this->getFlexType(), $name));\n    }\n\n    /**\n     * @return bool Return true if relationships were updated.\n     */\n    protected function updateRelationships(): bool\n    {\n        $modified = $this->getRelationships()->getModified();\n        if ($modified) {\n            foreach ($modified as $relationship) {\n                $name = $relationship->getName();\n                switch ($name) {\n                    case 'avatar':\n                        \\assert($relationship instanceof ToOneRelationshipInterface);\n                        $this->updateAvatarRelationship($relationship);\n                        break;\n                    default:\n                        throw new \\InvalidArgumentException(sprintf('%s: Relationship %s cannot be modified', $this->getFlexType(), $name), 400);\n                }\n            }\n\n            $this->resetRelationships();\n\n            return true;\n        }\n\n        return false;\n    }\n\n    /**\n     * @param ToOneRelationshipInterface $relationship\n     */\n    protected function updateAvatarRelationship(ToOneRelationshipInterface $relationship): void\n    {\n        $files = [];\n        $avatar = $this->getAvatarImage();","sourceCodeStart":727,"sourceCodeEnd":763,"githubUrl":"https://github.com/getgrav/grav/blob/6040efed04efa69b8209448ed81308e7c24147c2/system/src/Grav/Common/Flex/Types/Users/UserObject.php#L727-L763","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Drop the non-avatar relationship change from the modification set before save() — user media is managed through media uploads/fields, not the relationship API.","Validate submitted relationship names against ['avatar'] and reject others with your own 400/422 before save() is reached.","Catch InvalidArgumentException and map it to an HTTP 400 naming the read-only relationship so clients get an actionable message."],"exampleFix":"// before\nforeach ($payload['relationships'] ?? [] as $name => $data) {\n    $user->getRelationships()->get($name)->update($data);\n}\n$user->save(); // 400: Relationship media cannot be modified\n\n// after\nforeach ($payload['relationships'] ?? [] as $name => $data) {\n    if ('avatar' !== $name) {\n        continue; // or reject with 422\n    }\n    $user->getRelationships()->get('avatar')->update($data);\n}\n$user->save();","handlingStrategy":"validation","validationCode":"foreach ($payload['relationships'] ?? [] as $name => $_) {\n    if ('avatar' !== $name) {\n        unset($payload['relationships'][$name]); // or reject with 422\n    }\n}","typeGuard":"function isWritableUserRelationship(string $name): bool\n{\n    return 'avatar' === $name;\n}","tryCatchPattern":"try {\n    $user->save();\n} catch (\\InvalidArgumentException $e) {\n    if (str_contains($e->getMessage(), 'cannot be modified')) {\n        // return 400 listing the read-only relationship\n    }\n    throw $e;\n}","preventionTips":["Document which relationships are read-only ('media') vs writable ('avatar') in your API layer.","Strip unknown relationship keys from payloads before mutation, not after.","Manage user media via upload/field APIs, never via the relationship writer."],"tags":["flex","user-management","relationships","http-400","read-only"],"backgroundTag":"read-only-relationship-write","analyzedSha":"6040efed04efa69b8209448ed81308e7c24147c2","analyzedAt":"2026-08-17T05:07:31.593Z","schemaVersion":2},"datasetVersion":"2026-08-17T09:17:11.063Z"}