Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Widget SDK reference

Complete reference for configuring and customizing recommendation widgets, including templating, callbacks, and advanced rendering options.

The ip.js SDK is designed to be flexible, enabling both static widget placements and dynamic implementations. The script scans your page for special <script> tags that serve as both configuration and templates for recommendation widgets.

Configuration methods

You can configure widgets using data-* attributes directly on the <script> tag. This is the simplest and most common method.

Example

See the following example:

<script
    type="text/x-mustache"
    class="idio-recommendations"
    data-api-key="YOUR_DELIVERY_KEY_HERE"
    data-rpp="5"
    data-section="Case Studies"
    data-fallback="Popular Content"
></script>

Configuration options

The following data-* attributes are available for widget configuration.

AttributeRequiredDescription
data-api-keyYesThe unique API key for this widget delivery.
data-rppNoThe number of content items to fetch (Results Per Page). . Overrides the server-side setting.
data-pageNoThe page number of results to fetch, for pagination. Defaults to 1.
data-filterNoA Lucene query used for personalized recommendations. If data-section is also present, data-filter takes precedence.
data-sectionNoThis parameter is ignored if data-filter is provided. If you are using a filter you should specify the section as part of the query. Only include content from this section.
data-fallbackNoA fallback section name or strategy, for example, popular or recent, used if personalization is not possible.
data-fallback-filterNoA Lucene query used as a fallback. Takes precedence over data-fallback.

Templating

Widgets are rendered using the Mustache templating engine. The ip.js SDK passes an object to your template, with the primary property content, which is an array of recommended items.

Available Template Variables

Within a {{#content}}...{{/content}} block, you have access to the following variables for each item:

  • {{title}} – The title of the content
  • {{link_url}} – The destination URL
  • {{main_image_url}} – The URL for the main image
  • {{abstract}} – A short summary of the content
  • {{topics}} – A list of topics associated with the content
  • {{source}} – The source of the content item
  • {{author}} – The author of the content item
🚧

Important

To prevent HTML injection, all variables rendered with {{variable}} are HTML-escaped. If you need to render raw HTML, use the triple-mustache – {{{variable}}}. Use this with caution and only with trusted content.

Advanced usage with callbacks

For more dynamic control, define a global window._ipc object before the ip.js script is loaded. This lets you define global settings and callbacks.

Global callbacks example

See the following example:

<script type="text/javascript">
var _ipc = {
  complete: function(tag, statusCode) {
    console.log('Success! Widget has been rendered.', tag);
  },
  error: function(tag, statusCode) {
    console.log('There was an error rendering the widget:', statusCode);
  }
};
</script>

Data attributes on an individual widget always override any settings defined in the global _ipc object.

Advanced callbacks

For complete control over widget rendering, use the finish callback. This callback is invoked when the API response is received, letting you implement custom rendering logic, A/B testing, or conditional widget display.

The finish callback

The finish callback can be defined either in the global _ipc object or in individual widget configurations. It receives four parameters:

finish: function(apiResponse, statusCode, trackLinks, render) {
    // Your custom logic here
}

Parameters:

ParameterTypeDescription
apiResponseObjectThe complete API response containing recommendations and metadata.
statusCodeNumberThe HTTP status code of the API response like 200, 204, 404.
trackLinksFunctionA helper function to wrap existing links with click tracking.
renderFunctionA helper function to render a Mustache template and mark the recommendation as seen.

API response structure

The apiResponse object contains the following key properties:

apiResponse.content (Array) An array of recommended content items. Each item contains:

  • title – Content title
  • link_url – Destination URL
  • main_image_url – Main image URL
  • abstract – Content summary
  • topics – Associated topics
  • source – Content source
  • author – Content author

apiResponse.group (String) The A/B test group assignment for the visitor. Use this to implement different behavior for test vs. control groups:

  • 'control' – User is in the control group (no personalization)
  • 'test' – User is in the test group (personalized content)

apiResponse.model (String) Information about the personalization model used for this visitor. Indicates whether recommendations are based on a user interest profile or fallback logic.

apiResponse.recommendation_id (String) A unique identifier for this recommendation decision. This ID is used internally for tracking impressions and clicks.

Callback helper functions

trackLinks(arrayOfLinks)

Wraps an array of <a> elements with click tracking URLs. This is useful when you want to track clicks on existing content rather than rendered recommendations.

finish: function(apiResponse, statusCode, trackLinks, render) {
    var existingLinks = document.querySelectorAll('.content-area a');
    trackLinks(existingLinks);
}

render(apiResponse, statusCode, templateString)

Renders a Mustache template with the API response data and marks the recommendation as seen in the analytics system. Returns the rendered HTML string.

finish: function(apiResponse, statusCode, trackLinks, render) {
    var template = '<div>{{#content}}<a href="{{link_url}}">{{title}}</a>{{/content}}</div>';
    var html = render(apiResponse, statusCode, template);
    document.getElementById('widget-container').innerHTML = html;
}
🚧

Important

When using the finish callback, recommendations are created with a pending status. The recommendation is marked as visible and counted in analytics only when you call the render() function. If you do not call render(), the recommendation does not display in dashboard metrics.

Use cases and examples

A/B testing with control groups

Use the apiResponse.group field to display different content to test and control groups:

var _ipc = [{
    api_key: 'YOUR_DELIVERY_KEY',
    section: 'Blog Posts',
    finish: function(apiResponse, statusCode, trackLinks, render) {
        if (apiResponse.group === 'control') {
            // Control group: Track existing static content
            var staticLinks = document.querySelectorAll('#static-content a');
            trackLinks(staticLinks);
            console.log('User in control group - tracking static content');
        } else {
            // Test group: Render personalized widget
            var template = document.getElementById('widget-template').innerHTML;
            var html = render(apiResponse, statusCode, template);
            document.getElementById('personalized-widget').innerHTML = html;
            console.log('User in test group - showing personalized content');
        }
    }
}];

Conditional rendering based on content availability

Render the widget only if sufficient recommendations are available:

var _ipc = [{
    api_key: 'YOUR_DELIVERY_KEY',
    rpp: 5,
    finish: function(apiResponse, statusCode, trackLinks, render) {
        if (statusCode === 200 && apiResponse.content && apiResponse.content.length >= 3) {
            // Enough content available, render the widget
            var template = '{{#content}}<div class="item">{{title}}</div>{{/content}}';
            var html = render(apiResponse, statusCode, template);
            document.getElementById('recommendations').innerHTML = html;
        } else {
            // Not enough content, hide the widget container
            document.getElementById('recommendations').style.display = 'none';
        }
    }
}];

Custom template selection based on personalization

Use different templates depending on whether content is personalized:

var _ipc = [{
    api_key: 'YOUR_DELIVERY_KEY',
    finish: function(apiResponse, statusCode, trackLinks, render) {
        var template;

        if (apiResponse.model && apiResponse.model.includes('personalized')) {
            // Use premium template for personalized content
            template = document.getElementById('personalized-template').innerHTML;
        } else {
            // Use basic template for non-personalized content
            template = document.getElementById('basic-template').innerHTML;
        }

        var html = render(apiResponse, statusCode, template);
        document.getElementById('widget-area').innerHTML = html;
    }
}];

Programmatic widget loading

Dynamically load widgets based on user interaction:

function loadRecommendations(section, containerId) {
    var config = {
        api_key: 'YOUR_DELIVERY_KEY',
        section: section,
        finish: function(apiResponse, statusCode, trackLinks, render) {
            if (statusCode === 200) {
                var template = '{{#content}}<a href="{{link_url}}">{{title}}</a>{{/content}}';
                var html = render(apiResponse, statusCode, template);
                document.getElementById(containerId).innerHTML = html;
            }
        }
    };

    // Use the idio.load() API to load recommendations dynamically
    idio.load([config]);
}

// Call when user clicks a button
document.getElementById('load-more').addEventListener('click', function() {
    loadRecommendations('Related Articles', 'dynamic-widget-1');
});