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 component | Recommended replacement |
|---|---|
portal_normal.ftl | Master Pages, Page Templates, and supported page configuration options |
custom.css / SCSS | Theme CSS Client Extension |
main.js / global JavaScript | Global JS Client Extension or another suitable JS Client Extension type |
favicon.ico | Theme Favicon Client Extension |
| Theme SVG sprite maps | Theme 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: themeCSSUnderstanding 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.clayRTLURLandmainRTLURL— 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.scssUpdate 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 resourcesThe 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:
Open the target page in Liferay DXP.
Click Edit at the top of the page.
Open the Page Design Options panel in the sidebar.
Open the page configuration and navigate to the Look and Feel section.
Locate the Theme CSS Client Extension section and click Add.
Select the deployed Theme CSS Client Extension, such as
Liferay Sample Theme CSS 4.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.*.jsCheck 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'sassets/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.jsBefore 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:
The JavaScript resource loads successfully.
There are no JavaScript errors in the console.
The required event listeners and custom behaviors work correctly.
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/htmlthe 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
Post a Comment