IBM WCM: Analytics Implementation Guide | OpsBlu Docs

IBM WCM: Analytics Implementation Guide

Technical guide to implementing analytics on IBM Web Content Manager, covering DX theme modules, script portlets, WCM components, and DX API data layers.

Analytics Architecture on IBM Web Content Manager

IBM Web Content Manager (WCM) is part of IBM Digital Experience (DX), a portal-based platform built on WebSphere. Pages are composed of portlets arranged in portal page layouts, with WCM providing the content authoring and templating layer.

The rendering pipeline:

Request → Web Server (IHS/Apache) → WebSphere Portal → Theme Framework → Portlet Rendering → WCM Content → HTML

The Theme Framework (Portal 8.5+) controls how JavaScript and CSS are loaded on the page. It uses theme modules and contribution profiles to manage script loading order and conditional inclusion. This is the primary mechanism for analytics script injection.

IBM DX also provides a REST API (DX API / WCM REST) for headless content delivery, which shifts analytics responsibility to the consuming frontend.


Installing Tracking Scripts

Create a theme module that injects the analytics script. Theme modules are defined in the theme's contributions folder:

// theme/contributions/analytics.json
{
  "modules": {
    "analytics_tracking": {
      "contributions": {
        "head": [
          {
            "type": "config_static",
            "sub-contributions": {
              "js": {
                "uris": [
                  {
                    "value": "/analytics/datalayer-init.js",
                    "type": "head"
                  }
                ]
              }
            }
          }
        ],
        "config": [
          {
            "type": "config_static",
            "sub-contributions": {
              "js": {
                "uris": [
                  {
                    "value": "https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX",
                    "type": "head",
                    "attributes": {
                      "async": "async"
                    }
                  }
                ]
              }
            }
          }
        ]
      },
      "prereqs": ["wp_portal"],
      "titles": {
        "en": "Analytics Tracking"
      }
    }
  }
}

Register the module in the theme profile:

// theme/profiles/profile_analytics.json
{
  "moduleIDs": [
    "wp_portal",
    "wp_theme_portal_85",
    "analytics_tracking"
  ]
}

Method 2: Script Portlet

Deploy a JSR 286 portlet that outputs the analytics script. This approach is useful when you need portal-context data (user attributes, page metadata):

// AnalyticsPortlet.java
public class AnalyticsPortlet extends GenericPortlet {

    @Override
    protected void doView(RenderRequest request, RenderResponse response)
            throws PortletException, IOException {
        response.setContentType("text/html");
        PrintWriter writer = response.getWriter();

        String pagePath = request.getParameter("portal.page.path");
        String userId = request.getRemoteUser();
        boolean isAuthenticated = userId != null;

        writer.write("<script>");
        writer.write("window.dataLayer = window.dataLayer || [];");
        writer.write("window.dataLayer.push({");
        writer.write("  platform: 'ibm_dx',");
        writer.write("  pagePath: '" + escapeJs(pagePath) + "',");
        writer.write("  userAuthenticated: " + isAuthenticated);
        writer.write("});");
        writer.write("</script>");
    }

    private String escapeJs(String input) {
        if (input == null) return "";
        return input.replace("'", "\\'").replace("\n", "\\n");
    }
}

Place the portlet in a hidden container on the portal page layout so it renders but does not display visible UI.

Method 3: WCM HTML Component

Create a WCM HTML component that contains the tracking script, and include it in a WCM presentation template:

<!-- WCM HTML Component: Analytics Script -->
<script>
  window.dataLayer = window.dataLayer || [];
</script>
<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"></script>

Reference it in the presentation template:

<!-- WCM Presentation Template -->
[component name="AnalyticsScript"]
<div class="content-body">
  [element context="current" type="content" key="body"]
</div>

Data Layer Implementation

WCM Content Template Data Layer

Use WCM's content template tags to expose content metadata:

<!-- WCM Presentation Template with Data Layer -->
<script>
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    platform: 'ibm_wcm',
    contentId: '[Property context="current" type="content" field="id"]',
    contentTitle: '[Property context="current" type="content" field="title"]',
    contentType: '[Property context="current" type="content" field="authoring template"]',
    contentPath: '[Property context="current" type="content" field="path"]',
    library: '[Property context="current" type="content" field="library"]',
    lastModified: '[Property context="current" type="content" field="last modified"]',
    publishDate: '[Property context="current" type="content" field="publish date"]',
    authors: '[Property context="current" type="content" field="authors"]'
  });
</script>

Portal Page Context via Theme Module

Access portal page metadata in a theme module's JavaScript:

// datalayer-init.js (loaded via theme module)
(function() {
  var portalData = {
    platform: 'ibm_dx',
    portalPage: document.querySelector('meta[name="com.ibm.portal.pageId"]')?.content || '',
    portalTheme: document.querySelector('meta[name="com.ibm.portal.theme"]')?.content || '',
    locale: document.documentElement.lang || 'en',
    authenticated: document.querySelector('meta[name="com.ibm.portal.user"]')?.content !== 'anonymous'
  };

  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push(portalData);
})();

DX REST API for Headless Data Layer

When using IBM DX as a headless CMS, build the data layer from the WCM REST API response:

// Headless frontend consuming DX API
async function buildDataLayer(contentId) {
  const response = await fetch(
    `/wps/mycontenthandler/wcmrest/Content/${contentId}`,
    {
      headers: { 'Accept': 'application/json' }
    }
  );
  const content = await response.json();

  return {
    platform: 'ibm_wcm',
    contentId: content.id,
    contentTitle: content.title?.value || '',
    contentType: content.type,
    library: content.libraryId,
    lastModified: content.lastModified
  };
}

Common Issues

Theme Module Load Order

Theme modules load based on prerequisite declarations. If the data layer module loads after the GTM module, the data layer is empty when GTM initializes.

Fix by declaring the dependency:

{
  "modules": {
    "gtm_container": {
      "prereqs": ["analytics_datalayer"],
      "contributions": { ... }
    },
    "analytics_datalayer": {
      "prereqs": ["wp_portal"],
      "contributions": { ... }
    }
  }
}

Portlet Caching Stale Data Layer

WebSphere Portal caches portlet output by default. The analytics portlet may serve stale data if its cache timeout is too long.

Set the portlet to expire immediately:

<!-- portlet.xml -->
<portlet>
  <portlet-name>AnalyticsPortlet</portlet-name>
  <expiration-cache>0</expiration-cache>
  ...
</portlet>

Or use <cache-scope> to disable shared caching:

<container-runtime-option>
  <name>javax.portlet.renderHeaders</name>
  <value>true</value>
</container-runtime-option>

WCM Tags Not Rendering in Script Blocks

Some WCM tag processors do not evaluate inside <script> elements. If your WCM property tags render as literal text, move the data layer initialization to a hidden <div> with data-* attributes, then read them in JavaScript:

<!-- WCM Presentation Template -->
<div id="wcm-data" style="display:none"
     data-content-id="[Property context='current' type='content' field='id']"
     data-content-title="[Property context='current' type='content' field='title']">
</div>

<script>
  var el = document.getElementById('wcm-data');
  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({
    contentId: el.dataset.contentId,
    contentTitle: el.dataset.contentTitle
  });
</script>

Virtual Portals Requiring Separate Tracking

IBM DX supports virtual portals -- multiple logical portal instances on the same infrastructure. Each virtual portal may need its own tracking ID. Store the tracking ID in the virtual portal's theme configuration and read it in the theme module:

// Read from portal-specific meta tag or config
var trackingId = document.querySelector(
  'meta[name="analytics.trackingId"]'
)?.content || 'GTM-DEFAULT';

Platform-Specific Considerations

WebSphere theme optimization -- IBM DX's theme framework combines and minifies JavaScript modules into aggregated bundles. Your analytics scripts may be combined with other theme JavaScript, which can delay execution. Mark analytics modules with type: "head" to ensure they load in <head> before the combined body scripts.

Portal page hierarchy -- IBM DX organizes pages in a tree structure. Child pages inherit theme settings from parent pages. If you set the analytics theme module at the root page level, all child pages inherit it. Override at specific child pages if different tracking is needed for subsites.

Content templating limitations -- WCM presentation templates support a limited set of tags and expressions. Complex data layer logic (conditional fields, array building, content aggregation) should be handled in a portlet or custom rendering plugin rather than in WCM template markup.

Migration to HCL DX -- IBM sold the DX product line to HCL Technologies. HCL Digital Experience continues the same architecture but with different licensing and support. The analytics implementation patterns described here apply to both IBM DX 8.5/9.0 and HCL DX.

DX API versioning -- The WCM REST API has multiple versions. Ensure your headless data layer fetches use the correct API version path (/wcmrest/ for legacy, /dx/api/wcm/v2/ for newer versions). Response formats differ between versions, so adjust your data layer field mappings accordingly.