Data Tables n8n : remplacer Google Sheets dans tes workflows
Crée ta première Data Table n8n, maîtrise l’upsert et la déduplication, puis migre un workflow Google Sheets sans coupure, limites réelles comprises.

Quelque part dans ton Google Drive, il y a une feuille de calcul qui s’appelle « BDD clients ». Personne n’a décidé qu’elle deviendrait la mémoire centrale de tes automatisations. C’est arrivé tout seul. Un workflow y ajoute les nouveaux inscrits. Un autre y lit les adresses pour les relances. Un troisième y coche les paiements reçus.
Puis un matin, les exécutions échouent les unes après les autres. Le message parle d’un quota dépassé, avec un code 429. Trois workflows ont voulu lire la feuille dans la même minute, et l’API de Google a fermé la porte. Rien n’est perdu, mais tout est en retard, et tu viens de découvrir que ta base de données n’en a jamais été une.
Les Data Tables n8n existent pour reprendre ce rôle. Une Data Table est un tableau stocké directement dans n8n, sur ton serveur, avec des colonnes typées et un nœud dédié pour lire et écrire dedans. Pas de compte externe, pas de quota d’API, pas de feuille partagée que quelqu’un peut trier par erreur. La fonctionnalité est arrivée avec la version 1.113 en septembre 2025, et elle est disponible aussi bien sur n8n Cloud qu’en auto-hébergé.
Dans cet article, on crée une première table, on apprend à écrire dedans sans jamais produire de doublons, puis on migre un vrai workflow depuis Google Sheets, sans interruption de service. On finira par la question honnête : dans quels cas Sheets reste le bon choix. Parce qu’il y en a.
Ce qu’il te faut
- n8n en version 1.113.1 minimum, 1.122.0 pour créer une table depuis un fichier CSV, d’après le changelog officiel
- un workflow existant qui lit ou écrit dans Google Sheets
- aucune base de données externe, c’est justement le sujet
- environ 45 minutes, migration comprise
Pourquoi ton Google Sheet finit par lâcher comme base de données
Avant de construire quoi que ce soit, il faut comprendre pourquoi la feuille craque, parce que chacune de ses fragilités correspond à une fonctionnalité précise des Data Tables. Il y en a quatre, et tu as probablement déjà rencontré au moins deux d’entre elles.
La première, c’est le quota. L’API Google Sheets autorise 60 lectures par minute et par utilisateur, et 300 par minute pour tout le projet. Tous tes workflows n8n passent par la même credential, donc par le même utilisateur. Trois workflows qui tournent en même temps, chacun avec quelques lectures, et la limite est atteinte. Google répond alors ceci :
Quota exceeded for quota metric 'Read requests' and limit
'Read requests per minute per user' of service 'sheets.googleapis.com'
Ce message est temporaire, le quota se recharge chaque minute. Mais pendant cette minute, tes exécutions échouent ou attendent, et une automatisation qui dépend de la minute à laquelle elle tourne n’est pas une automatisation fiable. Autant jouer ton pipeline au lancer de dés.
La deuxième fragilité, ce sont les types. Une cellule Sheets devine ce qu’elle contient. Colle « 0612345678 » dans une colonne, la feuille décide qu’il s’agit d’un nombre et le zéro initial disparaît. Le numéro devient 612345678, silencieusement, et ta campagne SMS part vers un numéro invalide. Le même mécanisme transforme « 1/2 » en date et une référence produit en notation scientifique. Un tableur qui devine, mignon en démo, mortel en production.
La troisième est plus sournoise : l’API de Sheets n’a pas de verrou. Deux workflows qui mettent à jour la même ligne au même moment, c’est la dernière écriture qui gagne, sans erreur, sans avertissement, sans trace. Tu ne le découvres que des semaines plus tard, en cherchant pourquoi un statut est revenu en arrière.
La quatrième est structurelle : un classeur est plafonné à 10 millions de cellules. Ça paraît immense, mais une feuille de 20 colonnes atteint ce plafond à 500 000 lignes, et la lenteur arrive bien avant la limite.
Aucun de ces quatre problèmes n’est un bug. Sheets est un tableur, un très bon tableur, et on lui demande d’être un serveur de base de données. Autant demander à un scooter de tracter une caravane : ça roule, jusqu’à la première côte. La suite consiste à rendre ce rôle à un outil qui l’assume.
Créer ta première Data Table
La réponse de n8n à ces quatre fragilités tient dans l’onglet Data tables de ton projet. D’après la documentation, tu y crées une table avec le bouton en haut à droite, soit vide (« From scratch »), soit depuis un fichier CSV qui génère la structure et les données d’un coup.
On va créer le registre de relances de signature, celui qui suit chaque document envoyé et décide qui relancer à J+3. C’est le registre d’état évoqué dans l’article sur l’automatisation de la signature électronique, qui attendait précisément cet outil. Ses colonnes :
| Colonne | Type | Contenu |
|---|---|---|
document_id |
String | l’identifiant de la soumission, fourni par le webhook |
email |
String | le destinataire |
statut |
String | envoye, relance, signe |
envoye_le |
Date | la date d’envoi initiale |
tentatives |
Number | le nombre de relances déjà parties |
signe |
Boolean | le verdict final |
Les quatre types disponibles sont Boolean, Date, Number et String, d’après la documentation des opérations de table. Pas de type JSON à ce jour : si tu dois stocker une structure, elle passera par une colonne String et c’est une des limites qu’on retrouvera plus bas. Remarque le choix pour statut : un String avec des valeurs convenues, parce que le type le plus important de cette table est ailleurs. tentatives en Number et signe en Boolean ne peuvent plus être abîmés par une faute de frappe, là où une cellule Sheets aurait accepté « deux » ou « oui » sans broncher.

Les pictogrammes de la ligne d’en-tête portent l’information qui compte : le T des trois colonnes de texte, le calendrier de envoye_le, le dièse de tentatives, la case à cocher de signe. Un coup d’œil suffit pour repérer une colonne mal typée, ce qu’aucun tableur ne t’offrira jamais.
Avant de quitter cet écran, un chiffre à garder en tête : l’ensemble des Data Tables d’une instance partage 50 Mo par défaut. Un avertissement s’affiche à 80 %, puis les insertions échouent une fois la limite franchie. En auto-hébergé, le plafond se règle par variable d’environnement :
# docker-compose.yml, service n8n
environment:
# 200 Mo au lieu des 50 par defaut
- N8N_DATA_TABLES_MAX_SIZE_BYTES=209715200
Relever ce plafond est un pansement, pas une stratégie. La documentation qualifie les Data Tables de stockage « léger à modéré », et ce cadrage est honnête : un registre de relances y tient pendant des années, un historique d’événements bruts le remplira en quelques mois.
Lire et filtrer avec le nœud Data Table
La table existe, il faut maintenant que les workflows lui parlent, et c’est le rôle du nœud Data Table. Son opération Get récupère des lignes selon des conditions, et la documentation des opérations de ligne liste huit comparateurs : Equals, Not Equals, Greater Than, Greater Than or Equal, Less Than, Less Than or Equal, Is Empty, Is Not Empty.
Pour la relance à J+3, la requête se lit presque en français : les lignes dont statut vaut envoye et dont envoye_le est antérieur à il y a trois jours. Le champ Must Match réglé sur All Conditions exige que les deux soient vraies à la fois. Return All ramène tout, ou bien tu poses une Limit avec un Order By sur envoye_le pour traiter les plus anciens d’abord.
Chaque ligne renvoyée devient un item n8n ordinaire :
{
"id": 42,
"document_id": "sub_8f3a21",
"email": "client@exemple.fr",
"statut": "envoye",
"envoye_le": "2026-07-24T09:15:00.000Z",
"tentatives": 1,
"signe": false
}
Le champ id appartient à n8n : cette clé primaire auto-incrémentée est ajoutée à chaque table, et elle servira pour les mises à jour ciblées. Le reste porte exactement les noms de tes colonnes, donc les expressions en aval s’écrivent comme d’habitude, {{ $json.email }} et rien de plus exotique. Tranquille.
Une propriété de Get mérite d’être posée dès maintenant, parce qu’elle surprend tout le monde une première fois : quand aucune ligne ne correspond, le nœud ne sort rien. Pas un item vide, rien du tout. On y reviendra dans les blocages, c’est le piège numéro un.
Upsert et déduplication, le cœur du sujet
Lire ne suffit pas, il faut écrire, et c’est en écrivant que Sheets te faisait le plus mal : les doublons. Le webhook de signature qui arrive deux fois, le formulaire soumis deux fois, la reprise après panne qui rejoue un lot déjà traité. Le nœud Data Table apporte deux réponses, chacune pour un usage précis.
La première est l’opération Upsert : si une ligne correspond aux conditions, elle est mise à jour, sinon elle est créée. Pour le registre, la clé naturelle est document_id. La configuration tient en trois choix : les Conditions (ici document_id Equals l’identifiant reçu), le Must Match, et le mapping des colonnes, manuel ou automatique par nom.

Deux détails de cet écran valent le détour. Le sélecteur de colonne annonce le type entre parenthèses, document_id (string), donc une comparaison entre un texte et un nombre se voit avant l’exécution. Et sous le champ Value, n8n affiche déjà le résultat de l’expression, sub_8f3a21 : tu sais ce que ta condition va réellement chercher sans lancer le workflow.
Avant de brancher un Upsert sur des données réelles, active une fois l’option Dry Run, disponible aussi sur Update et Delete. Elle simule l’opération et renvoie les lignes concernées avec leur état avant et après, sans rien modifier. C’est l’équivalent d’une répétition générale, et ça t’évitera le classique « je pensais que ma condition matchait une ligne, elle en matchait quarante ».
La seconde réponse est le duo If Row Exists / If Row Does Not Exist. Ces opérations ne modifient rien : elles laissent passer l’item tel quel si la condition est remplie, et ne sortent rien sinon. C’est un portier. Pour un webhook rejoué, le montage devient :
Le premier passage traverse et crée sa ligne. Le rejeu trouve la ligne existante, le nœud ne sort rien, et tout ce qui suit ne s’exécute simplement pas. Pas de condition à écrire en aval, pas de branche d’erreur : le doublon meurt à la porte. C’est ce qu’on appelle rendre un traitement idempotent, et c’est exactement la propriété qui manquait au pipeline de signature quand son webhook arrivait en double.
Le même registre rend un second service, sans effort supplémentaire : la journalisation. L’article sur le nœud Guardrails recommandait de tracer les blocages sans recopier le contenu bloqué. Une Data Table journal_blocages avec l’identifiant du message, le nom du garde-fou, le score et l’horodatage fait ce travail proprement, et l’absence de quota d’API veut dire que journaliser mille événements ne coûte rien. Mille écritures, zéro quota consommé, ça passe crème.
Migrer un workflow existant sans coupure
Tout est en place pour la vraie question : comment passer de la feuille actuelle, vivante, remplie, branchée sur des workflows en production, à la Data Table, sans arrêter le service et sans perdre une ligne. La réponse tient en une règle : on ne bascule jamais tout d’un coup. On fait vivre les deux en parallèle, puis on débranche.
L’étape 1 se fait dans la feuille, avant toute chose. Passe les colonnes sensibles au format « Texte brut » et répare ce que Sheets a déjà abîmé, les zéros de téléphone en tête. Un CSV exporte ce que la feuille affiche : si le zéro est déjà perdu à l’écran, il sera perdu dans le fichier, et aucun outil en aval ne pourra le deviner.
L’étape 3 utilise l’import CSV disponible depuis la 1.122.0 : la structure et les données arrivent d’un coup. Ouvre la table créée et vérifie chaque type de colonne avant d’aller plus loin. Une colonne de montants détectée en String se filtrera mal, et mieux vaut recréer la table maintenant que découvrir le problème dans un filtre trois semaines plus tard.
L’étape 4 est le cœur de la méthode. Dans chaque workflow qui écrit vers Sheets, ajoute un nœud Data Table en Upsert juste à côté du nœud Sheets, avec les mêmes données. Les deux destinations reçoivent tout, les workflows de production ne voient aucune différence, et tu peux comparer à froid. Pendant cette période, la feuille reste la référence : en cas d’écart, c’est la Data Table qu’on corrige.
L’étape 5 dure le temps de ta confiance, quelques jours suffisent en général. Compte les lignes des deux côtés, échantillonne quelques enregistrements récents, vérifie les colonnes typées. Quand les comptes tombent juste plusieurs jours de suite, bascule les lectures (étape 6), puis seulement les écritures (étape 7). À la fin, plus aucune pièce d’origine n’est en service alors que le système n’a jamais cessé de tourner, le paradoxe du bateau de Thésée version automatisation, ce vieux casse-tête philosophique du navire dont on remplace les planches une à une pendant qu’il navigue.
Garde la feuille en lecture seule un mois, par prudence, puis archive-la. Si quelqu’un la modifie encore à la main pendant ce mois, c’est une information précieuse : il existe un usage humain que ta migration n’a pas prévu, et la section sur les cas où rester sur Sheets te concerne directement.
Où vivent ces données sur ton serveur
Débrancher Google soulève une question qu’il faut regarder en face : la feuille était sauvegardée par Google, et maintenant ? La réponse se trouve dans le code source de n8n : chaque Data Table devient une vraie table SQL nommée data_table_user_<identifiant> dans la base de données principale de ton instance. String devient TEXT, Number devient DOUBLE PRECISION sur PostgreSQL et REAL sur SQLite, Boolean et Date deviennent leurs équivalents natifs.
La conséquence pratique est rassurante : une sauvegarde de la base n8n emporte tes Data Tables avec elle, credentials, workflows et registre de relances dans le même fichier de dump. Pas de procédure séparée à inventer.
La conséquence stratégique l’est un peu moins : si ton instance tourne encore sur SQLite, ton registre de production vit dans un fichier local, avec les limites de concurrence que ça implique. C’est un argument de plus pour la migration décrite dans l’article sur le passage de n8n à PostgreSQL, et si tu la fais, tes Data Tables déménageront avec le reste de la base.
Ce qui va te bloquer
Cinq situations reviennent systématiquement dans les premiers jours d’utilisation. Les voici avec leur symptôme exact et leur sortie.
Le Get vide qui éteint le workflow. Ta relance J+3 tourne, aucun document n’attend, le Get ne sort aucun item, et tout ce qui suit ne s’exécute pas. Tu observes là le comportement normal de n8n : pas d’item, pas d’exécution en aval. Le symptôme trompeur, c’est un workflow « vert » qui n’a en réalité rien fait, y compris pas envoyé le rapport quotidien qui devait dire « rien à relancer ». Si un nœud doit tourner même sans résultat, active Always Output Data dans les réglages du nœud Get, puis gère l’item vide dans un If.
La limite de 50 Mo atteinte. La documentation est claire sur la séquence : avertissement à 80 %, puis échec des insertions et des mises à jour une fois le plafond franchi. Le piège est que les lectures continuent de fonctionner, donc la moitié de tes workflows tourne normalement pendant que l’autre échoue. Surveille l’indicateur de taille dans l’onglet Data tables, et décide avant d’être au pied du mur : relever N8N_DATA_TABLES_MAX_SIZE_BYTES, purger les vieilles lignes, ou déménager vers une vraie base. Attendre la panne pour choisir, c’est choisir la panne.
Le filtre qui ne trouve rien à cause d’un type. Ta colonne tentatives a été créée en String par un import CSV pressé, et ton filtre Greater Than 2 ne remonte plus rien, parce que comparer des chaînes n’est pas comparer des nombres. Aucune erreur ne s’affiche, le résultat est juste vide. Le réflexe : au premier filtre qui renvoie moins que prévu, ouvre la table et regarde le type réel de la colonne, pas celui que tu crois lui avoir donné.
L’upsert qui matche plusieurs lignes. Ta condition d’upsert porte sur email, et un client a deux documents en cours : deux lignes correspondent, et l’opération les met à jour toutes les deux. La documentation du nœud dit bien « one or more rows », c’est un comportement assumé, pas un accident. La parade est double : choisir une clé réellement unique (document_id, pas email), et passer chaque nouvel upsert au Dry Run pour voir combien de lignes il touche avant de le laisser écrire.
Le zéro perdu avant même la migration. Tu importes ton CSV, tu vérifies ta Data Table, et les numéros de téléphone y sont déjà faux : 612345678 au lieu de 0612345678. La Data Table n’y est pour rien, le mal était fait dans la feuille, et l’export l’a fidèlement recopié. C’est pour ça que le nettoyage des types est l’étape 1 de la migration et pas une note de bas de page. Si les zéros sont déjà perdus, il faudra les reconstruire, et pour des numéros français à neuf chiffres restants, un 0 préfixé dans un nœud Code répare l’essentiel.
Les cas où il faut rester sur Google Sheets
Après deux mille mots à démonter la feuille de calcul, l’honnêteté oblige à inverser la charge : Sheets fait des choses qu’une Data Table ne fera jamais, et certains de tes usages doivent y rester. Mon critère tient en une phrase : une Data Table sert aux machines, une feuille sert aux humains. Dès qu’un humain doit lire, éditer, trier ou partager la donnée, la feuille gagne.
| Le besoin | Le bon outil | Pourquoi |
|---|---|---|
| Registre d’état lu et écrit par des workflows | Data Table | types stricts, pas de quota, idempotence |
| Déduplication et journaux | Data Table | If Row Exists, volumes modestes, zéro API |
| Tableau que l’équipe consulte et annote | Sheets | partage, commentaires, tri à la volée |
| Calculs, TCD, graphiques | Sheets | c’est littéralement son métier |
| Export à envoyer à un client ou un comptable | Sheets | tout le monde sait l’ouvrir |
| Données au-delà de quelques dizaines de Mo | PostgreSQL | ni Sheets ni Data Table ne sont dimensionnés pour |
La dernière ligne mérite son paragraphe, parce que c’est là que je vois le plus d’erreurs d’aiguillage. Quand une Data Table déborde, le réflexe ne doit pas être de retourner vers Sheets, ni de pousser la variable d’environnement à 2 Go en espérant que ça tienne. Un volume qui déborde du « léger à modéré » est le signe qu’il te faut une base PostgreSQL dédiée, requêtable, indexable, sauvegardable selon tes règles. La Data Table est une étape, pas une destination finale pour des données qui grossissent sans fin.
Et le cas hybride existe : la Data Table comme source de vérité, et un export hebdomadaire vers une feuille de lecture pour l’équipe. Les machines écrivent d’un côté, les humains lisent de l’autre, et personne ne trie la base de production par la colonne C un vendredi soir.
Matrice de contrôle avant de débrancher Sheets
Avant l’étape 7 de la migration, celle qui coupe les écritures vers la feuille, chaque ligne de ce tableau doit avoir été passée sur ton instance. Chaque test associe une entrée à un résultat que tu peux constater, pas à une impression.
| Entrée de test | Résultat attendu et observable |
|---|---|
| Le même webhook envoyé deux fois | une seule ligne dans le registre, tentatives inchangé |
| Deux exécutions simultanées du même workflow | pas de doublon, l’upsert converge sur la même ligne |
| Un Get dont les conditions ne matchent rien | le comportement choisi : rien, ou un item vide si Always Output Data est actif |
| Un upsert en Dry Run sur une clé volontairement sale | le nombre de lignes touchées s’affiche, aucune n’est modifiée |
| Une valeur du mauvais type dans un filtre | résultat vide constaté, et le type de la colonne vérifié dans l’onglet |
| La table poussée au-delà de 80 % en environnement de test | l’avertissement de capacité apparaît |
| Une restauration de sauvegarde de la base n8n sur une instance de test | le contenu des Data Tables est bien revenu avec le reste |
La dernière ligne est celle que tout le monde saute, et c’est pourtant la seule qui prouve que tu as vraiment remplacé Google : une sauvegarde que tu n’as jamais restaurée est une hypothèse, pas une sauvegarde.
Une fois ce registre en production, il devient naturellement la mémoire courte de tes autres automatisations : les agents y vérifient un état avant d’agir, les garde-fous y journalisent leurs blocages, les relances y trouvent leur file d’attente. C’est le même mouvement que pour le reste de la stack : rapatrier chez toi ce qui n’avait pas de raison de vivre chez un tiers. La mémoire du patron vit chez le patron, pas chez le voisin.
Et si tu hésites encore sur un cas limite, un volume bâtard entre la feuille et la vraie base, viens le décrire sur le hub lesnocodeurs : les seuils réels où une Data Table commence à souffrir se cartographient mieux à plusieurs qu’en solo.


