matt-daneshvar/laravel-survey
Laravel Survey helps you build and manage surveys in Laravel. Create questionnaires with multiple question types, collect and store user responses, and use included models, migrations, and validation for a simple survey API.
This release (v0.6.0) is a maintenance drop: the only change is widened framework constraints for Laravel 13.x. There are no API, schema, or config changes, so if you are on Laravel 10–12 nothing you have written will break — composer update matt-daneshvar/laravel-survey is safe. The only reason to treat it as a deliberate upgrade step is if you pin illuminate/* or orchestra/testbench versions and need the widened constraint to resolve.
First steps for a new install:
composer require matt-daneshvar/laravel-surveyphp artisan vendor:publish --provider="MattDaneshvar\Survey\SurveyServiceProvider"
php artisan migrate
User (or any answerable model):
$user->surveys()),$user->answeredSurveys() / answers).
Read the README for the exact trait names in your installed version — they have been renamed across releases.tinker, not in a controller. A survey is just a record with questions(), and each question carries content, type, options, and a rules array.The fastest first use case is a single-page feedback form (NPS + free text): create one survey with two questions, render them in a Blade loop, validate using each question's rules, then persist answers. That exercises the whole package — schema, validation, persistence — in about 40 lines.
Define surveys as code, not clicks. Keep a SurveyDefinitions class or a seeder that is idempotent (firstOrCreate on the survey name, updateOrCreate on a stable question key). Surveys are content that needs to exist in dev, staging, and prod, and rebuilding them by hand is the single biggest source of "works on my machine" pain with this package.
Drive validation from the question's rules column. The package stores validation rules as a JSON array per question, so the canonical pattern is to build a rules array keyed by question id:
$rules = $survey->questions->mapWithKeys(fn ($q) => ["answers.{$q->id}" => $q->rules ?? []]);
$data = validator($request->all(), $rules)->validate();
This keeps validation next to the question definition instead of duplicated in a FormRequest. For radio/select questions, append Rule::in($question->options) — the stored rules rarely include the option whitelist, and without it a client can post any string.
One Blade partial per question type. type is a free-form string (text, textarea, radio, select, number…). The package does not ship views, so you own rendering. Map type to resources/views/surveys/questions/{type}.blade.php with a @includeIf fallback — this is the cleanest extension point and it stops you writing a switch in three places.
Wrap submission in a transaction. Creating a submission plus N answers is multi-row; a half-saved submission will poison your reporting queries. Wrap it and pair it with an idempotency guard (unique index on survey_id + user_id, or a client-supplied submission UUID) so a double-click does not create two entries.
Use the traits for scoping and authorization. $user->surveys() gives you ownership; back it with a policy ($this->authorize('view', $survey)) and a can:answer,survey middleware so you never leak a survey by id. For teams, add a team_id to the published surveys migration and a global scope — the package has no tenancy concept of its own.
Reporting is plain Eloquent. Answers join back to questions, so aggregation is Answer::where('question_id', $q->id)->selectRaw('content, count(*) as total')->groupBy('content')->get() for choice questions, and avg() over a numeric question for ratings. Build one SurveyReport class rather than scattering these queries across controllers; you will reuse them for exports and dashboards.
Headless/API usage works fine — expose questions as an API resource, accept answers as {question_id: value}, and let the same validation map run server-side.
survey_model the foreign keys in your published tables still point at the old table names. Decide the model map once, before you publish.php artisan config:cache after editing the model map, or your swapped classes silently do nothing in production while working locally.Model. Extending the shipped model is the safe route; implementing the interface from scratch usually breaks relation assumptions.rules is JSON, not a string. A question saved with "required|max:255" instead of ["required","max:255"] will blow up in the validator. Always write arrays in seeders.mapWithKeys, a question with null rules becomes an empty array — harmless — but do not use array_filter on the rules map, or optional questions that were answered will bypass validation entirely.onDelete for answers. Deleting a survey in production is a data-loss operation; add a soft-delete or an archival flag to surveys rather than relying on the package default.questions, others as a relation. Dump $question->toArray() first before writing your form partial — do not assume.order column and ->orderBy('order'); relying on id will drift as soon as you edit a survey.RefreshDatabase plus the package factories, and assert on the stored answer rows rather than the HTTP response — that catches validation-map mistakes that a 302 redirect hides.Survey::with('questions')->first()->toArray() gives you the full shape (types, options, rules) in one call, which is far faster than reading the migrations when a form field renders wrong.composer why-not matt-daneshvar/laravel-survey if resolution fails — the conflict will be your own pinned illuminate/support, not this package.How can I help you explore Laravel packages today?