PRODUIT

Pourquoi les limites de plans SaaS ne fonctionnent presque jamais (et comment on a fait autrement)

Temps de lecture: 8 min

Pourquoi les limites de plans SaaS ne fonctionnent presque jamais (et comment on a fait autrement)

"3 campagnes actives incluses dans le plan Starter."

C'est ce qu'il y a dans les conditions d'utilisation. Dans le code, rien ne l'empêche vraiment. Vous pouvez en créer 10, 20, 50. Le quota existe sur le papier. Pas dans le produit.

On appelle ça un quota fantôme. Et c'est un problème beaucoup plus courant qu'on ne le croit dans le monde SaaS.


Pourquoi les quotas fantômes existent

La raison est simple : implémenter de vrais quotas côté backend, c'est du travail. Il faut vérifier la limite à chaque action critique, renvoyer les bons messages d'erreur, gérer les cas limites, et s'assurer que ça tient sous charge.

Le chemin de moindre résistance, c'est d'afficher les limites dans l'interface et d'espérer que les utilisateurs les respectent. La plupart du temps, ils ne les voient même pas.

Résultat : des plans "Basic" qui donnent accès à tout le produit, une monétisation qui ne fonctionne pas, et des clients qui n'ont aucune raison d'upgrader.


La distinction qui change tout : frontend vs backend

Il y a deux endroits où vous pouvez appliquer une limite.

Côté frontend : vous masquez le bouton "Créer une campagne" quand l'utilisateur a atteint sa limite. Simple à implémenter. Inefficace en sécurité. N'importe quelle requête API directe contourne ça en 30 secondes.

Côté backend : à chaque appel API qui crée une entité (contact, deal, campagne...), le serveur vérifie le quota de l'organisation avant d'autoriser l'opération. Si la limite est atteinte, la requête est rejetée avec un message d'erreur explicite. Impossible à contourner.

Chez KinFlux, on a choisi le backend. Pas par idéologie — parce que c'est la seule approche qui fonctionne vraiment.


Comment on l'a construit

L'architecture repose sur un fichier central : src/lib/entitlements.ts.

Ce fichier définit, pour chaque plan (FREE / STARTER / GROW / PRO / ENTERPRISE), les quotas et les feature flags :

// Exemple simplifié
const PLANS = {
  FREE: {
    maxUsers: 1,
    maxContacts: 50,
    maxDeals: 5,
    features: {
      ai: false,
      portal: false,
      exports: false,
      whiteLabel: false,
      api: false,
    }
  },
  STARTER: {
    maxUsers: 2,
    maxContacts: 1000,
    maxDeals: Infinity,
    features: {
      ai: true, // basique
      portal: false,
      exports: true, // CSV uniquement
      whiteLabel: false,
      api: false,
    }
  },
  // ...
}

À chaque route API critique, avant d'autoriser l'opération :

// Dans /api/contacts/route.ts
const entitlements = getEntitlements(organization.plan);
const currentCount = await prisma.contact.count({
  where: { organizationId }
});

if (currentCount >= entitlements.maxContacts) {
  return apiError(403, "QUOTA_EXCEEDED", 
    "Vous avez atteint la limite de contacts de votre plan.");
}

Ce n'est pas de la magie. C'est juste rigoureux.


Le système de priorité

Il y a un cas que beaucoup d'outils ratent : que se passe-t-il quand vous voulez donner à un client spécifique plus que ce que son plan autorise ? Pour un test, pour un partenariat, pour un cas particulier.

On a intégré un système de priorité à trois niveaux :

  1. Mode dédié : si l'organisation est en déploiement dédié (contrat offline), les entitlements du plan sont ignorés. L'accès est total.
  2. Override organisation : depuis le panel super-admin, on peut forcer l'activation ou la désactivation d'une feature pour une organisation spécifique, indépendamment de son plan.
  3. Plan : dans tous les autres cas, c'est le plan souscrit qui détermine l'accès.

Ça permet de gérer des cas réels — un client en trial PRO, un partenaire à qui on offre l'API pendant 3 mois, une organisation en migration — sans modifier le code.


Les feature flags vs les quotas

Il y a deux types de limites fondamentalement différentes.

Les quotas sont des limites quantitatives : nombre de contacts, de deals, d'utilisateurs, de stockage. Ils s'incrémentent avec l'usage.

Les feature flags sont des accès binaires : soit vous avez accès au portail client, soit vous ne l'avez pas. Soit l'IA est activée, soit non.

Les deux doivent être vérifiés côté backend, mais la logique est différente.

Pour un quota, vous comptez les entités existantes à chaque création. Pour un feature flag, vous vérifiez le plan à chaque appel de la feature.

// Feature flag — portail client
async function assertCanUsePortal(organizationId: string) {
  const org = await getOrganization(organizationId);
  const entitlements = getEntitlements(org.plan);
  
  if (!entitlements.features.portal) {
    throw new ForbiddenError("Le portail client n'est pas inclus dans votre plan.");
  }
}

Ce que ça change pour la monétisation

Quand les quotas sont réels, les upgrades se font naturellement.

Un client qui atteint sa limite de 1 000 contacts reçoit un message clair dans l'interface : "Vous avez atteint la limite de contacts de votre plan Starter. Passez en Grow pour des contacts illimités." Avec un lien direct vers la page de billing.

Il n'a pas l'impression d'être bloqué arbitrairement. Il comprend qu'il a utilisé ce pour quoi il a payé, et que la suite est disponible.

C'est la différence entre de la friction frustrante et de l'upsell naturel.


La leçon

Construire de vrais entitlements côté backend demande du temps au départ. Mais c'est ce qui rend un modèle de monétisation crédible, à la fois pour les clients (ils savent exactement ce qu'ils paient) et pour le business (les plans correspondent à une réalité technique, pas juste à du marketing).

Si vous construisez un SaaS, faites-le dès le départ. Pas en retouche.


Vous voulez voir comment les entitlements KinFlux fonctionnent sur votre compte ? Créez un espace gratuit →



Prêt à tester le workflow ?

Lancez la démo KinFlux en 60 secondes puis créez votre espace si le parcours vous convient.