Skip to main content

Creating OSGi Modules and Sharing Packages

  Overview This guide walks through creating two OSGi modules in Liferay 7.4 and sharing a Java package between them using the standard API + Implementation pattern. This is the most common and correct approach for exposing reusable services in a Liferay-based architecture. In OSGi, a bundle (module) does not expose its internal packages by default — everything is private unless explicitly exported. To share code between modules, the standard practice is to split functionality into: · An API module — contains interfaces/DTOs and exports the package · A Service (Implementation) module — implements the interface, imports the package, and registers the implementation as an OSGi component/service The consuming module then simply imports the API package and looks up the service using @Reference injection. Step 1: Create the API Module Using Liferay Workspace + Blade CLI, generate a new API module: blade create -t api -p com.sgc.greeting greeting-api This generates a Gradle module unde...

Client Extensions-Liferay 7.4

 


Introduction to Client Extensions


Liferay 7.4 introduced Client Extensions (CX) as a paradigm shift in how developers extend and customize the Liferay DXP platform. Unlike traditional OSGi modules that require deployment inside the Liferay runtime, Client Extensions run outside the portal — making them language-agnostic, independently deployable, and significantly easier to maintain.


This guide walks you through building a fully functional React-based Client Extension (Custom Element) that integrates with Liferay's Headless Delivery API, and then covers every major Client Extension type available in Liferay 7.4.


Why Client Extensions?

Client Extensions decouple frontend and backend concerns. You can build in React, Vue, Angular, or plain JavaScript — without touching the portal's OSGi container. Deployments become simpler, CI/CD pipelines become faster, and teams can work independently.

Client Extension Types at a Glance

Extension Type

Description

Use Case

Custom Element

Renders a Web Component / React app on a page

Dashboards, Widgets

Remote App

Embeds external iFrame apps inside Liferay

Legacy SPA integration

Theme CSS

Overrides Liferay's default CSS theming

Brand styling

Frontend Token

Defines CSS design tokens for Style Books

Design system

Global CSS

Injects CSS globally across all pages

Global overrides

Global JS

Injects JavaScript globally across all pages

Analytics, tracking

Editor Config

Configures the CKEditor instance in Liferay

Rich text customization

Workflow Action

Triggers external HTTP actions from workflow

Integrations

Object Action

Fires HTTP calls on Liferay Object events

Microservice triggers

OAuth Headless Server

Registers OAuth2 app for server-to-server

Backend integrations

Part 1: Building a React Custom Element

In this section, we create a production-ready Invoice Widget using React 18 and register it as a Web Component that Liferay can render on any page.


Step 1

Create the Project Structure

Start by initializing a Liferay Workspace with Blade CLI targeting version 7.4:

blade init -v 7.4 my-workspace

cd my-workspace

Navigate into the client-extensions folder and scaffold a new React application:

cd client-extensions

npx create-react-app invoice-react-widget

cd invoice-react-widget


Step 2

Modify src/index.js — The Web Component Wrapper

Liferay renders Client Extensions as HTML Custom Elements. Replace the entire contents of src/index.js with the following Web Component wrapper that bootstraps your React app:


import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
class InvoiceWidget extends HTMLElement {
connectedCallback() {
// Collect Liferay Context (Auth Token, Site ID, etc.)
const props = {
authToken: window.Liferay?.authToken || '',
siteId: window.Liferay?.ThemeDisplay?.getGroupId() || '',
elementId: this.getAttribute('id')
};
this._root = createRoot(this);
this._root.render(<App {...props} />);
}
disconnectedCallback() {
this._root.unmount();
}
}
const ELEMENT_NAME = 'invoice-react-widget';
if (!customElements.get(ELEMENT_NAME)) {
customElements.define(ELEMENT_NAME, InvoiceWidget);
}


Step 3

Create App.js — The Headless API Consumer


This component fetches data from Liferay's Headless Delivery API using the auth token provided by the Web Component wrapper:

import React, { useEffect, useState } from 'react';



function App({ authToken }) {

const [invoices, setInvoices] = useState([]);

useEffect(() => {

// Fetching from Liferay Headless Delivery API

fetch('/o/headless-delivery/v1.0/sites/20121/blog-postings', {

headers: { 'x-csrf-token': authToken }

})

.then(res => res.json())

.then(data => setInvoices(data.items || []))

.catch(err => console.error(err));

}, [authToken]);

return (

<div style={{ padding: '20px', border: '1px solid #ccc' }}>

<h2>Invoice Widget</h2>

{invoices.length === 0 ? <p>No data found.</p> : (

<ul>

{invoices.map(item => (

<li key={item.id}>{item.headline || item.id}</li>

))}

</ul>

)}

</div>

);

}



export default App;


Note:
Replace /sites/20121/ with your actual Liferay Site ID, which can be found in Site Settings → Site Configuration.

Step 4

Create client-extension.yaml

This YAML file is the heart of your Client Extension. Place it in the root of your invoice-react-widget folder. It tells Liferay how to register and serve your extension:

assemble:

- from: build/static

into: static

invoice-react-widget:

name: Invoice React Widget

type: customElement

htmlElementName: invoice-react-widget

portletCategoryName: category.client-extensions

instanceable: true

useESM: true

urls:

- js/main.*.js

cssURLs:

- css/main.*.css


Key YAML Properties Explained

type: customElement — Tells Liferay this is a Web Component • htmlElementName — Must match the tag name defined in index.js • instanceable: true — Allows multiple instances on one page • useESM: true — Uses ES Modules for better tree-shaking • urls — Glob pattern to match the React build output JS file


Step 5

Build and Deploy

5a. Build the React Application

Inside the invoice-react-widget directory, run the React production build:


npm run build

5b. Build the Client Extension Package

From your Liferay Workspace root, trigger the Gradle build for your extension:


gradlew :modules:client-extensions:invoice-react-widget:build

5c. Force Rebuild (if changes don't reflect)

If your latest changes are not appearing, clean the build cache and rebuild with refreshed dependencies:


# Full clean rebuild with dependency refresh

gradlew clean :client-extensions:invoice-react-api-widget:build --refresh-dependencies



# Skip test cases to speed up the build

gradlew :client-extensions:invoice-react-api-widget:build -x test

5d. Deploy to Liferay

Copy the generated .zip file to your Liferay bundle's osgiclient-extensions/ directory:


# Option 1: Manual copy

# Copy from: client-extensions/invoice-react-widget/dist/*.zip

# Copy to: {liferay-home}/osgi/client-extensions/



# Option 2: Use Gradle deploy task

gradlew :client-extensions:react-invoice-widget:deploy


Step 6

Add the Widget to a Page


Follow these steps to add your newly deployed Client Extension widget to a Liferay page:

  1. Log in to your Liferay portal as an Administrator.

  2. Go to Applications (Global Menu) → Custom Apps → Client Extensions.

  3. Verify that "Invoice React Widget" shows status as Published.

  4. Navigate to a Site Page and click Edit (the pencil icon in the top bar).

  5. Open the Widgets panel in the right sidebar.

  6. Search for "Invoice React Widget" under the Client Extensions category.

  7. Drag and drop the widget onto your page layout.

  8. Click Publish to make the changes live.

Note: If the widget does not appear in the search, ensure the .zip was correctly placed in osgi/client-extensions/ and Liferay's auto-deploy has processed it (watch the console log for deployment confirmation).


Part 2: All Client Extension Types in Liferay 7.4

Beyond Custom Elements, Liferay 7.4 provides a full suite of Client Extension types. Below is a comprehensive guide to each type with project structure, YAML configuration, and deployment steps.

1. Theme CSS Client Extension

Override Liferay's default theme styling without building a full theme. This is the recommended approach for brand customization in Liferay 7.4.

Project Structure

client-extensions/

└── my-theme-css/

├── client-extension.yaml

└── src/

├── clay.css ← Override Clay/Bootstrap variables

└── main.css ← Custom styles

client-extension.yaml

assemble:

- from: build

into: static



my-theme-css:

name: My Brand Theme CSS

type: themeCSS

clayURL: css/clay.css

mainURL: css/main.css


Build & Deploy

gradlew :modules:client-extensions:my-theme-css:build

gradlew :modules:client-extensions:my-theme-css:deploy

Note: After deployment, go to Site Builder → Pages → Design → Theme and select your custom Theme CSS from the dropdown.

2. Frontend Token Definition Client Extension

Define CSS custom properties (design tokens) that power Liferay Style Books. This enables non-developers to adjust brand colors, typography, and spacing through the Style Book editor UI.

client-extension.yaml

assemble:

- from: build

into: static

my-frontend-tokens:

name: My Design Tokens

type: frontendTokenDefinition

frontendTokenDefinitionURL: token-definition.json

token-definition.json (sample)

{

"frontendTokenCategories": [

{

"frontendTokenSets": [

{

"frontendTokens": [

{

"defaultValue": "#1B4F8E",

"editorType": "ColorPicker",

"mappings": [{"type": "cssVariable", "value": "--primary-color"}],

"label": "Primary Color",

"name": "primaryColor",

"type": "String"

}

],

"label": "Brand Colors",

"name": "brandColors"

}

],

"label": "Brand",

"name": "brand"

}

]

}

3. Global CSS Client Extension

Inject CSS across every page of your Liferay site. Useful for third-party widget styles, global resets, or utility classes.

client-extension.yaml

assemble:

- from: build

into: static


my-global-css:

name: Global CSS

type: globalCSS

url: css/global.css

 Note: Global CSS is applied to ALL pages sitewide. For page-specific styles, use a Custom Element that injects a <style> tag.

4. Global JavaScript Client Extension

Inject JavaScript globally across all pages. Common uses include analytics integrations (Google Tag Manager, Hotjar), chat widgets, and global event listeners.

client-extension.yaml

assemble:

- from: build

into: static


my-global-js:

name: Global Analytics JS

type: globalJS

url: js/analytics.js

scriptLocation: bottom # top | bottom

Sample analytics.js

(function() {

// Google Tag Manager initialization

window.dataLayer = window.dataLayer || [];

function gtag(){ dataLayer.push(arguments); }

gtag('js', new Date());

gtag('config', 'GTM-XXXXXXX');


// Liferay page view tracking

Liferay.on('endNavigate', function(event) {

gtag('event', 'page_view', { page_path: event.path });

});

})();

5. Object Action Client Extension

Triggers an HTTP call to an external endpoint when a Liferay Object record is created, updated, or deleted. This is the primary integration pattern for connecting Liferay Objects to microservices.

client-extension.yaml

my-object-action:

name: Invoice Created Action

type: objectAction

url: https://your-microservice.com/api/invoice-created

Configuration in Liferay UI

  1. Go to Control Panel → Objects → select your Object (e.g., Invoice).

  2. Click on the Actions tab.

  3. Add Action → select Trigger: On After Add.

  4. Set Action type to "Client Extension" and choose your registered action.

  5. Save. The webhook fires automatically on every new record.

Note: The external endpoint receives a JSON payload with the full Object record. Your microservice can then sync data to SAP, Salesforce, or any external system.

6. Workflow Action Client Extension

Fires an HTTP request to an external service when a Kaleo Workflow reaches a specific action node. Use this to trigger document approvals, send notifications, or call external APIs from workflow.

client-extension.yaml

my-workflow-action:

name: Approval Notification Action

type: workflowAction

url: https://your-notification-service.com/notify

Usage in Kaleo Workflow Designer

In the Workflow Designer (Process Builder), add an Action node and select Client Extension as the action type. Choose your registered workflow action. Liferay will POST workflow context data (asset ID, current user, status) to your endpoint.

7. OAuth Headless Server Client Extension

Registers a server-to-server OAuth2 application in Liferay, enabling backend microservices to call Liferay's Headless APIs without user interaction using the Client Credentials grant flow.

client-extension.yaml

my-oauth-headless-server:

name: Invoice Service OAuth

type: oAuthApplicationHeadlessServer

scopes:

- Liferay.Headless.Delivery.everything

Using the Token in Your Microservice

// Spring Boot example — Client Credentials Token Exchange

@Bean

public WebClient liferayWebClient() {

return WebClient.builder()

.baseUrl("http://liferay:8080")

.filter(new ServerOAuth2AuthorizedClientExchangeFilterFunction(manager, repo))

.build();

}



// Then call Liferay Headless API

webClient.get()

.uri("/o/headless-delivery/v1.0/sites/{id}/blog-postings", siteId)

.retrieve()

.bodyToMono(BlogPostingPage.class);

8. Editor Config Client Extension

Customize the CKEditor configuration used in Liferay's rich text fields — Web Content, Blog entries, and Object fields. Add custom plugins, toolbar buttons, or content filters.

client-extension.yaml

my-editor-config:

name: Custom CKEditor Config

type: editorConfigContributor

url: js/editor-config.js

editorConfigurationKeys:

- CKEDITOR

editor-config.js

window.MyEditorConfig = {

apply: function(config) {

// Add custom toolbar group

config.toolbarGroups = [

{ name: 'document', groups: ['mode', 'document', 'doctools'] },

{ name: 'basicstyles', groups: ['basicstyles', 'cleanup'] },

{ name: 'paragraph' }

];


// Set max content height

config.height = '400px';



// Restrict allowed content for security

config.allowedContent = 'p h1 h2 h3 b i u a[href]; ul ol li;';

}

};

Best Practices & Production Tips

Project Organization

  • Keep all Client Extensions in the client-extensions/ folder of your Liferay Workspace for unified Gradle builds.

  • Use a monorepo approach — one workspace, multiple extension projects, one CI/CD pipeline.

  • Version your YAML files alongside your source code in Git.

Build & CI/CD

  • Always run npm run build before the Gradle build — Gradle packages whatever is in the build/ directory.

  • Use gradlew -x test in CI pipelines to skip slow unit tests during development iterations.

  • Use --refresh-dependencies only when you suspect a cached dependency is stale, not on every build

Liferay Context & Security

  • Always pass authToken from window.Liferay.authToken and include it in x-csrf-token headers for Headless API calls.

  • Never hardcode site IDs — read them dynamically from window.Liferay.ThemeDisplay.getGroupId().

  • Use HTTPS endpoints in all Object Action and Workflow Action URLs in production.

Troubleshooting

Widget not visible in sidebar search

Ensure the .zip was deployed to osgi/client-extensions/ and Liferay's log shows a successful deployment message. Try restarting the portal if hot-deploy is not enabled.


CSS not applying

Verify the cssURLs glob pattern in client-extension.yaml matches your actual build output filename. Check browser DevTools to confirm the file is loaded.


API returning 401 Unauthorized

Confirm the authToken is passed correctly in the x-csrf-token header. Token validity is session-scoped — refresh the page to get a new token if the session has expired.


Changes not reflecting after rebuild

Run: gradlew clean :client-extensions:<your-extension>:build --refresh-dependencies to fully invalidate the Gradle cache and force a clean build.


Comments

Popular posts from this blog

service builder with crud operation in liferay

   Crud and Search opration in Liferay:      ->  Liferay use service builder techniq for Crud opration.    ->  Service builder purform by  Service.xml file.    ->  Service.xml file create table in database and also create class and diffrent method.   1)      Create service.xml file.             ->Create service.xml file in WEB-INF and write below code.             ->CODE:        < service-builder package-path = "com.test" >         < namespace > qr </ namespace >           < entity name = "Searchclass" local-service = "true"                     ...

How to create new site programmaticly in liferay with validation

Create site in liferay <%@page import="javax.portlet.PortletPreferences"%> <%@page import="com.liferay.portal.kernel.util.ParamUtil"%> <%@page import="com.liferay.portal.kernel.util.HtmlUtil"%> <%@page import="com.liferay.portal.kernel.util.StringPool"%> <%@page import="com.liferay.portal.kernel.util.UnicodeProperties"%> <%@page import="com.liferay.portal.service.LayoutSetPrototypeServiceUtil"%> <%@page import="com.liferay.portal.model.LayoutSetPrototype"%> <%@page import="com.liferay.portal.service.GroupLocalServiceUtil"%> <%@page import="java.util.List"%> <%@page import="com.liferay.portal.kernel.bean.BeanParamUtil"%> <%@page import="com.liferay.portal.theme.ThemeDisplay"%> <%@page import="com.liferay.portal.model.Group"%> <%@page import="com.liferay.portal.kernel.util.WebK...

The Ultimate Guide to Liferay DXP Performance Tuning: Speed Up Your Portal

The Ultimate Guide to Liferay DXP Performance Tuning: Speed Up Your Portal In the enterprise web space, milliseconds equal millions. Whether you are running a B2B commerce storefront, a customer support portal, or an employee intranet on Liferay DXP, slow load times will devastate your user experience and destroy your SEO rankings. Out of the box, Liferay is configured to run on almost any machine. This means its default settings are highly conservative to ensure compatibility, not maximum performance. If you are launching a production environment without tuning your server, you are leaving massive amounts of speed and scalability on the table. In this comprehensive, deep-dive guide, we are going to explore the critical layers of Liferay performance tuning. We will cover backend Java Virtual Machine (JVM) configuration, Database Connection Pooling, Elasticsearch optimization, and Frontend caching strategies. By the end of this guide, you will have a blazing-fast, enterprise-grade...