From 5349989ffd641e37c8746c49cf2d1216107904d9 Mon Sep 17 00:00:00 2001 From: Freddy Montes Date: Thu, 25 Jun 2026 13:08:00 -0600 Subject: [PATCH 1/2] docs(openapi): clarify dataType-per-field-class and host/pathToMove rules (#36321) The MCP server builds payloads from openapi.yaml. Two doc gaps caused it to generate 400-failing requests: * POST /api/v1/contenttype: the field schema documented the dataType enum but not the per-field-class restriction, so image fields were sent dataType:SYSTEM and rejected. Document the field-class -> dataType mapping (ImageField/BinaryField=TEXT, etc.) in ContentTypeResource. * PUT /api/v1/workflow/.../fire/{systemAction}: setting host/folder on the contentlet is rejected by design (system fields are dropped by MapToContentletPopulator). The supported path is the top-level pathToMove property (Move actionlet). Surface this in the fire @Operation description and enrich the BadRequestException message with a concrete pathToMove example. The guard itself is unchanged. openapi.yaml regenerated. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../api/v1/contenttype/ContentTypeResource.java | 4 ++++ .../rest/api/v1/workflow/WorkflowResource.java | 13 +++++++++++-- dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml | 3 +++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java index f9fada8309b7..e8ca695c9e2b 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java @@ -518,6 +518,10 @@ public final Response createType(@Context final HttpServletRequest req, "`ImmutableDateField`, `ImmutableDateTimeField`, `ImmutableRowField` *(layout marker)*, `ImmutableColumnField` *(layout marker)*\n" + "- `name`, `variable`, `dataType` (one of `TEXT`, `LONG_TEXT`, `SYSTEM`, `BOOL`, `INTEGER`, `FLOAT`, `DATE`), " + "`required`, `indexed`, `listed`, `sortOrder` *(integer, position in the fields array)*\n" + + " ⚠️ **`dataType` is restricted per field class** — sending a value outside a class's accepted set fails with " + + "`400 Field Type:... does not accept datatype ...`. Use: `ImmutableImageField`, `ImmutableBinaryField` → `TEXT`; " + + "`ImmutableTextAreaField`, `ImmutableStoryBlockField` → `LONG_TEXT`; `ImmutableCustomField` → `LONG_TEXT` (or `TEXT`); " + + "`ImmutableTextField` → `TEXT`, `LONG_TEXT`, `INTEGER`, or `FLOAT`. Do **not** send `dataType: SYSTEM` for an image field.\n" + "- `values` *(string)* — for Radio/Select/Checkbox: newline-separated `Display|value` pairs. " + "For a boolean field use `ImmutableRadioField` + `dataType: BOOL` + `values: 'True|true\\r\\nFalse|false'` — there is no dedicated Boolean field class.\n\n" + "**Layout encoding:** Rows and columns are regular field entries placed in `fields[]`. " + diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java index 230bd5e4b8e1..72e540acd5e4 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java @@ -3131,8 +3131,10 @@ private void validateFireActionForm(final FireActionForm fireActionForm, throw new BadRequestException(String.format( "System fields %s cannot be set via this endpoint. " - + "To change a contentlet's location, fire a workflow action that includes " - + "the Move actionlet (see 'pathToMove').", + + "To set or change a contentlet's location, fire a workflow action that includes " + + "the Move actionlet and pass the target location via the top-level 'pathToMove' " + + "property (a sibling of 'contentlet', not a field inside it), " + + "e.g. {\"pathToMove\": \"//demo.dotcms.com/application\", \"contentlet\": {...}}.", protectedFields)); } } @@ -3215,6 +3217,13 @@ private boolean needSave (final FireActionForm fireActionForm) { "`errorCode` values: `required`, `unknown`. `fieldName` is the field `variable` for field-specific errors, " + "or `null` for content-level errors. Note: when the content type is not found, `message` returns " + "the raw translation key `Workflow-does-not-exists-content-type` instead of translated text.\n\n" + + "⚠️ **Setting a contentlet's location (host/folder):** Do **not** put `host`, `hostId`, `hostname`, " + + "or `folder` inside the `contentlet` object — these system fields are rejected with " + + "`400 System fields [...] cannot be set via this endpoint`. To place or move a contentlet, fire an " + + "action that includes the **Move actionlet** and pass the target location via the top-level " + + "`pathToMove` property (sibling of `contentlet`, not inside it), e.g. " + + "`{\"pathToMove\": \"//demo.dotcms.com/application\", \"contentlet\": {...}}`. " + + "The path must include a host. See the `pathToMove` row in the request body table below.\n\n" + "**Binary and image fields** — These fields cannot receive raw file data or asset paths in the JSON body. " + "Use one of the patterns below.\n\n" + "**Pattern A — single-use file (works for all binary/image fields):**\n\n" + diff --git a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml index 7e84e7edee2e..c792325e8418 100644 --- a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml +++ b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml @@ -7765,6 +7765,7 @@ paths: **Field object schema** (each item in `fields[]`): - `clazz` *(string, required)* — e.g. `com.dotcms.contenttype.model.field.ImmutableTextField`, `ImmutableTextAreaField`, `ImmutableStoryBlockField`, `ImmutableBinaryField`, `ImmutableTagField`, `ImmutableRadioField`, `ImmutableSelectField`, `ImmutableDateField`, `ImmutableDateTimeField`, `ImmutableRowField` *(layout marker)*, `ImmutableColumnField` *(layout marker)* - `name`, `variable`, `dataType` (one of `TEXT`, `LONG_TEXT`, `SYSTEM`, `BOOL`, `INTEGER`, `FLOAT`, `DATE`), `required`, `indexed`, `listed`, `sortOrder` *(integer, position in the fields array)* + ⚠️ **`dataType` is restricted per field class** — sending a value outside a class's accepted set fails with `400 Field Type:... does not accept datatype ...`. Use: `ImmutableImageField`, `ImmutableBinaryField` → `TEXT`; `ImmutableTextAreaField`, `ImmutableStoryBlockField` → `LONG_TEXT`; `ImmutableCustomField` → `LONG_TEXT` (or `TEXT`); `ImmutableTextField` → `TEXT`, `LONG_TEXT`, `INTEGER`, or `FLOAT`. Do **not** send `dataType: SYSTEM` for an image field. - `values` *(string)* — for Radio/Select/Checkbox: newline-separated `Display|value` pairs. For a boolean field use `ImmutableRadioField` + `dataType: BOOL` + `values: 'True|true\r\nFalse|false'` — there is no dedicated Boolean field class. **Layout encoding:** Rows and columns are regular field entries placed in `fields[]`. `ImmutableRowField` begins a new row; `ImmutableColumnField` begins a new column inside that row; following content fields belong to the most-recent column until the next marker. @@ -18728,6 +18729,8 @@ paths: ``` `errorCode` values: `required`, `unknown`. `fieldName` is the field `variable` for field-specific errors, or `null` for content-level errors. Note: when the content type is not found, `message` returns the raw translation key `Workflow-does-not-exists-content-type` instead of translated text. + ⚠️ **Setting a contentlet's location (host/folder):** Do **not** put `host`, `hostId`, `hostname`, or `folder` inside the `contentlet` object — these system fields are rejected with `400 System fields [...] cannot be set via this endpoint`. To place or move a contentlet, fire an action that includes the **Move actionlet** and pass the target location via the top-level `pathToMove` property (sibling of `contentlet`, not inside it), e.g. `{"pathToMove": "//demo.dotcms.com/application", "contentlet": {...}}`. The path must include a host. See the `pathToMove` row in the request body table below. + **Binary and image fields** — These fields cannot receive raw file data or asset paths in the JSON body. Use one of the patterns below. **Pattern A — single-use file (works for all binary/image fields):** From a0d1ddc6aced144c491b41663fb5a6cf809e332a Mon Sep 17 00:00:00 2001 From: Freddy Montes Date: Thu, 25 Jun 2026 13:25:00 -0600 Subject: [PATCH 2/2] docs(openapi): drop emojis from added doc strings (#36321) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../dotcms/rest/api/v1/contenttype/ContentTypeResource.java | 2 +- .../com/dotcms/rest/api/v1/workflow/WorkflowResource.java | 2 +- dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java index e8ca695c9e2b..d764e5e059e5 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/contenttype/ContentTypeResource.java @@ -518,7 +518,7 @@ public final Response createType(@Context final HttpServletRequest req, "`ImmutableDateField`, `ImmutableDateTimeField`, `ImmutableRowField` *(layout marker)*, `ImmutableColumnField` *(layout marker)*\n" + "- `name`, `variable`, `dataType` (one of `TEXT`, `LONG_TEXT`, `SYSTEM`, `BOOL`, `INTEGER`, `FLOAT`, `DATE`), " + "`required`, `indexed`, `listed`, `sortOrder` *(integer, position in the fields array)*\n" + - " ⚠️ **`dataType` is restricted per field class** — sending a value outside a class's accepted set fails with " + + " **Note: `dataType` is restricted per field class** — sending a value outside a class's accepted set fails with " + "`400 Field Type:... does not accept datatype ...`. Use: `ImmutableImageField`, `ImmutableBinaryField` → `TEXT`; " + "`ImmutableTextAreaField`, `ImmutableStoryBlockField` → `LONG_TEXT`; `ImmutableCustomField` → `LONG_TEXT` (or `TEXT`); " + "`ImmutableTextField` → `TEXT`, `LONG_TEXT`, `INTEGER`, or `FLOAT`. Do **not** send `dataType: SYSTEM` for an image field.\n" + diff --git a/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java b/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java index 72e540acd5e4..3f5b90d37a0f 100644 --- a/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java +++ b/dotCMS/src/main/java/com/dotcms/rest/api/v1/workflow/WorkflowResource.java @@ -3217,7 +3217,7 @@ private boolean needSave (final FireActionForm fireActionForm) { "`errorCode` values: `required`, `unknown`. `fieldName` is the field `variable` for field-specific errors, " + "or `null` for content-level errors. Note: when the content type is not found, `message` returns " + "the raw translation key `Workflow-does-not-exists-content-type` instead of translated text.\n\n" + - "⚠️ **Setting a contentlet's location (host/folder):** Do **not** put `host`, `hostId`, `hostname`, " + + "**Setting a contentlet's location (host/folder):** Do **not** put `host`, `hostId`, `hostname`, " + "or `folder` inside the `contentlet` object — these system fields are rejected with " + "`400 System fields [...] cannot be set via this endpoint`. To place or move a contentlet, fire an " + "action that includes the **Move actionlet** and pass the target location via the top-level " + diff --git a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml index c792325e8418..e1189d47d6d0 100644 --- a/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml +++ b/dotCMS/src/main/webapp/WEB-INF/openapi/openapi.yaml @@ -7765,7 +7765,7 @@ paths: **Field object schema** (each item in `fields[]`): - `clazz` *(string, required)* — e.g. `com.dotcms.contenttype.model.field.ImmutableTextField`, `ImmutableTextAreaField`, `ImmutableStoryBlockField`, `ImmutableBinaryField`, `ImmutableTagField`, `ImmutableRadioField`, `ImmutableSelectField`, `ImmutableDateField`, `ImmutableDateTimeField`, `ImmutableRowField` *(layout marker)*, `ImmutableColumnField` *(layout marker)* - `name`, `variable`, `dataType` (one of `TEXT`, `LONG_TEXT`, `SYSTEM`, `BOOL`, `INTEGER`, `FLOAT`, `DATE`), `required`, `indexed`, `listed`, `sortOrder` *(integer, position in the fields array)* - ⚠️ **`dataType` is restricted per field class** — sending a value outside a class's accepted set fails with `400 Field Type:... does not accept datatype ...`. Use: `ImmutableImageField`, `ImmutableBinaryField` → `TEXT`; `ImmutableTextAreaField`, `ImmutableStoryBlockField` → `LONG_TEXT`; `ImmutableCustomField` → `LONG_TEXT` (or `TEXT`); `ImmutableTextField` → `TEXT`, `LONG_TEXT`, `INTEGER`, or `FLOAT`. Do **not** send `dataType: SYSTEM` for an image field. + **Note: `dataType` is restricted per field class** — sending a value outside a class's accepted set fails with `400 Field Type:... does not accept datatype ...`. Use: `ImmutableImageField`, `ImmutableBinaryField` → `TEXT`; `ImmutableTextAreaField`, `ImmutableStoryBlockField` → `LONG_TEXT`; `ImmutableCustomField` → `LONG_TEXT` (or `TEXT`); `ImmutableTextField` → `TEXT`, `LONG_TEXT`, `INTEGER`, or `FLOAT`. Do **not** send `dataType: SYSTEM` for an image field. - `values` *(string)* — for Radio/Select/Checkbox: newline-separated `Display|value` pairs. For a boolean field use `ImmutableRadioField` + `dataType: BOOL` + `values: 'True|true\r\nFalse|false'` — there is no dedicated Boolean field class. **Layout encoding:** Rows and columns are regular field entries placed in `fields[]`. `ImmutableRowField` begins a new row; `ImmutableColumnField` begins a new column inside that row; following content fields belong to the most-recent column until the next marker. @@ -18729,7 +18729,7 @@ paths: ``` `errorCode` values: `required`, `unknown`. `fieldName` is the field `variable` for field-specific errors, or `null` for content-level errors. Note: when the content type is not found, `message` returns the raw translation key `Workflow-does-not-exists-content-type` instead of translated text. - ⚠️ **Setting a contentlet's location (host/folder):** Do **not** put `host`, `hostId`, `hostname`, or `folder` inside the `contentlet` object — these system fields are rejected with `400 System fields [...] cannot be set via this endpoint`. To place or move a contentlet, fire an action that includes the **Move actionlet** and pass the target location via the top-level `pathToMove` property (sibling of `contentlet`, not inside it), e.g. `{"pathToMove": "//demo.dotcms.com/application", "contentlet": {...}}`. The path must include a host. See the `pathToMove` row in the request body table below. + **Setting a contentlet's location (host/folder):** Do **not** put `host`, `hostId`, `hostname`, or `folder` inside the `contentlet` object — these system fields are rejected with `400 System fields [...] cannot be set via this endpoint`. To place or move a contentlet, fire an action that includes the **Move actionlet** and pass the target location via the top-level `pathToMove` property (sibling of `contentlet`, not inside it), e.g. `{"pathToMove": "//demo.dotcms.com/application", "contentlet": {...}}`. The path must include a host. See the `pathToMove` row in the request body table below. **Binary and image fields** — These fields cannot receive raw file data or asset paths in the JSON body. Use one of the patterns below.