Appearance
Manifeste, affichage et déclencheurs (infos.json)
infos.json est le manifeste de l'appli : il décrit ses permissions, ses pages et ses ressources. Une appli démarre, fonctionne jusqu'à sa fin (kapp.finish()) ou son arrêt, et affiche éventuellement du contenu. Aucun mode de durée de vie n'est à déclarer.
launch-mode
Omettre ce champ pour une appli ordinaire. La seule valeur à utiliser pour un nouveau manifeste est external, lorsqu'une appli doit obligatoirement s'ouvrir dans une fenêtre popout interactive dédiée (séparée de l'overlay OBS). Cette contrainte d'affichage ne détermine pas combien de temps l'appli fonctionne.
externable: true ajoute le mode popout interactif en plus du mode classique. Par exemple, une appli sans launch-mode avec externable: true peut être lancée soit sur l'overlay OBS, soit dans la popout. Le flag est redondant et ignoré avec launch-mode: external, puisque la popout y est déjà obligatoire.
| Manifeste | Overlay OBS | Popout interactive |
|---|---|---|
Sans launch-mode, externable: false (défaut) | oui | non |
Sans launch-mode, externable: true | oui | oui |
launch-mode: external | non | oui, obligatoire |
Déclencheurs à l'installation
Toutes les applis compatibles avec l'overlay utilisent les mêmes déclencheurs de lancement. Le streamer peut les ajouter, modifier, désactiver ou supprimer. PROCESSUS maintient l'app en exécution tant que ses conditions et son activation le permettent ; ce comportement appartient au déclencheur, indépendamment du manifeste de l'app.
Pour proposer un processus à l'installation, déclarer dans config/default_triggers.config.json (ou dans sa source compilée par le HUB) :
json
[{"type":"PROCESSUS"}]Une liste [] est autorisée : aucun déclencheur de lancement n'est alors installé. Plusieurs defaults, de types autorisés différents, sont possibles. Le bouton du dashboard streamer correspond à CHDASHB ; MANUAL_LAUNCH reste un mécanisme interne.
Le backend attribue un contrat à chaque version d'app lors de sa création. Depuis le contrat 5, seuls les defaults déclarés sont appliqués. Par compatibilité, les anciennes backtasks (launch-mode: backtask ou l'ancien back-task: true sans launch-mode reconnu) de contrat antérieur conservent un unique PROCESSUS implicite à l'installation et continuent d'ignorer leur fichier de defaults. Ce numéro est attribué par la plateforme, sans champ à ajouter au manifeste ni lien avec la version du SDK. Les brouillons déjà créés conservent leur contrat.
Les defaults sont appliqués à une nouvelle installation. Une mise à jour, une relecture du manifeste ou une réactivation de l'app ne les rétablit pas. Une modification des defaults dans un brouillon ne reconfigure donc pas l'installation de développement existante.
Le streamer peut réinitialiser les déclencheurs par défaut depuis les applis installées ou la liste des déclencheurs de l'app. Après confirmation, cette action remplace les déclencheurs de lancement par ceux de la version installée, selon le même contrat de compatibilité qu'à l'installation (y compris une liste vide). Elle conserve les préférences, leurs déclencheurs, les callbacks dynamiques et le lancement manuel interne, sans mettre à jour l'app. Les processus restaurés sont pris en charge à la prochaine réévaluation normale de l'overlay. Cette action permet aussi de tester les defaults modifiés d'un brouillon déjà installé.
Savoir dans quel contexte l'exécution tourne
Le manifeste indique les contextes autorisés. Pour connaître le contexte réel de l'exécution courante, utilise :
js
if (kapp.overlay.isExternal()) {
// UI interactive de la popout
} else {
// rendu destiné à l'overlay OBS
}kapp.overlay.isExternal() est l'API recommandée. La propriété brute kapp.overlay.displayMode vaut toujours overlay ou external. overlay correspond au rendu OBS non cliquable ; external à la popout qui reçoit les interactions souris/clavier. Ne déduis pas ce contexte de kapp.overlay.type.
kapp.overlay.type === 'oneshot'désigne le runtime de la page popup qui héberge l'exécution, pas une catégorie d'appli. Une applaunch-mode: externalest actuellement hébergée par ce runtime. UtiliserisExternal()pour connaître la surface réelle.
Anciens manifestes
Le runtime continue de lire les valeurs historiques launch-mode: oneshot, launch-mode: backtask et la clé back-task. Elles n'imposent ni fin automatique ni absence de visuel, et n'empêchent pas un lancement par kapp.apps.run(). Les champs historiques exposés aux clients restent présents pour compatibilité. Ne pas réécrire les versions publiées. Une sauvegarde d'un autre réglage dans le DEV HUB préserve ces clés, notamment pour les defaults des versions antérieures au contrat 5.
Depuis la mise à jour du 31 octobre 2026, toute nouvelle tentative de soumission sur le portail refuse launch-mode: backtask et la présence de back-task, même avec false ou null, y compris dans un brouillon créé sous un ancien contrat. Retirer ces déclarations dans les sources et envoyer les fichiers corrigés. Pour conserver un démarrage automatique sur l'overlay, déclarer explicitement [{"type":"PROCESSUS"}] dans les defaults ci-dessus ; ce déclencheur n'est pas obligatoire pour les autres applis et n'est pas ajouté par la soumission. launch-mode: oneshot produit un avertissement non bloquant : l'omettre également. launch-mode: external reste accepté. Les versions déjà soumises ou publiées ne sont pas réécrites par ces contrôles. L’upload et la synchronisation signalent aussi ces déclarations sans empêcher d’enregistrer le brouillon ; le blocage porte sur sa soumission. Voir les diagnostics communs.
Champs clés
jsonc
{
"textset": "textset.js",
"externable": false,
"app-index": "index.html",
"scopes": ["chat:read", "chat:write"], // permissions applicatives, distinctes d'OAuth Twitch
"services": [], // services exposés par l'appli (cf. services.md)
"run-output": null, // format de sortie pour apps.run (cf. reference/app-to-app.md)
"kind": null, // contrat standard apps.run, ex. "pick-string"
"overlay-formats": ["16:9"],
"viewers-plugin": null, // dossier viewers (cf. plugins.md)
"viewers-plugin-index": null, // fichier HTML relatif à ce dossier
"streamer-plugin": null, // dossier streamer
"streamer-plugin-index": null, // fichier HTML relatif à ce dossier
"viewers-readme": null,
"viewers-optout": false, // le viewer peut se retirer lui-même de l'appli
"streamer-readme": null,
"public-assets": null,
"required": []
}Le bootstrap contient aussi display-type, icon, banner et required. Ce sont des champs historiques : aucun effet sur l'exécution n'a été trouvé pour les trois premiers, et required n'est que réexposé aux outils de découverte. Conserve les valeurs du bootstrap (0, null, null, []) sans leur donner de sens. La présentation du catalogue (vignette, galerie) se règle ailleurs : voir livraison.
overlay-formats n'ajoute aucun format implicitement lorsqu'il est déclaré ; ["9:16"] seul est valide. L'iframe occupe tout le cadre du format choisi ; l'app place son contenu à l'intérieur, éventuellement via une préférence containerArea. window est accepté mais ignoré. La popout démarre dans le premier format déclaré. Voir reference/vertical.md pour les valeurs par défaut et la recette.
viewers-optout
À passer à true si ton appli peut cibler un viewer sans qu'il l'ait demandé (tirage au sort d'un adversaire, désignation d'une victime, etc.). Le viewer voit alors, sur sa page d'appli (/v/{channel}/{slug}), un bouton « ne plus me faire participer ».
Un viewer retiré devient invisible pour ton appli : kapp.users.get() ne le trouve plus, il n'apparaît plus dans kapp.chat.getActiveUsers(), tu ne reçois plus ses messages ni ses events (follow, sub, raid…), il ne peut plus lancer l'appli et ne peut plus être passé en argument de trigger. Écris donc ton code comme si ce viewer n'existait pas — c'est déjà le cas pour toi.
Le flag ne gouverne que le droit du viewer : le streamer, lui, peut interdire n'importe quelle appli à n'importe quel viewer depuis son dashboard, que le flag soit présent ou non.
Pièges
- L'affichage est facultatif : appelle
display()si tu en as besoin etfinish()lorsque le travail est terminé. Sans fin explicite, l'appli reste ouverte jusqu'à son arrêt. - N'utilise jamais
kapp.overlay.type === 'oneshot'pour détecter une popout interactive ; utilisekapp.overlay.isExternal(). scopesdoit couvrir les actions de l'appli, y compris celles sans appel Twitch :chat:writepourkapp.chat.say,rewards:distribpour les récompenses,app-plugin-viewers:writepour les diffusions et réponses aux viewers.- Déclare dans
servicesuniquement si d'autres briques doivent piloter ton appli (voir les services). viewers-optout: une liste de viewers peut revenir plus courte que demandée (kapp.users.get([...])) ou ungetActiveUsers()vide — gère le cas au lieu de supposer que la cible existe.