{"id":18253578,"url":"https://github.com/bayareawebpro/laravel-multistep-forms","last_synced_at":"2026-04-01T18:18:35.013Z","repository":{"id":44545349,"uuid":"255453570","full_name":"bayareawebpro/laravel-multistep-forms","owner":"bayareawebpro","description":"Responsable Multistep Form Builder for Laravel","archived":false,"fork":false,"pushed_at":"2026-03-19T04:43:54.000Z","size":106,"stargazers_count":96,"open_issues_count":0,"forks_count":11,"subscribers_count":7,"default_branch":"master","last_synced_at":"2026-03-19T16:19:30.287Z","etag":null,"topics":["builder","forms","laravel","laravel-multistep-forms","multistep","multistep-forms","sessions","validation"],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bayareawebpro.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2020-04-13T22:18:23.000Z","updated_at":"2026-03-19T04:42:24.000Z","dependencies_parsed_at":"2023-12-25T02:42:36.799Z","dependency_job_id":"63eb6e42-3b2f-4aeb-9397-64335f4eba83","html_url":"https://github.com/bayareawebpro/laravel-multistep-forms","commit_stats":{"total_commits":71,"total_committers":1,"mean_commits":71.0,"dds":0.0,"last_synced_commit":"3a4ea415a6500d510d8aac7124ea6ebfc4cb1d9d"},"previous_names":[],"tags_count":19,"template":false,"template_full_name":null,"purl":"pkg:github/bayareawebpro/laravel-multistep-forms","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Flaravel-multistep-forms","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Flaravel-multistep-forms/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Flaravel-multistep-forms/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Flaravel-multistep-forms/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bayareawebpro","download_url":"https://codeload.github.com/bayareawebpro/laravel-multistep-forms/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bayareawebpro%2Flaravel-multistep-forms/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31290808,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-01T13:12:26.723Z","status":"ssl_error","status_checked_at":"2026-04-01T13:12:25.102Z","response_time":53,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["builder","forms","laravel","laravel-multistep-forms","multistep","multistep-forms","sessions","validation"],"created_at":"2024-11-05T10:07:20.556Z","updated_at":"2026-04-01T18:18:35.005Z","avatar_url":"https://github.com/bayareawebpro.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Laravel MultiStep Forms\n\n![](https://github.com/bayareawebpro/laravel-multistep-forms/workflows/ci/badge.svg)\n![](https://codecov.io/gh/bayareawebpro/laravel-multistep-forms/branch/master/graph/badge.svg)\n![](https://img.shields.io/github/v/release/bayareawebpro/laravel-multistep-forms.svg)\n![](https://img.shields.io/packagist/dt/bayareawebpro/laravel-multistep-forms.svg)\n![](https://img.shields.io/badge/License-MIT-success.svg)\n\n\u003e https://packagist.org/packages/bayareawebpro/laravel-multistep-forms\n\nMultistep Form Builder is a \"[responsable](https://laravel-news.com/laravel-5-5-responsable)\" class that can be returned from controllers.\n\n* Specify a view to use Blade or go headless with JSON for use with Javascript frameworks.\n* Configure the rules, messages and supporting data for each step with simple arrays.\n* Submit to the same route multiple times to merge each validated request into a namespaced session key.\n* Hook into each step **before** or **after** validation to interact with the form or return a response.\n\n## Installation\n\n```shell script\ncomposer require bayareawebpro/laravel-multistep-forms\n```\n\n### Example Usage\n\n\n\n```php\n\u003c?php\n\nuse BayAreaWebPro\\MultiStepForms\\MultiStepForm;\n\n// Render a view with data.\nreturn Form::make('my-form', [\n        'title' =\u003e 'MultiStep Form'\n    ])\n\n    // Namespace the session data.\n    -\u003enamespaced('my-session-key')\n\n    // Allow backwards navigation via get request. ?form_step=x\n    -\u003ecanNavigateBack(true)\n\n    // Tap invokable Class __invoke(Form $form)\n    -\u003etap(new InvokableClass)\n\n    // Before x step validation...\n    -\u003ebeforeStep(1, function (MultiStepForm $form) {\n        // Maybe return early or redirect?\n    })\n    // Before all step validation...\n    -\u003ebeforeStep('*', function (MultiStepForm $form) {\n        // Maybe return early or redirect?\n    })\n\n    // Validate Step 1\n    -\u003eaddStep(1, [\n        'rules' =\u003e ['name' =\u003e 'required'],\n        'messages' =\u003e ['name.required' =\u003e 'Your name is required.'],\n    ])\n\n    // Validate Step 2\n    -\u003eaddStep(2, [\n        'rules' =\u003e ['role'  =\u003e 'required|string'],\n        'data'  =\u003e ['roles' =\u003e fn()=\u003eRole::forSelection()] // Lazy Loaded Closure\n    ])\n\n    // Add non-validated step...\n    -\u003eaddStep(3,[\n       'data' =\u003e ['message' =\u003e \"Great Job, Your Done!\"]\n    ])\n\n    // After step validation...\n    -\u003eonStep(3, function (MultiStepForm $form) {\n        // Specific step, logic if needed.\n    })\n    -\u003eonStep('*', function (MultiStepForm $form) {\n        // All steps, logic if needed.\n    })\n   \n    // Modify data before saved to session after each step.\n    -\u003ebeforeSave(function(array $data) {\n    \n        // Transform non-serializable objects to paths, array data etc...\n        return $data;\n    })\n   \n    // Modify data before saved to session after each step.\n    -\u003eonComplete(function(MultiStepForm $form) {\n    \n        // Final submission logic.\n    })\n;\n```\n\n---\n\n### Make New Instance\n\nMake a new instance of the builder class with optional view and data array. You\nshould always set the `namespace` for the form session to avoid conflicts with\nother parts of your application that use the session store.\n\n* `GET` requests will load the form state and data for the saved current step or fallback to step 1.\n* `POST`,`PUT`,`PATCH` etc... will validate and process the request for any step and proceed to the next step.\n* `DELETE` will reset the session state and redirect back (blade), or return a `JsonResponse`.\n* Backwards navigation (via get param) can be enabled via the `canNavigateBack` method.\n\n```php\n\u003c?php\n\nuse BayAreaWebPro\\MultiStepForms\\MultiStepForm;\n\n$form = MultiStepForm::make('onboarding.start', [\n    'title' =\u003e 'Setup your account'\n]);\n\n$form-\u003enamespaced('onboarding');\n$form-\u003ecanNavigateBack(true);\n```\n\n---\n\n### Configure Steps\n\nDefine the rules, messages and data for the step. Data will be merged\nwith any view data defined in the `make` method and be included in the `JsonResponse`.\n\n** Use a `Closure` to lazy load data per-key.\n\n**Use an array**:\n\n```php\n$form-\u003eaddStep(2, [\n    'rules' =\u003e [\n        'role' =\u003e 'required|string'\n    ],\n    'messages' =\u003e [\n        'role.required' =\u003e 'Your name is required.'\n    ],\n    'data' =\u003e [\n        'roles' =\u003e fn() =\u003e Role::query()...,\n    ],\n])\n```\n\n**Or use an invokable class** (recommended)\n\n```php\nuse BayAreaWebPro\\MultiStepForms\\MultiStepForm;\n\nclass ProfileStep\n{\n    public function __construct(private int $step)\n    {\n        //\n    }\n    \n    public function __invoke(MultiStepForm $form) \n    {\n        $form-\u003eaddStep($this-\u003estep, [\n            'rules' =\u003e [\n                'name' =\u003e 'required|string'\n            ],\n            'messages' =\u003e [\n                'name.required' =\u003e 'Your name is required.'\n            ],\n            'data' =\u003e [\n                'placeholders' =\u003e [\n                    'name' =\u003e 'Enter your name.'\n                ]\n            ],\n        ]);\n    }\n}\n```\n\n```php\n$form-\u003etap(new ProfileStep(1));\n```\n\n---\n\n### BeforeStep / OnStep Hooks\n\nDefine a callback to fired **before** a step has been validated. Step Number or * for all.\n\n- Use a step integer, or asterisk (*) for all steps.\n- You can return a response from these hooks.\n\n```php\n$form-\u003ebeforeStep('*', function(MultiStepForm $form){\n    //\n});\n$form-\u003eonStep('*', function(MultiStepForm $form){\n    //\n});\n$form-\u003eonComplete(function(MultiStepForm $form){\n    //\n});\n```\n\n### Handle UploadedFiles\n\nSpecify a callback used to transform UploadedFiles into paths.\n\n```php\nuse Illuminate\\Http\\UploadedFile;\n\n$form-\u003ebeforeSave(function(array $data){\n    if($data['avatar'] instanceof UploadedFile){\n        $data['avatar'] = $data['avatar']-\u003estore('avatars');\n    }\n    return $data;\n});\n```\n\n### Reset / Clear Form\n\n- Ajax: Submit a DELETE request to the form route.\n- Blade: Use an additional submit button that passes a boolean (truthy) value.\n\n```\n\u003cbutton type=\"submit\" name=\"reset\" value=\"1\"\u003eReset\u003c/button\u003e\n```\n\n### JSON Response Schema\n\nThe response returned will have two properties:\n\n```json\n{\n  \"form\": {\n    \"form_step\": 1\n  },\n  \"data\": {}\n}\n```\n\n### Public Helper Methods\n\n#### stepConfig\n\nGet the current step configuration (default), or pass an integer for a specific step:\n\n```php\n$form-\u003estepConfig(2): Collection\n```\n\n#### getValue\n\nGet a field value (session / old input) or fallback:\n\n```php\n$form-\u003egetValue('name', 'John Doe'): mixed\n```\n\n#### setValue\n\nSet a field value and store in the session:\n\n```php\n$form-\u003esetValue('name', 'Jane Doe'): MultiStepForm\n```\n\n#### save\n\nMerge and save key/values array directly to the session (does not fire `beforeSaveCallback`):\n\n```php\n$form-\u003esave(['name' =\u003e 'Jane Doe']): MultiStepForm\n```\n\n#### reset\n\nReset the form state to defaults passing an optional array of data to seed.\n\n```php\n$form-\u003ereset(['name' =\u003e 'Jane Doe']): MultiStepForm\n```\n\n#### withData\n\nAdd additional non-form data to all views and responses:\n\n```php\n$form-\u003ewithData(['date' =\u003e now()-\u003etoDateString()]);\n```\n\n#### currentStep\n\nGet the current saved step number:\n\n```php\n$form-\u003ecurrentStep(): int\n```\n\n#### requestedStep\n\nGet the incoming client-requested step number:\n\n```php\n$form-\u003erequestedStep(): int\n```\n\n#### isStep\n\nIs the current step the provided step:\n\n```php\n$form-\u003eisStep(3): bool\n```\n\n#### prevStepUrl\n\nGet the previous step url.\n\n```php\n$form-\u003eprevStepUrl(): string|null\n```\n\n#### lastStep\n\nGet the last step number:\n\n```php\n$form-\u003elastStep(): int\n```\n\n#### isLastStep\n\nIs the current step the last step:\n\n```php\n$form-\u003eisLastStep(): bool\n```\n\n#### isPast,isActive,isFuture\n\n```php\n// Boolean Usage\n$form-\u003eisPast(2): bool\n$form-\u003eisActive(2): bool\n$form-\u003eisFuture(2): bool\n\n// Usage as HTML Class Helpers\n$form-\u003eisPast(2, 'truthy-class', 'falsy-class'): string\n$form-\u003eisActive(2, 'truthy-class', 'falsy-class'): string\n$form-\u003eisFuture(2, 'truthy-class', 'falsy-class'): string\n```\n\n--- \n\n### Blade Example\n\nData will be injected into the view as well as the form itself allowing you to access the form values and other helper\nmethods.\n\n```php\n\u003c?php\nuse BayAreaWebPro\\MultiStepForms\\MultiStepForm as Form;\n\n$form = Form::make('my-view', $data);\n$form-\u003enamespaced('onboarding');\n$form-\u003ecanNavigateBack(true);\n```\n\n```blade\n\u003cform method=\"post\" action=\"{{ route('submit') }}\"\u003e\n    \u003cinput type=\"hidden\" name=\"form_step\" value=\"{{ $form-\u003ecurrentStep() }}\"\u003e\n    @csrf\n    \u003ca\n        href=\"{{ route('submit', ['form_step' =\u003e 1]) }}\"\n        class=\"{{ $form-\u003eisPast(1, 'text-blue-500', $form-\u003eisActive(1, 'font-bold', 'disabled')) }}\"\u003e\n        Step 1\n    \u003c/a\u003e\n    \u003ca\n        href=\"{{ route('submit', ['form_step' =\u003e 2]) }}\"\n        class=\"{{ $form-\u003eisPast(2, 'text-blue-500', $form-\u003eisActive(2, 'font-bold', 'disabled')) }}\"\u003e\n        Step 2\n    \u003c/a\u003e\n    \u003ca\n        href=\"{{ route('submit', ['form_step' =\u003e 3]) }}\"\n        class=\"{{ $form-\u003eisPast(3, 'text-blue-500', $form-\u003eisActive(3, 'font-bold', 'disabled')) }}\"\u003e\n        Step 3\n    \u003c/a\u003e\n\n    \n    @switch($form-\u003ecurrentStep())\n    \n        @case(1)\n            \u003clabel\u003eName\u003c/label\u003e\n            \u003cinput type=\"text\" name=\"name\" value=\"{{ $form-\u003egetValue('name') }}\"\u003e\n            @error('name') \n                \u003cp\u003e{{ $errors-\u003efirst('name') }}\u003c/p\u003e\n            @enderror\n        @break\n    \n        @case(2)\n            \u003clabel\u003eRole\u003c/label\u003e\n            \u003cinput type=\"text\" name=\"role\" value=\"{{ $form-\u003egetValue('role') }}\"\u003e\n             @error('role') \n                \u003cp\u003e{{ $errors-\u003efirst('role') }}\u003c/p\u003e\n            @enderror\n        @break\n    \n        @case(3)\n            \u003cp\u003eReview your submission:\u003c/p\u003e\n            \u003cp\u003e\n             Name: {{ $form-\u003egetValue('name') }}\u003cbr\u003e\n             Role: {{ $form-\u003egetValue('role') }}\u003cbr\u003e\n            \u003c/p\u003e\n        @break\n    \n    @endswitch\n    \n    @if($form-\u003eisLastStep())\n        \u003cbutton type=\"submit\" name=\"submit\"\u003eSave\u003c/button\u003e\n        \u003cbutton type=\"submit\" name=\"reset\" value=\"1\"\u003eReset\u003c/button\u003e\n    @else\n        \u003cbutton type=\"submit\" name=\"submit\"\u003eContinue\u003c/button\u003e\n    @endif\n\n\u003c/form\u003e\n```\n\n### Vue Example\n\nForm state and data will be returned as JSON when no view is\nspecified or the request prefers JSON. You can combine both\ntechniques to use Vue within blade as well.\n\n```html\n\n\u003cv-form route=\"{{ route('onboarding') }}\"\u003e\n    \u003ctemplate v-slot:default=\"{form, options, errors, reset, back}\"\u003e\n\n        \u003cnav aria-label=\"Steps\" v-if=\"form.form_step \u003c 4\"\u003e\n            \u003ca\n                @click=\"back(1)\"\n                :class=\"{'text-gray-500': form.form_step \u003e 1, 'text-blue-500': form.form_step === 1}\"\u003e\n                Step 1\n            \u003c/a\u003e\n            \u003ca\n                @click=\"back(2)\"\n                :class=\"{'text-gray-500': form.form_step \u003e 2, 'text-blue-500': form.form_step === 2}\"\u003e\n                Step 2\n            \u003c/a\u003e\n            \u003ca\n                @click=\"back(3)\"\n                :class=\"{'text-gray-500': form.form_step \u003e 3, 'text-blue-500': form.form_step === 3}\"\u003e\n                Step 3\n            \u003c/a\u003e\n        \u003c/nav\u003e\n\n        \u003ctemplate v-if=\"form.form_step === 1\"\u003e\n            \u003cv-input\n                name=\"name\"\n                label=\"Name\"\n                :errors=\"errors\"\n                v-model=\"form.name\"\u003e\n            \u003c/v-input\u003e\n        \u003c/template\u003e\n\n        \u003ctemplate v-if=\"form.form_step === 2\"\u003e\n            \u003cv-input\n                name=\"email\"\n                label=\"Email\"\n                :errors=\"errors\"\n                v-model=\"form.email\"\u003e\n            \u003c/v-input\u003e\n        \u003c/template\u003e\n\n        \u003ctemplate v-if=\"form.form_step === 3\"\u003e\n            \u003cv-input\n                name=\"phone\"\n                label=\"Phone\"\n                :errors=\"errors\"\n                v-model=\"form.phone\"\u003e\n            \u003c/v-input\u003e\n        \u003c/template\u003e\n\n        \u003ctemplate v-if=\"form.form_step === 4\"\u003e\n            \u003cul\u003e\n                \u003cli\u003eName: @{{ form.name }}\u003c/li\u003e\n                \u003cli\u003eEmail: @{{ form.email }}\u003c/li\u003e\n                \u003cli\u003ePhone: @{{ form.phone }}\u003c/li\u003e\n            \u003c/ul\u003e\n        \u003c/template\u003e\n\n        \u003ctemplate v-if=\"form.form_step \u003c= 4\"\u003e\n            \u003cbutton type=\"submit\"\u003eSave\u003c/button\u003e\n            \u003cbutton type=\"button\" @click=\"reset\"\u003eReset\u003c/button\u003e\n        \u003c/template\u003e\n        \u003ctemplate v-else\u003e\n            \u003cbutton type=\"submit\"\u003eDone\u003c/button\u003e\n        \u003c/template\u003e\n    \u003c/template\u003e\n\u003c/v-form\u003e\n```\n\n#### Example Form Component\n\n```vue\n\n\u003cscript\u003e\n  export default {\n    name: 'v-form',\n    props: ['route'],\n    data: () =\u003e ({\n      errors: {},\n      options: {},\n      form: {form_step: 1},\n    }),\n    methods: {\n      reset() {\n        this.form.reset = 1\n        this.submit()\n      },\n      back(step) {\n        if (step \u003c this.form.form_step) {\n          this.fetch({form_step: step})\n        }\n      },\n      fetch(params = {}) {\n        axios\n            .get(this.route, {params})\n            .then(this.onResponse)\n            .catch(this.onError)\n      },\n      submit() {\n        axios\n            .post(this.route, this.form)\n            .then(this.onResponse)\n            .catch(this.onError)\n      },\n      onError({response}) {\n        this.errors = response.data.errors\n      },\n      onResponse({data}) {\n        this.errors = {}\n        this.options = (data.data || {})\n        this.form = (data.form || {})\n      },\n    },\n    created() {\n      this.fetch()\n    }\n  }\n\u003c/script\u003e\n\u003ctemplate\u003e\n  \u003cform @submit.prevent=\"submit\"\u003e\n    \u003cslot\n        :form=\"form\"\n        :errors=\"errors\"\n        :options=\"options\"\n        :back=\"back\"\n        :reset=\"reset\"\n    /\u003e\n  \u003c/form\u003e\n\u003c/template\u003e\n```\n\n#### Example Input Component\n\n```vue\n\n\u003cscript\u003e\n  export default {\n    name: \"v-form-input\",\n    emits: ['update:modelValue'],\n    props: ['name', 'modelValue', 'errors'],\n    computed: {\n      field: {\n        get() {\n          return this.modelValue\n        },\n        set(val) {\n          return this.$emit('update:modelValue', val)\n        }\n      }\n    }\n  }\n\u003c/script\u003e\n\u003ctemplate\u003e\n  \u003cinput\n      type=\"text\"\n      :name=\"name\"\n      v-model=\"field\"\n      class=\"form-input\"\n  /\u003e\n  \u003cdiv v-if=\"errors[name]\"\u003e\n    {{ errors[name][0] }}\n  \u003c/div\u003e\n\u003c/template\u003e\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbayareawebpro%2Flaravel-multistep-forms","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbayareawebpro%2Flaravel-multistep-forms","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbayareawebpro%2Flaravel-multistep-forms/lists"}