Using the Script Engine in Workflow

Liferay’s workflow engine can be scripted using Groovy scripts embedded in XML workflow definitions and run during workflow execution.

Here are the workflow scripting topics:

Adding Scripts to Workflow Nodes

Workflow scripts can be invoked from actions in these workflow node types:

  • <fork>

  • <join>

  • <state>

  • <task>

Here’s the format for an action that invokes a script:

        <![CDATA[script code goes here]]>

One common operation is to set the workflow status. For example, this script sets the workflow status to approved.

    import com.liferay.portal.kernel.workflow.WorkflowStatusManagerUtil;
    import com.liferay.portal.kernel.workflow.WorkflowConstants;

    WorkflowStatusManagerUtil.updateStatus(WorkflowConstants.getLabelStatus("approved"), workflowContext);

Predefined Variables

Some variables are common to all node types, while others are exclusively available to task nodes.

Variables Common to All Node Types

These variables are available from anywhere that you can run a workflow script:




kaleoInstanceToken (KaleoInstanceToken)

A workflow instance and corresponding instance token (the KaleoInstanceToken) are created each time a User clicks Submit for Publication.

Use the injected token to retrieve its ID by calling kaleoInstanceToken.getKaleoInstanceTokenId(). This is often passed as a method parameter in a script.


The userId returned is context dependent.

Technically, the logic works like this: if the KaleoTaskInstanceToken.getcompletionUserId() is null, check KaleoTaskInstanceToken.getUserId(). If that’s null too, call KaleoInstanceToken.getUserId(). It’s the ID of the last User to intervene in the workflow at the time the script is run. In the created node, this would be the User that clicked Submit for Publication, whereas it’s the ID of the reviewer upon exit of the review node of the Single Approver definition.

workflowContext (Map<String, Serializable>)

The workflow context contains information you can use in scripts.

The context is typically passed as a parameter, but all of the WorkflowContext’s attributes are available in the script as well. The workflow context in the script is context dependent. If a call to ExecutionContext.getWorkflowContext() comes back null, then the workflow context is obtained by KaleoInstanceModel.getWorkflowContext().

Variables Injected into Task Nodes

These variables are injected into Task Nodes:




kaleoTaskInstanceToken (KaleoTaskInstanceToken)

The task’s token itself is available in the workflow script.

Use it to get its ID, for use in other useful programmatic workflow activities, like programmatic assignment.

taskName (String) The task’s own name is accessible (returns the same as KaleoTak.getName()).

workflowTaskAssignees (List<WorkflowTaskAssignee>)

Lists the task’s assignees.

kaleoTimerInstanceToken (KaleoTimerInstanceToken)

If a task timer exists, get its ID by calling kaleoTimerInstanceToken.getKaleoTimerInstanceTokenId().

Script Example

At virtually any point in a workflow, you can use Liferay’s script engine to access workflow APIs or other Liferay APIs. Here are a few practical ways you can use workflow scripts:

  • Getting a list of Users who have a specific Role

  • Sending an email to the designated content approver with a list of people to contact if he is unable to review the content

  • Creating an alert to be displayed in the Alerts portlet for any User assigned to approve content

The following workflow script is written using Groovy and is used with a Condition Node. The script uses Liferay’s asset framework to determine an asset’s category and uses the category to determine the correct approval process automatically. If the asset is in the legal category, it is sent to the Legal Review task upon submission. Otherwise, the asset is sent to the Default Review task.

        import com.liferay.portal.kernel.util.GetterUtil;
        import com.liferay.portal.kernel.workflow.WorkflowConstants;
        import com.liferay.portal.kernel.workflow.WorkflowHandler;
        import com.liferay.portal.kernel.workflow.WorkflowHandlerRegistryUtil;
        import com.liferay.asset.kernel.model.AssetCategory;
        import com.liferay.asset.kernel.model.AssetEntry;
        import com.liferay.asset.kernel.model.AssetRenderer;
        import com.liferay.asset.kernel.model.AssetRendererFactory;
        import com.liferay.asset.kernel.service.AssetEntryLocalServiceUtil;

        import java.util.List;

        String className = (String)workflowContext.get(

        WorkflowHandler workflowHandler =

        AssetRendererFactory assetRendererFactory =

        long classPK =

        AssetRenderer assetRenderer =

        AssetEntry assetEntry = assetRendererFactory.getAssetEntry(
            assetRendererFactory.getClassName(), assetRenderer.getClassPK());

        List<AssetCategory> assetCategories = assetEntry.getCategories();

        returnValue = "Default Review";

        for (AssetCategory assetCategory : assetCategories) {
            String categoryName = assetCategory.getName();

            if (categoryName.equals("legal")) {
                returnValue = "Legal Review";



A Condition node script’s returnValue variable must be a valid transition name to determine the next task or state.

For Liferay Portal, a valid transition name is the transition’s <name> element value as entered in the XML file or in the source view of the Process Builder. For Liferay DXP, when you view the source of the definition in the Process Builder, you must instead use the value of the transition ID as specified in the transition’s <id> element.

See Crafting Workflow Definitions for links to downloadable workflow script examples.

Calling OSGi Services

Service Trackers retrieve OSGi services that are available. If the Service Tracker returns null for the service, that service is unavailable and you can do something appropriate in response.

Here’s a workflow script written in Groovy that uses a JournalArticleLocalService to get an article count:

import com.liferay.journal.model.JournalArticle;
import com.liferay.journal.service.JournalArticleLocalService;
import com.liferay.portal.scripting.groovy.internal.GroovyExecutor;

import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.util.tracker.ServiceTracker;

ServiceTracker<JournalArticleLocalService, JournalArticleLocalService> st;

try {
    Bundle bundle = FrameworkUtil.getBundle(GroovyExecutor.class);

    st = new ServiceTracker(bundle.getBundleContext(), JournalArticleLocalService.class, null);;

    JournalArticleLocalService jaService = st.waitForService(500);

    if (jaService == null) {
        _log.warn("The required service 'JournalArticleLocalService' is not available.");
    else {
        java.util.List<JournalArticle>articles = jaService.getArticles();
        if (articles != null) {
  "Article count: " + articles.size());
        } else {
  "no articles");
catch(Exception e) {
    //Handle error appropriately
finally {
    if (st != null) {

The script tracks the service using the OSGi bundle of the class that executes the script. Since a com.liferay.portal.scripting.groovy.internal.GroovyExecutor instance executes the script, the instance’s bundle is used to track the service.

Bundle bundle = FrameworkUtil.getBundle(GroovyExecutor.class);

Liferay’s Kaleo Workflow Engine and Liferay’s Script Engine makes for a powerful combination. When configuring your permissions, be aware of the potential consequences of poorly or maliciously written scripts inside a workflow definition.

Additional Information