> ## Knowledge Base Index
> Fetch the complete knowledge base index at: https://documentation.celestory.io/sitemap.xml
> Use this file to discover available pages before exploring further.
> Pure-Markdown content can be obtained by appending a '.md' suffix to the content URLs listed in the sitemap (without the trailing slash).

# 📦 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__](https://documentation.celestory.io/fr/article/creer-son-export-scorm-pour-lms-d3ne2m/) : reprenez le graphe d'exemple SCORM ([https://creator.celestory.io/project/phHUew7e6](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](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.