Skip to content

fix(plugin-import-export): fix CSV import of arrays and richText nested inside blocks#16922

Merged
paulpopus merged 2 commits into
mainfrom
fix/plugin-import-export-malformed-data-with-richtext-fields-when-deeply-nested
Jun 8, 2026
Merged

fix(plugin-import-export): fix CSV import of arrays and richText nested inside blocks#16922
paulpopus merged 2 commits into
mainfrom
fix/plugin-import-export-malformed-data-with-richtext-fields-when-deeply-nested

Conversation

@paulpopus

Copy link
Copy Markdown
Contributor

Summary

  • Arrays nested inside a block (e.g. a faqs array field inside a faqSection block) were reconstructed as plain objects with numeric keys ({"0": {...}}) instead of proper arrays on import
  • richText fields inside those nested arrays were left as raw JSON strings instead of being parsed back to objects

Root causes

Bug 1 — wrong container type for array fields inside blocks

unflattenObject has a special-case branch for blocks fields. When the path continued past the block field name (e.g. faqContent_0_faqSection_faqs_0_question), it always created blockObject[fieldName] = {}, never []. Two fixes applied:

  • Detect whether the segment immediately after the field name is a numeric index and create [] accordingly
  • Add an else if branch in the general traversal to handle the case where currentObject is already an array and the current segment is a numeric index (which happens after the block-field jump)

Bug 2 — import hook not firing for richText inside nested arrays

toLogicalKey strips array index segments from flat keys to resolve import hook registrations (e.g. faqContent_0_faqSection_faqs_0_answerfaqContent_faqSection_faqs_answer). It only strips a numeric segment when the previous segment is in arrayLikeNames. collectArrayLikeNames recurses via field.flattenedFields but not via field.blocks[i].flattenedFields, so array field names declared inside block definitions (like faqs) were never added to the set. The hook lookup therefore failed and the JSON string was left unparsed.

Defence-in-depth — normalizeRichText key-name heuristic replaced

The post-processing fallback in normalizeRichText previously matched block properties by checking whether the runtime key name contained "richText". This never reached fields nested inside arrays inside blocks. Replaced with a schema-driven recursive walker (normalizeRichTextInFields) that handles richText, blocks, array, group, and tab fields at any depth.

@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

📦 esbuild Bundle Analysis for payload

This analysis was generated by esbuild-bundle-analyzer. 🤖

Meta File Out File Size (raw) Note
packages/next/meta_index.json esbuild/index.js 201.84 KB ✅ No change
packages/payload/meta_index.json esbuild/index.js 1.41 MB ✅ No change
packages/payload/meta_shared.json esbuild/exports/shared.js 192.56 KB ✅ No change
packages/richtext-lexical/meta_client.json esbuild/exports/client_optimized/index.js 305.26 KB ⚠️ +4 B (+0.0%)
packages/ui/meta_client.json esbuild/exports/client_optimized/index.js 1.35 MB ✅ No change
packages/ui/meta_shared.json esbuild/exports/shared_optimized/index.js 18.64 KB ✅ No change
Largest paths These visualization shows top 20 largest paths in the bundle.

Meta file: packages/next/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ████████████████████████▋ }}}$ 98.9%, 197.86 KB
dist/adapters/router.js ${{\color{Goldenrod}{ }}}$ 0.3%, 663 B
dist/adapters/server.js ${{\color{Goldenrod}{ }}}$ 0.3%, 533 B
dist/adapters/layout.js ${{\color{Goldenrod}{ }}}$ 0.3%, 526 B
dist/adapters/views.js ${{\color{Goldenrod}{ }}}$ 0.2%, 409 B
dist/esbuildEntry.js ${{\color{Goldenrod}{ }}}$ 0.0%, 0 B

Meta file: packages/payload/meta_index.json, Out file: esbuild/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ █████████████████ }}}$ 68.3%, 960.54 KB
dist/fields/hooks ${{\color{Goldenrod}{ ▊ }}}$ 3.1%, 44.05 KB
dist/collections/operations ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 40.26 KB
dist/versions/migrations ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 18.50 KB
dist/auth/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.63 KB
dist/utilities/configToJSONSchema.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.01 KB
dist/fields/config ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.40 KB
dist/globals/operations ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 13.40 KB
dist/queues/operations ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 12.63 KB
dist/fields/validations.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.57 KB
dist/collections/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.53 KB
dist/bin/generateImportMap ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 9.47 KB
dist/config/orderable ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.92 KB
dist/uploads/fetchAPI-multipart ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.80 KB
dist/index.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.77 KB
dist/hierarchy/utils ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.64 KB
dist/database/migrations ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 7.55 KB
dist/collections/endpoints ${{\color{Goldenrod}{ }}}$ 0.4%, 6.23 KB
dist/queues/config ${{\color{Goldenrod}{ }}}$ 0.4%, 5.59 KB
dist/auth/strategies ${{\color{Goldenrod}{ }}}$ 0.4%, 5.43 KB
(other) ${{\color{Goldenrod}{ ███████▉ }}}$ 31.7%, 445.71 KB

Meta file: packages/payload/meta_shared.json, Out file: esbuild/exports/shared.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ███████████████████▉ }}}$ 79.5%, 150.12 KB
dist/fields/validations.js ${{\color{Goldenrod}{ █▍ }}}$ 5.6%, 10.57 KB
dist/config/orderable ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 3.13 KB
dist/fields/baseFields ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 2.79 KB
dist/utilities/deepCopyObject.js ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 2.54 KB
dist/auth/cookies.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.55 KB
dist/utilities/flattenTopLevelFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 1.42 KB
dist/fields/config ${{\color{Goldenrod}{ ▏ }}}$ 0.7%, 1.29 KB
dist/utilities/getVersionsConfig.js ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 1.04 KB
dist/utilities/flattenAllFields.js ${{\color{Goldenrod}{ ▏ }}}$ 0.5%, 943 B
dist/utilities/unflatten.js ${{\color{Goldenrod}{ }}}$ 0.4%, 779 B
dist/utilities/sanitizeUserDataForEmail.js ${{\color{Goldenrod}{ }}}$ 0.4%, 713 B
dist/utilities/getFieldPermissions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 651 B
dist/collections/config ${{\color{Goldenrod}{ }}}$ 0.3%, 570 B
dist/bin/generateImportMap ${{\color{Goldenrod}{ }}}$ 0.3%, 561 B
dist/auth/sessions.js ${{\color{Goldenrod}{ }}}$ 0.3%, 525 B
dist/fields/getFieldPaths.js ${{\color{Goldenrod}{ }}}$ 0.3%, 485 B
dist/utilities/appendDateTimezoneSelectFields.js ${{\color{Goldenrod}{ }}}$ 0.2%, 451 B
dist/utilities/getSafeRedirect.js ${{\color{Goldenrod}{ }}}$ 0.2%, 423 B
dist/utilities/deepMerge.js ${{\color{Goldenrod}{ }}}$ 0.2%, 413 B
(other) ${{\color{Goldenrod}{ █████▏ }}}$ 20.5%, 38.66 KB

Meta file: packages/richtext-lexical/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
dist/features/blocks ${{\color{Goldenrod}{ ███ }}}$ 12.4%, 37.43 KB
dist/lexical/ui ${{\color{Goldenrod}{ ██▊ }}}$ 11.3%, 34.16 KB
dist/lexical/plugins ${{\color{Goldenrod}{ ██▋ }}}$ 10.9%, 32.88 KB
dist/features/experimental_table ${{\color{Goldenrod}{ ██▎ }}}$ 9.0%, 27.16 KB
dist/packages/@lexical ${{\color{Goldenrod}{ █▌ }}}$ 6.3%, 18.99 KB
dist/features/link ${{\color{Goldenrod}{ █▌ }}}$ 6.2%, 18.82 KB
dist/features/toolbars ${{\color{Goldenrod}{ █▍ }}}$ 5.5%, 16.58 KB
dist/features/upload ${{\color{Goldenrod}{ █▏ }}}$ 4.7%, 14.09 KB
dist/features/textState ${{\color{Goldenrod}{ ▉ }}}$ 3.7%, 11.08 KB
dist/features/relationship ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 9.61 KB
dist/lexical/utils ${{\color{Goldenrod}{ ▋ }}}$ 2.9%, 8.79 KB
dist/features/converters ${{\color{Goldenrod}{ ▋ }}}$ 2.8%, 8.36 KB
dist/utilities/fieldsDrawer ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 8.12 KB
dist/features/debug ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 7.40 KB
dist/lexical/config ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.08 KB
dist/features/lists ${{\color{Goldenrod}{ ▍ }}}$ 1.7%, 5.00 KB
dist/features/format ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.46 KB
dist/lexical/LexicalEditor.js ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.23 KB
dist/features/horizontalRule ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 3.18 KB
dist/field/Field.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 2.84 KB
(other) ${{\color{Goldenrod}{ █████████████████████▉ }}}$ 87.6%, 264.60 KB

Meta file: packages/ui/meta_client.json, Out file: esbuild/exports/client_optimized/index.js

Path Size
../../node_modules ${{\color{Goldenrod}{ ██████████▊ }}}$ 43.3%, 580.42 KB
dist/elements/Hierarchy ${{\color{Goldenrod}{ ▊ }}}$ 3.2%, 43.08 KB
dist/elements/BulkUpload ${{\color{Goldenrod}{ ▌ }}}$ 2.1%, 28.13 KB
dist/views/Version ${{\color{Goldenrod}{ ▌ }}}$ 2.0%, 27.46 KB
dist/views/HierarchyList ${{\color{Goldenrod}{ ▍ }}}$ 1.5%, 20.43 KB
dist/elements/Table ${{\color{Goldenrod}{ ▎ }}}$ 1.4%, 19.28 KB
dist/views/Dashboard ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.73 KB
dist/views/Edit ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.62 KB
dist/elements/WhereBuilder ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 17.34 KB
dist/forms/Form ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 15.89 KB
dist/fields/Relationship ${{\color{Goldenrod}{ ▎ }}}$ 1.2%, 15.48 KB
dist/fields/Blocks ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 15.11 KB
dist/fields/Upload ${{\color{Goldenrod}{ ▎ }}}$ 1.1%, 14.40 KB
dist/elements/QueryPresets ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 10.22 KB
dist/elements/PublishButton ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.67 KB
dist/elements/HTMLDiff ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.38 KB
dist/elements/LivePreview ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.26 KB
dist/views/List ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 8.15 KB
dist/elements/ReactSelect ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.83 KB
dist/fields/Array ${{\color{Goldenrod}{ ▏ }}}$ 0.6%, 7.76 KB
(other) ${{\color{Goldenrod}{ ██████████████▏ }}}$ 56.7%, 761.50 KB

Meta file: packages/ui/meta_shared.json, Out file: esbuild/exports/shared_optimized/index.js

Path Size
dist/graphics/Logo ${{\color{Goldenrod}{ ███████▋ }}}$ 30.9%, 5.57 KB
../../node_modules ${{\color{Goldenrod}{ ███▋ }}}$ 14.7%, 2.65 KB
dist/graphics/Icon ${{\color{Goldenrod}{ ██▏ }}}$ 8.5%, 1.52 KB
dist/utilities/formatDocTitle ${{\color{Goldenrod}{ █▊ }}}$ 7.4%, 1.32 KB
dist/providers/TableColumns ${{\color{Goldenrod}{ █▏ }}}$ 4.8%, 866 B
dist/utilities/getGlobalData.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 762 B
dist/utilities/api.js ${{\color{Goldenrod}{ █ }}}$ 4.2%, 756 B
dist/utilities/groupNavItems.js ${{\color{Goldenrod}{ █ }}}$ 4.1%, 745 B
dist/elements/Translation ${{\color{Goldenrod}{ ▋ }}}$ 2.7%, 493 B
dist/utilities/handleTakeOver.js ${{\color{Goldenrod}{ ▌ }}}$ 2.4%, 440 B
dist/utilities/traverseForLocalizedFields.js ${{\color{Goldenrod}{ ▌ }}}$ 2.2%, 399 B
dist/elements/withMergedProps ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 339 B
dist/utilities/getNavGroups.js ${{\color{Goldenrod}{ ▍ }}}$ 1.9%, 338 B
dist/utilities/getVisibleEntities.js ${{\color{Goldenrod}{ ▍ }}}$ 1.8%, 329 B
dist/elements/WithServerSideProps ${{\color{Goldenrod}{ ▎ }}}$ 1.3%, 232 B
dist/utilities/handleGoBack.js ${{\color{Goldenrod}{ ▎ }}}$ 1.0%, 180 B
dist/fields/mergeFieldStyles.js ${{\color{Goldenrod}{ ▏ }}}$ 0.9%, 157 B
dist/utilities/handleBackToDashboard.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 152 B
dist/forms/Form ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 148 B
dist/utilities/abortAndIgnore.js ${{\color{Goldenrod}{ ▏ }}}$ 0.8%, 146 B
(other) ${{\color{Goldenrod}{ █████████████████▎ }}}$ 69.1%, 12.45 KB
Details

Next to the size is how much the size has increased or decreased compared with the base branch of this PR.

  • ‼️: Size increased by 20% or more. Special attention should be given to this.
  • ⚠️: Size increased in acceptable range (lower than 20%).
  • ✅: No change or even downsized.
  • 🗑️: The out file is deleted: not found in base branch.
  • 🆕: The out file is newly found: will be added to base branch.

@PatrikKozak PatrikKozak self-assigned this Jun 8, 2026

@PatrikKozak PatrikKozak left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

normalizeRichTextInFields reaches nested fields, but not sure it can parse a richText string. The only place a string becomes an object is the import hook in getImportFieldFunctions.ts I.e:

if (typeof value === 'string') {
  try {
    return JSON.parse(value)
  } catch {
    return value
  }
}

processRichTextField.ts bails on non-objects, so it never parses:

if (!value || typeof value !== 'object') {
  return value
}

If the lookup misses (blocks-in-blocks, arrayLikeNames collision), the richText could stay a string and normalizeRichText wouldn't recover it I believe

@paulpopus paulpopus requested a review from PatrikKozak June 8, 2026 18:49
@paulpopus paulpopus merged commit 855a6ab into main Jun 8, 2026
330 of 336 checks passed
@paulpopus paulpopus deleted the fix/plugin-import-export-malformed-data-with-richtext-fields-when-deeply-nested branch June 8, 2026 19:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants