Skip to content

fix(entries): exclude current entry from slug uniqueness on update (#27)#28

Merged
sylvesterdamgaard merged 1 commit into
cboxdk:mainfrom
SAY-5:fix/update-entry-slug-self-collision-27
May 5, 2026
Merged

fix(entries): exclude current entry from slug uniqueness on update (#27)#28
sylvesterdamgaard merged 1 commit into
cboxdk:mainfrom
SAY-5:fix/update-entry-slug-self-collision-27

Conversation

@SAY-5
Copy link
Copy Markdown
Contributor

@SAY-5 SAY-5 commented Apr 28, 2026

Closes #27.

Reproduction (from the issue)

{
  \"action\": \"update\",
  \"collection\": \"your_collection\",
  \"id\": \"<entry-uuid>\",
  \"data\": { \"title\": \"Updated Title\" }
}

fails with:

Field validation failed: slug: This value has already been taken.

even though slug is not part of data.

Root cause

updateEntry() always re-injected the entry's current slug into the FieldsValidator payload:

$mergedData = array_merge($entry->data()->all(), $data);
$mergedData['slug'] = $entry->slug();   // <-- always added

UniqueEntryValue then ran against that slug without an exclusion, so Statamic found the slug on the entry being updated and rejected it as "already taken". Every update through the MCP tool failed.

Fix

Mirror the slug handling already used in createEntry(), with one small addition: pass $entry->id() as the $except argument so UniqueEntryValue excludes the current entry from the uniqueness check. Slug-rename, idempotent-slug, and slug-not-supplied paths are now all handled before blueprint validation, and the line that re-injected the slug into mergedData is removed so FieldsValidator never sees the slug column.

if (array_key_exists('slug', $data)) {
    $newSlug = is_string($data['slug']) ? $data['slug'] : '';
    $slugValidator = Validator::make(['slug' => $newSlug], [
        'slug' => [
            'required',
            'string',
            new UniqueEntryValue($entry->collectionHandle(), $entry->id(), $site),
        ],
    ]);
    if ($slugValidator->fails()) { ... return error ... }
    $entry->slug($newSlug);
    unset($data['slug']);
}

Test plan

Added four regression tests in tests/Feature/Routers/EntriesRouterTest.php:

  • test_update_entry_without_slug_in_data_succeeds — exact repro from Update action fails with "slug: This value has already been taken" for existing entries #27 (no slug field): update now succeeds.
  • test_update_entry_with_unchanged_slug_succeeds — resending the current slug is idempotent.
  • test_update_entry_with_new_unique_slug_succeeds — renaming to a unique slug works and is persisted.
  • test_update_entry_to_existing_slug_returns_error — colliding with another entry's slug still surfaces the validation error (the legitimate case the original check was protecting against).

Results:

  • vendor/bin/pest tests/Feature/Routers/EntriesRouterTest.php18 passed; 0 failed (4 new + 14 pre-existing).
  • vendor/bin/pest --filter test_update_entry18 passed; 0 failed across feature + integration suites that exercise the update flow (DateNormalizationTest, GroupFieldBugTest, TermsFieldUpdateTest, UpdateEntryBugTest).

Notes

  • The proposed fix in the issue is implemented as-is.
  • No public API changes; only the internal validation flow.

Closes cboxdk#27.

`updateEntry()` always injected the entry's *current* slug into
the FieldsValidator payload:

    $mergedData = array_merge($entry->data()->all(), $data);
    $mergedData['slug'] = $entry->slug();   // <-- always added

`UniqueEntryValue` then ran against that slug without an
exclusion, so Statamic found the slug on the entry being updated
and rejected it as 'already taken'. Every update through the MCP
tool failed, regardless of whether the caller passed a slug at all.

Mirror the slug handling already used in `createEntry()`, with one
small addition: pass `$entry->id()` as the `$except` argument so
`UniqueEntryValue` excludes the current entry from the uniqueness
check. Slug rename, idempotent slug, and slug-not-supplied paths
are now all handled before blueprint validation. The line that
re-injected the slug into `mergedData` is removed so
`FieldsValidator` never sees the slug column.

Tests:

* `test_update_entry_without_slug_in_data_succeeds` — exact
  reproduction from cboxdk#27: update without slug now succeeds.
* `test_update_entry_with_unchanged_slug_succeeds` — resending
  the current slug is idempotent.
* `test_update_entry_with_new_unique_slug_succeeds` — renaming
  to a unique slug works.
* `test_update_entry_to_existing_slug_returns_error` — colliding
  with another entry's slug still surfaces the validation error.
@sylvesterdamgaard sylvesterdamgaard merged commit 5bfe2d2 into cboxdk:main May 5, 2026
7 of 8 checks passed
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.

Update action fails with "slug: This value has already been taken" for existing entries

2 participants