Widgets
Une intégration peut fournir des widgets : des blocs réutilisables que les utilisateurs insèrent dans leurs emails, popups, smart content ou tableaux de bord statistiques. Vous les déclarez avec le kit @shopimind/integration-kit-js, ShopiMind se charge du reste.
Rien à héberger de votre côté
Un widget est une déclaration (JSON) portée par votre intégration. ShopiMind le rend lui-même, côté serveur. Vous n'exposez aucune route de rendu ni iframe, vos templates ne touchent jamais le navigateur, et aucun code partenaire n'est jamais exécuté.
Les widgets se déclarent dans le tableau widgets de votre defineIntegration — un tableau de WidgetDeclaration typé contre le contrat du kit (@shopimind/integration-kit-js), miroir exact du modèle interne de ShopiMind : une déclaration non conforme ne compile pas.
import { defineIntegration } from '@shopimind/integration-kit-js';
import type { WidgetDeclaration } from '@shopimind/integration-kit-js';
const widgets: WidgetDeclaration[] = [/* … */];
export const myIntegration = defineIntegration({
// …
widgets,
});Le contrat WidgetDeclaration
Chaque déclaration partage un socle commun, puis se spécialise selon deux axes : render_type puis (pour le HTML) render_mode.
| Champ | Type | S'applique à | Rôle |
|---|---|---|---|
key | string | toutes | Identifiant/slug du widget (le champ est littéralement nommé key). |
name | WidgetLocalizedText | toutes | Nom affiché, localisé ({ fr?, en?, … }). |
description | WidgetLocalizedText | optionnel | Description localisée. |
icon_url | string | optionnel | Icône du widget. |
preview_image_url | string | optionnel | Image d'aperçu. |
targets | WidgetTarget[] | toutes (requis) | Où le déposer : email_template · popup · smart_content · stats_dashboard. |
render_type | 'image' | 'html' | toutes (requis) | Axe 1 : type de sortie. |
render_mode | 'static' | 'dynamic' | html uniquement | Axe 2 (omis pour image). |
image_url_template | string | image | URL d'image à templatiser. |
html_template | string | html + static | Votre HTML. {var=…} laissé littéral. Jamais de JS. |
css_template | string | html + static (optionnel) | Votre CSS, sanitisé au rendu. Jamais de JS. |
renderer_key | string | html + dynamic | Clé d'un renderer qui doit exister chez ShopiMind. |
default_width | number | optionnel | Largeur par défaut. |
config_schema | WidgetConfigSchema | optionnel | Panneau de configuration (voir plus bas). |
Les trois formes de rendu
| Forme | Champs | Rendu |
|---|---|---|
render_type: "image" | image_url_template | Une balise <img> construite à partir de votre URL. Pas de rendu serveur. |
render_type: "html" + render_mode: "static" | html_template (+ css_template) | Votre HTML/CSS, validé puis sanitisé (aucun JS). Compatible email. |
render_type: "html" + render_mode: "dynamic" | renderer_key | Réutilise un renderer ShopiMind existant (ex. spm_countdown). |
Ce que vous faites seul vs co-conçu avec ShopiMind
Vous pouvez livrer seul les widgets image et html + static : aucun changement de code côté ShopiMind. Un widget html + dynamic est co-conçu avec ShopiMind : votre déclaration ne porte qu'un renderer_key, et la classe WidgetRenderer correspondante doit être écrite et enregistrée dans le code de ShopiMind. Réservez le dynamic à une logique conçue avec nous (ex. spm_countdown).
Widget image
ShopiMind ne rend pas l'image côté serveur : l'éditeur substitue les placeholders dans image_url_template et émet la balise <img>. Les {var=…} restent littéraux dans l'URL et sont résolus par destinataire au moment de l'envoi.
{
"key": "promo_banner",
"name": { "fr": "Bannière promo", "en": "Promo banner" },
"targets": ["email_template", "popup"],
"render_type": "image",
"image_url_template": "https://votre-service/img?promo={widget.code}&u={var=contact.email}"
}Aucun secret dans une URL d'image
Seules les valeurs non sensibles de la config d'intégration alimentent {integration.*}. Un secret ne transite jamais dans une URL d'image ni dans un HTML rendu.
Widget HTML statique (render_mode: "static")
Votre html_template (+ css_template optionnel) est stocké avec la déclaration, puis substitué et sanitisé au rendu (aucun <script>, compatible email). Exemple réel (carte de fidélité) :
{
key: 'loyalty_card',
name: { fr: 'Carte de fidélité', en: 'Loyalty card' },
description: { fr: 'Solde de points, palier et n° de carte du client.', en: 'Points, tier and card number.' },
targets: ['email_template', 'popup', 'smart_content'],
render_type: 'html',
render_mode: 'static',
default_width: 320,
html_template:
'<table role="presentation" cellpadding="0" cellspacing="0" border="0" class="spm-loy"><tbody><tr>' +
'<td class="spm-loy__card"><div class="spm-loy__row">' +
'<span class="spm-loy__brand">{integration.brand_name}</span>' +
'<span class="spm-loy__tier">{var=custom_data.pos_profile.loyalty_tier}</span></div>' +
'<div class="spm-loy__pts">{var=custom_data.pos_profile.loyalty_points} <span>{widget.points_label}</span></div>' +
'<div class="spm-loy__card-no">CARTE {var=custom_data.pos_profile.loyalty_card}</div>' +
'</td></tr></tbody></table>',
css_template:
'.spm-loy{margin:0 auto}.spm-loy__card{background:{widget.bg};border-radius:14px;padding:20px 22px;color:{widget.fg}}',
config_schema: {
groups: [
{
label: { fr: 'Contenu' },
fields: [
{ key: 'points_label', type: 'text', default: 'points', label: { fr: 'Libellé points' } },
{ key: 'bg', type: 'color', default: '#3CB4A4', label: { fr: 'Couleur de fond' } },
{ key: 'fg', type: 'color', default: '#FFFFFF', label: { fr: 'Couleur du texte' } },
],
},
],
},
}Widget HTML dynamique (render_mode: "dynamic")
Vous réutilisez un renderer ShopiMind existant via renderer_key : aucun template, aucun script de votre part. La déclaration ne porte que la clé du renderer et son config_schema. Exemple réel (compte à rebours fidélité) :
{
key: 'loyalty_countdown',
name: { fr: 'Compte à rebours fidélité', en: 'Loyalty countdown' },
description: { fr: "Urgence : fin d'une offre fidélité / expiration des points.", en: 'Loyalty deadline countdown.' },
targets: ['email_template', 'popup', 'smart_content'],
render_type: 'html',
render_mode: 'dynamic',
renderer_key: 'spm_countdown',
default_width: 360,
config_schema: {
fields: [
{ key: 'endDate', type: 'datetime', label: { fr: "Date de fin de l'offre" } },
{ key: 'timezone', type: 'text', default: 'Europe/Paris', label: { fr: 'Fuseau' } },
{ key: 'labelDays', type: 'text', default: 'JOURS' },
{ key: 'digitBgColor', type: 'color', default: '#1a1a1a', strip_hash: true, label: { fr: 'Fond des chiffres' } },
],
},
}renderer_key doit exister chez ShopiMind
renderer_key: 'spm_countdown' ne fonctionne que parce que ShopiMind embarque et enregistre un renderer sous cette clé. Un renderer_key inconnu n'est pas rendu. Vous ne pouvez pas livrer seul un widget dynamic pour un renderer qui n'existe pas encore — il se conçoit avec ShopiMind.
Le rendu est toujours côté ShopiMind
Le rendu d'un widget est toujours effectué par ShopiMind, jamais par un appel au partenaire : il n'y a aucun webhook ni endpoint widget côté intégration — integration.widgets est de la donnée JSON pure.
image— pas de rendu serveur. L'éditeur substitue les placeholders dansimage_url_templateet émet la balise<img>.html+static— rendu côté serveur : ShopiMind substitue{widget.*}et{integration.*}(échappés), laisse{var=…}littéral, puis sanitise le HTML et le CSS. Jamais de JavaScript.html+dynamic— rendu côté serveur en résolvantrenderer_keydans le registre de renderers de ShopiMind. Le renderer s'exécute dans le processus de ShopiMind (code de confiance).
Vos templates ne quittent jamais le serveur
La déclaration publique exposée au navigateur ne contient ni html_template, ni css_template, ni renderer_key. Ces champs sont strictement côté serveur.
Placeholders dans le rendu
Quatre familles, toutes résolues côté serveur :
| Placeholder | Source | Résolu |
|---|---|---|
{var=…} | Données ShopiMind par destinataire — ex. {var=contact.first_name}, {var=shop.url}, {var=custom_data.<schema>.<champ>} | Par destinataire, au moment de l'envoi |
{widget.<champ>} | La config du widget choisie par l'utilisateur (son config_schema) | Au rendu (échappé) |
{integration.<champ>} | Les valeurs non sensibles de la config de l'intégration | Au rendu (échappé) |
{LANG_…} | Valeur traduite d'un champ translatable | Par langue du destinataire, à l'envoi |
Un champ active {var=…} avec supports_variables: true, et la traduction par langue avec translatable: true. Les valeurs sensibles de la config d'intégration sont exclues de {integration.*}.
Config posée par l'intégrateur
Un champ de config d'intégration déclaré owner: "integrator" est invisible côté marchand : c'est l'intégrateur qui pose sa valeur par boutique (via l'API, voir Configuration). Elle est résolue ici en {integration.<key>}, avec le default déclaré comme repli. Pratique pour une URL de tracking, un identifiant de compte, etc.
config_schema d'un widget
Le panneau de configuration d'un widget est décrit par config_schema. Les valeurs choisies par l'utilisateur sont résolues via {widget.<champ>}.
- Disposition :
fields(à plat) ougroups(chaque groupe = un onglet) pour la section « Configuration » ;style_groupspour une section « Styles » séparée. - Types de champ :
text·number·color·select·checkbox·datetime.
Champ (WidgetConfigField) :
| Clé | Rôle |
|---|---|
key | Identifiant du champ, résolu via {widget.<key>}. |
type | text · number · color · select · checkbox · datetime. |
default | Valeur par défaut. |
label | Libellé localisé. |
options | Pour select : tableau d'{ value, label? }. |
supports_variables | Autorise un {var=…} dans le champ. |
translatable | Texte traduit par langue (résolu via {LANG_…}). |
preview_value | Valeur d'exemple pour l'aperçu image quand le champ porte un {var=…}. |
strip_hash | type: color — émet le hex sans le # (pour certaines URLs d'image). |
visible_when | Visibilité conditionnelle : { field, in: [...] }. |
refresh_fields | Re-parse le panneau quand ce champ change (pilote les visible_when). |
À retenir
- Déclarez vos widgets dans
integration.widgets(WidgetDeclaration[]), typés contre le kit. - Choisissez les axes :
render_type: 'image'ou'html'; pour le HTML,render_mode: 'static'(votre template) ou'dynamic'(renderer_key). imageethtml+staticse livrent seuls — pas de changement de code ShopiMind.html+dynamicse conçoit avec ShopiMind : seul lerenderer_keyvoyage, la logique vit chez ShopiMind.- Le rendu est toujours côté ShopiMind, jamais un appel au partenaire ; vos templates ne touchent jamais le navigateur ; le statique est sanitisé (aucun JS) ; les
{var=…}restent littéraux et se résolvent par destinataire à l'envoi. - Enregistrement manuel : vos widgets voyagent dans le manifeste neutre que vous transmettez à ShopiMind (voir → Le manifeste).
→ Le kit en détail · Configuration · Le studio visuel · Le manifeste