{"record":{"id":"0796dabda559a22b","repo":"octobercms/october","slug":"missing-relation-definition-for-inverse-field-t","errorCode":null,"errorMessage":"Missing relation definition for inverse field '{$this->inverse}' for model class '{$this->modelClass}' for '{$this->fieldName}'.","messagePattern":"Missing relation definition for inverse field '(.+?)' for model class '(.+?)' for '(.+?)'\\.","errorType":"exception","errorClass":"SystemException","httpStatus":null,"severity":"error","filePath":"modules/tailor/contentfields/RecordFinderField.php","lineNumber":222,"sourceCode":"                'table' => $model->getBlueprintDefinition()->getJoinTableName(),\n                'name' => $this->fieldName,\n                'relationClass' => CustomMultiJoinRelation::class,\n                'relatedKey' => ($relatedMultisite && !$isDualMultisite) ? 'site_root_id' : 'id',\n                'relatedMultisite' => $relatedMultisite\n            ];\n        }\n    }\n\n    /**\n     * defineInverseModelRelationship for the inverse relationship. These definitions\n     * do not rely on multisite via propagatable attribute. Inverse relations do not\n     * replicate and should be treated as read-only.\n     */\n    protected function defineInverseModelRelationship($model)\n    {\n        $relatedModel = $this->getRelatedModel();\n        if (!$relatedModel->hasRelation($this->inverse)) {\n            throw new SystemException(\"Missing relation definition for inverse field '{$this->inverse}' for model class '{$this->modelClass}' for '{$this->fieldName}'.\");\n        }\n\n        $isNested = $model instanceof RepeaterItem;\n\n        // RepeaterItem doesn't have getBlueprintDefinition\n        $parentMultisite = $isNested ? false : $model->getBlueprintDefinition()->useMultisite();\n        $relationDefinition = $relatedModel->getRelationDefinition($this->inverse);\n        $relatedMultisite = $relatedModel->isClassInstanceOf(\\October\\Contracts\\Database\\MultisiteInterface::class) &&\n            $relatedModel->isMultisiteEnabled();\n\n        // Dual-multisite: both parent and related use multisite\n        $isDualMultisite = $parentMultisite && $relatedMultisite;\n\n        $isSingular = $this->maxItems === 1;\n        $otherIsSingular = $relatedModel->isRelationTypeSingular($this->inverse);\n        $otherIsPropagatable = $relatedMultisite ? $relatedModel->isAttributePropagatable($this->inverse) : false;\n\n        // In dual-multisite, use 'id' for proper site isolation; otherwise use site_root_id for sharing","sourceCodeStart":204,"sourceCodeEnd":240,"githubUrl":"https://github.com/octobercms/october/blob/b608633a7e8922487d91a8161499020121c3b3bf/modules/tailor/contentfields/RecordFinderField.php#L204-L240","documentation":"For a recordfinder field with `inverse:`, Tailor requires the related model to already define a relation with that name ($relatedModel->hasRelation($this->inverse)) before wiring the inverse. If the target blueprint never created such a relation — typically because the inverse side is not an entries field pointing back — the SystemException fires during model extension.","triggerScenarios":"`inverse: items` is set on a recordfinder whose source blueprint/model defines no `items` relation: the inverse field was renamed, is a different field type that doesn't generate that relation, or the source blueprint failed to compile its relations.","commonSituations":"Assuming `inverse:` creates the reverse relation instead of requiring one to exist; renaming the entries field on the source blueprint without updating the recordfinder; deploying only one side of a blueprint pair.","solutions":["Define an entries field named exactly like the `inverse:` value on the source blueprint so the relation exists on its model.","Remove `inverse:` if bidirectional navigation isn't needed.","Re-run `php artisan tailor:refresh` after the change so model relations are rebuilt."],"exampleFix":"# before (recordfinder)\nfields:\n  primary_author:\n    type: recordfinder\n    source: authors\n    inverse: authored_posts   # no such relation on Author model\n\n# after (authors blueprint must define)\nfields:\n  authored_posts:\n    type: entries\n    source: blog/posts\n    inverse: primary_author","handlingStrategy":"validation","validationCode":"$relatedModel = $sourceBlueprint->newModelInstance();\nif (!$relatedModel->hasRelation($inverse)) {\n    throw new InvalidArgumentException(\"Related model has no relation '{$inverse}' — define an entries field with that handle on the source blueprint.\");\n}","typeGuard":"function inverseRelationExists($relatedModel, string $inverse): bool\n{\n    return $relatedModel->hasRelation($inverse);\n}","tryCatchPattern":"try {\n    // model extension / tailor:refresh\n} catch (\\SystemException $e) {\n    // message names the missing relation; add the matching entries field on the source blueprint and refresh\n}","preventionTips":["Before adding `inverse:` to a recordfinder, confirm the target model exposes that relation (an entries field with the same handle).","Keep both sides of an inverse pair in one commit and refresh Tailor after changes.","Never rename one side of an inverse pair without grepping for the old handle."],"tags":["tailor","recordfinder","inverse-relation","blueprint"],"backgroundTag":"unresolved-reference","analyzedSha":"b608633a7e8922487d91a8161499020121c3b3bf","analyzedAt":"2026-08-21T04:24:57.515Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}