Certificate System (PDF Generation) for LMS

Our company is engaged in the development, support and maintenance of sites of any complexity. From simple one-page sites to large-scale cluster systems built on micro services. Experience of developers is confirmed by certificates from vendors.
Development and maintenance of all types of websites:
Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Showing 1 of 1 servicesAll 2065 services
Certificate System (PDF Generation) for LMS
Medium
~3-5 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    847
  • image_website-sbh_0.png
    Website development for SBH Partners
    999
  • image_website-_0.png
    Website development for Red Pear
    451

Development of a Certificate System (PDF Generation) for LMS

A course completion certificate is a tangible achievement for students. Technically, it involves generating PDFs from templates with personal data, unique identification numbers, and QR codes for verification. While seemingly simple, the task has nuances: PDF quality, template customization, scaling during peak loads (semester end—thousands of certificates simultaneously).

Architecture

Student completes course → Grade >= passing_score?
  ↓ YES
Job queue (BullMQ/Celery)
  ↓
Certificate generator worker
  ├── Fetch student data + course data
  ├── Render HTML template
  ├── Generate PDF (Puppeteer/WeasyPrint)
  ├── Upload to S3
  ├── Save certificate record (DB)
  └── Send email with PDF link

Generation is asynchronous—students receive a notification when the certificate is ready, rather than waiting for a synchronous response.

PDF Generation: Tools

Puppeteer / Playwright—renders HTML to PDF via Chromium. The best option for complex designs with CSS, fonts, gradients:

const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
const page = await browser.newPage();

const html = await renderCertificateHtml({
  studentName: 'Ivan Petrov',
  courseName: 'React Professional',
  completionDate: '28 March 2026',
  certificateId: 'CERT-2026-001234',
  instructorName: 'Alexey Smirnov',
  qrCodeDataUrl: await generateQrCode(`https://example.com/verify/CERT-2026-001234`),
});

await page.setContent(html, { waitUntil: 'networkidle0' });
const pdf = await page.pdf({
  format: 'A4',
  landscape: true,
  printBackground: true,
  margin: { top: '0', right: '0', bottom: '0', left: '0' },
});

await browser.close();

WeasyPrint—Python library converting HTML/CSS to PDF. Faster than Puppeteer but supports complex CSS less well.

PDFKit (Node.js)—generates PDFs programmatically without HTML. Precise positioning control, but templates are harder to maintain.

Certificate Templates

HTML templates with Handlebars or Jinja2 stored in the database—instructors or administrators can change designs without deployment:

<!-- certificate-template.hbs -->
<!DOCTYPE html>
<html>
<head>
  <style>
    @import url('https://fonts.googleapis.com/css2?family=Playfair+Display:wght@700&family=Open+Sans&display=swap');
    body { margin: 0; width: 297mm; height: 210mm; font-family: 'Open Sans'; }
    .container { position: relative; width: 100%; height: 100%; }
    .background { position: absolute; width: 100%; height: 100%; }
    .content { position: relative; z-index: 1; padding: 40mm 30mm; text-align: center; }
    .student-name { font-family: 'Playfair Display'; font-size: 36pt; color: #1a1a2e; }
    .course-name { font-size: 18pt; color: #16213e; margin: 8mm 0; }
    .cert-id { font-size: 8pt; color: #666; position: absolute; bottom: 10mm; left: 15mm; }
    .qr-code { position: absolute; bottom: 10mm; right: 15mm; width: 25mm; }
  </style>
</head>
<body>
  <div class="container">
    <img class="background" src="{{backgroundUrl}}" />
    <div class="content">
      <p>This is to certify that</p>
      <div class="student-name">{{studentName}}</div>
      <p>has successfully completed the course</p>
      <div class="course-name">{{courseName}}</div>
      <p>{{completionDate}} · {{totalHours}} hours</p>
    </div>
    <div class="cert-id">ID: {{certificateId}}</div>
    <img class="qr-code" src="{{qrCodeDataUrl}}" />
  </div>
</body>
</html>

Data Model

CREATE TABLE certificates (
  id              UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  certificate_id  VARCHAR(50) UNIQUE NOT NULL, -- CERT-2026-001234
  student_id      UUID REFERENCES users(id),
  course_id       UUID REFERENCES courses(id),
  template_id     UUID REFERENCES certificate_templates(id),
  pdf_url         VARCHAR(2000),
  issued_at       TIMESTAMPTZ DEFAULT NOW(),
  revoked_at      TIMESTAMPTZ,  -- NULL if active
  revoke_reason   TEXT,
  metadata        JSONB DEFAULT '{}'  -- score, hours, instructor
);

QR Code Verification

A public verification page without authorization: GET /verify/{certificate_id}—displays certificate data and confirms authenticity.

app.get('/verify/:certId', async (req, res) => {
  const cert = await db.certificates.findOne({ certificateId: req.params.certId });
  if (!cert || cert.revokedAt) {
    return res.status(404).render('certificate-invalid');
  }
  res.render('certificate-valid', {
    studentName: cert.student.name,
    courseName: cert.course.name,
    issuedAt: cert.issuedAt,
  });
});

Scaling

Under peak load (semester end, 1000+ certificates), launch multiple workers in parallel. Puppeteer is memory-intensive (~200MB per browser instance)—use a pool of 3–5 browsers via puppeteer-cluster.

Timeline

Basic system with a single template, PDF generation via Puppeteer, S3 storage, and email delivery—4–5 days. Visual template editor for administrators and public verification—another 3–4 days.