Skip to content

Instantly share code, notes, and snippets.

@aaronschachter
Created June 27, 2026 00:29
Show Gist options
  • Select an option

  • Save aaronschachter/75d521731e56156a720f09caf15b69f7 to your computer and use it in GitHub Desktop.

Select an option

Save aaronschachter/75d521731e56156a720f09caf15b69f7 to your computer and use it in GitHub Desktop.
Refactor: derive validation skip flags from payload inspection in updateCMSItems

Refactor: Derive validation skip flags from payload inspection in updateCMSItems

Problem

In updateCMSItems.ts, skipUniqueValidation and skipItemRefValidation are coupled. Both read from opts.skipUniqueValidation:

// entrypoints/server/lib/logic/cms/items/updateCMSItems.ts, lines 408-413
skipUniqueValidation:
  isSubsequentPublicationOperation ||
  Boolean(opts.skipUniqueValidation),
skipItemRefValidation:
  isSubsequentPublicationOperation ||
  Boolean(opts.skipUniqueValidation),  // <-- same flag controls both

This means callers that set skipUniqueValidation: true also silently skip ItemRef validation, which is a data integrity risk for general-purpose callers that might be updating ItemRef fields without touching slugs.

Today two callers set opts.skipUniqueValidation:

  • changeCMSItemsStatus (hardcodes true because it only changes _draft/_archived/_noSearch)
  • The DDA UpdateCMSItemsProvider (sets !changesSlug)

Both happen to be safe because neither touches ItemRef fields, but the coupling is fragile and forces callers to reason about internal validation mechanics.

Solution

Make updateCMSItems derive both skip flags from the per-item payload, using the collection's field schema (which it already has loaded). No caller needs to pass skip flags.

The logic:

  • Skip slug uniqueness validation when the item's payload doesn't contain a slug field
  • Skip ItemRef validation when the item's payload doesn't contain any field slugs that map to ItemRef or ItemRefSet type in the collection
  • isSubsequentPublicationOperation still skips both (already validated on first pass in writeToAllLive)

Files to change

1. entrypoints/server/lib/logic/cms/items/updateCMSItems.ts (core change)

After collection is resolved (~line 213), build a set of ItemRef field slugs:

const collectionItemRefFieldSlugs = new Set(
  (collection?.allItemRefFields || []).map((f: {slug: string}) => f.slug)
);

In the per-item loop, before building validateItemInput (~line 388), inspect the payload:

const itemFieldSlugs = Object.keys(item.fields);
const changesSlug = itemFieldSlugs.includes('slug');
const changesTouchesItemRefs = itemFieldSlugs.some((slug) =>
  collectionItemRefFieldSlugs.has(slug)
);

Replace the coupled flags:

// Before:
skipUniqueValidation:
  isSubsequentPublicationOperation || Boolean(opts.skipUniqueValidation),
skipItemRefValidation:
  isSubsequentPublicationOperation || Boolean(opts.skipUniqueValidation),

// After:
skipUniqueValidation:
  isSubsequentPublicationOperation || !changesSlug,
skipItemRefValidation:
  isSubsequentPublicationOperation || !changesTouchesItemRefs,

2. entrypoints/server/lib/logic/cms/items/changeCMSItemsStatus.ts

Remove cmsOpts.skipUniqueValidation = true; (line 92) and its comment (lines 90-91). The optimization now happens automatically in updateCMSItems because status updates only contain _draft/_archived/_noSearch, none of which are slug or ItemRef fields.

3. entrypoints/server/lib/logic/cms/transformers/items/toCMSItemsOpts.ts

Remove skipUniqueValidation from:

  • CMSItemsOpts type (line 158)
  • AdditionalOpts type (line 226)
  • The transform output (line 345)

4. Tests

entrypoints/server/test/logic/cms/items/updateCMSItems_test.ts:

  • The existing test at line 1573 (skipUniqueValidation option describe block) passes skipUniqueValidation: true on opts and asserts it propagates. Replace it with tests that verify the payload-inspection behavior:
    • Patch with slug in payload: skipUniqueValidation should be false
    • Patch without slug in payload: skipUniqueValidation should be true
    • Patch with ItemRef field slug in payload: skipItemRefValidation should be false
    • Patch without ItemRef field slug in payload: skipItemRefValidation should be true

entrypoints/server/test/logic/cms/items/changeCMSItemsStatus_test.ts:

  • Remove or update the test at line 355 that asserts skipUniqueValidation: true is passed. Since changeCMSItemsStatus no longer sets it, this test should instead verify that updateCMSItems is called without skipUniqueValidation on opts (or just remove the test, since the behavior is now internal to updateCMSItems).

entrypoints/server/feature-integration/server-monolith/internals/lib/logic/cms/providers/items/updateItems.test.ts:

  • Remove assertions about skipUniqueValidation in the toCMSItemsOpts call expectations (lines 93, 127, 145, 162)

What NOT to change

  • ValidateItemInput.skipUniqueValidation and ValidateItemInput.skipItemRefValidation in validateItem.ts stay as-is. These are internal types that updateCMSItems and insertCMSItems compute and pass.
  • insertCMSItems.ts already derives skipUniqueValidation: isSubsequentPublicationOperation locally. No change needed.
  • SchemaValidatorOpts.skipUniqueValidation in schemaValidator/index.ts stays. It's consumed by textField.ts for the actual slug uniqueness check.

Context

  • PR #104123 introduced skipUniqueValidation for the changeCMSItemsStatus N+1 fix
  • PR #112751 (aaron/dda-update-item) is adding the DDA updateItems method and surfaced this coupling via reviewer feedback: https://github.com/webflow/webflow/pull/112751#discussion_r3484320012
  • collection.allItemRefFields is a Mongoose virtual that returns fields with type === 'ItemRef' || type === 'ItemRefSet'

Claude Code prompt

Refactor updateCMSItems to derive skip-validation flags from payload inspection instead of relying on callers to pass them.

Context: In entrypoints/server/lib/logic/cms/items/updateCMSItems.ts, the _updateItems function currently reads opts.skipUniqueValidation to set both skipUniqueValidation and skipItemRefValidation on the validateItemInput. This couples the two flags and forces callers (changeCMSItemsStatus, the DDA UpdateCMSItemsProvider) to reason about internal validation mechanics.

The fix: updateCMSItems already has the collection loaded (which has allItemRefFields, a virtual returning fields with type ItemRef or ItemRefSet). In the per-item loop, inspect the item's payload field slugs to determine:
- skipUniqueValidation: true when the payload doesn't include 'slug'
- skipItemRefValidation: true when the payload doesn't include any field slugs matching the collection's ItemRef/ItemRefSet fields
- isSubsequentPublicationOperation still skips both (already handled separately)

Changes needed:
1. updateCMSItems.ts: Build a Set of ItemRef field slugs from collection.allItemRefFields after collection is resolved. In the per-item loop, compute changesSlug and changesTouchesItemRefs from Object.keys(item.fields), then use those instead of opts.skipUniqueValidation.
2. changeCMSItemsStatus.ts: Remove cmsOpts.skipUniqueValidation = true and its comment. The optimization now happens automatically.
3. toCMSItemsOpts.ts: Remove skipUniqueValidation from CMSItemsOpts type, AdditionalOpts type, and the transform output.
4. Update tests in updateCMSItems_test.ts (replace the skipUniqueValidation option test with payload-inspection tests), changeCMSItemsStatus_test.ts (remove the skipUniqueValidation assertion), and the DDA provider test updateItems.test.ts (remove skipUniqueValidation from toCMSItemsOpts call expectations).

Do NOT change: ValidateItemInput in validateItem.ts, insertCMSItems.ts (already derives its own flag), or SchemaValidatorOpts in schemaValidator/index.ts.

Branch off dev. Run the mocha integration tests for updateCMSItems and changeCMSItemsStatus after making changes: ./bin/babel-mocha entrypoints/server/test/logic/cms/items/updateCMSItems_test.ts --timeout 60000 and ./bin/babel-mocha entrypoints/server/test/logic/cms/items/changeCMSItemsStatus_test.ts --timeout 60000. Run the Jest unit test for the provider: npx nx anytest -- entrypoints/server/feature-integration/server-monolith/internals/lib/logic/cms/providers/items/updateItems.test.ts --skip-nx-cache
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment