Dynamic Certificate Generation for Online Courses (LMS)

If you run an online course platform, you know the drill: a student completes a course, and they expect a professional certificate they can share on LinkedIn or print for their wall.

Building PDF certificates from scratch using PHP libraries like TCPDF or FPDF means wrestling with coordinates, fonts, and image placements. It is tedious and error-prone.

A better approach: design your certificate using HTML/CSS (which you already know), and let an API convert it to PDF. This tutorial shows you how to build an automated certificate generator using PHP and the free aPDF.io API.

Quick Start: The PHP Code

Here is a complete working example. We will break it down step by step afterward.
<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$apiToken = 'YOUR_API_TOKEN_HERE';
$apiUrl = 'https://apdf.io/api/pdf/file/create';

// Student data (from your database)
$student = [
    'name' => 'Sarah Johnson',
    'course' => 'Advanced PHP Development',
    'date' => 'December 5, 2025',
    'instructor' => 'John Smith',
    'certificate_id' => 'CERT-2025-001847'
];

// Certificate HTML template
$html = '
<!DOCTYPE html>
<html>
<head>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body {
            font-family: Georgia, serif;
            background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
            color: #fff;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
        }
        .certificate {
            width: 900px;
            padding: 60px;
            text-align: center;
            border: 3px solid #d4af37;
            margin: 20px;
            background: rgba(255,255,255,0.03);
        }
        .header { font-size: 14px; letter-spacing: 3px; color: #d4af37; }
        .title { font-size: 48px; margin: 20px 0; color: #d4af37; }
        .subtitle { font-size: 18px; margin-bottom: 30px; color: #ccc; }
        .student-name { font-size: 36px; font-style: italic; margin: 20px 0; }
        .course-name { font-size: 24px; color: #d4af37; margin: 20px 0; }
        .details { font-size: 14px; color: #999; margin-top: 40px; }
        .footer { display: flex; justify-content: space-between; margin-top: 50px; }
        .footer div { text-align: center; }
        .line { width: 150px; border-top: 1px solid #666; margin: 0 auto 10px; }
    </style>
</head>
<body>
    <div class="certificate">
        <div class="header">CERTIFICATE OF COMPLETION</div>
        <div class="title">Certificate</div>
        <div class="subtitle">This is to certify that</div>
        <div class="student-name">' . $student['name'] . '</div>
        <div class="subtitle">has successfully completed the course</div>
        <div class="course-name">' . $student['course'] . '</div>
        <div class="details">
            Completed on ' . $student['date'] . '<br>
            Certificate ID: ' . $student['certificate_id'] . '
        </div>
        <div class="footer">
            <div>
                <div class="line"></div>
                <div>' . $student['instructor'] . '</div>
                <div style="color:#666;font-size:12px;">Instructor</div>
            </div>
            <div>
                <div class="line"></div>
                <div>Academy Director</div>
                <div style="color:#666;font-size:12px;">Signature</div>
            </div>
        </div>
    </div>
</body>
</html>';

$client = new Client();

try {
    $response = $client->post($apiUrl, [
        'headers' => [
            'Authorization' => 'Bearer ' . $apiToken,
            'Accept'        => 'application/json',
            'Content-Type'  => 'application/json',
        ],
        'json' => [
            'html' => $html,
            'orientation' => 'landscape',
            'format' => 'a4',
            'margin_top' => '0',
            'margin_bottom' => '0',
            'margin_left' => '0',
            'margin_right' => '0'
        ]
    ]);

    $result = json_decode($response->getBody(), true);

    echo "Certificate generated!\n";
    echo "Download: " . $result['file'] . "\n";

} catch (\Exception $e) {
    echo "Error: " . $e->getMessage();
}

Step-by-Step Breakdown

1. Set Up Your Project

First, install Guzzle for making HTTP requests:
composer require guzzlehttp/guzzle

Then get your API token from aPDF.io (it's free).

2. The Certificate Template

The HTML template above creates a professional dark-themed certificate. Key design choices:

  • Landscape orientation: Certificates look better wide than tall
  • Zero margins: The design extends to the edges
  • Gold accents: The classic certificate color (#d4af37)
  • Dynamic data: Student name, course, date, and ID are injected from your database

3. The API Parameters

The magic happens in the API request. These parameters are key:

'orientation' => 'landscape',  // Wide format for certificates
'format' => 'a4',              // Standard paper size
'margin_top' => '0',           // Full-bleed design

The API accepts any valid HTML/CSS, so you can customize colors, add your logo via URL, or use custom fonts via Google Fonts.

Run the Script

php generate_certificate.php

Output

Certificate generated!
Download: https://apdf-files.s3.eu-central-1.amazonaws.com/abc123def456.pdf

That URL can be emailed directly to the student or saved to your database. Note: Download URLs are valid for 1 hour. If you need permanent storage, download the file and save it to your own storage.

Integrating with Your LMS

In a real LMS, you would trigger certificate generation when a student completes a course. Here is a simplified Laravel example:

// In your CourseController
public function complete(Course $course, User $user)
{
    // Mark course as completed
    $user->courses()->updateExistingPivot($course->id, [
        'completed_at' => now()
    ]);

    // Generate certificate
    $certificateUrl = $this->generateCertificate($user, $course);

    // Email to student
    Mail::to($user)->send(new CourseCertificate($certificateUrl));

    return redirect()->back()->with('success', 'Certificate sent!');
}

Next Steps

Now that you have automated certificate generation, consider these enhancements:
  • Add a QR Code: Embed a verification URL in the certificate HTML so employers can verify authenticity
  • Password Protect: Use the Password Protection endpoint to secure certificates with the student's ID
  • Compress for Email: Use the Compress endpoint to reduce file size before emailing
Ready to build?
Get Started for Free