octobercms/october · error · SystemException

Missing relation definition for inverse field '{$this->inver

Error message

Missing relation definition for inverse field '{$this->inverse}' for model class '{$this->modelClass}' for '{$this->fieldName}'.

What it means

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.

Source

Thrown at modules/tailor/contentfields/RecordFinderField.php:222

                'table' => $model->getBlueprintDefinition()->getJoinTableName(),
                'name' => $this->fieldName,
                'relationClass' => CustomMultiJoinRelation::class,
                'relatedKey' => ($relatedMultisite && !$isDualMultisite) ? 'site_root_id' : 'id',
                'relatedMultisite' => $relatedMultisite
            ];
        }
    }

    /**
     * defineInverseModelRelationship for the inverse relationship. These definitions
     * do not rely on multisite via propagatable attribute. Inverse relations do not
     * replicate and should be treated as read-only.
     */
    protected function defineInverseModelRelationship($model)
    {
        $relatedModel = $this->getRelatedModel();
        if (!$relatedModel->hasRelation($this->inverse)) {
            throw new SystemException("Missing relation definition for inverse field '{$this->inverse}' for model class '{$this->modelClass}' for '{$this->fieldName}'.");
        }

        $isNested = $model instanceof RepeaterItem;

        // RepeaterItem doesn't have getBlueprintDefinition
        $parentMultisite = $isNested ? false : $model->getBlueprintDefinition()->useMultisite();
        $relationDefinition = $relatedModel->getRelationDefinition($this->inverse);
        $relatedMultisite = $relatedModel->isClassInstanceOf(\October\Contracts\Database\MultisiteInterface::class) &&
            $relatedModel->isMultisiteEnabled();

        // Dual-multisite: both parent and related use multisite
        $isDualMultisite = $parentMultisite && $relatedMultisite;

        $isSingular = $this->maxItems === 1;
        $otherIsSingular = $relatedModel->isRelationTypeSingular($this->inverse);
        $otherIsPropagatable = $relatedMultisite ? $relatedModel->isAttributePropagatable($this->inverse) : false;

        // In dual-multisite, use 'id' for proper site isolation; otherwise use site_root_id for sharing

View on GitHub (pinned to b608633a7e)

Solutions

  1. Define an entries field named exactly like the `inverse:` value on the source blueprint so the relation exists on its model.
  2. Remove `inverse:` if bidirectional navigation isn't needed.
  3. Re-run `php artisan tailor:refresh` after the change so model relations are rebuilt.

Example fix

# before (recordfinder)
fields:
  primary_author:
    type: recordfinder
    source: authors
    inverse: authored_posts   # no such relation on Author model

# after (authors blueprint must define)
fields:
  authored_posts:
    type: entries
    source: blog/posts
    inverse: primary_author
Defensive patterns

Strategy: validation

Validate before calling

$relatedModel = $sourceBlueprint->newModelInstance();
if (!$relatedModel->hasRelation($inverse)) {
    throw new InvalidArgumentException("Related model has no relation '{$inverse}' — define an entries field with that handle on the source blueprint.");
}

Type guard

function inverseRelationExists($relatedModel, string $inverse): bool
{
    return $relatedModel->hasRelation($inverse);
}

Try / catch

try {
    // model extension / tailor:refresh
} catch (\SystemException $e) {
    // message names the missing relation; add the matching entries field on the source blueprint and refresh
}

Prevention

When it happens

Trigger: `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.

Common situations: 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.

Related errors


AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21). Data as JSON: /api/errors/0796dabda559a22b. Report an issue: GitHub.