RSS Amplifier

Hello there. on xrazis · Haris Razis · Aug 1, 2025

Liferay Client Extensions – a breath of fresh air

0
Sign in to vote or save

Haris Razis · xrazis.com

Standards are good

The most popular way to incorporate a frontend framework to a Liferay application was to create a Liferay widget. That widget would be a module that had it’s own Liferay specific bundler with some idiomatic code that produced an OSGi jar, ready to be deployed to the Portal. That bundler was liferay-npm-bundler and it was the stuff of nightmares for me.

Fast forward a couple of DXP versions and we now have Client Extensions. CX’s are self contained units of code, running independently from the Liferay core, that extend DXP’s capabilities. They are decoupled from the portal and can be developed standalone. When it’s time to bring them over to Liferay, a yaml configuration file and some small modifications are all that’s needed. The distributable is a compiled Liferay Universal File Format Archive (LUFFA) that is a deployable zip file.

How did they manage such abstraction? Standards*. By using technologies like web components they have decoupled them from the Liferay core.

*OSGi is a standard already used for modules by Liferay, but is tightly coupled with the Liferay core.

What can you build with Client Extensions?

Well, many things. A microfrontend ${NEW_HOT_JS_FRAMEWORK} application, an importer/exporter of your very specific data, or a new endpoint. There are four types of CX’s:

  1. Frontend; anything that has to do with the presentation layer.
  2. Microservices; they communicate with the server through OAuth 2.0 and leverage Liferay API’s.
  3. Batch; importing and exporting of large datasets.
  4. Configuration, configuration as code.

The code for the following examples can be found on my Forgejo instance

A simple frontend CX

Let’s start simple by porting a Vite vanilla JS boilerplate. Installation instructions can be seen on the Vite website . There are some modifications we need to make to the default project in order for it to be considered a client extension:

Add the custom HTML element

Replace the root div with a custom element.

<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite App</title>
  </head>
  <body>
    <my-very-custom-element id="app"></my-very-custom-element>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
Change the Vite configuration

Give generated files a static name. Keep in mind that this will disable cache busting. The static links will be needed in the Client Extension configuration.

export default {
  build: {
    rollupOptions: {
      output: {
        entryFileNames: `assets/my-very-custom-element.js`,
        assetFileNames: `assets/my-very-custom-element.css`,
      },
    },
  },
};
Add client-extension.yaml
assemble:
  - from: dist
    into: static
my-very-custom-element:
  cssURLs:
    - /assets/my-very-custom-element.css
  friendlyURLMapping: my-very-custom-element
  htmlElementName: my-very-custom-element
  instanceable: false
  name: My very custom element
  portletCategoryName: category.client-extensions
  type: customElement
  urls:
    - /assets/my-very-custom-element.js
  useESM: true

Client extension configuration explained:

  • The assemble/from path is where the distributables are placed after running npm build. That will be the source of your CX. The assemble/into path is where the distributables will be placed for the for the CX build process.
  • cssURLs and urls are the URLs of your individual CSS and JS files. Why you need to explicitly set them? Because Liferay appends them to the page, and if you happen to use a CX more than once there is no need for duplication of a resource. That’s where instanceable comes in use; if the CX is to be used more than once in the page.
  • friendlyURLMapping and htmlElementName should match the name of the custom HTML element.

Building and deploying

The nice thing is that even with these modifications the project can be viewed locally as previously with npm run dev. This makes development faster and is a huge improvement compared to what we had before. To build it for Liferay, you can use blade gw buildClientExtensionZip or gradlew buildClientExtensionZip. Your file tree should look something like this:

.
├── build
│   └── liferay-client-extension-build
│       ├── Dockerfile
│       ├── LCP.json
│       ├── my-very-custom-element.client-extension-config.json
│       ├── static
│       │   ├── assets
│       │   │   ├── my-very-custom-element.css
│       │   │   └── my-very-custom-element.js
│       │   ├── index.html
│       │   └── vite.svg
│       └── WEB-INF
│           └── liferay-plugin-package.properties
├── client-extension.yaml
├── dist
│   ├── assets
│   │   ├── my-very-custom-element.css
│   │   └── my-very-custom-element.js
│   ├── index.html
│   ├── my-very-custom-element.zip
│   └── vite.svg
├── index.html
├── node_modules
├── package.json
├── package-lock.json
├── public
│   └── vite.svg
├── README.md
├── src
│   ├── counter.js
│   ├── javascript.svg
│   ├── main.js
│   └── style.css
└── vite.config.js

The build/liferay-client-extension-build is the directory gradlew uses for the build process. There we can see cloud specific files like the LCP.json and the Dockerfile. We can also inspect the *.client-extension.config.json file and find the baseURL. That will be useful for any images used, as they do not reside in the same path when the app is imported into Liferay. The zip placed in the dist directory is the compressed build directory.

Deploying differs from environment to environment.

  • For on-prem it is a simple as placing the zip in the deploy folder.
  • For SaaS use the lcp cli to upload the CX.
  • For PaaS an additional pipeline needs to be created. The new pipeline will not affect the main DXP pipeline, and there should be no downtime.

Detailed instructions are on Liferay Learn . When client extensions are deployed on SaaS or PaaS they are standalone services and not hosted by DXP.

Resources

Read the original on xrazis.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.