• Back

    Personalizing learning in Articulate Rise 360: a practical guide by Oleh Selezen

    Date published: 02.10.2025
    Views: 17

    An employee opens the course and sees a message addressed to them, text tailored to their gender, and sometimes even their avatar or photo. Sounds cool, doesn’t it? For the learner, it seems like a minor detail, but for L&D specialists, it is a tool that significantly increases engagement and learning effectiveness. Personalized courses are better at holding attention and creating a sense of value: «this course was made just for me».

    In addition, all data is already stored in the LMS. So all you need to do is configure it so that it is automatically uploaded to the course.

    Together with Oleh Selezny, we have already shared our experience of using user data in Articulate Storyline. Now let’s take a look at another tool – Articulate Rise 360, which is often chosen for its simplicity, speed, and modern design.

    In this article, we will focus on how to combine the convenience of Rise with the personalization capabilities of LMS Collaborator. Oleh Selezen, head of the distance learning department at VIYAR, will share his experience.

    Let’s move from theory to practice: what data from LMS Collaborator can be used to personalize courses in Rise 360?

    Oleh Selezen – Head of Distance Learning at VIYAR

    blank

    What parameters can we obtain?

    All data is transferred from LMS Collaborator to SCORM via the cmi.core.student_info variable. It can also be used for personalization in Articulate Rise 360.

    Unlike Storyline, Rise 360 does not directly work with triggers or conditions. However, the values obtained can be used for personalization: to customize learning scenarios, display the necessary blocks, and tailor appeals.

    To implement this, you will need to interact with the code a little. But don’t worry – it’s as simple as possible.😊

    blank

    Next, step by step, how it looks in practice.

    Step 1. Formatting variables in Articulate Rise 360

    To pull user data, simply specify the variable from LMS Collaborator enclosed in percent signs, for example:

    • user_id → %user_id%
    • user_firstname → %user_firstname%
    • user_email → %user_email%

    When a user opens a course, the system automatically replaces the values with their actual data.

    Important: they do not work on their own. If you do not add the LMS connection code and configure the publication for SCORM, the course will simply remain %user_firstname%.

    What to do next?

    1. Publish the course in Rise 360: Publish → LMS, select SCORM 1.2 or 2004, and save as ZIP..
    2. Unzip the archive into any folder.
    3. Find and open the scormcontent folder..
    4. Find the index.html file and open it in any editor.
    5. Add this code immediately after the <title> tag:
    Code
    
    <script defer src="https://cdn.jsdelivr.net/gh/asimut/user-variables/selezen_v1.js"></script>
    

    Now the course will pull up user data.

    Step 2. User photos instead of standard images

    Another simple way to personalize is to replace images with photos of specific users. This works anywhere in the course where there are images.

    This provides noticeable personalization “in the face” without changing the block layout and works in any places of the course where there are images.

    How to do it:

    1. Open the desired block in Articulate Rise 360 and click Image Alt Text.
    2. Enter replace_user_img and confirm.
    3. Repeat for other images as needed.

    When opening the course, the system will automatically insert the user’s photo from the LMS.

    blank

    Step 3. Addressing by gender

    When the LMS transmits information about the user’s gender (e.g., male or female), we can automatically change words and their endings in the course to match the desired gender.

    An example of such a text block in Articulate Rise 360:

    blank

    The image shows a standard text block. It is in such blocks that you can configure replacements so that the addressees sound natural: «you have completed» or «you have completed».

    To do this, you need to prepare a small script that checks the received value and substitutes the desired option.

    What to pay attention to:

    By default, all forms of address can be left in the masculine form. The system will then change them to the appropriate form in the blocks where you specify this. It is important not to enable replacement everywhere, otherwise words outside the context of forms of address may be accidentally changed. This will look like an error.

    How to do it:

    1. Add code that receives data from LMS.
    2. Add a module for correct gender references:

    – Connect the main script with replacement logic (it performs search and replacement).
    – In the configuration, specify the words to be replaced (for example, completed) and the IDs of the blocks (targetBlocks) where these replacements are allowed.

    As a result, the system will automatically substitute the correct forms of address depending on the user’s gender.

    Code
    
    <script defer src="https://cdn.jsdelivr.net/gh/asimut/user_gender_replacer/selezen_v1.js"></script>
    
    <script>
      // Конфіг (тільки дані, ніякої логіки)
      window.GenderReplacer = {
        debug: false,
        genderReplacements: {
          female: {
            'завершив': 'завершила',
            'дізнався': 'дізналася',
            'ознайомився': 'ознайомилася',
            'зрозумів': 'зрозуміла',
            'пройшов': 'пройшла',
            'успішно завершив': 'успішно завершила',
            'готовий': 'готова',
            'вивчив': 'вивчила',
            'переглянув': 'переглянула',
            'опанував': 'опанувала',
            'засвоїв': 'засвоїла',
            'досяг': 'досягла',
            'здобув': 'здобула',
            'отримав': 'отримала',
            'виконав': 'виконала',
            'склав': 'склала',
            'закінчив': 'закінчила'
          }
        },
        targetBlocks: [
          'cmckqk51l001c357b79yaongk' 
        ]
      };
    </script>
    

    Important: the lower block is only a configuration (data). The actual substitution is performed by the main script with replacement logic, connected by the line above.

    Working with block identifiers

    To let the system know which text block to replace, you specify it in the targetBlocks list. To do this, each block in Rise has its own unique ID.

    There are two ways to find the ID:

    Method 1.
    Through DevTools developer tools. If you are confident using the browser console, open the page code and copy the required ID.

    Method 2.
    Using a plugin. It’s more convenient and faster, especially if you don’t want to work with the code directly.

    You can also expand the list of words to replace. If you don’t find what you need among the examples provided, feel free to add your own options to the configuration.

    To simplify the search for block identifiers, it is convenient to use a plugin.

    How to install the plugin:

    1. Open your browser and go to Extensions – Manage Extensions;
    2. Turn on Developer Mode;
    3. Download the archive with the plugin, unzip it, and add the extension from this folder to your browser;
      Link to archive
    4. After that, it will automatically appear on your course editing page.

    The plugin can be enabled or disabled at any time. With its help, you can quickly find block IDs and add them to the configuration.

    blank

    Once we have learned how to quickly find IDs using the plugin, we can use these identifiers not only for point replacements, but also to control the visibility of entire course fragments. This will allow different departments to display their content within a single course – without duplicating materials and creating unnecessary copies.

    Step 4. Managing content for different departments

    We already have all the variables from the LMS, including user_department. This means that there is no need to search for the department: the system reads it from the user data and opens the necessary course blocks based on this information. To do this, you only need to specify the name of the department that is in the LMS and the list of blocks that it should see. For everyone else, the default branch works.

    Code
    
    <script>
      (function () {
        'use strict';
      
        // Конфіг
        // Заповни mapping під свої департаменти та їхні data-block-id
        window.DepartmentBlocks = window.DepartmentBlocks || {
          debug: false,
          mapping: {
            'назава департаменту':    ['ТУТ ВКАЖИТИ ІD БЛОКУ'], // назва має буди із меленької літери
            'default':     ['ТУТ ВКАЖИТИ ІD БЛОКУ'],     // блок для всіх інших
          },
        };
      
        const DB = window.DepartmentBlocks;
      
        const ATTR_CANDIDATES = ['data-block-id','data-blockid','data-block','blockid','data-id','id'];
        const HIDE_CLASS = 'dept-hidden-by-mapping';
        const STYLE_ID = 'dept-visibility-style';
        const OBS_CONFIG = { childList: true, subtree: true, attributes: false, characterData: false };
      
        let allListedIds = new Set();      // усі ID, що зустрічаються у mapping (для первинного приховування)
        let allowedIds = new Set();        // ID, дозволені для поточного департаменту
        let currentDept = null;            // нормалізоване значення поточного департаменту
        let initialized = false;           // щоб не ініціалізувати двічі
        let mainObserver = null;
      
        const log = (...args) => { if (DB.debug) console.log('[DepartmentBlocks]', ...args); };
        const warn = (...args) => console.warn('[DepartmentBlocks]', ...args);
      
        function normalize(str) {
          return (str == null) ? '' : String(str).toLowerCase().trim();
        }
      
        function injectStyleOnce(doc = document) {
          if (doc.getElementById(STYLE_ID)) return;
          const style = doc.createElement('style');
          style.id = STYLE_ID;
          style.textContent = `
            .${HIDE_CLASS} { display: none !important; }
          `;
          doc.head.appendChild(style);
        }
      
        function getBlockIdFromElement(el) {
          for (const attr of ATTR_CANDIDATES) {
            if (el.hasAttribute && el.hasAttribute(attr)) {
              const v = el.getAttribute(attr);
              if (v && v.trim()) return v.trim();
            }
          }
          return null;
        }
      
        function collectAllListedIds() {
          allListedIds = new Set();
          const map = DB.mapping || {};
          Object.values(map).forEach(arr => {
            (arr || []).forEach(id => allListedIds.add(String(id)));
          });
          log('All listed IDs:', Array.from(allListedIds));
        }
      
        function computeAllowedIdsFor(dept) {
          const mappingKey = DB.mapping?.[dept] ? dept : 'default';
          allowedIds = new Set(DB.mapping?.[mappingKey] || []);
          log('Allowed IDs for', dept, '(using mapping key:', mappingKey, '):', Array.from(allowedIds));
        }
      
        function isListed(id) {
          return allListedIds.has(id);
        }
      
        function shouldShow(id) {
          return allowedIds.has(id);
        }
      
        function hideEl(el) {
          if (!el.classList.contains(HIDE_CLASS)) {
            el.classList.add(HIDE_CLASS);
          }
        }
      
        function showEl(el) {
          if (el.classList.contains(HIDE_CLASS)) {
            el.classList.remove(HIDE_CLASS);
          }
        }
      
        function initialHideListedBlocks(root = document) {
          const candidates = root.querySelectorAll('*');
          candidates.forEach(el => {
            const id = getBlockIdFromElement(el);
            if (id && isListed(id)) hideEl(el);
          });
        }
      
        function applyDepartmentVisibility(root = document) {
          const candidates = root.querySelectorAll('*');
          candidates.forEach(el => {
            const id = getBlockIdFromElement(el);
            if (!id) return;
      
            if (!isListed(id)) return;
            if (shouldShow(id)) showEl(el);
            else hideEl(el);
          });
        }
      
        function isUserDataReady() {
          const sources = [
            window.UserVariables?.data,
            window.UserVariables2?.data,
            window.userData
          ];
          return sources.some(d => d && typeof d === 'object');
        }
      
        function getUserDepartment() {
          if (DB.overrideDepartment) return normalize(DB.overrideDepartment);
      
          const sources = [
            window.UserVariables?.data,
            window.UserVariables2?.data,
            window.userData
          ];
          for (const d of sources) {
            if (!d || typeof d !== 'object') continue;
            for (const key of ['user_department','department','dept','user_dept','org_unit','division']) {
              if (d[key]) return normalize(d[key]);
            }
          }
          return '';
        }
      
        function processIframe(iframe) {
          try {
            const doc = iframe?.contentDocument;
            if (!doc) return;
            injectStyleOnce(doc);
            initialHideListedBlocks(doc);
            if (currentDept) applyDepartmentVisibility(doc);
            const obs = new MutationObserver(() => {
              if (!currentDept) initialHideListedBlocks(doc);
              else applyDepartmentVisibility(doc);
            });
            obs.observe(doc.documentElement, OBS_CONFIG);
          } catch (e) {}
        }
      
        function watchIframes() {
          const iframes = document.querySelectorAll('iframe');
          iframes.forEach(ifr => {
            if (ifr._deptBound) return;
            ifr._deptBound = true;
            if (ifr.contentDocument && ifr.contentDocument.readyState !== 'loading') {
              processIframe(ifr);
            } else {
              ifr.addEventListener('load', () => processIframe(ifr), { passive: true });
            }
          });
        }
      
        function applyNow(root = document) {
          injectStyleOnce(document);
          watchIframes();
          if (!currentDept) initialHideListedBlocks(root);
          else applyDepartmentVisibility(root);
        }
      
        function startObserver() {
          if (mainObserver) return;
          mainObserver = new MutationObserver(() => applyNow(document));
          mainObserver.observe(document.documentElement, OBS_CONFIG);
          ['#app','.rise-player','[data-rise]','.course-container'].forEach(sel => {
            const c = document.querySelector(sel);
            if (c && !c._deptObserver) {
              const o = new MutationObserver(() => applyNow(c));
              o.observe(c, OBS_CONFIG);
              c._deptObserver = o;
            }
          });
        }
      
        function resolveDepartmentAndApply() {
          const dept = getUserDepartment();
          if (!dept) {
            log('user_department не знайдено (поки що). Працюємо у режимі initial-hide.');
            return;
          }
          if (dept === currentDept) return;
          currentDept = dept;
          computeAllowedIdsFor(currentDept);
          applyNow(document);
        }
      
        function waitForDataThenInit() {
          if (initialized) return;
          initialized = true;
          injectStyleOnce(document);
          collectAllListedIds();
          initialHideListedBlocks(document);
          watchIframes();
          startObserver();
      
          let tries = 0;
          const quick = setInterval(() => {
            tries++;
            if (isUserDataReady()) {
              resolveDepartmentAndApply();
              if (currentDept) clearInterval(quick);
            }
            if (tries >= 20) clearInterval(quick);
          }, 500);
      
          setInterval(resolveDepartmentAndApply, 2000);
        }
      
        DB.forceApply = () => applyNow(document);
        DB.setDepartment = (deptName) => {
          DB.overrideDepartment = deptName;
          resolveDepartmentAndApply();
        };
      
        if (document.readyState === 'loading') {
          document.addEventListener('DOMContentLoaded', () => setTimeout(waitForDataThenInit, 50), { once: true });
        } else {
          setTimeout(waitForDataThenInit, 50);
        }
      })();
    </script>
    

    How it works:

    • At the start, all “managed” temporary blocks are hidden.
    • When the system reads user_department, it shows only those blocks that you have assigned to this department.
    • Blocks from other lists remain hidden, and those that are not in the mapping are visible.
    • If the department is not found or is empty, the user sees default.

    As a result, each employee receives only their own content – without duplicating courses or unnecessary versions.

    blank

    Let’s summarize

    Now you have three ready-made tools:

    • Script №1 – gets user data, pulling in the department name or personal photo.
    • Script №2 – changes words and endings in the text depending on the user’s gender.
    • Script №3 – manages content: hides or shows course blocks for different departments.

    You don’t have to connect all of them at once – just use the ones that are really necessary for your course.

    blank

    Final step: packaging and uploading the course to the LMS

    Once all the changes have been made, the simplest thing left to do is to package the course correctly.

    How to do it:

    1. Select all files and folders within the project (but not the top-level folder).
    2. Compress them into a ZIP file using any convenient archiver.
    3. Upload the ZIP file you received to LMS Collaborator.

    blank

    Conclusion

    In this article, we have covered everything from how to pull data from the LMS and insert real photos to changing gender references and managing course blocks for different departments.
    Now the course works «out of the box» and can be converted into multiple versions without duplication or extra work.

    What you get:

    • Personalization – addressing by name, user photo, relevant texts.
    • Content control – display different blocks for different departments.
    • Time savings – less manual work, less duplication.
    • Convenience for the team – learning becomes understandable, lively, and relevant to the needs of employees.

    The main idea is simple: less technical manipulation, more personalization, flexibility, and value for everyone.😊

    At LMS Collaborator, we help make learning more personal and convenient for every employee. The platform allows you to customize courses for different roles, departments, and team needs. If you want to create courses that take into account user characteristics and make learning more effective, try LMS Collaborator.
    blank
    Atamanenko Katya
    Try LMS Collaborator in action
    Need guidance picking the right features for digitizing and automating your enterprise learning processes? We're here to help.
    Get demo
    Or call our manager
    +44 20457 73128