Developing a Custom Form Field for Liferay 7.2

React-based custom form fields can be developed for Liferay 7.3 and Liferay 7.4. See Writing Custom Form Field Types to learn how. Liferay 7.2 used a different front-end technology, based on MetalJS and Soy closure templates. In this tutorial you can learn to adapt the React-based Acme C2P9 Slider field to run on a Liferay 7.2 installation.

Adjusting the Acme C2P9 Slider to Run on Liferay 7.2

Liferay 7.2 used a different front-end framework for custom form fields. To adjust the Acme C2P9 Slider field in order to run it on Liferay 7.2,

  1. Download and unzip the Custom Forms Field Type project.

    curl -O
  2. Open the file and change the line



  3. Open the file and change the line

    classpath group: "com.liferay", name: "com.liferay.gradle.plugins.workspace", version: "latest.release"


    classpath group: "com.liferay", name: "com.liferay.gradle.plugins.workspace", version: "3.4.17"
  4. Open the file and add the lines

     compileOnly group: "org.osgi", name: "osgi.cmpn"
     jsCompile group: "com.liferay", name: ""
  5. Open the file. Begin by replacing the devDependencies line

     "@liferay/portal-7.4": "*"

    with these two lines:

     "@liferay/portal-7.2": "*",
     "metal-tools-soy": "4.3.2"

    Then replace the existing scripts content (two lines) with these three lines:

    "build": "npm run build-soy && npm run build-js && liferay-npm-bundler",
    "build-js": "babel --source-maps -d build/resources/main/META-INF/resources src/main/resources/META-INF/resources",
    "build-soy": "metalsoy --externalMsgFormat \"Liferay.Language.get('\\$2')\" --soyDeps \"./node_modules/clay-*/src/**/*.soy\" \"./node_modules/|components)/**/*.soy\""
  6. Open the file and add these lines:

  7. Replace the contents of with

    module.exports = {
        presets: ['@babel/preset-env'],

    Checkpoint: The project is reconfigured to expect the 7.2 front-end framework, so you must replace the front-end of the form field project.

  8. Remove the existing file and create these three files to replace it:

    • the new slider component using the MetalJS + Soy front-end.

      import 'dynamic-data-mapping-form-field-type/FieldBase/';
      import './';
      import templates from './';
      import Component from 'metal-component';
      import Soy from 'metal-soy';
      import {Config} from 'metal-state';
      class Slider extends Component {
          dispatchEvent(event, name, value) {
              this.emit(name, {
                  fieldInstance: this,
                  originalEvent: event,
          _handleFieldChanged(event) {
              const {value} =;
                  () => this.dispatchEvent(event, 'fieldEdited', value)
      Slider.STATE = {
          label: Config.string(),
          name: Config.string().required(),
          predefinedValue: Config.oneOfType([Config.number(), Config.string()]),
          required: Config.bool().value(false),
          showLabel: Config.bool().value(true),
          spritemap: Config.string(),
          value: Config.string().value('')
      Soy.register(Slider, templates);
      export default Slider;
    • the Soy template for the field.

      {namespace Slider}
      {template .render}
          {@param label: string}
          {@param name: string}
          {@param showLabel: bool}
          {@param tip: string}
          {@param value: ?}
          {@param? _handleFieldChanged: any}
          {@param? predefinedValue: any}
          {@param? required: bool}
          {@param? spritemap: string}
          {call FieldBase.render}
              {param contentRenderer kind="html"}
                  {call .content}
                      {param _handleFieldChanged: $_handleFieldChanged /}
                      {param name: $name /}
                      {param predefinedValue: $predefinedValue /}
                      {param value: $value /}
              {param label: $label /}
              {param name: $name /}
              {param required: $required /}
              {param showLabel: $showLabel /}
              {param spritemap: $spritemap /}
              {param tip: $tip /}
      {template .content}
          {@param name: string}
          {@param value: ?}
          {@param? _handleFieldChanged: any}
          {@param? predefinedValue: any}
          {let $attributes kind="attributes"}
              class="ddm-field-slider form-control slider"
              {if $value}
          <input {$attributes}>
    • the registration code for the Slider’s Soy template.

      {namespace SliderRegister}
      {deltemplate PageRenderer.RegisterFieldType variant="'slider'"}
          {call Slider.render data="all" /}
  9. A minor adjustment to the DDMFormFieldType is needed: the identifier for the field (the name) cannot have a hyphen (-) because it is referenced as a String literal in the Soy template. Open the C2P9DDMFormFieldType class and remove the hyphen from

    • the component property

    • the String returned by getName

    The value in both locations is now c2p9slider.

  10. Once the project’s front-end is replaced, deploy it to a running Liferay 7.2.

    • To start a Liferay 7.2 Docker container run

      docker run -it -m 8g -p 8080:8080 liferay/portal:
    • To deploy the reconfigured form field project, go to the folder and run

      ./gradlew deploy$(docker ps -lq)
  11. Confirm the deployment in the Liferay Docker container console.

    STARTED com.acme.c2p9.impl_1.0.0 [1009]

The form field is deployed and ready to use on Liferay 7.2.