Skip to main content

Migrating a Legacy Liferay DXP Theme to Client Extensions

Migrating a legacy WAR-based Liferay theme to modern Theme CSS and JavaScript Client Extensions is an important step toward a more modular, maintainable, and upgrade-friendly Liferay DXP architecture.

Traditional themes package styling, JavaScript, templates, and static resources together in a deployable WAR. With client extensions, you can separate styling and JavaScript from the core portal runtime, simplify deployments, and reduce dependencies on traditional theme modules.

This guide explains how to analyze a legacy theme, migrate its components to client extensions, reorganize project files, configure the required YAML files, and apply the new extensions to Liferay pages.

1. Understand the Traditional Theme Components

Before starting the migration, identify the components used by your existing theme and determine their modern Liferay equivalents.

Legacy theme componentRecommended replacement
portal_normal.ftlMaster Pages, Page Templates, and supported page configuration options
custom.css / SCSSTheme CSS Client Extension
main.js / global JavaScriptGlobal JS Client Extension or another suitable JS Client Extension type
favicon.icoTheme Favicon Client Extension
Theme SVG sprite mapsTheme Sprite Map Client Extension

Important: Not every theme component has a direct one-to-one replacement. Review custom FreeMarker logic, navigation behavior, JavaScript dependencies, and theme-specific functionality before migrating. Some functionality may need to be redesigned using Liferay's page editor, configuration options, or other extension points.

2. Migrate the Theme CSS to a Client Extension

A Theme CSS Client Extension allows you to apply custom CSS to Liferay pages without deploying a traditional WAR-based theme.

You can use the official Liferay sample as a reference:

Theme CSS Client Extension – Official Liferay Sample

Step 1: Understand client-extension.yaml

The client-extension.yaml file defines the client extension, its properties, and how its static resources are assembled into the final bundle.

A simplified example is shown below. Adapt the source and output paths to match your workspace and build configuration.

assemble:
    - from: build/buildTheme/img
      into: static/img

    - from: build/buildTheme/images
      into: static/images

    - from: src/fonts
      into: static/fonts

    - from: src/css/lib
      into: static/css/lib

liferay-sample-theme-css-1:
    clayRTLURL: css/clay_rtl.css
    clayURL: css/clay.css
    frontendTokenDefinitionJSON: src/frontend-token-definition.json
    mainRTLURL: css/main_rtl.css
    mainURL: css/main.css
    name: Liferay Sample Theme CSS 1
    type: themeCSS

Understanding the assemble section

The assemble section specifies which resources should be copied into the generated client extension bundle.

  • Images: Copies generated images into the bundle's static resource directory.

  • Fonts: Includes custom fonts that are referenced by your CSS.

  • CSS libraries: Packages additional stylesheets, such as third-party libraries, when required by the project.

The source directories must exist, and the expected build artifacts must be generated before assembly. Adjust these paths to match your actual project structure.

Understanding the Theme CSS Client Extension definition

  • type: themeCSS — Registers the extension as a Theme CSS Client Extension.

  • name — Specifies the display name used to identify the extension in Liferay's page configuration.

  • clayURL — Identifies the compiled Clay stylesheet.

  • mainURL — Identifies the compiled main stylesheet containing your theme styles.

  • clayRTLURL and mainRTLURL — Specify the corresponding right-to-left stylesheets, where applicable.

  • frontendTokenDefinitionJSON — References the frontend token definitions used to integrate supported design tokens with Liferay's styling configuration.

The configured stylesheet paths must match the files generated and packaged by your build process. If a configured CSS file is missing, or the URL points to an incorrect location, the browser may fail to load the stylesheet.

3. Understand the Project Structure Changes

A major part of the migration is reorganizing your existing theme files to fit the client extension's build and deployment model.

Legacy theme structure

Traditional Liferay themes commonly place their source files under a structure such as:

src/main/webapp/
├── css/
│   ├── custom.css
│   ├── main.scss
│   └── _custom.scss
├── images/
├── js/
├── templates/
│   └── portal_normal.ftl
└── WEB-INF/

Your actual legacy structure may differ depending on the theme generator, Liferay version, and customizations.

Legacy theme structure screenshot:



Client extension structure

A Theme CSS Client Extension separates styling and static assets from the traditional theme WAR. A typical project may look like this:

liferay-sample-theme-css-1/
├── client-extension.yaml
├── src/
│   ├── css/
│   │   ├── _custom.scss
│   │   └── lib/
│   ├── fonts/
│   └── frontend-token-definition.json
└── build/
    └── buildTheme/
        ├── css/
        ├── img/
        └── images/

The exact directory structure depends on the sample and build scripts you use. The important point is to keep source files, generated build artifacts, and packaged static resources aligned with the configuration.

Upgraded client extension structure screenshot:



4. Migrate the CSS and SCSS Files

Follow these steps to migrate your existing styling.

Step 1: Move your custom styles

Identify the CSS and SCSS files that contain your theme-specific styling. Move or adapt those files into the appropriate source directories of the Theme CSS Client Extension.

For example:

Legacy theme
    └── custom.scss
            ↓
Theme CSS Client Extension
    └── src/css/_custom.scss

Update relative paths to images, fonts, and other resources as needed.

Step 2: Consolidate your SCSS customizations

Move your custom styling rules into _custom.scss if that is the convention used by your selected sample's build process.

Avoid blindly copying every legacy file. Review the existing imports, variables, mixins, and dependencies to ensure they are compatible with the new build configuration.

If the sample generates main.css and clay.css, keep the configured output names consistent with the generated files. Do not remove main.scss or other entry-point files unless your build setup no longer requires them.

Step 3: Build the stylesheets

In a typical sample-based setup, _custom.scss contains your custom rules, while the build process compiles the source files into CSS artifacts such as main.css and clay.css.

The relationship is:

_custom.scss
     |
     v
SCSS build process
     |
     v
Compiled CSS files
     |
     v
Client extension assembly
     |
     v
Packaged static resources

The YAML file references the generated CSS files; it does not compile SCSS by itself.

Troubleshooting tip: If main.css or clay.css is missing after the build, verify the build scripts, SCSS imports, output directories, and assemble configuration before deploying the extension.

5. Apply the Theme CSS Client Extension to a Page

Once the Theme CSS Client Extension has been built and deployed, you can apply it to a Liferay page.

Follow these steps:

  1. Open the target page in Liferay DXP.

  2. Click Edit at the top of the page.

  3. Open the Page Design Options panel in the sidebar.

  4. Open the page configuration and navigate to the Look and Feel section.

  5. Locate the Theme CSS Client Extension section and click Add.

  6. Select the deployed Theme CSS Client Extension, such as Liferay Sample Theme CSS 4.

  7. Save the configuration and verify the page styling.

Screenshot: Theme CSS Client Extension configuration



Screenshot: Selecting the deployed extension



After applying the extension, inspect the page to ensure that your custom styles, fonts, images, responsive layouts, and right-to-left styles behave as expected.

6. Migrate Theme JavaScript to a JavaScript Client Extension

Legacy themes often contain custom JavaScript in files such as main.js, custom.js, or other theme-specific scripts.

Depending on how the scripts are used, they can be migrated to a Global JS Client Extension or another suitable JavaScript Client Extension.

A Global JS Client Extension is appropriate when the script must run across portal pages. For functionality that should be limited to a particular page or component, consider whether another extension approach would be more suitable.

Official reference:

Global JS Client Extension – Liferay Sample

Step 1: Configure client-extension.yaml

The following is an illustrative configuration based on the sample's assembly approach:

assemble:
    - from: assets
      hashify: global.js
      into: static

liferay-sample-global-js-1:
    name: Liferay Sample Global JS 1
    type: globalJS
    url: global.*.js

Check the sample for your target Liferay version before adopting the configuration, especially the supported assembly properties and URL conventions.

Understanding the configuration

  • from: assets — Uses the project's assets/ directory as the source.

  • hashify: global.js — Generates a hashified filename for the JavaScript resource, supporting cache busting.

  • into: static — Places the processed resource in the output bundle's static directory.

  • name — Specifies the extension's display name.

  • type: globalJS — Registers the extension as a Global JS Client Extension.

  • url: global.*.js — Matches the hashified JavaScript filename produced by the assembly process.

The assembly configuration, generated filename, and URL pattern must agree. Otherwise, the JavaScript resource may not load correctly.

Step 2: Move your JavaScript

Copy the required custom JavaScript into the configured source file.

For example:

Legacy theme
    └── main.js
            ↓
Global JS Client Extension
    └── assets/
        └── global.js

Before moving the scripts, review their dependencies and execution timing.

Pay particular attention to:

  • DOM elements that may not exist when the script executes.

  • Event listeners that could be registered multiple times.

  • Dependencies on jQuery, Clay, or other libraries.

  • Hard-coded theme resource paths.

  • Scripts that rely on legacy FreeMarker templates or theme-specific markup.

Test the migrated JavaScript on pages with different layouts and components. A script that worked in the legacy theme may need adjustments when executed through a client extension.

Step 3: Deploy and verify the JavaScript extension

Build and deploy the Global JS Client Extension using your workspace's normal client extension deployment process.

Open the browser's developer tools and verify that:

  1. The JavaScript resource loads successfully.

  2. There are no JavaScript errors in the console.

  3. The required event listeners and custom behaviors work correctly.

  4. The script does not interfere with page editing or other Liferay functionality.

7. Common Migration Issues and Troubleshooting

CSS is not applied after deployment

Check the generated CSS files and confirm that mainURL, clayURL, and their RTL equivalents point to valid packaged resources.

If the browser reports an error such as:

Refused to apply style because its MIME type is text/html

the requested CSS URL may be returning an HTML error page instead of a stylesheet.

Investigate the following:

  • Confirm that the CSS file exists in the generated client extension bundle.

  • Verify the resource URL and assembly paths.

  • Open the stylesheet URL directly in the browser and inspect the response.

  • Check for HTTP 404 errors, incorrect resource mappings, or deployment problems.

  • Confirm that the deployed extension matches the configuration you are testing.

Images and fonts are missing

Check the relative URLs in your CSS and ensure that the corresponding resources are copied into the packaged static directories. Update any paths that previously depended on the legacy theme's WAR structure.

Right-to-left styling is incorrect

Verify that the RTL stylesheets are generated, packaged, and configured correctly. Test Arabic pages independently rather than assuming that the left-to-right stylesheet will work without adjustments.

JavaScript does not execute

Check the browser console and network tab, verify the configured URL pattern, and ensure the generated resource is included in the deployed bundle. Also confirm that the script is compatible with the page's DOM and JavaScript dependencies.

8. Final Migration Checklist

Before considering the migration complete, verify the following:

  • All required legacy CSS and SCSS rules have been migrated.
  • SCSS compiles successfully into the expected CSS files.
  • The client extension YAML paths match the generated artifacts.
  • Images, fonts, and third-party CSS resources load correctly.
  • RTL styles have been tested where applicable.
  • Legacy FreeMarker templates have been reviewed and replaced or redesigned where necessary.
  • JavaScript has been migrated to an appropriate client extension.
  • The extension has been applied to the intended pages.
  • Browser console and network errors have been resolved.
  • The migrated pages have been tested across relevant layouts and user journeys.

Conclusion

Migrating from a traditional Liferay theme to Client Extensions helps separate presentation assets from the legacy WAR deployment model and supports a more modular frontend architecture.

By migrating CSS and JavaScript independently, reorganizing the project structure, and validating the generated resources, you can make your Liferay DXP implementation easier to maintain and prepare it for future upgrades.

The key is to treat this as an architectural migration rather than simply copying files. Review legacy templates, dependencies, resource paths, and custom behaviors carefully, and test each component before retiring the original theme.

Official references

Comments

Popular posts from this blog

Liferay 7 overriding MVC Commands

MVCActionCommand : An interface that allows the portlet to process a particular action request. By extending  BaseMVCActionCommand , we can customize processAction method. MVCRenderCommand : An interface that handles the render phase of the portlet. By   extending  BaseMVCRenderCommand , we can customize render method. MVCResourceCommand : An interface that allows the portlet to serve a resource. By  extending  BaseMVCResourceCommand , we can customize serveResource method. Now , we will see how to customize processAction method. Step 1: Initially, Create one module as mentioned in Liferay 7 document. Step 2: In controller class, write the component property depends on which modules you are customizing, for eg., if we customize create account page, write as following @Component( property = { "javax.portlet.name=com_liferay_login_web_portlet_FastLoginPortlet", "javax.portlet.name=com_liferay_l...

Embed portlet in liferay 7 FTL theme

To include portlet in liferay 7 ftl theme , use the code as follows. <@liferay_portlet["runtime"] defaultPreferences="${freeMarkerPortletPreferences}" portletProviderAction=portletProviderAction.VIEW instanceId="blablabla1" portletName="com_liferay_site_navigation_directory_web_portlet_SitesDirectoryPortlet" /> where , instanceId="blablabla1" have to be given only for instanceable portlet, "com_liferay_site_navigation_directory_web_portlet_SitesDirectoryPortlet" is portletId.