@@ -41,6 +41,7 @@ const createMockCustomField = (
4141 datetimeMax ?: string ;
4242 list ?: boolean ;
4343 nullable ?: boolean ;
44+ readonly ?: boolean ;
4445 } = { } ,
4546) => ( {
4647 name,
@@ -305,6 +306,53 @@ describe('form-schema-tools', () => {
305306 expect ( ( ) => schema . parse ( undefinedData ) ) . not . toThrow ( ) ;
306307 } ) ;
307308
309+ // Readonly custom fields with nullable: false should not block form submission.
310+ // The server excludes readonly fields from Create/Update GraphQL input types,
311+ // and the dashboard strips them from the mutation payload before submitting.
312+ // So the Zod schema must treat them as optional.
313+ it ( 'should treat readonly custom fields as optional even when nullable is false' , ( ) => {
314+ const fields = [ createMockField ( 'customFields' , 'Object' , false , false , [ ] ) ] ;
315+ const customFields = [
316+ createMockCustomField ( 'type' , 'string' , { nullable : false , readonly : true } ) ,
317+ ] ;
318+
319+ const schema = createFormSchemaFromFields ( fields , customFields , false ) ;
320+
321+ // Should accept null/undefined since readonly fields cannot be submitted
322+ const nullData = { customFields : { type : null } } ;
323+ expect ( ( ) => schema . parse ( nullData ) ) . not . toThrow ( ) ;
324+
325+ const undefinedData = { customFields : { type : undefined } } ;
326+ expect ( ( ) => schema . parse ( undefinedData ) ) . not . toThrow ( ) ;
327+
328+ const emptyData = { customFields : { } } ;
329+ expect ( ( ) => schema . parse ( emptyData ) ) . not . toThrow ( ) ;
330+
331+ // Should still accept a value if one happens to be present
332+ const withValue = { customFields : { type : 'manually-created' } } ;
333+ expect ( ( ) => schema . parse ( withValue ) ) . not . toThrow ( ) ;
334+ } ) ;
335+
336+ // Guards against the readonly fix accidentally making every field optional.
337+ // A plain nullable: false field (no readonly) must still reject null/undefined.
338+ it ( 'should reject null/undefined for non-readonly custom fields with nullable false' , ( ) => {
339+ const fields = [ createMockField ( 'customFields' , 'Object' , false , false , [ ] ) ] ;
340+ const customFields = [
341+ createMockCustomField ( 'sku' , 'string' , { nullable : false } ) ,
342+ ] ;
343+
344+ const schema = createFormSchemaFromFields ( fields , customFields , false ) ;
345+
346+ const validData = { customFields : { sku : 'AB-123' } } ;
347+ expect ( ( ) => schema . parse ( validData ) ) . not . toThrow ( ) ;
348+
349+ const nullData = { customFields : { sku : null } } ;
350+ expect ( ( ) => schema . parse ( nullData ) ) . toThrow ( ) ;
351+
352+ const undefinedData = { customFields : { sku : undefined } } ;
353+ expect ( ( ) => schema . parse ( undefinedData ) ) . toThrow ( ) ;
354+ } ) ;
355+
308356 it ( 'should only include non-translatable fields in root context' , ( ) => {
309357 const fields = [ createMockField ( 'customFields' , 'Object' , false , false , [ ] ) ] ;
310358 const customFields = [
0 commit comments