Chez Alpic, nous pensons que la prochaine génération de produits et de services reposera sur des expériences conçues autour de l’IA : des interfaces où les utilisateurs collaborent avec des modèles au lieu de suivre des parcours classiques et prédéfinis dans l’interface.
Dès qu’OpenAI a lancé l’ Apps SDK, nous avons commencé à l’utiliser. En trois mois, nous avons développé deux douzaines d’applications ChatGPT, pour nos besoins internes comme pour nos clients, dans des secteurs B2B et B2C tels que le voyage, le commerce de détail et le SaaS.
Nous avons vite découvert que développer des applications ChatGPT est fondamentalement différent de développer des applications web ou mobiles classiques. Les approches qui fonctionnent bien sur le web (récupération des données au moment où elles sont nécessaires, état piloté par l’interface, configuration explicite par l’utilisateur, etc.) montrent souvent leurs limites, voire dégradent l’expérience, dans un environnement agentique.
Cet article résume les 15 leçons les plus importantes que nous avons tirées du développement d’applications ChatGPT en conditions réelles. Nous expliquons ensuite comment nous les avons intégrées dans Skybridge, un framework open source destiné à la communauté, et dans un Skill Codex pour aider les développeurs à imaginer, développer, tester et livrer des applications bien plus rapidement.
Le problème à trois corps
Avec les applications web classiques, les choses étaient simples : il n’y avait qu’un utilisateur et une interface. Dans une App ChatGPT, un troisième corps entre en jeu : le modèle.
L’une des principales difficultés du développement pour ChatGPT consiste à gérer la circulation des informations au sein de ce trio. Si un utilisateur clique sur un bouton « Sélectionner » dans votre widget, l’interface se met à jour visuellement. Mais le modèle, qui pilote la conversation, n’en sait rien tant que vous ne lui transmettez pas explicitement ce contexte. Si l’utilisateur demande ensuite « Donnez-moi plus de détails sur ce produit » , le modèle ne sait absolument pas quel produit l’utilisateur regarde.
Nous appelons cela l’ asymétrie du contexte : chaque corps ne connaît qu’une partie du système, et aucun n’en a une vue d’ensemble. Pour créer de bonnes applications ChatGPT, il ne s’agit pas de tout synchroniser, mais de décider quelles informations partager, quand les partager et qui doit y avoir accès. Résoudre ce problème fait toute la différence entre une application peu pratique et une expérience agentique fluide.
1. Tout le contexte n’a pas vocation à être partagé
Notre premier réflexe a été de « tout partager partout, tout simplement ». Cela s’est révélé être l’une de nos premières erreurs.
En pratique, les différentes parties d’une App ChatGPT ont souvent besoin de vues volontairement différentes d’un même état. Pourquoi ?
- Pour les performances : les widgets de l’interface ont souvent besoin de bien plus de données que le modèle ne devrait jamais en avoir besoin. Dans une application de réservation de voyages, il peut s’agir d’images, de variantes tarifaires ou d’options préchargées. Envoyer toutes ces données au modèle augmenterait la consommation de tokens, la latence et le bruit cognitif.
- Pour la logique : certaines informations doivent rester asymétriques par conception. Dans l’une de nos premières applications, le jeu d’enquête Murder in the Valleys , le modèle doit connaître l’identité du meurtrier pour jouer correctement son rôle, tandis que l’interface et l’utilisateur doivent l’ignorer. Dans un jeu de type Time’s Up, c’est l’inverse : l’interface montre le mot secret à l’utilisateur, tandis que le modèle ne doit pas le connaître.
La leçon n’était donc pas de « toujours tout synchroniser », mais de décider explicitement qui doit savoir quoi. Nous avons formalisé cette distinction en utilisant différents champs dans les sorties d’outils :
| Champ | Rôle | Visible par |
|---|---|---|
| structuredContent | Données typées pour le widget et le modèle | Le widget et le modèle (via les fonctions toolOutput et callTool) |
| _meta | Métadonnées de la réponse | Le widget uniquement, masquées au modèle |
Par exemple, pour le jeu Time’s Up, nous transmettions le mot secret uniquement au widget, dans le champ _meta, laissant le modèle le deviner à partir des indices de l’utilisateur.
2. Le chargement différé se prête mal aux applications d’IA
Venant du développement web, nous avions le réflexe du chargement différé : récupérer les données au clic de l’utilisateur, charger les détails à la demande et réduire au minimum le volume de données transmis au départ.
Dans ChatGPT, la logique s’inverse : les appels d’outils entraînent des délais, souvent de plusieurs secondes, en raison de l’isolation dans un bac à sable à des fins de sécurité et du raisonnement du modèle.
En pratique, nous avons appris à charger un maximum de données dès le départ : en envoyer autant que possible dans la réponse initiale de l’outil, puis alimenter le widget via window.openai.toolOutput. Cela rendait presque toujours l’expérience plus rapide et plus réactive.
Bien sûr, si le widget peut récupérer des données en toute sécurité depuis un point de terminaison d’API public et n’a pas besoin de partager d’informations avec le modèle, vous pouvez toujours y utiliser des appels XHR classiques. Mais la plupart du temps, il est souhaitable que le modèle puisse appeler les outils de manière autonome pour préserver le caractère conversationnel de l’expérience.
3. Le modèle doit savoir ce qui se passe dans l’interface
Un problème subtil mais crucial se pose lorsque l’utilisateur interagit avec un widget (par exemple, en sélectionnant un produit précis dans une liste), puis pose une question dans la discussion. Si le modèle ne sait pas à quelle partie de l’interface l’utilisateur fait référence, il ne pourra pas répondre correctement.
Pour cela, nous avons utilisé window.openai.setWidgetState(state), qui permet de stocker des données d’état précises. Celles-ci sont ajoutées au contexte du modèle lors de l’interaction suivante entre l’utilisateur et le modèle.
À mesure que les applications gagnaient en complexité, nous avons constaté que nous ajoutions setWidgetState à de nombreux endroits pour permettre au modèle de suivre la navigation. Nous avons donc décidé d’introduire une approche déclarative pour décrire le contexte de l’interface. Au lieu de mettre à jour le modèle de manière impérative à chaque interaction, nous ajoutons un attribut data-llm directement aux composants :
<div
data-llm={
selectedTab === "details"
? "User is viewing product details"
: "User is viewing reviews"
}
>
Pour automatiser ce fonctionnement, nous avons créé un plugin Vite qui extrait ces attributs et met automatiquement à jour widgetState. Le modèle reçoit ainsi le contexte pertinent de l’interface au bon moment, sans que les développeurs aient à synchroniser manuellement chaque interaction.
Vous trouverez ce plugin Vite, ainsi que de nombreux autres conseils présentés dans cet article, dans le framework open source que nous avons créé pour partager nos enseignements avec la communauté.
4. À chaque type d’interaction son API
Les applications ChatGPT font intervenir plusieurs voies de communication entre le widget, le serveur et le modèle. Ces voies ne sont pas interchangeables : chacune répond à un type d’interaction différent.
L’une des principales leçons du développement d’applications ChatGPT est de définir clairement ces voies de communication et de choisir délibérément le mécanisme qui prend en charge chaque partie de l’expérience.
Voici comment on peut représenter ces échanges :

Ces leçons posent les bases d’une App ChatGPT : comment le contexte est partagé, comment le modèle prend connaissance de ce qui se passe et comment les différentes interactions se propagent dans le système. La section suivante s’appuie sur ces bases pour examiner leurs implications pour la conception des interfaces.
Réinventer les interfaces pour l’IA
Les applications ChatGPT constituent un environnement entièrement nouveau. Nous avons donc vite appris à mettre de côté nos idées préconçues sur les interfaces et à exploiter pleinement les nouvelles possibilités. Cette section présente les principes de conception d’interface que nous avons dû assimiler, et ceux que nous avons dû remettre en question, pour créer des applications efficaces.
5. L’interface doit s’adapter aux différents modes d’affichage et à leurs contraintes
Les applications ChatGPT ne se limitent pas à une seule disposition. Selon la manière et le moment où elles sont appelées, un même widget peut s’afficher dans trois modes différents.
Les applications peuvent s’afficher directement dans la conversation , en incrustation (PiP) par-dessus celle-ci, ou en plein écran lorsqu’elles ont besoin de plus d’espace. Si les modes PiP et plein écran permettent de créer des interfaces plus riches, ils ajoutent aussi des éléments d’interface superposés que le widget ne contrôle pas. Il est essentiel de tenir compte des zones de sécurité propres à chaque appareil, comme celle du bouton de fermeture toujours présent sur mobile, pour éviter que le contenu soit tronqué et optimiser les interactions.
Au fil du temps, nous avons dégagé des principes pour choisir le mode d’affichage adapté :
| Présentation | Quand l’utiliser | |
|---|---|---|
| Dans la conversation | Mode d’affichage par défaut. Le widget reste dans l’historique de la conversation. | pour des interactions rapides |
| Plein écran | Le widget occupe tout l’écran, avec la barre de discussion en bas. | si votre widget est complexe et a besoin de beaucoup d’espace (par exemple, pour des cartes) |
| Incrustation | Même taille que dans la conversation, mais le widget reste affiché par-dessus celle-ci | si votre widget reste pertinent pour la suite de la conversation après sa génération |
6. La cohérence de l’interface compte dans un environnement intégré
Au début, nous nous demandions quelle liberté visuelle une App ChatGPT pouvait se permettre. Cette nouvelle interface devait sembler familière aux utilisateurs et rester cohérente, tant au sein de nos propres applications qu’avec l’écosystème ChatGPT environnant. Contrairement à un produit autonome, un widget s’intègre dans une interface existante, où les incohérences visuelles sautent immédiatement aux yeux.
Heureusement, l’OpenAI Apps SDK UI Kit nous a fourni une base claire.
Basé sur Tailwind CSS, ce kit fournit des composants prêts à l’emploi, des icônes et des tokens de design conformes au système de design de ChatGPT. Il nous a permis d’avancer rapidement tout en veillant à ce que nos widgets s’intègrent naturellement et restent visuellement cohérents avec le reste de l’interface, même lorsque nous créions des composants personnalisés, par exemple pour notre intégration Mapbox.
7. Le langage naturel comme premier moyen de filtrage
Les tableaux de bord traditionnels reposent sur des barres latérales remplies de cases à cocher et de curseurs de plage. Dans une interface agentique, c’est souvent un retour en arrière. Quand les utilisateurs peuvent exprimer directement leur intention en langage naturel, par exemple « Des destinations ensoleillées en Europe pour moins de 200 $ », les obliger à manipuler plusieurs commandes de l’interface complique le parcours. Ils devraient pouvoir simplement formuler leur demande.
Nous avons donc choisi de nous passer de filtres dans la plupart de nos applications. Au lieu d’une barre latérale proposant des options de filtrage et de tri, nous fournissons au modèle une liste de valeurs (LOV) pour les paramètres de nos outils.
Le modèle peut ainsi utiliser directement le message de l’utilisateur en entrée, sans avoir à « deviner » les options disponibles. Autrement dit, il peut faire correspondre directement le langage naturel aux exigences de l’API de notre backend. Si un utilisateur dit « ensoleillé », le modèle sait qu’il doit appeler l’outil avec weather="sunny".
8. Les fichiers ouvrent la voie à des interactions plus riches
En créant des applications plus complexes, nous avons compris que les fichiers ne devaient pas être considérés comme des entrées secondaires. Dans les applications ChatGPT, ils peuvent permettre de nouvelles interactions. Au lieu de commencer par des formulaires ou des filtres, le parcours peut partir d’un élément que l’utilisateur possède déjà.
Par exemple, dans une application de commerce en ligne, un utilisateur peut importer la photo d’un produit dans la discussion, demander au modèle de l’identifier, puis poursuivre directement dans le widget pour trouver des produits correspondants ou en découvrir d’autres.
Cela devient possible en laissant les fichiers circuler des deux côtés du système. Côté modèle, les outils peuvent exploiter directement les fichiers importés dans la discussion via openai/fileParams, ce qui permet au modèle de raisonner sur des images ou d’autres ressources fournies par l’utilisateur. Côté interface, les widgets peuvent également manipuler directement des fichiers avec window.openai.uploadFile et window.openai.getFileDownloadUrl, afin de demander l’importation de fichiers au cours du parcours utilisateur ou de générer des fichiers que les utilisateurs peuvent télécharger et réutiliser.
Passage en production
Lorsque les applications dépassent le stade du développement local, de nouvelles questions se posent en matière de sécurité, de configuration et d’outillage. C’est l’objet de cette troisième série de leçons.
9. Les CSP sont le nouveau CORS
Pour des raisons de sécurité, OpenAI affiche les applications dans une iframe imbriquée dans une autre iframe. Les politiques de sécurité du contenu (CSP) sont un mécanisme natif d’isolation des iframes. Cette configuration les applique strictement, ce qui se manifeste souvent par le classique « ça marche en local, mais ça casse en production ».
En développement web traditionnel, une politique permissive peut parfois suffire. Avec l’Apps SDK, il faut être d’une grande précision.
Dans le manifeste de l’application, cela implique de déclarer soigneusement les domaines autorisés pour chaque type d’interaction :
| Champ | Rôle | Exemple | Erreurs courantes |
|---|---|---|---|
| connectDomains | Requêtes API et XHR | https://api.weather.com | Oublier l’API de préproduction en ne pensant qu’à celle de production. |
| resourceDomains | Images, polices, scripts | https://cdn.jsdelivr.net | Utiliser un CDN générique comme delivr.net sans l’ajouter à la liste des domaines autorisés |
| frameDomains | Intégration d’iframes | https://www.youtube.com | Intégrer une vidéo YouTube ou une instance Mapbox sans ajouter son domaine à la liste des domaines autorisés. |
| redirectDomains | Liens externes ouverts sans avertissement | https://app.alpic.ai | Oublier le domaine de paiement ou de rappel OAuth. |
Accorder dès le départ une attention prioritaire à la configuration des CSP nous a évité beaucoup de débogage en production par la suite.
10. De petits paramètres de widget ont un impact considérable
Au-delà des CSP, quelques paramètres du widget déterminent la répartition du contrôle entre le widget, le modèle et l’environnement hôte. Ils passent facilement inaperçus, mais fixent des limites essentielles pour la navigation, l’accès aux outils et la publication.
Limites liées à l’hôte et à la navigation
widgetDomainest obligatoire pour soumettre l’application. Il définit la destination par défaut du bouton « Ouvrir dans <App> » en mode plein écran et intervient dans l’autorisation des origines, puisque les widgets sont affichés sous<widgetDomain>.web-sandbox.oaiusercontent.com. Nous avons utilisésetOpenInAppUrlpour diriger les utilisateurs vers le chemin approprié selon le contexte.
Limites liées au modèle et aux outils
- Les annotations des outils doivent respecter les consignes de publication. Les paramètres tels que
readOnly,destructiveHintetopenWorldHintsont obligatoires et sont validés lors de la soumission. - La visibilité des outils compte : les outils que le modèle ne doit pas pouvoir appeler doivent être explicitement marqués comme privés.
Limites d’exécution du widget
widgetAccessibledétermine si le widget peut appeler lui-même des outils aveccallTool.
Pris séparément, ces paramètres semblent mineurs, mais ensemble, ils déterminent si une application se comporte correctement une fois publiée.
Accélérer les itérations
L’Apps SDK évolue rapidement, et nous avons pris plaisir à développer nos applications au fil de ses évolutions. Pour rendre le workflow de développement fluide et efficace, nous avons décidé de créer notre propre framework open source et de le partager avec la communauté. Voici quelques leçons qui vous aideront à éviter certaines difficultés de développement que nous avons rencontrées au début.
11. Itérer rapidement nécessite le rechargement à chaud
La vitesse d’itération a été l’un de nos premiers chantiers. La combinaison d’une mise en cache des ressources avec un TTL long et de l’utilisation de JSON-RPC pour transmettre ces ressources rend le mécanisme standard de rechargement à chaud des modules, tel qu’on le trouve dans Vite ou Next.js, incompatible avec les applications ChatGPT sans adaptation.
Après avoir consacré beaucoup de temps à comprendre le fonctionnement interne de Vite, nous avons créé un plugin Vite qui permet de recharger les widgets en direct dans ChatGPT. Ce plugin intercepte les demandes de ressources adressées au serveur MCP et injecte les mises à jour en temps réel dans l’iframe de ChatGPT. Voir une modification effectuée dans l’IDE se répercuter immédiatement dans ChatGPT a considérablement réduit le délai entre un changement et l’observation de son résultat.

12. Tous les tests n’ont pas besoin de se faire dans ChatGPT
Les tests dans ChatGPT restent la référence, mais pour les premières itérations, un émulateur local peut vous aider à avancer plus vite, surtout lorsque vous travaillez sur des définitions d’outils qui nécessitent de recharger l’application en mode développeur.
Pour accélérer les premières itérations, nous avons créé un émulateur local léger qui simule l’environnement hôte de ChatGPT, avec des outils de débogage et des journaux propres aux applications. Nous avons ainsi pu itérer sur l’état React et la mise en page en quelques millisecondes, en réservant les tests dans ChatGPT à la validation des interactions avec le modèle et des cas limites.
13. Les tests sur mobile nécessitent une prise en charge spécifique
Les tests sur mobile ont posé un autre problème : pour tester dans ChatGPT, il faut rendre le serveur local accessible via un tunnel, mais comme Vite utilise localhost par défaut, la même URL reste inaccessible depuis d’autres appareils.
Nous avons résolu ce problème en étendant notre plugin Vite pour prendre en charge la redirection de domaine sur les ports exposés par un tunnel. Cela a débloqué les tests sur les appareils iOS et Android et nous a permis d’intégrer la validation mobile à notre workflow habituel.
14. Des abstractions familières (comme les hooks React) accélèrent le développement frontend
L’Apps SDK offre des fonctionnalités puissantes, mais principalement via des API JavaScript de bas niveau. Comme nous utilisions React depuis longtemps, nous voulions retrouver des concepts que nous maîtrisions déjà.
Nous avons donc introduit des abstractions adaptées à React : des hooks comme useCallTool, useWidgetState et useLocale, ainsi que des mécanismes de gestion d’état plus avancés, comme createStore, basé sur Zustand, pour les flux de données complexes. Retrouver des pratiques frontend familières a réduit le code répétitif et rapproché le développement de widgets des workflows web modernes.
Transformer les leçons en Skill Codex
15. Transformez les leçons en outils réutilisables
À mesure que ces pratiques se retrouvaient dans plusieurs applications, il est devenu évident que les redécouvrir à chaque fois nous ralentissait. Pour rendre le développement d’applications ChatGPT plus rapide et plus prévisible, nous avons décidé d’intégrer directement ces leçons à nos outils, pour nous-mêmes, mais aussi pour la communauté.
Cela a donné naissance à deux projets complémentaires :
- Le Skybridge Framework : ce framework React open source regroupe de nombreuses approches décrites dans cet article sous forme de briques réutilisables, notamment nos hooks (
useCallTool,useToolInfo), les outils de développement (HMR et émulateur local) et l’attribut data-llm. - La Skill Codex chatgpt-apps-builder : nous avons créé une Skill Codex dédiée qui s’appuie sur le framework pour accompagner l’ensemble du cycle de vie d’une application :
- Recherche d’idées : réfléchir à la manière de rendre une application « agentique », au-delà d’un simple portage d’une application web.
- Génération de code : écrire simultanément le frontend React et le backend du serveur MCP, préconfigurés avec tous les modèles de conception UX et UI adaptés.
- Tests en local : démarrer les serveurs de développement et connecter les applications locales à ChatGPT pour itérer en temps réel grâce au rechargement à chaud.
- Assurance qualité et publication : effectuer des vérifications structurées de conformité aux consignes de soumission d’OpenAI, notamment la validation des CSP, la prise en compte des zones de sécurité et les tests en production.
- Déploiement de l’application : accompagner les dernières étapes nécessaires à la mise en ligne d’une application et à ses évolutions successives.
Pour installer et utiliser la Skill, utilisez simplement la commande suivante :
npx skills add alpic-ai/skybridge
Conclusion
Créer des applications ChatGPT exige de repenser la circulation du contexte, le comportement des interfaces et la collaboration entre utilisateurs et modèles. Nombre des leçons présentées dans cet article découlent des écarts entre les pratiques habituelles du développement web et les réalités des systèmes agentiques.
En partageant ces leçons et en les intégrant à notre framework open source et à notre skill Codex, nous espérons aider les équipes à passer moins de temps à redécouvrir les mêmes problèmes et davantage à explorer les possibilités de ce nouveau modèle d’interaction. Les applications ChatGPT les plus convaincantes ne seront pas de simples portages de produits existants, mais des expériences délibérément conçues autour de cette nouvelle approche qui place l’IA au premier plan.