FREN

Loi 25 pour les OSBL : un générateur open-source en 200 lignes

Comprendre la Loi 25 du Québec et l'automatiser. Politique conforme générée par formulaire, export DOCX et PDF côté client.

TL;DR — La Loi 25 impose depuis 2022-2024 des obligations concrètes à tous les organismes québécois, y compris les OSBL : politique de confidentialité publiée, désignation d’un RPRP, registre d’incidents. La plupart n’ont rien. J’ai bâti un générateur de politique conforme en ~200 lignes de TypeScript : questionnaire de 10 questions, sortie Word + PDF 100% côté client, sans serveur, sans données envoyées nulle part. Voici comment ça fonctionne sous le capot.


La Loi 25 (officiellement : Loi modernisant des dispositions législatives en matière de protection des renseignements personnels) est entrée en vigueur en trois vagues : septembre 2022, septembre 2023, et septembre 2024. Chaque vague a ajouté des obligations plus contraignantes.

En 2026, la situation est simple : si votre OSBL a un site web avec un formulaire de contact, vous êtes concerné. Si vous utilisez Google Analytics, vous êtes concerné. Si vous avez des employés ou des bénévoles, vous êtes concerné.

La plupart des OSBL québécois n’ont toujours pas de politique publiée.

Les obligations minimales en 2026

Voici ce que la Loi 25 impose concrètement aux OSBL :

1. Politique de confidentialité publiée — Document accessible au public expliquant quels renseignements vous collectez, pourquoi, et comment vous les protégez.

2. Responsable de la Protection des Renseignements Personnels (RPRP) — Désigner une personne nominativement responsable. Pas de structure exotique requise — le directeur général peut l’être.

3. Registre des incidents — Documenter tout incident d’accès ou de divulgation non autorisé. Le registre est obligatoire même si vous n’avez aucun incident (vous devez pouvoir le prouver).

4. Consentement explicite — Pour tout renseignement sensible (santé, données financières, biométrie), le consentement doit être explicite, documenté, et révocable.

La CAI (Commission d’accès à l’information) peut imposer des amendes jusqu’à 25 M$ ou 4% du chiffre d’affaires mondial pour les cas graves. Pour les OSBL, les sanctions sont graduelles — mais l’absence totale de politique est un facteur aggravant.

Structure obligatoire d’une politique

Une politique conforme doit couvrir au minimum :

1. Identité de l'organisme et coordonnées du RPRP
2. Types de renseignements collectés (catégories)
3. Finalités de la collecte (pourquoi)
4. Base juridique du traitement
5. Durée de conservation
6. Partage avec des tiers (et qui ils sont)
7. Droits des personnes concernées
8. Mécanisme de plainte
9. Coordonnées pour exercer ces droits
10. Date d'entrée en vigueur

C’est du contenu conditionnel : un OSBL qui n’a pas de dons en ligne n’a pas besoin d’une section sur le traitement des données de paiement. Un OSBL sans employés n’a pas besoin de couvrir les dossiers RH.

L’approche générateur : questionnaire conditionnel

L’idée est simple : 10 questions → politique personnalisée. Les sections de la politique sont générées conditionnellement selon les réponses.

interface OsblProfile {
  nom: string;
  adresse: string;
  rprpNom: string;
  rprpEmail: string;
  collecteEmails: boolean;       // Formulaire de contact ?
  utiliseDons: boolean;          // Dons en ligne ?
  utiliseSousTraitants: boolean; // Hébergement externe, MailChimp, etc. ?
  conserveDosBenev: boolean;     // Dossiers bénévoles ?
  analyticsTiers: boolean;       // Google Analytics, Matomo, etc. ?
  hebergementHorsQc: boolean;    // Données stockées hors Québec ?
}

function generatePolicy(profile: OsblProfile): string {
  const sections: string[] = [
    generateHeader(profile),
    generateCollectionSection(profile),
    generateRetentionSection(profile),
    profile.utiliseSousTraitants ? generateThirdPartySection(profile) : '',
    profile.hebergementHorsQc ? generateTransferSection(profile) : '',
    generateRightsSection(profile),
    generateContactSection(profile),
  ];

  return sections.filter(Boolean).join('\n\n');
}

La fonction generateCollectionSection adapte son contenu selon ce que l’OSBL collecte réellement :

function generateCollectionSection(profile: OsblProfile): string {
  const items: string[] = [];

  if (profile.collecteEmails) {
    items.push(
      '**Adresses courriel** : collectées via le formulaire de contact pour ' +
      'répondre à vos demandes. Base : consentement.'
    );
  }

  if (profile.utiliseDons) {
    items.push(
      '**Données de paiement** : traitées par notre prestataire de paiement ' +
      '(Stripe / PayPal). Nous ne stockons aucune donnée de carte.'
    );
  }

  if (profile.conserveDosBenev) {
    items.push(
      '**Dossiers de bénévolat** : nom, coordonnées, disponibilités, ' +
      'conservés pendant la durée de l\'engagement et 2 ans après.'
    );
  }

  return `## Renseignements collectés\n\n${items.map(i => `- ${i}`).join('\n')}`;
}

Export DOCX et PDF côté client

Le point technique le plus intéressant : tout se passe dans le navigateur. Aucune donnée n’est envoyée à un serveur.

Pour le DOCX, la bibliothèque docx permet de générer des fichiers Word directement en JavaScript :

import { Document, Paragraph, TextRun, HeadingLevel, Packer } from 'docx';
import { saveAs } from 'file-saver';

async function exportDocx(content: string, filename: string): Promise<void> {
  const paragraphs = parseMarkdownToParagraphs(content);

  const doc = new Document({
    sections: [{
      properties: {},
      children: paragraphs,
    }],
  });

  const blob = await Packer.toBlob(doc);
  saveAs(blob, `${filename}.docx`);
}

function parseMarkdownToParagraphs(md: string): Paragraph[] {
  return md.split('\n').map(line => {
    if (line.startsWith('## ')) {
      return new Paragraph({
        text: line.slice(3),
        heading: HeadingLevel.HEADING_2,
      });
    }
    if (line.startsWith('**') && line.endsWith('**')) {
      return new Paragraph({
        children: [new TextRun({ text: line.slice(2, -2), bold: true })],
      });
    }
    return new Paragraph({ text: line });
  });
}

Pour le PDF, pdfmake offre une API similaire mais orientée layout :

import pdfMake from 'pdfmake/build/pdfmake';
import pdfFonts from 'pdfmake/build/vfs_fonts';

pdfMake.vfs = pdfFonts.pdfMake.vfs;

function exportPdf(content: string, orgName: string): void {
  const docDefinition = {
    content: parseMarkdownToPdfContent(content),
    defaultStyle: {
      font: 'Roboto',
      fontSize: 11,
      lineHeight: 1.4,
    },
    styles: {
      header: { fontSize: 16, bold: true, margin: [0, 0, 0, 8] },
      subheader: { fontSize: 13, bold: true, margin: [0, 12, 0, 4] },
    },
    info: {
      title: `Politique de confidentialité — ${orgName}`,
      author: orgName,
      subject: 'Politique de confidentialité Loi 25',
    },
  };

  pdfMake.createPdf(docDefinition).download(`politique-confidentialite-${orgName}.pdf`);
}

Ce que le générateur ne remplace pas

Soyons directs : ce générateur produit une base conforme pour les cas simples. Il ne remplace pas un avis juridique pour :

  • Les OSBL qui traitent des données sensibles (santé, situation familiale, convictions religieuses)
  • Les organismes subventionnés par le gouvernement qui ont des obligations additionnelles
  • Les situations impliquant un transfert de données vers des États sans niveau de protection adéquat

Pour ces cas, la politique générée est un point de départ — pas le document final.

La philosophie du projet : 80% des OSBL peuvent partir de cette base et l’adapter eux-mêmes. Les 20% restants ont besoin d’aide humaine, et ce générateur leur donne au moins le vocabulaire pour avoir une conversation productive avec un juriste.

Déploiement et maintenance

L’outil est déployé sur Cloudflare Pages — zéro serveur, zéro base de données, zéro coût d’infrastructure. Mise à jour via un PR GitHub + déploiement automatique.

La maintenance principale : mettre à jour les textes quand la CAI publie de nouvelles lignes directrices ou quand le règlement évolue. C’est un fichier de templates en Markdown — pas de code à toucher pour une mise à jour éditoriale.

Pour aller plus loin