Skip to content

[6.x] Support reference updates for custom fieldtypes and working copies#13693

Open
morhi wants to merge 9 commits intostatamic:6.xfrom
morhi:feature/reference-update-custom-fieldtypes
Open

[6.x] Support reference updates for custom fieldtypes and working copies#13693
morhi wants to merge 9 commits intostatamic:6.xfrom
morhi:feature/reference-update-custom-fieldtypes

Conversation

@morhi
Copy link
Contributor

@morhi morhi commented Jan 27, 2026

Problem

Statamic's DataReferenceUpdater delegates asset reference updates to AssetReferenceUpdater, which only supports Statamic's built-in fieldtypes. Custom fieldtypes referencing assets or containing nested fields (including nested Statamic Asset fields) don't receive reference updates when assets are renamed, moved, or deleted. This causes broken references.

Because of this, I had to disable renaming and moving files entirely in all of my projects.

Also, while working on this PR I noticed, that working copies don't receive any data updates, which makes assets and terms disappear if the entry has a working copy that you are working on. The PR also fixes that issue.

Solution

This PR moves reference update logic into the fieldtypes themselves via the UpdatesReferences trait. Since only the fieldtype knows its internal data structure, it's best positioned to locate and replace references.

All built-in fieldtypes (Assets, Link, Bard, Markdown, Grid, Group, Replicator, Terms) now use this trait, serving as reference implementations for addon developers. The updater classes (AssetReferenceUpdater, TermReferenceUpdater, DataReferenceUpdater) are simplified to generic loops that delegate to fieldtype methods, with a one-time cached Blink lookup to detect which fieldtypes participate.

The trait provides three no-op methods to override:

Method Purpose
replaceAssetReferences($data, $newValue, $oldValue, $container) Replace direct asset references
replaceTermReferences($data, $newValue, $oldValue, $taxonomy) Replace direct term references
iterateReferenceFields($data, NestedFieldUpdater $updater) Traverse nested sub-fields for recursion

Plus three helpers: replaceValue(), replaceValuesInArray(), replaceStatamicUrls().

Examples

Custom fieldtype with asset references

class MyLinkFieldtype extends Fieldtype
{
    use UpdatesReferences;

    public function replaceAssetReferences($data, ?string $newValue, string $oldValue, string $container)
    {
        if ($this->config('container') !== $container) {
            return $data;
        }

        return $this->replaceValue($data, $newValue, $oldValue);
    }
}

Custom fieldtype with term references

class MyTagsFieldtype extends Fieldtype
{
    use UpdatesReferences;

    public function replaceTermReferences($data, ?string $newValue, string $oldValue, string $taxonomy)
    {
        return $this->replaceValuesInArray($data, $newValue, $oldValue);
    }
}

Custom fieldtype with nested sub-fields

use Statamic\Data\NestedFieldUpdater;
use Statamic\Fields\Fields;
use Statamic\Fields\Fieldtype;
use Statamic\Fieldtypes\UpdatesReferences;

class ColumnsFieldtype extends Fieldtype
{
    use UpdatesReferences;

    public function iterateReferenceFields($data, NestedFieldUpdater $updater): void
    {
        if (! is_array($data)) {
            return;
        }

        $fields = new Fields($this->config('fields'));

        foreach (array_keys($data) as $idx) {
            $updater->update($fields, "{$idx}.");
        }
    }
}

Closes statamic/ideas#698

@duncanmcclean duncanmcclean changed the title [5.x] Support reference updates for custom fieldtypes [6.x] Support reference updates for custom fieldtypes Jan 28, 2026
@duncanmcclean duncanmcclean changed the base branch from 5.x to 6.x January 28, 2026 17:25
@daun
Copy link
Contributor

daun commented Feb 10, 2026

This would be amazing to have! We developed a custom asset fieldtype to better manage contextual metadata. While great for using custom fieldtype components, it also disables automatic reference updates for those fields. This PR would really help make this possible without re-implementing the logic of the reference updater classes in our addon.

@morhi
Copy link
Contributor Author

morhi commented Feb 10, 2026

Thanks @daun Do you have any thoughts or concerns about the implementation I suggested?

morhi and others added 3 commits February 10, 2026 13:47
- Use strict null checks instead of falsy checks to avoid skipping valid data like empty arrays or 0
- Replace json_encode comparison with native PHP strict equality for change detection
- Add non-array guards in helper methods to prevent TypeError on unexpected data
- Standardize helper parameter ordering: callable always last
@daun
Copy link
Contributor

daun commented Feb 10, 2026

@morhi No concerns here, looks logical to me :) An approach we explored previously was to create a new AssetReferencesUpdating event that fires for every item/entry and field during the update, but that ended up firing an endless amount of events, even for no-ops. So your solution looks much more efficient.

@morhi
Copy link
Contributor Author

morhi commented Feb 10, 2026

@morhi No concerns here, looks logical to me :) An approach we explored previously was to create a new AssetReferencesUpdating event that fires for every item/entry and field during the update, but that ended up firing an endless amount of events, even for no-ops. So your solution looks much more efficient.

Yea, I developed something similar too. It works, but it requires going through all fields twice.

I was just thinking if there is a solution that would be easier to integrate in custom fieldtypes. Currently, you have to use the trait and implement the given methods.

Instead of hardcoding type checks in the updater classes, each fieldtype
now owns its reference update logic. Built-in fieldtypes use the same
trait as addon developers, serving as examples. Updater classes are
simplified to generic loops with cached fieldtype detection via Blink.
…elds

Improves IDE discoverability for addon developers by replacing the opaque
callable parameter with a typed NestedFieldUpdater class.
The current data reference update system only replaced data in the
published entry. This commits also replaces the data of a working copy,
if it exists.
@morhi morhi changed the title [6.x] Support reference updates for custom fieldtypes [6.x] Support reference updates for custom fieldtypes and working copies Feb 10, 2026
@morhi morhi marked this pull request as ready for review February 10, 2026 17:34
@morhi
Copy link
Contributor Author

morhi commented Feb 10, 2026

Hey @ryanmitchell @jasonvarga @jacksleight @marcorieser ! Would love to hear your opinion on this PR. I am quite sure that all of us came across this issue at some point and it would be super cool to have this fixed :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support custom callbacks in AssetReferenceUpdater for handling custom fieldtypes

2 participants