Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Quiz Laravel Package

harishdurga/laravel-quiz

Laravel Quiz is a ready-to-use package for adding quizzes to your Laravel app. Create questions with multiple answers, let users take quizzes, and automatically score results. Includes migrations, models, and configurable options for quick setup.

View on GitHub
Deep Wiki
Context7

Getting Started

composer require harishdurga/laravel-quiz
php artisan vendor:publish --provider="Harishdurga\LaravelQuiz\LaravelQuizServiceProvider"
php artisan migrate

The service provider is auto-discovered. Publishing gives you the config and the migrations that create the package tables (quizzes, questions, options, plus participant/attempt/answer tables).

Add the trait to your User model so participants can be resolved:

use Harishdurga\LaravelQuiz\Traits\HasQuizzes;

class User extends Authenticatable
{
    use HasQuizzes;
}

Look at these first, in order:

  1. config/laravel-quiz.php — table names, model bindings, and any scoring/type settings.
  2. The published migrations — they are the real spec of the domain model (what a Quiz, Question, Option, Participant, and answer record actually are).
  3. src/Models/Quiz.php — the aggregate root; most of the useful behaviour (questions(), attempts/participants, publish/validity, score helpers) lives here or hangs off it.
  4. The README example block — it is short, and it is the fastest way to confirm current method signatures.

First use case — seed a two-question quiz:

use Harishdurga\LaravelQuiz\Models\Quiz;
use Harishdurga\LaravelQuiz\Models\Question;

$quiz = Quiz::create([
    'title'         => 'Laravel Basics',
    'slug'          => 'laravel-basics',
    'description'   => 'Short screening quiz',
    'is_published'  => true,
    'valid_from'    => now(),
    'valid_until'   => now()->addWeek(),
]);

$question = Question::create([
    'title'     => 'Which facade resolves the container?',
    'type'      => 'single_choice',
    'is_active' => true,
]);

$question->options()->createMany([
    ['name' => 'App',   'is_correct' => true],
    ['name' => 'DB',    'is_correct' => false],
    ['name' => 'Cache', 'is_correct' => false],
]);

$quiz->addQuestions([$question->id]);

Then in your controller: create/resolve the participant for $request->user(), record the submitted answers, and read the score back from the quiz. Confirm the exact addParticipant / answer-submission / getScore signatures against the README at the tag you installed before wiring the controller — those are the methods that have moved between releases.

Implementation Patterns

Wrap authoring in a transaction. Creating a quiz, its questions, and its options is three writes that users expect to be atomic. A DB::transaction() around the whole authoring action also makes it trivial to re-run in a seeder.

Treat questions as a reusable bank. The quiz↔question relation is a pivot, so author questions once and attach them to many quizzes. Keep question authoring in its own service (CreateQuestionAction) and let quiz authoring only attach IDs. This is what makes the package usable beyond a single hard-coded quiz.

Put the attempt lifecycle in a service, not a controller. The pattern that holds up:

class SubmitQuizAttempt
{
    public function __construct(private ValidateAnswers $validate) {}

    public function handle(Quiz $quiz, User $user, array $payload)
    {
        $quiz->load('questions.options');          // score needs options

        $this->validate->handle($quiz, $payload);   // package does not validate shape

        return DB::transaction(function () use ($quiz, $user, $payload) {
            $participant = $quiz->addParticipant($user);
            $participant->addAnswers($payload['answers']);

            return [
                'score' => $quiz->getScore($participant),
                'total' => $quiz->getMaxScore(),
            ];
        });
    }
}

Enforce the validity window yourself. is_published, valid_from, and valid_until are data, not middleware. Add a query scope and a policy:

// Quiz model or a local scope
public function scopeAvailable($query)
{
    return $query->where('is_published', true)
        ->where(fn ($q) => $q->whereNull('valid_from')->orWhere('valid_from', '<=', now()))
        ->where(fn ($q) => $q->whereNull('valid_until')->orWhere('valid_until', '>=', now()));
}

Validate answer payloads against question type. The package stores what you give it. A multiple_choice question will happily accept a single option, or an option belonging to a different question. Build a FormRequest that asserts: every question_id is attached to this quiz, every option_id belongs to that question, and the count matches the question type.

Use eager loading everywhere. Results pages and "review your answers" screens are the N+1 hotspots:

$quiz->load(['questions.options']);
$participant->load('answers.question', 'answers.option');

Own the HTTP layer. The package ships domain models, not controllers, routes, or views. That is a feature: build thin controllers + API Resources (QuizResource, QuestionResource, AttemptResultResource) so you control shape, auth, and versioning. Route-model-bind on slug rather than ID for shareable quiz links.

Gate with policies, not middleware on the package. A QuizPolicy covering view, attempt, and manage keeps authorization in one place and works identically for web and API.

Test with explicit factories. The package’s models generally don’t ship factories. Write your own in database/factories (or a test helper) that build a quiz with N questions and M options, including a known correct answer. Then score assertions become deterministic.

Gotchas and Tips

  • Publish before you migrate, and read the migrations before running them. The package creates an options table — a very common collision. If you already have one, either rename the package table in the migration (and update the model’s $table) or rename yours. Doing this after migrating means a rollback plus data surgery.
  • Confirm the trait namespace on install. Harishdurga\LaravelQuiz\Traits\HasQuizzes is the current location, but it has moved; if relations like $user->quizzes return nothing, the trait is the first thing to check, not the schema.
  • php artisan config:cache after publishing. If table names or model bindings appear ignored in production but work locally, it’s a stale config cache.
  • Validity is not enforced for you. is_published and the date columns are inert unless you scope them. The most common bug report on this package is “users can take expired quizzes.”
  • Multiple-choice scoring may be all-or-nothing. Verify partial-credit behaviour in getScore() before you promise it to a product owner. If you need per-option weighting or negative marking for wrong answers, extend the Quiz model (or compute the score in your own ScoreCalculator service) rather than fighting the built-in method.
  • Prevent duplicate submissions. Nothing in the package stops a double POST. Use a unique constraint on (participant_id, quiz_id, completed_at) or an idempotency key from the client. Retries on flaky mobile networks are the usual source.
  • Watch foreign keys on delete. Deleting a question that already has recorded answers will either fail or orphan rows depending on your migration settings. Prefer deactivating (is_active = false) over deleting once a quiz has attempts.
  • Slugs are your responsibility. Quiz::create() does not generate one. Generate it in an observer/mutator, and add a unique index; otherwise your route-model binding on slug will quietly return the wrong quiz.
  • Pin the version. The last release was August 2024. Check composer.json in the package for the Laravel constraint before upgrading your app — this is the most likely thing to block a Laravel major upgrade, and there is no guarantee of a quick patch.
  • Extension points are limited to overriding. You can subclass Quiz/Question and bind your subclass if the config exposes model keys; otherwise override by extending the model and updating the relationship ->using() or by swapping the class in a service provider. There are no events to hook, so wrap package calls in your own actions if you need audit trails.
  • Debugging scores: dump $quiz->load('questions.options') and compare option is_correct flags against the stored answers first. Nearly every “score is wrong” report is either a missing eager load or an option attached to the wrong question during seeding.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings