fix: apply localized defaultValue only to the current locale#17407
Open
MurzNN wants to merge 3 commits into
Open
fix: apply localized defaultValue only to the current locale#17407MurzNN wants to merge 3 commits into
MurzNN wants to merge 3 commits into
Conversation
| req, | ||
| }: Args<T>): Promise<T> => { | ||
| if (operation === 'update') { | ||
| req.context.incomingFieldKeys = new Set(Object.keys(incomingData)) |
Contributor
There was a problem hiding this comment.
How would this work for nested fields?
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Which branch should this PR target?
main— Payload v4 developmentWhat?
Fixes a bug where localized fields with
defaultValuehad that default applied to all locales instead of only the locale being created or updated.Changes span the Payload field hooks (create/update/read paths), the Drizzle adapter (schema + read transform), and the MongoDB adapter (schema). Adds a regression test suite at
test/localized-default-value/.Why?
When a field is marked
localized: trueand has adefaultValue, Payload is expected to apply that default only for the active locale during create/update, and not when reading locales that were never written.The bug had multiple causes across the stack:
1. Write path —
beforeValidateapplied defaults on updateDuring update,
beforeValidatestill rangetFallbackValue()for localized fields whose value wasundefinedin the incoming payload. That meant updating localeeswith{ title: 'Spanish title' }could still populatemyFieldwith the default, even though the field was not in the request body.2. Write path —
beforeChangemerged defaults into the active locale on updateThe locale-merge logic in
beforeChangetreated the active locale the same on create and update. On update, if the field was omitted from the request, it could still pick up a default (or a fallback-filled value from the fetched document) for the locale being updated.3. Read path —
afterReadapplied defaults to unwritten localesWhen reading a document (especially with
locale: 'all'),afterReadapplieddefaultValueto any locale key that wasundefined, including locales that had never been written. That madefindByID({ locale: 'es' })return a default even when onlyenhad been created.4. Drizzle adapter — SQL column defaults on
_localestables (Postgres/SQLite)For SQL adapters, localized field columns live in a separate
_localestable. The schema builder usedwithDefault()to set SQLDEFAULTconstraints fromfield.defaultValueon those columns.On update, Drizzle deletes all locale rows for a document and re-inserts them. When inserting a new locale row for a partial update (e.g. only
titlefores), omitted columns likemy_fieldwere not included in theINSERT— so SQLite/Postgres silently applied the column default ('Default Value'), bypassing Payload hooks entirely.5. MongoDB adapter — Mongoose defaults on locale sub-schemas
MongoDB stores localized values as nested subdocuments (
myField.en,myField.es).localizeSchemacopied the full Mongoose sub-schema — includingdefault: field.defaultValue— onto every locale path. Mongoose then applied those defaults for locales that were never written, producing the same user-visible bug as the SQL column-default issue (e.g.findByID({ locale: 'all' })returningmyField.es: 'Default Value'after creating only inen).How?
Payload hooks (
packages/payload/src/fields/hooks/):beforeValidatereq.context.incomingFieldKeyson update so downstream hooks know which fields were explicitly sent.defaultValueto localized fields during update (shouldApplyLocalizedDefaultValue).req.locale || defaultLocale(not empty string) ingetFallbackValue.beforeChangedocWithLocales, skip the active locale, and do not apply defaults.defaultValueonly foroperationLocale.afterReaddefaultValueon read when the requested locale actually exists in storage (or when reading non-localized /locale: 'all'edge cases are handled safely).locale: 'all', strip locale keys that were never stored or arenull(leftover from partial locale rows in SQL adapters).Drizzle adapter (
packages/drizzle/):schema/withDefault.ts— Do not set SQL column defaults on columns in_localestables. Localized defaults must be applied in Payload hooks for the active locale only.transform/read/traverseFields.ts— Skipnullvalues when assembling localized field objects, so partial locale rows do not surface asnull/defaultfor fields that were never written in that locale.MongoDB adapter (
packages/db-mongodb/):models/buildSchema.ts— Strip Mongoosedefaultfrom sub-schemas before wrapping them inlocalizeSchema, so defaults are not baked into every locale path at the schema level.Tests:
test/localized-default-value/int.spec.ts— Covers create, update, read-with-fallback-disabled, and functiondefaultValuewith locale awareness.Test plan
pnpm test:int localized-default-value(SQLite) — 4/4 passingpnpm test:int localized-default-value(MongoDB) — 4/4 passingtest/fields(should update without overwriting other locales with defaultValue)Fixes #17406