A dynamic multi-step form builder built with Laravel, featuring conditional logic, custom validation, and comprehensive form management.
wizard-form/
โโโ app/
โ โโโ Http/Controllers/
โ โ โโโ FormController.php # Form management
โ โ โโโ FormStepController.php # Step management
โ โ โโโ FormFieldController.php # Field management
โ โ โโโ SubmissionController.php # Submission handling
โ โโโ Models/
โ โ โโโ Form.php # Form model
โ โ โโโ FormStep.php # Step model
โ โ โโโ FormField.php # Field model
โ โ โโโ FormSubmission.php # Submission model
โ โโโ Services/
โ โ โโโ FormService.php # Form business logic
โ โ โโโ ValidationService.php # Custom validation
โ โ โโโ ExportService.php # Data export
โ โโโ Policies/
โ โโโ FormPolicy.php # Authorization policies
โโโ database/
โ โโโ migrations/
โ โโโ create_forms_table.php
โ โโโ create_form_steps_table.php
โ โโโ create_form_fields_table.php
โ โโโ create_form_submissions_table.php
โโโ resources/
โ โโโ views/
โ โโโ forms/
โ โ โโโ index.blade.php # Form listing
โ โ โโโ create.blade.php # Form builder
โ โ โโโ show.blade.php # Form display
โ โ โโโ edit.blade.php # Form editor
โ โโโ submissions/
โ โ โโโ index.blade.php # Submission listing
โ โ โโโ show.blade.php # Submission details
โ โโโ admin/
โ โโโ dashboard.blade.php # Admin panel
โโโ routes/
โ โโโ web.php # Application routes
โโโ README.md # This file
# Clone the project
git clone <repository-url>
cd wizard-form
# Install dependencies
composer install
# Copy environment file
cp .env.example .env
# Generate application key
php artisan key:generate
# Configure database in .env
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=wizard_form
DB_USERNAME=root
DB_PASSWORD=
# Run migrations
php artisan migrate
# Install Laravel Breeze
composer require laravel/breeze --dev
php artisan breeze:install blade
# Install frontend assets
npm install
npm run build
# Create storage link
php artisan storage:link
# Start server
php artisan serve
class Form extends Model
{
protected $fillable = [
'title', 'description', 'slug', 'status', 'public',
'multiple_submissions', 'max_submissions', 'expires_at'
];
// Relationships
public function steps()
{
return $this->hasMany(FormStep::class)->orderBy('order');
}
public function submissions()
{
return $this->hasMany(FormSubmission::class);
}
public function user()
{
return $this->belongsTo(User::class);
}
// Scopes
public function scopeActive($query)
{
return $query->where('status', 'active');
}
public function scopePublic($query)
{
return $query->where('public', true);
}
}
class FormService
{
public function createForm($data)
{
$form = Form::create($data);
// Create default step
$form->steps()->create([
'title' => 'Step 1',
'order' => 1,
'required' => true
]);
return $form;
}
public function addFieldToStep($stepId, $fieldData)
{
$step = FormStep::findOrFail($stepId);
$field = $step->fields()->create([
'label' => $fieldData['label'],
'type' => $fieldData['type'],
'required' => $fieldData['required'] ?? false,
'options' => $fieldData['options'] ?? null,
'validation_rules' => $fieldData['validation_rules'] ?? null,
'order' => $step->fields()->count() + 1
]);
return $field;
}
public function processSubmission($formId, $data)
{
$form = Form::findOrFail($formId);
// Validate submission
$this->validateSubmission($form, $data);
// Create submission
$submission = $form->submissions()->create([
'data' => $data,
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent()
]);
// Send notifications
$this->sendNotifications($form, $submission);
return $submission;
}
}
class FormController extends Controller
{
protected $formService;
public function __construct(FormService $formService)
{
$this->middleware('auth');
$this->formService = $formService;
}
public function index(): View
{
$forms = auth()->user()->forms()
->withCount('submissions')
->latest()
->paginate(10);
return view('forms.index', compact('forms'));
}
public function store(StoreFormRequest $request): RedirectResponse
{
$form = $this->formService->createForm($request->validated());
return redirect()->route('forms.edit', $form)
->with('success', 'Form created successfully!');
}
public function show(Form $form): View
{
$form->load(['steps.fields', 'submissions']);
return view('forms.show', compact('form'));
}
}
class StoreFormRequest extends FormRequest
{
public function rules(): array
{
return [
'title' => 'required|string|max:255',
'description' => 'nullable|string|max:1000',
'status' => 'required|in:draft,active,inactive',
'public' => 'boolean',
'multiple_submissions' => 'boolean',
'max_submissions' => 'nullable|integer|min:1',
'expires_at' => 'nullable|date|after:now',
];
}
}
# Run tests
php artisan test
# Specific tests
php artisan test --filter=FormTest
class FormTest extends TestCase
{
use RefreshDatabase;
public function test_can_create_form()
{
$user = User::factory()->create();
$this->actingAs($user);
$response = $this->post('/forms', [
'title' => 'Test Form',
'description' => 'Test form description',
'status' => 'active',
'public' => true
]);
$response->assertRedirect();
$this->assertDatabaseHas('forms', ['title' => 'Test Form']);
}
public function test_can_submit_form()
{
$form = Form::factory()->create(['status' => 'active']);
$step = $form->steps()->create(['title' => 'Step 1', 'order' => 1]);
$field = $step->fields()->create([
'label' => 'Name',
'type' => 'text',
'required' => true
]);
$response = $this->post("/forms/{$form->id}/submit", [
'step_1' => [
'field_' . $field->id => 'John Doe'
]
]);
$response->assertRedirect();
$this->assertDatabaseHas('form_submissions', ['form_id' => $form->id]);
}
}
# Create Heroku app
heroku create wizard-form-laravel
# Configure environment variables
heroku config:set APP_KEY=base64:your-key
heroku config:set DB_CONNECTION=postgresql
heroku config:set FILESYSTEM_DISK=s3
# Deploy
git push heroku main
# Run migrations
heroku run php artisan migrate
# Clone on server
git clone <repository-url>
cd wizard-form
# Install dependencies
composer install --optimize-autoloader --no-dev
npm install && npm run build
# Configure environment
cp .env.example .env
php artisan key:generate
# Run migrations
php artisan migrate
# Optimize for production
php artisan config:cache
php artisan route:cache
php artisan view:cache
The wizard form can be extended with a REST API:
// routes/api.php
Route::apiResource('forms', FormApiController::class);
Route::post('forms/{form}/submit', [FormApiController::class, 'submit']);
Route::get('forms/{form}/submissions', [FormApiController::class, 'submissions']);
GET /api/forms
- List formsPOST /api/forms
- Create formGET /api/forms/{id}
- Get form detailsPOST /api/forms/{id}/submit
- Submit formGET /api/forms/{id}/submissions
- Get form submissionsgit checkout -b feature/AmazingFeature
)git commit -m 'Add some AmazingFeature'
)git push origin feature/AmazingFeature
)This project is licensed under the MIT License. See the LICENSE
file for details.
For any questions or issues:
Wizard Form - Dynamic multi-step form builder with conditional logic ๐งโโ๏ธ