---
title: "Construire une extension d'application Shopify Sidekick : ce que la doc sous-estime"
description: "Guide développeur des extensions Sidekick : les trois couches, le contrat lien de ressource et app:, et les pièges qui nous ont coûté une journée."
url: "https://www.quotway.com/fr/blog/building-shopify-sidekick-app-extensions"
type: "blog post"
category: "Engineering"
published: "2026-07-04"
updated: "2026-10-07"
verified: "2026-10-07"
shopify_api_version: "2026-10"
audience: "Développeurs d'applications Shopify qui construisent une extension Sidekick"
scope: "Les extensions d'application Sidekick en API 2026-10 : les trois couches, le contrat lien de ressource et lien profond, le budget par application (20 outils, 5 intentions, 4 000 jetons, 1 seconde pour les données et 3 pour les actions) et l'état actuel du vocabulaire d'intentions"
locale: "fr"
source: "QuotWay - B2B Quote & Negotiation App for Shopify"
---

# Construire une extension d'application Shopify Sidekick : ce que la doc sous-estime

**En bref :** une extension d'application Sidekick comporte trois couches - une déclaration (`tools.json` plus un résumé d'une ligne au niveau de l'application), un runtime léger qui déclare les outils et appelle votre serveur, et votre API existante. La documentation explique les formes ; elle sous-estime la réalité opérationnelle. Les deux choses qui nous ont coûté le plus de temps : Sidekick n'achemine vers vos outils que si le `extensions_summary` de votre application tient sous une limite stricte, et un résultat ne devient *ouvrable* que si son lien profond est un champ `url` de premier rang - et non glissé dans `_meta`. Cet article est le parcours que nous aurions aimé lire en premier, issu de la livraison de l'intégration dans [QuotWay](/features/shopify-sidekick), notre application de devis B2B.

> **Mise à jour du 31 août 2026.** La limite sur les extensions d'action décrite plus bas a depuis été levée. Nous avons déposé une demande, et Shopify a ajouté le type d'intention `application/quote` à la plateforme en août 2026 : un devis dans une réponse Sidekick est désormais ouvrable. La suite, y compris ce que nous avions mal compris du fonctionnement des intentions, se trouve dans [nous avons demandé à Shopify un nouveau type d'intention Sidekick](/blog/shopify-sidekick-intent-types). Le reste de cet article est laissé tel qu'il a été écrit en juillet 2026.

## Ce qu'est réellement une extension d'application Sidekick

Sidekick est l'assistant IA de l'interface d'administration Shopify. Une **extension d'application** permet à Sidekick d'appeler *votre* application pour répondre à la question d'un marchand - « quels devis attendent une validation ? » - et d'afficher le résultat en ligne, sans que le marchand quitte la page.

Il en existe deux sortes, et la distinction compte avant d'écrire la moindre ligne de code :

| | **Extension de données** | **Extension d'action** |
|---|---|---|
| Cible | `admin.app.tools.data` | `admin.app.intent.link` / `admin.app.intent.render` |
| Rôle | Lire : chercher, consulter, résumer | Écrire : préparer un changement que le marchand confirme |
| Vocabulaire d'intentions | Aucun - outils libres | Vocabulaire Shopify **fixe** (email, ticket, review, …) |
| Livrable aujourd'hui ? | Oui, pour toute application | Seulement s'il existe un `type` d'intention correspondant |

Nous avons livré une **extension de données** avec 15 outils en lecture seule (rechercher des devis, indicateurs de pipeline, validations en attente, enseignements par entreprise, etc.). À l'époque, nous n'avons *pas* livré d'extension d'action - une limite qu'il valait mieux comprendre tôt (plus bas), et qui a depuis été levée.

## Le modèle mental : trois couches

Presque toutes nos erreurs sont venues d'une confusion entre ces couches. Gardez-les distinctes.

**1. La déclaration (ce que Sidekick lit pour décider s'il vous appelle).** Deux fichiers et une chaîne au niveau de l'application :

- `shopify.app.toml` reçoit un bloc `[sidekick]` avec un `extensions_summary` - une description en langage courant de *ce à quoi votre application sait répondre*. C'est le **routeur** : Sidekick le lit pour décider si une question donnée relève ou non de votre application.
- `tools.json` déclare chaque outil : `name`, `description`, `inputSchema`. Les descriptions sont ce qui permet à Sidekick de choisir *quel* outil appeler.
- `instructions.md` est une consigne remise à Sidekick sur la façon d'utiliser les résultats.

```toml
# shopify.app.toml
[sidekick]
extensions_summary = "QuotWay answers questions about B2B quotes: search and filter by status/buyer/company/amount, look up a quote's status and next step, list quotes pending approval, report pipeline analytics, and summarize B2B companies."
```

```toml
# extensions/sidekick-data/shopify.extension.toml
api_version = "2026-04"

[[extensions]]
name = "QuotWay quote tools"
handle = "quotway-sidekick-data"
type = "ui_extension"

  [[extensions.targeting]]
  module = "./src/index.js"
  target = "admin.app.tools.data"
  tools = "./tools.json"
  instructions = "./instructions.md"
```

**2. Le runtime (la glu en bac à sable).** Votre `module` s'exécute sans interface dans le bac à sable de Shopify. Il déclare chaque outil et, quand Sidekick en appelle un, interroge votre serveur et transpose la réponse dans la forme de résultat attendue. Gardez-le léger, sans logique métier :

```js
export default () => {
  shopify.tools.register("pending_approvals", async () => {
    const res = await fetch("/api/sidekick/quotes?status=awaiting_approval", {
      headers: { Accept: "application/json" },
    });
    const data = res.ok ? await res.json() : null;
    return { results: (data?.quotes ?? []).map(quoteLink) };
  });
};
```

**3. Le serveur (l'API réelle de votre application).** Le bac à sable interroge vos propres points d'entrée, sur le même domaine, et Shopify y attache le jeton de session du marchand : votre authentification, votre cloisonnement par locataire et vos contrôles de droits existants s'appliquent donc inchangés. Nous avons ajouté une petite famille de routes `/api/sidekick/*` qui réutilisent la même authentification par jeton de session et les mêmes droits que le reste de l'application :

```ts
export const loader = async ({ request }) =>
  withAdminShopContext(request, async ({ shopId, staff }) => {
    requirePermission(staff.role, "quote.view");
    // ...requête, mise en forme d'un DTO simple, JSON avec CORS
  });
```

Les appels du bac à sable sont **inter-origines** : chaque route répond donc à une requête préliminaire CORS (`OPTIONS → 204`) et renvoie des en-têtes CORS. Le guide de Shopify demande d'ajouter l'origine du bac à sable, `https://extensions.shopifycdn.com`, à votre liste d'origines autorisées ; un `Access-Control-Allow-Origin` générique fonctionne aussi et est sans risque ici, car l'authentification passe par un jeton de session en en-tête, et non par des cookies.

## Le contrat de résultat - et le champ qui rend les liens ouvrables

Les outils de données renvoient des **liens de ressource** au format Model Context Protocol :

```json
{
  "results": [
    {
      "type": "resource_link",
      "uri": "gid://application/quotway.quote/123",
      "name": "QW-1042 — Acme Co.",
      "mimeType": "application/vnd.quotway.quote",
      "_meta": { "status": "Proposal sent", "total": "USD 5,000.00" }
    }
  ]
}
```

Voici la chose la plus importante que nous ayons apprise, et c'est à peine une phrase dans la documentation : **`_meta` sert uniquement à l'affichage. Ce n'est pas une cible de navigation.** Nous avions d'abord mis le lien profond dans `_meta.url` : la fiche s'affichait correctement, mais cliquer dessus ne faisait rien. Sidekick n'avait aucun moyen d'*ouvrir* la ressource, parce que notre `uri` était un `gid://application/...` synthétique qu'il ne sait pas résoudre, et que la vraie URL était enfouie dans des métadonnées qu'il traite comme décoratives.

Le correctif est un champ **`url` de premier rang** sur le résultat, utilisant le protocole `app:` pour une application intégrée :

```js
function quoteLink(q) {
  return {
    type: "resource_link",
    uri: `gid://application/quotway.quote/${q.id}`,
    name: `${q.reference} — ${q.buyerName}`,
    mimeType: "application/vnd.quotway.quote",
    url: `app://app/quotes/${q.id}`, // ← de premier rang, ouvrable
    _meta: { status: q.statusLabel, total: q.total },
  };
}
```

Shopify résout `app://<chemin>` par rapport à l'URL de base de votre application intégrée et navigue directement dedans - sans handle de boutique, sans clé d'API, sans construction d'URL absolue. (Une URL absolue `admin.shopify.com/store/<boutique>/apps/<client_id>/<chemin>` fonctionne aussi, mais `app:` est plus simple et indépendant de la boutique.) Avec l'`url` sortie de `_meta`, « montre-moi le devis QW-1007 » renvoie désormais le résumé *et* un lien qui ouvre ce devis précis dans l'application.

Le guide des extensions de données de Shopify, relu le 7 octobre 2026, documente désormais deux formes de résultat distinctes : un lien de ressource (`uri`, `name`, `mimeType`), qui devient actionnable quand son `mimeType` correspond à un type d'intention enregistré, et un lien d'application intégrée, qui est une `url` seule. Le résultat combiné ci-dessus est ce que nous avons livré quand cet article a été écrit ; maintenant que `application/quote` existe, la voie de l'intention est celle qui est documentée - [l'article sur les types d'intention](/blog/shopify-sidekick-intent-types) montre le changement de deux lignes.

## Les pièges (ce qui nous a coûté une journée)

| Symptôme | Cause | Correctif |
|---|---|---|
| Sidekick répond avec des connaissances génériques ; **aucun appel n'atteint votre serveur** | `extensions_summary` dépasse la limite du validateur de déploiement et est silencieusement ignoré, si bien que le bloc `[sidekick]` ne s'enregistre jamais | Gardez le résumé sous la taille réellement imposée (pour nous, le plafond était de **500 caractères**, et non les « 256 jetons » que la documentation laisse entendre). Vérifiez dans vos journaux serveur les appels à `/api/sidekick` : zéro appel signifie *pas acheminé*, pas un bug serveur. |
| La fiche de résultat s'affiche mais le lien ne s'ouvre pas | Le lien profond est dans `_meta`, ou l'`uri` est un `gid://application/...` non résoluble | Mettez une URL `app://<chemin>` (ou absolue) dans un champ **`url` de premier rang** |
| « C'est cassé » juste après un déploiement | La disponibilité des outils Sidekick dépend de la session et de la propagation | Réessayez dans une **conversation Sidekick toute neuve** ; ne concluez pas à une panne depuis une session périmée |
| Les appels d'outils échouent en 404 ou en CORS en production | Vous avez déployé l'extension mais pas les routes serveur (ou l'inverse) | Déployez les **deux ensemble** : l'extension via `shopify app deploy`, les routes via votre hébergeur |
| Une poussée vers votre hébergeur ne déploie rien, en silence | (Propre à l'hébergeur) par exemple un réglage « exiger des commits vérifiés » qui annule les poussées non signées | Signez vos commits, pour que le serveur parte bien en même temps que l'extension |
| L'aperçu en ligne de commande se comporte différemment | Les outils passent par la véritable interface d'administration | **Testez dans le Sidekick de l'interface d'administration**, pas dans l'aperçu en ligne de commande |

Deux de ces points méritent un développement.

**Diagnostiquez l'acheminement avec vos propres journaux.** Quand Sidekick « ne marche pas », le signal le plus rapide est de savoir si votre serveur a été appelé. Nous avons cherché `/api/sidekick` dans les journaux de production et vu **zéro appel** - ce qui a immédiatement écarté le CORS, l'authentification et les bugs serveur, et pointé vers le routeur (`extensions_summary`). Un silence côté serveur signifie que la question n'a jamais été acheminée vers votre application.

**Les descriptions sont le produit.** L'acheminement de Sidekick est une décision de modèle de langage prise sur votre `extensions_summary` et les `description` de vos outils. Des descriptions vagues sont ignorées. Chargez-les des mots exacts qu'emploierait un marchand (« devis », « appels d'offres », « en attente de validation », « taux de conversion », « pipeline ») - tout en restant factuel. La politique de contenu de Shopify les analyse au déploiement *et* à l'exécution, et interdit les incitations à la vente, les comparaisons avec des concurrents, et **les consignes qui tentent d'orienter le comportement de Sidekick**. Nous avons dû supprimer une ligne d'`instructions.md` qui dictait à Sidekick ce qu'il devait dire : décrivez vos données, ne scénarisez pas l'assistant.

## La lecture est livrable aujourd'hui ; l'écriture est conditionnée

C'est la limite stratégique à anticiper. Une extension de **données** (lecture) fonctionne dès maintenant pour n'importe quelle application. Une extension d'**action** (écriture : créer, modifier, envoyer, convertir) se rattache à un vocabulaire d'intentions Shopify *fixe*, et `shopify app deploy` rejette un `type` d'intention qui n'y figure pas. Au moment de la rédaction, il n'existait pas de type `application/quote` : nous ne pouvions donc pas livrer d'action native « ouvrir ce devis depuis Sidekick ». Shopify l'a ajouté en août 2026 après notre demande, et au 7 octobre 2026 le guide des actions d'application de Shopify liste quinze types - onze `application/*` (ad, campaign, email, faq, loyalty-program, optimization-plan, quote, return, review, shipment, ticket) et quatre `shopify/*` (Customer, Discount, Order, Product). Les types `application/*` acceptent exactement `create` et `edit` ; `shopify/Customer`, `shopify/Order` et `shopify/Product` prennent `import` et `import+bulk`, et `shopify/Discount`, ajouté le 30 septembre 2026 pour les applications de réduction construites sur une Shopify Function, prend `create` et `edit`.

Le pont pragmatique - confirmé par Shopify sur le forum développeurs - est le champ `url` ci-dessus : vous ne pouvez pas *agir* depuis Sidekick, mais vous pouvez lui donner un lien profond ouvrable, pour que le marchand arrive sur la page exacte où agir, à un clic. Si vous êtes dans la même situation, la bonne démarche est de déposer une proposition pour votre type d'intention dans le dépôt de demandes [`app-intent-types`](https://github.com/Shopify/app-intent-types) de Shopify, et de livrer dès maintenant l'expérience lecture plus lien profond. Nous avons fait les deux.

## Des contraintes à retenir

- **Budget de réponse :** gardez chaque réponse d'outil sous **4 000 jetons**. Une extension de données doit répondre en **1 seconde** ; une action dispose de **3 secondes**. Ce sont des outils de lecture qui répondent à un tour de conversation : renvoyez ce qui a été demandé, pas tout votre graphe d'objets.
- **Budget par application : 20 outils sur l'ensemble de vos extensions, et 5 intentions.** C'est toute l'allocation, et ce sont les outils qui manquent en premier : une extension de données riche plus quelques outils de préparation d'action vous amènent dans les hauts de la vingtaine. Modélisez-les autour des vraies questions des marchands, pas autour de vos tables de base de données, et budgétez les outils avant de concevoir les intentions.
- **L'authentification est offerte :** le bac à sable réutilise la session de votre application ; appuyez-vous donc sur votre cloisonnement et vos contrôles de droits existants. Un outil de lecture doit être exactement aussi verrouillé que la page qu'il reflète.

## Un premier jalon raisonnable

1. Une extension de données, **deux outils** correspondant à de vraies questions (« chercher X », « consulter un X »).
2. Un `[sidekick] extensions_summary` sous la limite de taille, et des descriptions d'outils factuelles et riches en mots-clés.
3. Une glu légère dans le bac à sable qui interroge deux points d'entrée `/api/*` en réutilisant votre authentification existante et la requête préliminaire CORS.
4. Une `url` de premier rang sur chaque résultat, pour qu'il ouvre la bonne page dans l'application.
5. Déployez l'extension **et** le serveur ensemble ; testez dans la **véritable interface d'administration**, dans une conversation neuve.

C'est une journée de travail pour une intégration réellement utile - dont l'essentiel consiste à réutiliser l'API que vous avez déjà. Le difficile n'est pas le code : ce sont les trois ou quatre faits opérationnels ci-dessus.

Si vous êtes marchand plutôt que développeur : l'extension décrite ici est livrée dans QuotWay sous la forme de [Shopify Sidekick pour les devis B2B](/features/shopify-sidekick), sur tous les forfaits - voir les [forfaits et tarifs](/pricing).

## Pour aller plus loin

Revérifié sur les pages de Shopify le 7 octobre 2026 (les budgets ci-dessus viennent de la première, le vocabulaire du guide des actions d'application) :

- Shopify : [les extensions d'application Sidekick](https://shopify.dev/docs/apps/build/sidekick) et [exposer les données d'une application](https://shopify.dev/docs/apps/build/sidekick/build-app-data)
- Shopify : [créer des actions d'application pour Sidekick](https://shopify.dev/docs/apps/build/sidekick/build-app-actions) (les types d'intention pris en charge et les deux cibles d'intention)
- Shopify : le dépôt de demandes [`app-intent-types`](https://github.com/Shopify/app-intent-types) (proposez votre type d'intention d'action)
- L'article jumeau destiné aux marchands : [utiliser Shopify Sidekick pour gérer vos devis B2B](/blog/shopify-sidekick-b2b-quotes)
