If you run an online school or course platform, manually tracking attendance and progress is a drag on your growth. Hours wasted in spreadsheets, late updates, students asking about their status while you dig through emails and files. We at Meteora Web see this every day in projects that come from SMEs in Southern Italy: training schools, corporate academies, content creators selling courses. And we have a concrete solution. You don’t need a €50,000 enterprise software. You need the right data structure, automated logging, and dashboards that speak the language of numbers. Let’s start with the problem you’re facing right now.
How to automate attendance logging in a course platform?
The first distinction is between synchronous attendance (live lesson) and asynchronous progress (on-demand modules). For live classes you need a mechanism that automatically marks entry – via login, a “Join” button click, or QR code scanning. For asynchronous courses, “attendance” is replaced by activity completion: video watched, document read, quiz passed.
Data structure for attendance and completions
A solid register relies on two main tables: attendance and progress. Here’s a simple SQL schema we use in our Laravel projects:
CREATE TABLE attendance (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
student_id BIGINT NOT NULL,
course_id BIGINT NOT NULL,
lesson_id BIGINT,
event_type ENUM('live', 'recorded') DEFAULT 'live',
started_at DATETIME NOT NULL,
ended_at DATETIME,
duration_seconds INT,
source_ip VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (student_id) REFERENCES users(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
CREATE TABLE progress (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
student_id BIGINT NOT NULL,
course_id BIGINT NOT NULL,
activity_type ENUM('video', 'quiz', 'document', 'assignment'),
activity_id BIGINT NOT NULL,
status ENUM('started', 'completed', 'failed') DEFAULT 'started',
score DECIMAL(5,2),
completed_at DATETIME,
FOREIGN KEY (student_id) REFERENCES users(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);
Insert a row in attendance when the student joins a live session. For recorded modules, log to progress with activity_type and activity_id pointing to the specific content.
Sponsored Protocol
Endpoint to log attendance
If you use PHP/Laravel, a starting point for a live lesson:
public function attend(Request $request): JsonResponse
{
$validated = $request->validate([
'course_id' => 'required|exists:courses,id',
'lesson_id' => 'required|exists:lessons,id',
'event_type' => 'in:live,recorded',
]);
$attendance = Attendance::create([
'student_id' => auth()->id(),
'course_id' => $validated['course_id'],
'lesson_id' => $validated['lesson_id'],
'event_type' => $validated['event_type'] ?? 'live',
'started_at' => now(),
]);
return response()->json(['message' => 'Attendance logged', 'attendance_id' => $attendance->id], 201);
}
Immediate action: implement a “Log attendance” button on the frontend that calls this endpoint. Add a cron job to close attendance sessions automatically 30 minutes after lesson starts.
Sponsored Protocol
How to calculate student progress reliably?
Not all content weighs the same. An introductory lesson is not worth a final exam. Progress must be weighted.
Weighted progression algorithm
Assign a weight to each activity. For example: video (1), quiz (2), assignment (3). Then for each course calculate:
SELECT
s.id AS student_id,
c.id AS course_id,
ROUND(SUM(p.completed_weight) / SUM(p.total_weight) * 100, 2) AS progress_percent
FROM students s
JOIN progress p ON s.id = p.student_id
WHERE p.course_id = ?
GROUP BY s.id, c.id;
In Laravel/Eloquent use relationships and accessors:
public function getProgressAttribute(): float
{
$activities = $this->course->activities()->withPivot('completed_at', 'score')->get();
$totalWeight = $activities->sum('weight');
$completedWeight = $activities->filter(fn($a) => !is_null($a->pivot->completed_at))
->sum('weight');
return $totalWeight > 0 ? round($completedWeight / $totalWeight * 100, 2) : 0;
}
Immediate action: assign a weight to each activity in the database. Display the percentage on the student dashboard. Add a “completed” flag only when 100% is reached.
Sponsored Protocol
Which metrics to monitor for teaching effectiveness?
The register isn’t just for the student – it helps you understand if the course works. We at Meteora Web worked with vocational schools where the dropout rate was high. We found that cross-referencing attendance and quiz scores revealed which modules were critical.
Essential KPIs
- Completion rate – percentage of students finishing the course.
- Average attendance per lesson – indicates if timing or format is off.
- Average dwell time – on videos, if too high it signals unclear content.
- Score distribution – a cluster of low scores shows a tough topic.
Integrate these into an automatic weekly report or live dashboard. Example query for average attendance:
SELECT lesson_id, COUNT(*) / (SELECT COUNT(DISTINCT student_id) FROM enrollments WHERE course_id = ?) AS avg_attendance
FROM attendance
WHERE course_id = ?
GROUP BY lesson_id;
Immediate action: create a materialized view in the database that aggregates these metrics overnight. Expose via an endpoint /api/courses/{id}/analytics.
Sponsored Protocol
How to ensure GDPR compliance in attendance and progress data?
A register contains sensitive data: timestamps, IP addresses, study times. GDPR requires limited retention and explicit consent. We know this well, coming from accounting: attendance data must be treated like a book entry – kept only as long as necessary.
Quick best practices
- Collect consent for processing navigation data during enrollment.
- Anonymize IP addresses after 30 days (e.g., zero out last octet).
- Set a cron job to delete
attendancerows older than 12 months (unless tax obligations apply). - Never export raw data via email; use password-protected dashboards.
For details, refer to the official GDPR documentation.
How to integrate attendance and progress with payment and certification?
A real case: a client of ours offers paid courses and issues certificates only if attendance exceeds 80% and all activities are completed. We linked attendance and progress tables to the certification module. When the student meets criteria, the certificate is automatically generated (PDF with digital signature).
Eligibility logic
public function checkCertificateEligibility($studentId, $courseId): bool
{
$attendanceRate = Attendance::where('student_id', $studentId)
->where('course_id', $courseId)
->count() / Lesson::where('course_id', $courseId)->count();
$progress = Student::find($studentId)->courses()->find($courseId)->pivot->progress;
return $attendanceRate >= 0.8 && $progress >= 100;
}
Immediate action: add a certificate_issued column to the enrollments table. Run a daily job checking for students who have met requirements and send the certificate email.
Sponsored Protocol
What to do next
- Define attendance and completion rules for each course type (live vs asynchronous).
- Structure your database with
attendanceandprogresstables as shown above. - Implement API endpoints to log events and calculate real-time progress.
- Build a dashboard for teachers and students with essential KPIs (average attendance, completion percentage, module bar chart).
- Integrate with the certification module using the threshold logic.
This guide is part of our series on the platform for online courses and schools. If you prefer not to build everything from scratch, we can do it for you – custom software that already speaks your numbers. Contact us.
Zenith Academy & EdTech is the all-in-one platform to run your business — clients, scheduling, deadlines, invoicing and WhatsApp reminders, all from your browser. No installation required.
Discover Zenith Academy & EdTech →