Articles sur : Export, Monétisation

📩 CrĂ©er son export cmi5 pour LMS

Le cmi5 est un profil xAPI standardisé pour remplacer le SCORM dans les LMS modernes : il garde le lancement encadré par un LMS (comme le SCORM), mais fait circuler des statements xAPI vers un LRS pour un suivi beaucoup plus fin.

Définition du format cmi5

Contrairement au SCORM (API globale simple) et au xAPI "libre" (statements envoyés à volonté), le cmi5 impose une séquence stricte de statements, envoyés dans cet ordre :

  • Initialized : au lancement du module
  • Completed : quand le contenu a Ă©tĂ© parcouru
  • Passed / Failed : selon le score obtenu
  • Terminated : Ă  la fermeture, toujours en tout dernier

Caractéristiques du format cmi5

  • NĂ©cessite un LMS et un LRS compatibles cmi5 : le LMS fournit un jeton d'authentification Ă  rĂ©cupĂ©rer dynamiquement au lancement (contrairement au xAPI "libre", oĂč les accĂšs peuvent ĂȘtre codĂ©s en dur)
  • Suivi aussi prĂ©cis que le xAPI classique (score dĂ©taillĂ©, progression, rĂ©ussite/Ă©chec)
  • Structure du cours dĂ©finie par un fichier cmi5.xml (l'Ă©quivalent de l'imsmanifest.xml du SCORM)
  • ⚠ Ne peut pas ĂȘtre testĂ© en dehors d'un LMS/LRS compatible cmi5 : les identifiants de session sont fournis dynamiquement au lancement, impossible de les coder en dur comme pour un xAPI autonome
  • ⚠ Comme pour le xAPI, les statements sont envoyĂ©s en HTTP direct : le LRS doit autoriser le CORS depuis l'origine de votre export

Ressource d'exemple

Le placement des blocs dans le graphe suit la mĂȘme logique que le tuto SCORM : reprenez le graphe d'exemple SCORM (https://creator.celestory.io/project/phHUew7e6) et remplacez les blocs SCORM par les blocs cmi5 ci-dessous, aux mĂȘmes emplacements (dĂ©but, Ă©tapes intermĂ©diaires, fin).
Note importante : Seuls les blocs commençant par cmi5 sont Ă  rajouter oĂč vous le souhaitez dans votre graphe.

Étape 1 : Ajouter les blocs Javascript

Bloc 1 : cmi5 - Récupération des paramÚtres de lancement

function cmi5_getParam(name) {
   const url = new URL(window.location.href);
   return url.searchParams.get(name);
}
​
window.CMI5 = {
   endpoint: cmi5_getParam("endpoint"),
   fetchUrl: cmi5_getParam("fetch"),
   actor: JSON.parse(cmi5_getParam("actor")),
   registration: cmi5_getParam("registration"),
   activityId: cmi5_getParam("activityId"),
   authToken: null
};
​
if (window.CMI5.endpoint && !window.CMI5.endpoint.endsWith("/")) {
   window.CMI5.endpoint += "/";
}

Ces paramÚtres (endpoint, fetch, actor, registration, activityId) sont ajoutés automatiquement à l'URL de votre module par le LMS au moment du lancement : rien à configurer en dur ici.

Bloc 2 : cmi5 - Fonction d'envoi + récupération du jeton + Initialize

function cmi5_sendStatement(verbId, verbDisplay, resultObj) {
   const category = [{ id: "https://w3id.org/xapi/cmi5/context/categories/cmi5" }];
​
   const moveOnVerbs = [
       "http://adlnet.gov/expapi/verbs/completed",
       "http://adlnet.gov/expapi/verbs/passed",
       "http://adlnet.gov/expapi/verbs/failed"
  ];
   if (moveOnVerbs.includes(verbId)) {
       category.push({ id: "https://w3id.org/xapi/cmi5/context/categories/moveOn" });
  }
​
   const statement = {
       actor: window.CMI5.actor,
       verb: {
           id: verbId,
           display: { "fr-FR": verbDisplay }
      },
       object: {
           id: window.CMI5.activityId,
           objectType: "Activity"
      },
       context: {
           registration: window.CMI5.registration,
           contextActivities: { category: category }
      }
  };
​
   if (resultObj) {
       statement.result = resultObj;
  }
​
   fetch(window.CMI5.endpoint + "statements", {
       method: "POST",
       headers: {
           "Authorization": window.CMI5.authToken,
           "Content-Type": "application/json",
           "X-Experience-API-Version": "1.0.3"
      },
       body: JSON.stringify(statement)
  }).then(() => console.log("cmi5 : statement envoyĂ© -", verbDisplay))
    .catch(error => console.log("cmi5 erreur envoi", error));
}
​
fetch(window.CMI5.fetchUrl, { method: "POST" })
  .then(response => response.json())
  .then(data => {
       window.CMI5.authToken = "Basic " + data["auth-token"];
       cmi5_sendStatement("http://adlnet.gov/expapi/verbs/initialized", "initialisĂ©");
  })
  .catch(error => console.log("cmi5 erreur rĂ©cupĂ©ration du jeton", error));

Le jeton récupéré via fetch est valable pour toute la session : c'est lui qui sert d'authentification pour tous les statements suivants.

Bloc 3 : cmi5 - Step

const step = 70;
​
cmi5_sendStatement(
   "http://adlnet.gov/expapi/verbs/progressed",
   "progression",
  {
       extensions: {
           "https://w3id.org/xapi/cmi5/result/extensions/progress": step
      }
  }
);

Vous pouvez rajouter autant d'étapes que vous le souhaitez en dupliquant ce bloc avec différents pourcentages, comme pour les steps SCORM.

Bloc 4 : cmi5 - Completed + Passed

const scorePercent = 100;
​
cmi5_sendStatement("http://adlnet.gov/expapi/verbs/completed", "complété", {
   completion: true,
   duration: "PT0H5M0S"
});
​
cmi5_sendStatement("http://adlnet.gov/expapi/verbs/passed", "réussi", {
   score: {
       scaled: scorePercent / 100,
       raw: scorePercent,
       min: 0,
       max: 100
  },
   success: true
});

duration suit le format ISO 8601 (ex : PT0H5M0S = 5 minutes) — Ă  ajuster ou calculer dynamiquement selon le temps rĂ©ellement passĂ©. En cas d'Ă©chec, envoyez le verbe failed (http://adlnet.gov/expapi/verbs/failed) avec success: false Ă  la place de passed.

Bloc 5 : cmi5 - Finish Session (toujours en dernier)

cmi5_sendStatement("http://adlnet.gov/expapi/verbs/terminated", "clÎturé");
console.log("cmi5 : session terminée");

Le statement Terminated doit impĂ©rativement ĂȘtre le dernier envoyĂ© : aucun autre statement ne doit partir aprĂšs celui-ci.

Étape 2 : Exporter le projet

Exportez votre projet au format Web ou PWA.

Étape 3 : CrĂ©er la structure des dossiers

Créez un dossier avec en titre le nom de votre module (ex : module1) et déplacez-y l'ensemble du contenu exporté.

Étape 4 : Ajouter le fichier cmi5.xml

À la racine de votre package (Ă  cĂŽtĂ© du dossier module1), ajoutez un fichier cmi5.xml dĂ©crivant la structure du cours :

<?xml version="1.0" encoding="UTF-8"?>
<courseStructure xmlns="https://w3id.org/xapi/profiles/cmi5/v1/CourseStructure.xsd">
   <course id="https://votredomaine.com/courses/nom-du-cours">
       <title><langstring lang="fr-FR">Nom du cours</langstring></title>
       <description><langstring lang="fr-FR">Description du cours</langstring></description>
   </course>
   <au id="https://votredomaine.com/courses/nom-du-cours/module1" moveOn="CompletedOrPassed">
       <title><langstring lang="fr-FR">Nom du module</langstring></title>
       <description><langstring lang="fr-FR">Description du module</langstring></description>
       <url>module1/index.html</url>
   </au>
</courseStructure>

L'id de l'<au> doit correspondre à l'activityId que le LMS transmettra au lancement : c'est ce qui permet de relier les statements reçus au bon module. moveOn définit la condition de complétion (Completed, Passed, CompletedAndPassed, CompletedOrPassed ou NotApplicable).

Étape 5 : Compresser en ZIP

Compressez le dossier parent (contenant cmi5.xml et le dossier module1) en fichier zip.

Étape 6 : Tester le fichier cmi5

Importez le zip dans un LMS ou un testeur compatible cmi5, par exemple https://app.cloud.scorm.com/sc/user/Home (SCORM Cloud gÚre aussi le cmi5). Lancez le module puis vérifiez dans le LRS la réception des statements dans l'ordre : Initialized, Completed, Passed (ou Failed), Terminated.

Mis Ă  jour le : 02/07/2026

Cet article a-t-il répondu à vos questions ?

Partagez vos commentaires

Annuler

Merci !