Mastering Liferay Client Extension Development: Comprehensive Guide

blog-banner

In this blog, we will learn how to create a custom element type client extension for a React application from scratch, and how to build and deploy it using the Gradle build tool. As a bonus, we will also create a batch type client extension to automate the process of importing objects into the Liferay server.

By the end of this blog, you will have a solid understanding of how to create these types of client extension, build and deploy them using Gradle.

Prerequisite:

  • Basic knowledge of Liferay
  • Liferay DXP/Portal 7.4+

What is Client Extension?

Client extensions enable developers to create separate components that extend Liferay's functionality without modifying Liferay’s core codebase, ensuring maintainability and ease of upgrades. These components can be built with modern web technologies such as React, Angular, and other modern JavaScript frameworks.

Key Advantages of Client Extension

  1. Loose Coupling: By interacting with Liferay’s APIs, client extensions reduce dependencies on the platform's core code, minimizing the risk of breaking changes when Liferay updates.
  2. Cross-Environment Deployment: Whether you are deploying to SaaS, PaaS, or self-hosted Liferay instances, client extensions offer a flexible, scalable solution that works across multiple environments.

Build and deploy Custom Element type client extension

CustomElement extensions can be created using modern web frameworks like React, Vue, or plain JavaScript, and deployed as standalone components.

Step 1: Add your react project in Liferay workspace

  • Set up your Liferay workspace and copy your React app directory into the client-extensions directory.
  • If the client-extensions directory does not already exist in your workspace, create it manually at the root level of your Liferay workspace.

Step 2: Update index.js

import React from "react";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App";
import { CONSTANTS } from "./constants/constants";
import store from "./redux/store";
import ConfigurationContext from "./util/ConfigurationContext";

class WebComponent extends HTMLElement {
  constructor() {
    super();
    const configuration = {
      vocabulary:
        this.getAttribute(CONSTANTS.VOCABULARY) === null
          ? 0
          : this.getAttribute(CONSTANTS.VOCABULARY),
    };
    createRoot(this).render(
       <ConfigurationContext.Provider value={configuration}>
        <Provider store={store}>
          <div className="applications custom-element app-main flex-column flex-row-fluid">
            <App {...configuration} />
          </div>
        </Provider>
      </ConfigurationContext.Provider>
    );
  }
}

const ELEMENT_ID = "stpl-training-custom-element-application";
if (!customElements.get(ELEMENT_ID)) {
  customElements.define(ELEMENT_ID, WebComponent);
}
  
  • The WebComponent class, which extends the HTMLElement class, is responsible for handling actions when the custom React element is inserted or removed from the page.
  • We need to make sure that our custom tag is listed correctly in public/index.html or index.html and update the index.js accordingly for the connecting logic as shown above.
  • We are fetching the configurations of the client-extension, which we will define in client-extension.yaml file.

Step 3: Update index.html

<!DOCTYPE html>
<html lang="en">
<body>
  <stpl-training-custom-element-application></stpl-training-custom-element-application>
</body>
</html>
  • The next step is to update the index.html file to register the React custom element. This ensures that when the browser encounters the <stpl-training-custom-element-application> tag, it uses the WebComponent to render the application.

Step 4: Create the client-extension.yaml file

assemble:
 - from: build/static
   into: static
stpl-training-custom-element-application:
  cssURLs:
    - css/main.*.css
  #Name of your HTML element defined in your index.js and index.html
  htmlElementName: stpl-training-custom-element-application
  instanceable: false
  name: Applications
  portletCategoryName: category.client-extensions
  #Type of client-extension
  type: customElement
  urls:
    - js/main.*.js
  useESM: true
  #To set property for client-extension.
  properties:
   vocabulary: applications
  • At the root level of your React application, create the client-extension.yaml file.
  • This file is used by the Gradle build tool to specify the type of client extension and other necessary properties to build and package your client extension into a ZIP file.

client-extension.yaml file explaination

  • assemble block is responsible for identifying the source of the assets and determining where to place them after they are assembled.
  • stpl-training-custom-element-application block defines the metadata and configuration for your custom client extension in Liferay. Each property here is crucial for proper deployment and functioning of the extension.
  • cssURLs define list of CSS files required by the client extension.
  • htmlElementName block Specifies the name of the HTML element that will be defined by the client extension.
  • instanceable A boolean value that determines whether this client extension can have multiple instances on the same page.
  • portletCategoryName defines the category under which the client extension will be listed in the Liferay admin panel.
  • type The type of client extension being registered.
  • useESM A boolean that indicates whether ECMAScript modules (ESM) are used.
  • properties This section defines custom properties or configurations that can be passed to the client extension.

Step 5: Build the client-extension

  • Open the terminal and navigate to your client-extensions/stpl-training-custom-element-application and enter the below command to build your client-extension.
  • ../../gradlew clean build
  • After successful build you will find a zip file named as stpl-training-custom-element-application.zip in dist directory as shown below.

Step 6: Deploy the client-extension

  • To deploy the client extension, copy the stpl-training-custom-element-application.zip file and paste it into the deploy directory of the Liferay bundle.
  • After deployment, the client extension will be available on the Liferay server. You can confirm that it has been properly configured from the workspace.

Batch Type Client Extension

Exporting Object Types with Liferay Batch Client Extensions

To import or export data in Liferay, Batch client extensions are used. Batch type client extensions work with Liferay’s batch engine framework to provide data entry to your Liferay server instance.

Step 1: Create a batch type client-extension in Liferay Workspace

  • Create a directory with a valid name for your batch type client extension inside client-extensions directory of your workspace.
  • At root level of your batch client extension directory create another directory called batch and client-extension.yaml file.


 

Step 2: Define object-definition.batch-engine-data.json file

  • Define the object-definition.batch-engine-data.json file in batch directory.
  • To export object types or other data using Liferay Batch Client Extensions, you must adhere to the correct file naming conventions. These files should be in JSON format and must use the *.batch-engine.json extension to function properly with the batch engine.
  • Similarly, if you're exporting a Picklist, the file name should be list-type-definition.batch-engine-data.json. The content of each file will vary depending on what you are exporting.

Step 3: Placing Your Object JSON Content

{
  "configuration": {
    "className": "com.liferay.object.admin.rest.dto.v1_0.ObjectDefinition",
    "parameters": {
      "containsHeaders": "true",
      "createStrategy": "UPSERT",
      "importStrategy": "ON_ERROR_FAIL",
      "updateStrategy": "UPDATE"
    },
    "taskItemDelegateName": "DEFAULT"
  },
  "items": [
    {"enableComments":false,"objectRelationships":[],"enableCategorization":true,"accountEntryRestrictedObjectFieldName":"","objectActions":[],"accountEntryRestricted":false,"externalReferenceCode":"additional_payment_detail","objectFields":[{"indexed":false,"objectFieldSettings":[],"readOnly":"true","DBType":"String","label":{"en_US":"Author"},"type":"String","required":false,"externalReferenceCode":"be0b3aa2-a3a8-df0e-28ba-d316719fae39","indexedAsKeyword":false,"system":true,"indexedLanguageId":"","name":"creator","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":false,"objectFieldSettings":[],"readOnly":"true","DBType":"Date","label":{"en_US":"Create Date"},"type":"Date","required":false,"externalReferenceCode":"2f4a60a9-c79f-0a24-667c-b1a11856aadb","indexedAsKeyword":false,"system":true,"indexedLanguageId":"","name":"createDate","state":false,"businessType":"Date","readOnlyConditionExpression":""},{"indexed":false,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"External Reference Code"},"type":"String","required":false,"externalReferenceCode":"b1684cfc-0fa0-2ea5-dc9e-c55fab1c83d1","indexedAsKeyword":false,"system":true,"indexedLanguageId":"","name":"externalReferenceCode","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"true","DBType":"Long","label":{"en_US":"ID"},"type":"Long","required":false,"externalReferenceCode":"82bad67a-f874-8519-0d96-cf0bafb6a692","indexedAsKeyword":true,"system":true,"indexedLanguageId":"","name":"id","state":false,"businessType":"LongInteger","readOnlyConditionExpression":""},{"indexed":false,"objectFieldSettings":[],"readOnly":"true","DBType":"Date","label":{"en_US":"Modified Date"},"type":"Date","required":false,"externalReferenceCode":"69d236c8-4076-13f4-9b51-d2034dfcedd4","indexedAsKeyword":false,"system":true,"indexedLanguageId":"","name":"modifiedDate","state":false,"businessType":"Date","readOnlyConditionExpression":""},{"indexed":false,"objectFieldSettings":[],"readOnly":"true","DBType":"String","label":{"en_US":"Status"},"type":"String","required":false,"externalReferenceCode":"ef9d806b-305d-b21b-dc58-b57276d31222","indexedAsKeyword":false,"system":true,"indexedLanguageId":"","name":"status","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf1"},"type":"String","required":false,"externalReferenceCode":"d134a1e6-42ac-bbf5-455e-be2fd9c096ec","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf1","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf2"},"type":"String","required":false,"externalReferenceCode":"5f627b10-48f0-cd8d-685f-205e32bd51ea","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf2","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf3"},"type":"String","required":false,"externalReferenceCode":"268e76af-200b-2e10-136f-e5e90c56bb1a","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf3","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf4"},"type":"String","required":false,"externalReferenceCode":"13e9f565-9cfb-7603-763f-db84db99195a","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf4","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf5"},"type":"String","required":false,"externalReferenceCode":"bba7db4f-75e5-9786-3e76-535095f94b28","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf5","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf6"},"type":"String","required":false,"externalReferenceCode":"46cbf6ea-5b48-2403-96ea-d71175962e47","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf6","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf7"},"type":"String","required":false,"externalReferenceCode":"46d3eb83-b3ad-2dd6-b15c-e5a0cf778569","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf7","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf8"},"type":"String","required":false,"externalReferenceCode":"66acbf75-484e-49ce-940f-e337073826fa","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf8","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"udf9"},"type":"String","required":false,"externalReferenceCode":"e4a8ce65-13a7-77e8-cd35-4c17b2949944","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf9","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Udf10"},"type":"String","required":false,"externalReferenceCode":"e2473408-afcf-ca71-90be-756849722e98","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"udf10","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"Error"},"type":"String","required":false,"externalReferenceCode":"84f5bd5a-26ba-5bad-7c8e-ee03e5840679","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"error","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"ErrorText"},"type":"String","required":false,"externalReferenceCode":"2340c932-8d6f-c872-8c6e-4d523249dc32","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"errorText","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"correlationId"},"type":"String","required":false,"externalReferenceCode":"26cb59cd-60cd-0d3a-6db2-3aa761dc4715","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"correlationId","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"TransactionStatus"},"type":"String","required":false,"externalReferenceCode":"3a292192-c14b-8762-c270-6d9841e1fc06","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"transactionStatus","state":false,"businessType":"Text","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"Long","label":{"en_US":"Payment Detail Id"},"type":"Long","required":false,"externalReferenceCode":"756dd90c-60a2-00d4-099b-c4855ab9272d","indexedAsKeyword":false,"system":false,"indexedLanguageId":"","name":"paymentDetailId","state":false,"businessType":"LongInteger","readOnlyConditionExpression":""},{"indexed":true,"objectFieldSettings":[],"readOnly":"false","DBType":"String","label":{"en_US":"TrackId"},"type":"String","required":false,"externalReferenceCode":"845a489b-5836-b388-cea6-122804f9a6f6","indexedAsKeyword":false,"system":false,"indexedLanguageId":"en_US","name":"trackId","state":false,"businessType":"Text","readOnlyConditionExpression":""}],"restContextPath":"/o/c/additionalpaymentdetails","scope":"site","portlet":true,"parameterRequired":false,"enableObjectEntryHistory":false,"titleObjectFieldName":"id","objectValidationRules":[],"active":true,"defaultLanguageId":"en_US","label":{"en_US":"AdditionalPaymentDetail"},"panelCategoryKey":"site_administration.content","pluralLabel":{"en_US":"AdditionalPaymentDetails"},"objectLayouts":[],"system":false,"objectViews":[],"name":"AdditionalPaymentDetail","actions":{"permissions":{},"get":{},"update":{},"delete":{}},"status":{"label_i18n":"Approved","code":0,"label":"approved"}}
  ]
}

  • In the items section of the object-definition.batch-engine-data.json file, we will define the specific object content being imported. This section should include all relevant object details from your Liferay instance, such as externalReferenceCode, objectFields, and other key metadata. Ensure that each object entry contains all the necessary attributes to ensure proper data import.
  • We can place multiple object json content, wrapping each object JSON with {} and separated by (,) comma

Step 4: Define the client-extension.yaml file

assemble:
  - from: batch
    into: batch
stpl-object-exporter:
  name: STPL ObjectExporter
  oAuthApplicationHeadlessServer: liferay-sample-batch-oauth-application-headless-server
  type: batch
liferay-sample-batch-oauth-application-headless-server:
  .serviceAddress: localhost:8080
  .serviceScheme: http
  name: Liferay Sample OAuth Application Headless Server
  scopes:
    - Liferay.Headless.Batch.Engine.everything
    - Liferay.Object.Admin.REST.everything
  type: oAuthApplicationHeadlessServer

  • Create the client-extension.yaml file at the root level of your application project

client-extension.yaml file explaination

  • type indicates the type of client-extension, which is basically batchType processing client-extension
  • ServiceAddress indicates the address of our liferay server with port number.
  • serviceScheme defines the communication protocol used to connect to the service. Here, it is set to http.
  • oAuthApplicationHeadlessServer This points to the OAuth application used for authentication with the headless server. It connects to the liferay-sample-batch-oauth-application-headless-server configuration.

Step 5: Build and Deploy

  • Follow the same process we did to Custom Element type client extension to build and deploy the batch type client extension.
  • Open the terminal and navigate to your client-extensions/batch-object-export and enter the following command to build your client-extension.
  • ../../gradlew clean build
  • After successfully build you will find a zip file named as batch-object-export.zip.
  • To deploy the client extension, copy the batch-object-export.zip file and paste it into the deploy directory of the Liferay bundle.

Conclusion

In this blog, we explored how to create and deploy two distinct types of Liferay client extensions: Custom Elements and Batch Types. Custom Elements utilize modern web frameworks like React to extend Liferay’s user interface, allowing for dynamic, reusable front-end components that can be seamlessly integrated. On the other hand, Batch Types are designed to automate backend operations such as data export/import, leveraging Liferay's batch engine for streamlined data management. While each serves a different function, both types can be built and deployed efficiently by configuring the client-extension.yaml file and running simple Gradle commands. Together, they offer a flexible, scalable approach to enhancing Liferay’s capabilities without modifying its core architecture.

Let's connect to discuss how we can help to unlock the full potential of your digital platform with Liferay development services! For free consultation contact us today!

Contact us

For Your Business Requirements

Text to Identify Refresh CAPTCHA
Background Image Close Button

2 - 4 October 2024

Hall: 10, Booth: #B8 Brussels, Belgium