Skip to content

Unable to save piece containing array fields with readOnly: true #5335

@haroun

Description

@haroun

PLEASE NOTE: make sure the bug exists in the latest patch level of the project. For instance, if you are running a 2.x version of Apostrophe, you should use the latest in that major version to confirm the bug.

To Reproduce

Note: I used starter-kit-astro-essentials

Step by step instructions to reproduce the behavior:

  1. Create a new piece type Article in modules/article/index.js
    export default {
      extend: '@apostrophecms/piece-type',
      options: {
        label: 'Article'
      },
      fields: {
        add: {
          contactInfo: {
            label: 'Address',
            type: 'object',
            required: false,
            readOnly: true,
            fields: {
              add: {
                street: {
                  type: 'array',
                  label: 'Street',
                  required: false,
                  readOnly: true, // Can't see array items if it's read-only
                  fields: {
                    add: {
                      type: {
                        label: 'type',
                        type: 'string',
                        readOnly: true,
                        required: false
                      },
                      value: {
                        label: 'value',
                        type: 'string',
                        readOnly: true,
                        required: false
                      }
                    }
                  }
                },
                city: {
                  type: 'string',
                  label: 'City',
                  required: false,
                  readOnly: true
                },
                state: {
                  type: 'string',
                  label: 'State',
                  required: false,
                  readOnly: true
                }
              }
            }
          }
        },
        group: {
          basics: {
            label: 'Basics',
            fields: [
              'title',
              'contactInfo'
            ]
          }
        }
      }
    };
  2. Enable the article module in app.js
  3. Create a new piece using the admin UI
  4. Run the following mongodb query db.aposDocs.updateMany({type:'article'},{$unset:{contactInfo:1}}) to delete the contactInfo field. Why? The array/object field are read-only, not required and does not have a default value. You might have an external service creating article pieces using the Apostrophe API.
  5. Using the admin UI, edit the existing article piece (you can update the Visibility field) and try to save.
  6. You should see the following error in the server console
    article: api-error: Cannot read properties of undefined (reading 'street')
    {
      module: 'article',
      type: 'api-error',
      severity: 'error',
      url: '/api/v1/article/frvq49pb7vk93b200x5ireyk:en:draft?aposMode=draft&aposLocale=en',
      path: '/api/v1/article/frvq49pb7vk93b200x5ireyk:en:draft',
      method: 'PUT',
      ip: '::1',
      query: { aposMode: 'draft', aposLocale: 'en' },
      requestId: 'stzkw3ycbben20mfclq2q53w',
      name: 'error',
      status: 500,
      stack: [
        'at Object.getNonVisibleFields (/srv/www/starter-kit-astro-essentials/backend/node_modules/apostrophe/modules/@apostrophecms/schema/index.js:716:48)',
        'at process.processTicksAndRejections (node:internal/process/task_queues:103:5)',
        'at async Object.getNonVisibleFields (/srv/www/starter-kit-astro-essentials/backend/node_modules/apostrophe/modules/@apostrophecms/schema/index.js:727:13)',
        'at async Object.convert (/srv/www/starter-kit-astro-essentials/backend/node_modules/apostrophe/modules/@apostrophecms/schema/index.js:665:34)',
        'at async Object.convert (/srv/www/starter-kit-astro-essentials/backend/node_modules/apostrophe/modules/@apostrophecms/doc-type/index.js:829:9)',
        'at async /srv/www/starter-kit-astro-essentials/backend/node_modules/apostrophe/modules/@apostrophecms/piece-type/index.js:968:11',
        'at async Object.withLock (/srv/www/starter-kit-astro-essentials/backend/node_modules/apostrophe/modules/@apostrophecms/lock/index.js:185:18)',
        'at async /srv/www/starter-kit-astro-essentials/backend/node_modules/apostrophe/modules/@apostrophecms/module/index.js:193:30'
      ],
      cause: undefined,
      errorPath: undefined,
      data: {}
    }
    

Expected behavior

I should be able to save the updated piece without errors.

Describe the bug

One way to fix it can be to add some optional chaining in schema/index.js

diff --git a/packages/apostrophe/modules/@apostrophecms/schema/index.js b/packages/apostrophe/modules/@apostrophecms/schema/index.js
index f2d1ca922..7e3f5201e 100644
--- a/packages/apostrophe/modules/@apostrophecms/schema/index.js
+++ b/packages/apostrophe/modules/@apostrophecms/schema/index.js
@@ -713,7 +713,7 @@ module.exports = {

           // Relationship does not support conditional fields right now
           if ([ 'array' /*, 'relationship' */].includes(field.type) && field.schema) {
-            for (const arrayItem of destination[field.name] || []) {
+            for (const arrayItem of destination?.[field.name] || []) {
               await self.getNonVisibleFields({
                 req,
                 schema: field.schema,
@@ -727,7 +727,7 @@ module.exports = {
             await self.getNonVisibleFields({
               req,
               schema: field.schema,
-              destination: destination[field.name],
+              destination: destination?.[field.name],
               nonVisibleFields,
               fieldPath: curPath,
               parentFollowingValues

If we have an error on an invisible field, there is some logic to handle this in schema/index.js. I don't think the optional chaining solution covers all scenario.

          const nonVisibleField = hiddenAncestors || nonVisibleFields.has(errorPath);

          // We set default values only on final error fields
          if (nonVisibleField && !error.data?.errors) {
            const curSchema = self.getFieldLevelSchema(schema, error.schemaPath);
            self.setDefaultToInvisibleField(curDestination, curSchema, error.path);
            continue;
          }

Details

Version of Node.js:
PLEASE NOTE: Only stable LTS versions (10.x and 12.x) are fully supported but we will do our best with newer versions.

Node 24.13.1 + Npm 11.8.0

Server Operating System:
The server (which might be your dev laptop) on which Apostrophe is running. Linux? MacOS X? Windows? Is Docker involved?

Docker on MacOS (mongo:8.2.5 + node:24.13.1-alpine3.23)

Additional context:

Add any other context about the problem here. If the problem is specific to a browser, OS or mobile device, specify which.

/srv/www/starter-kit-astro-essentials/backend $ npm ls
starter-kit-astro@1.0.0 /srv/www/starter-kit-astro-essentials/backend
+-- @apostrophecms/blog@1.0.6
+-- @apostrophecms/vite@1.1.1
+-- apostrophe@4.27.0
+-- cross-env@10.1.0
+-- eslint-config-apostrophe@6.0.2
`-- nodemon@3.1.14

/srv/www/starter-kit-astro-essentials/frontend $ npm ls
astro-frontend@1.0.0 /srv/www/starter-kit-astro-essentials/frontend
+-- @apostrophecms/apostrophe-astro@1.9.0
+-- @astrojs/node@9.5.4
+-- astro@5.17.3
+-- cross-env@10.1.0
+-- dayjs@1.11.19
+-- postcss-viewport-to-container-toggle@1.1.0
`-- vite@7.3.1

Screenshots
If applicable, add screenshots to help explain your problem.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions