Qu’est-ce qu’un plugin Bubble, et quand en développer un ?

Bubble offre plusieurs façons d’injecter du code : l’élément HTML, l’action « Run JavaScript », et le plugin. Les deux premiers dépannent ; le plugin est d’une autre catégorie. Un élément HTML fait une injection ponctuelle, non réutilisable et difficile à paramétrer. Un plugin, lui, est un citoyen de première classe de l’écosystème : il expose des propriétés configurables dans l’éditeur, des états lisibles par d’autres éléments, des évènements qui déclenchent des workflows, et des actions utilisables dans la logique de l’app.

On distingue trois types, qu’un même plugin peut combiner.

TypeCe qu’il fait
Élément visuelCrée un nouveau type d’élément (sélecteur de date, éditeur, pad de signature). Tourne entièrement dans le navigateur.
ActionAjoute une action de workflow, côté client (DOM, analytics) ou serveur (appels d’API, calcul lourd, secrets).
Connexion d’APIAjoute des connexions authentifiées, réutilisables et partageables entre applications.

On développe un plugin dès qu’un besoin est réutilisable, doit se configurer dans l’éditeur, exposer des états, tourner côté serveur, ou intégrer une bibliothèque tierce.

Le cycle de vie d’un élément : initialize, update, reset

C’est le cœur d’un élément, et la source numéro un de bugs. Bubble appelle trois fonctions à des moments précis.

initialize s’exécute une fois, quand l’élément devient visible : on y construit le DOM, on instancie les bibliothèques, on pose les écouteurs. On attache toujours son contenu via instance.canvas, jamais via document.body, sous peine de sortir du flux de rendu de Bubble.

update s’exécute à chaque changement de propriété, donc potentiellement plusieurs fois. C’est le piège classique : votre code doit être idempotent. On garde en mémoire la valeur précédente pour ne relancer une opération coûteuse que si elle a réellement changé.

function(instance, properties, context) {
  // update() peut se rappeler à chaque changement : on garde l’état
  if (instance.data.lastTitle === properties.title) return;
  instance.data.lastTitle = properties.title;

  loadContent(properties.title); // l’opération coûteuse, une seule fois
}

Un piège discret mais coûteux : quand vous enveloppez un accès aux données dans un try...catch, vous devez relancer l’erreur not ready. L’avaler laisse l’élément figé, sans jamais charger ses données, et sans erreur visible.

reset restaure l’état initial quand un workflow le demande (vider un formulaire, réinitialiser une carte).

Client ou serveur : où placer chaque action

La distinction est fondamentale, notamment pour la sécurité.

Côté client (navigateur)Côté serveur (Node.js)
Accès au DOM, aux API du navigateurPas de DOM, mais tout le runtime Node
Bibliothèques front-endModules npm
Inspectable par l’utilisateurExécution isolée, invisible du client
UI, analytics, tokenisationAppels d’API sécurisés, calcul lourd, secrets

Règle de fer : jamais de clé secrète dans une action côté client. Tout ce qui tourne dans le navigateur est inspectable via les DevTools. Les secrets vivent dans les Additional Keys, lus côté serveur via context.keys. Depuis l’API v4, les actions serveur tournent sur Node.js (Node 22 au moment où nous écrivons) et s’écrivent en Promises et async/await ; la syntaxe synchrone de l’ancienne v3 ne fonctionne plus.

Gérer les erreurs d’API, explicitement

Un plugin de production ne plante pas devant l’utilisateur : il échoue proprement. La bonne pratique est un helper fail() qui renvoie toutes les valeurs de sortie avec le bon type, sur tous les chemins, y compris celui de l’erreur.

async function(properties, context) {
  function fail(msg) {
    // chaque sortie déclarée, avec un type correct et jamais undefined
    return { success: false, error_message: msg, result_id: "" };
  }
  try {
    // 1. valider les entrées  2. appeler l’API  3. renvoyer le succès
  } catch (e) {
    var msg = e.message || "Erreur inconnue";
    if (e.statusCode) msg = "HTTP " + e.statusCode + " : " + msg;
    console.error("[MonPlugin] " + msg);
    return fail(msg);
  }
}

Pourquoi fail() plutôt que throw ? Un throw affiche à l’utilisateur une popup générique, sans recours. Un fail() lui rend un error_message structuré qu’il peut afficher ou traiter dans son propre workflow. Chaque message devrait répondre à trois questions : ce qui s’est passé, ce que l’utilisateur a fourni, et comment corriger.

Un mot sur le contrat de type : Bubble est strict. Une sortie déclarée « boolean » qui renvoie la chaîne "false", ou un champ texte qui renvoie undefined, fait planter l’action. D’où le helper qui garantit les types partout, y compris sur le chemin d’échec.

L’accessibilité, dès la conception

Quand un plugin produit de l’interface, il hérite des mêmes obligations qu’une application : utilisable au clavier, lisible par les technologies d’assistance, cibles suffisantes, états perceptibles autrement que par la seule couleur. Un bouton personnalisé doit répondre à Entrée et Espace, exposer un rôle, gérer le focus. Ce n’est pas une finition mais une contrainte de conception : un composant qu’on ne pilote qu’à la souris exclut une partie des utilisateurs de l’app finale, et engage la conformité de votre client au regard de l’European Accessibility Act.

Ce que la documentation de Bubble ne dit pas

Le vrai savoir-faire commence là où la doc s’arrête. Les actions serveur tournent sur AWS Lambda, avec des limites que Bubble ne documente pas : environ 128 Mo de mémoire, 30 secondes de délai total, aucun système de fichiers, aucune API navigateur. On code en conséquence : peu de dépendances (chaque module ajoute un risque de faille connue qui bloque la publication, et pèse sur les 128 Mo), les modules natifs de Node quand ils suffisent (crypto plutôt que uuid, fetch plutôt qu’axios), et console.error pour tracer, puisque c’est le seul niveau que les Server Logs capturent. Ce sont ces détails, invisibles dans l’éditeur, qui séparent un plugin qui marche d’un plugin qui tient en production.

Notre banc d’essai

On ne théorise pas : on conçoit, teste et documente nos propres plugins Bubble sur une app de démo publique. Scientific Calculators, Toggle Studio et Observable Plot Charts y vivent, éprouvés en conditions réelles avant publication. C’est là qu’on vérifie ce que cet article décrit : cycle de vie propre, erreurs explicites, accessibilité au clavier. Voir le laboratoire.


Un plugin à concevoir ? Parlons-en.