# Form Customization (Form Hook)

The system provides form-level customization through Form Hook (form-level hook) and Data Hook (data-level hook), used to implement field default values, linkage, visibility, read-only state, required state, options, data transformation and similar behaviors. This capability replaces the field-level customization of earlier versions (Field Hook / Dynamic Field Hook), which has been removed from the platform.

# Target Audience

This document is intended for developers and implementers of the system.

# Overview

Both Form Hook and Data Hook are associated with a Dynamic Logic object and are configured through three fields on DynamicForm:

Field Type Description
formHook.name DynamicLogic Form-level hook: field default values, linkage, visibility, read-only state, required state, options, etc.
formHookTriggerFields String A comma-separated list of field names; when any of these fields changes in the UI, the Form Hook runs again
dataHook.name DynamicLogic Data-level hook: transforms the data returned for lists/details

TIP

The "field customization" described in older documentation (Dynamic Field Hook, Field dependencies hook, field quick-search logic, etc.) has been removed as the platform evolved. The related capabilities are now all provided by Form Hook; do not configure them the old way.

# Form Hook (Form-Level Hook)

# Trigger Timing

Form Hook runs at the following times, and the injected object differs by timing:

Timing object
When a create form is opened Empty Map [:]
When a detail or edit form is opened The record in the database (a GORM entity); if the form has a Data Hook configured, it is the record returned by the Data Hook (a Map)
When list data is loaded, once per row Same as above, the record of that row
When a field in formHookTriggerFields is modified in the UI The current form values in the UI (not yet saved), of type org.grails.web.json.JSONObject

Only for the last timing (and the supplementary call after opening an edit form or detail, described below) is initiated true, with changedFields holding the names of the fields that changed; at the other timings initiated is false and changedFields is an empty list. For the API used at each timing, see Related APIs.

One extra execution when opening an edit form or detail

When an edit form or detail is opened, after the form data has loaded, the frontend also treats the trigger fields as changed fields and calls POST /form/$formId/refresh once more: this time initiated is true, changedFields holds those field names (in testing, the trigger fields that have a value in the UI and are editable), and object is the form values in the UI. In other words, even if the user has not changed any field, the field linkage logic runs once when the edit form is opened. So do not unconditionally return a field's value in the field linkage branch; otherwise the value already saved in the record will be overwritten when the edit form is opened. When a create form is opened, this call does not happen as long as the trigger fields have no value (for example, when no default value is set for them by the Form Hook).

On create and edit, Form Hook is used to set field default values and field properties; when a field is modified, it is used for field linkage; in details and lists, it is used to adjust field properties dynamically based on the record content.

# Injected Variables

When a Form Hook runs, the system injects the following variables:

Variable name Variable type Description
form tech.muyan.dynamic.form.DynamicForm The current form object
changedFields java.util.List<String> The names of the fields that triggered this execution; only has values when initiated is true, otherwise an empty list
initiated boolean true when triggered by a field change (including the supplementary call after opening an edit form or detail), otherwise false
object Varies by timing The current record; see the table in Trigger Timing above
owner Object The master record in embedded sub-table and related-list scenarios (for example, the order when the order line list is loaded in the order detail); null in other scenarios
userContext tech.muyan.api.security.MuyanAuthentication Information about the current user

You can also use the application, invoke and logger variables common to all dynamic logic. Form Hook has no log variable; use logger to print logs.

# Return Value

Form Hook returns a Map keyed by field key. Each field entry can contain:

Key Type Description
value Any The field's default value / new value
display String hide hides the field; show shows the field; readonly displays it read-only. Returning hide on a field change currently crashes the form; see the warning after the example in this section
required boolean Whether the field is required (when false, the field automatically becomes nullable)
options / enumOptions Array Options of a selection field
min / max Number Validation range of a numeric field
editable boolean Whether the field is editable
helpText String Help text of the field
Other FieldProps Any Any field property supported by the platform (including field group extInfo, etc.)

Example (the order create and edit forms share this Form Hook; for the trigger fields, see Configuration below):

// Field linkage: when "order type" is urgent (URGENT), "delivery date" is required
if (initiated && changedFields.contains('orderType')) {
  return [
    deliveryDate: [
      required: object?.orderType == 'URGENT'
    ]
  ]
}
// Create form opened: object is an empty Map; set the default value and options of "status"
if (!initiated && !object) {
  return [
    status: [
      value  : 'DRAFT',
      options: ['DRAFT', 'SUBMITTED', 'APPROVED']
    ]
  ]
}
// Other timings (edit form opened, detail viewed, list loaded, other trigger fields changed): no changes
return [:]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Key points of this code:

  • The branch that sets the default value must be limited to create with !initiated && !object. Without this check, value would also be returned when an edit form is opened or a detail is viewed, and the frontend would use it to overwrite the status already saved in the record; the same applies when another trigger field (for example customer in the CSV below) changes.
  • The field linkage branch only adjusts field properties (required) and does not change field values. For the reason, see One extra execution when opening an edit form or detail above.
  • In all other cases it returns an empty Map [:], meaning no field is modified.

Do not return display: 'hide' on a field change

In the current version, returning display: 'hide' when triggered by a field change (initiated is true) crashes the form, which shows "Something went wrong" (the frontend reports React error #300). This happens in both create and edit forms and is a UI issue in the current version. Returning display: 'hide' when the form is opened (initiated is false) hides the field normally. Until this is fixed, avoid toggling field visibility in field linkage; toggle other properties such as required instead.

# Configuration

Configure it in DynamicForm.csv through CSV seed data:

name(*),label,description,objectType.shortName(*),type.name(*),formHook.name,formHookTriggerFields,dataHook.name,extInfo,accessRequirement.name
Create order,,,Order,CREATE,order_form_hook,"orderType,customer",,,USER
List order,,,Order,LIST,,,order_list_data_hook,,USER
1
2
3
  • formHook.name: the name of the associated Form Hook dynamic logic
  • formHookTriggerFields: a comma-separated list of trigger field names. When there are several fields, the whole column must be enclosed in English double quotes; otherwise the commas are treated as column separators and the following field names shift into the next columns
  • dataHook.name: the name of the associated Data Hook dynamic logic

The platform does not check the logic type when executing a Form Hook / Data Hook. It is recommended to use the FUNCTION_LOGIC type consistently (the platform's built-in DynamicFormField FormHook is of this type).

# Built-in Example

The platform's built-in dynamic logic DynamicFormField FormHook is a Form Hook attached to the create and edit forms of form fields (Create dynamic form field, Update dynamic form field). You can search for this name under Development > Logics > Logics to view its code. Its logic is as follows:

  • When initiated is false and object contains the owning form (form): restrict the options of "Group" (group) to the field groups of that form (through extInfo.defaultOptionsCondition), and set the options (enumOptions) of "Field type" (fieldType) according to the form type. When a create form is opened, object is an empty Map, so this part only takes effect when editing or viewing a detail.
  • Regardless of the value of initiated, iterate over changedFields; when the changed field is name, split the field name by camel case and fill it back into label. The trigger field configured on these two forms is fieldName, not name, so this part is currently never triggered.

# Data Hook (Data-Level Hook)

Data Hook transforms the data returned by the list and detail APIs. Injected variables:

Variable name Variable type Description
ids List<Long> The ids of the records to fetch. When viewing a detail, a list containing only that record's id; when fetching data by multiple ids, those ids; for GET /form/data/{formId}, an empty list; for list, search, related-list and tree data queries, null
offset Integer Pagination offset; only has a value in list, search and related-list queries
cursor Integer Cursor; may only have a value in related-list queries
max Integer Page size; only has a value in list, search and related-list queries
owner Object The master object in related-list scenarios; null in other scenarios
conditions / parsedConditions Object Query conditions
userContext tech.muyan.api.security.MuyanAuthentication Information about the current user

As with Form Hook, Data Hook can use application, invoke and logger, and has no log variable.

For a form with a Data Hook configured, the database is no longer queried; the list, detail and other APIs use the return value of the Data Hook directly. The return structure is as follows:

return [
  data : [               // The list of records on this page
    [id: 1, name: "..."],
  ],
  total: 1               // Total number of records, used for pagination
]
1
2
3
4
5
6

When viewing a detail, the platform takes the first record in data. The Data Hook therefore needs to check whether ids is null to tell whether it is querying a list or fetching records by id.

TIP

The result of the Data Hook also serves AMIS forms: the data of an AMIS form is obtained through GET /form/data/$formId, and this API runs the Data Hook to transform the data.

The paths in the table below omit the /api prefix; add it when calling from outside, see Address Prefix.

API Description
POST /form/formHook/$formId Gets the Form Hook data when a create form is opened
POST /form/$formId/refresh Refreshes the form field properties in batch after a field change; also called once after opening an edit form or detail, see Trigger Timing
GET /data/$domainName/$id/withFormHook?formId= Gets the record data + Form Hook result for detail and edit
GET /form/data/$formId AMIS form data (including the Data Hook transformation)
Last Updated: 9/24/2026, 2:27:35 PM