Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

dcranmer's avatar

Validation triggers redirect , GET request (SOLVED)

I'm mystified by some unexpected behavior in a Laravel 9/Inertia/Vue3 app. I am posting data to a controller method ('generate') from my 'Edit' form via Inertia's useForm(). If I try to validate the request input, it triggers a redirect (302) back to 'Edit', which in turn triggers a 405 because the route to the edit form is post-only.

In the network tab of Dev Tools, I can see that the post data is all there in the payload for generate, so there should not be a validation error. And if there were a validation error, Inertia should go back to the original form and display the errors. But it doesn't; all I see is the 405. If I remove the validation code, the redirect goes away and I can see the request data, which looks fine. If I enable the route to respond to a GET request, the 405 goes away, but a long series of 302s ensues before failing with too many redirects.

What's puzzling me is why is the validation code triggering a redirect? It ought to either go back to the original form and display the errors, or continue with the rest of the method (which generates and saves a PDF, then redirects the user o another page to view and download the PDF).

Here is the code for submitting the form:

const submit = () => {
  form.post(
    route('auth.report.generate'),
  );
};

The validation code (truncated below) is simple and straightforward, I think.

        $v = $request->validate([
          'report_title'             => 'required',
          'report_file_name'         => 'required',
          'report_type'              => 'required',
          'district'                 => 'required',
          'current_year'             => 'required',
          'trans_total'              => 'required',
          'alumni'                   => 'required',
          'nurses'                   => 'required',
          'engineers'                => 'required',
          'employees'                => 'required',
        ]);
0 likes
10 replies
LaryAI's avatar
Level 58

It sounds like the issue you're encountering is related to how Inertia handles validation errors. When using Inertia, if validation fails, it expects to receive a response with a 422 status code and the validation errors, which it will then use to update the page and show the errors without a full page reload.

However, if the validation fails and you're not using Inertia's error handling correctly, Laravel's default behavior is to redirect back to the previous page with the validation errors in the session. This can cause the unexpected behavior you're seeing, especially if the previous page is not set up to handle a GET request.

To solve this issue, make sure you're handling validation errors in a way that's compatible with Inertia. Here's how you can modify your controller method to properly return validation errors to an Inertia form:

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Redirect;
use Inertia\Inertia;

public function generate(Request $request)
{
    $validatedData = $request->validate([
        'report_title'             => 'required',
        'report_file_name'         => 'required',
        'report_type'              => 'required',
        'district'                 => 'required',
        'current_year'             => 'required',
        'trans_total'              => 'required',
        'alumni'                   => 'required',
        'nurses'                   => 'required',
        'engineers'                => 'required',
        'employees'                => 'required',
    ]);

    // If validation passes, continue with the method logic
    // ...

    // If you reach this point, validation has passed and you can generate and save the PDF
    // Then redirect the user to another page to view and download the PDF
    // For example:
    // return Redirect::route('pdf.view', ['id' => $pdfId]);
}

// If validation fails, Inertia will automatically handle the errors
// and return a 422 response with the errors, which will be displayed in the form.

Make sure that your Inertia form is set up to handle and display these errors. In your Vue component, you should have something like this:

const form = useForm({
  report_title: '',
  report_file_name: '',
  // ... other fields
});

const submit = () => {
  form.post(route('auth.report.generate'), {
    onError: (errors) => {
      // Handle the errors, e.g., by showing an error message
      // This block is optional, as Inertia will automatically
      // update the form's error object with the validation errors
    }
  });
};

By using form.post with Inertia's useForm, Inertia will automatically catch the 422 response and update the form's error object with the validation errors. Your Vue component should be reactive to these errors and display them accordingly.

If you're still encountering redirects, make sure that your Inertia middleware is correctly configured and that you're not accidentally triggering a redirect in your controller or any other middleware.

dcranmer's avatar

If anyone else can help, that would be appreciated. I've "talked" with Lary AI (bless their heart) about this already, and am still unable to resolve this.

Thunderson's avatar

@pweil have you try to display dd($request) for checking data, Check also if web route is post

piljac1's avatar

@pweil Can you post your complete route and controller definitions, as well as the Vue component that posts the builds and post the form? Can you also dd(request()->all()) before the validator (or before returning the rules array if you're using a form request class)?

dcranmer's avatar

@piljac1 I have done dd(request()->all()) before and without the validator stuff. Everything appears to be there.

Some background: purpose is to provide a form to enable the user to generate a PDF report of data for state legislators. In form 1, user selects the type of report and for which political district. That posts to the Edit form, where the user can edit the data. When done, the Edit form posts to the generate method, which should a) validate the post data, then b) generate a PDF with Spatie/Browsershot, c) save it, and then d) take the user to another page to view and download the PDF.

The whole process works fine in a standard Laravel (non-inertia) environment, relying largely on blade/PHP and some Vue js code. I am in the process of migrating the entire application (which involves much more than this ) to Inertia and Vue 3. I think Inertia is playing a role in the problem I'm seeing, but at the moment I'm stuck.

Request data:

array:25 [▼ // app/Http/Controllers/ReportController.php:383
  "district" => 7
  "ordinal" => "th"
  "report_title" => "Assembly District"
  "report_file_name" => "uw-impact-assembly-district-7-2022.pdf"
  "report_type" => "District"
  "current_year" => 2022
  "fy" => "2021–2022"
  "students" => 103
  "employees" => 17
  "alumni" => 630
  "mds" => 18
  "nurses" => 25
  "pharms" => 22
  "engineers" => 40
  "vets" => 4
  "trans_total" => 577020.4
  "exec_dev_companies_total" => 4
  "med_companies_total" => 2
  "selected" => array:6 [▶]
  "vet_clients" => 17
  "includeVetClients" => true
  "loan_aid" => 500
  "financial_aid" => 806371
  "promises" => "39"
  "extension" => 0
]

Routes (just the ones behind auth; the reports routs are the ones involved here):

 function () {
    Route::middleware(['wi.autologin'])->group(function () {
      Route::resource('/user', UserController::class);
      Route::resource('/project', ProjectController::class);
      Route::resource('/site', SiteController::class);
      Route::patch('/project/confirm/{project}', [
        ProjectController::class,
        'confirm',
      ])->name('project.confirm');

      Route::patch('/project/archive/{project}', [
        ProjectController::class,
        'archive',
      ])->name('project.archive');

      Route::patch('/project/approve-edits/{project}', [
        ProjectController::class,
        'approveEdits',
      ])->name('project.approve-edits');

      Route::get('/admin/panel', AdminPanelController::class)
        ->middleware('wi.admins-only')
        ->name('admin.dashboard');
      Route::get('/projects/retired', [ProjectController::class, 'retired'])
        ->middleware('wi.admins-only')
        ->name('project.retired');
      Route::get('/profile', [ProfileController::class, 'edit'])->name(
        'profile.edit'
      );
      Route::patch('/profile', [ProfileController::class, 'update'])->name(
        'profile.update'
      );
      Route::delete('/profile', [ProfileController::class, 'destroy'])->name(
        'profile.destroy'
      );

        Route::middleware(['wi.reports'])->group(function () {
            Route::get('report', [ReportController::class, 'index'])->name('report.index');

            Route::post('report/edit', [ReportController::class, 'edit'])->name('report.edit');

            Route::post('report/generate', [ReportController::class, 'generate',])->name('report.generate');

            Route::get('report/pdf', [ReportController::class, 'showPdf'])->name('report.pdf');

            Route::get('report/success', [ReportController::class, 'reportSuccess'])->name('report.success');
        });
    });

The Vue component (just the <script setup> section, minus some irrelevant code for readability. The <template> section is s really, really long form)

<script setup>
import { reactive, computed } from 'vue';
import { Head, usePage, useForm } from '@inertiajs/vue3';
import AppLayout from '@/Layouts/AppLayout.vue';
import BaseCheckbox from '@/components/BaseCheckbox.vue';

const { props } = usePage();

const form = useForm({
  district: props.district,
  ordinal: props.ordinal,
  report_title: props.report_title,
  report_file_name: props.report_name,
  report_type: props.report_type,
  current_year: props.current_year,
  fy: props.fy,
  students: props.students,
  employees: props.employees,
  alumni: props.alumni,
  mds: props.mds,
  nurses: props.nurses,
  pharms: props.pharms,
  engineers: props.engineers,
  vets: props.vets,
  trans_total: props.trans_total,
  exec_dev_companies_total: props.exec_dev_companies.length,
  med_companies_total: props.med_companies.length,
  selected: {
    vendors: [],
    cals_interns: [],
    med_companies: [],
    cheesemakers: [],
    food_science: [],
    exec_dev_companies: [],
  },
  vet_clients: props.vet_clients,
  includeVetClients: false,
  loan_aid: props.loan_aid,
  financial_aid: props.financial_aid,
  promises: props.promises,
  extension: props.extension
});

const submit = () => {
  // if (form.processing) return;
  // if (!form.isDirty) alert('The form contains no changes');
  form.post(
    route('auth.report.generate'), {
      onSuccess: () => console.log('success'),
      onError: () => console.log('error'),
      onInvalid: () => console.log('invalid'),
      onStart: () => console.log('start')
    }
  );
};
</script>

For the validation, I am using a form request class:

<?php

    namespace App\Http\Requests;

    use Illuminate\Foundation\Http\FormRequest;

    class ReportGenerateRequest extends FormRequest
    {
        public function rules(): array
        {
            return [
              'report_title' => ['required'],
              'report_file_name' => ['required'],
              'report_type' => ['required'],
              'district' => ['required'],
              'current_year' => ['required'],
              'fy' => ['required'],
              'trans_total' => ['required'],
              'alumni' => ['required'],
              'employees' => ['required'],
              'vets' => ['required'],
              'students' => ['required'],
              'ordinal'                  => ['required'],
              'mds'                      => ['required'],
              'nurses'                   => ['required'],
              'pharms'                   => ['required'],
              'engineers'                => ['required'],
              'exec_dev_companies_total' => ['required'],
              'med_companies_total'      => ['required'],
              'vet_clients'              => ['required'],
              'includeVetClients'        => ['required'],
              'loan_aid'                 => ['required'],
              'financial_aid'            => ['required'],
              'promises'                 => ['required'],
              'extension'                => ['required'],
              'selected'                 => ['required']
            ];
        }

        public function authorize(): bool
        {
            return true;
        }
    }

The controller generate method (most recent iteration). Earlier I tried doing a redirect and flashing a success message. I thought that it actually worked once, but I can't recall whether the validation was commented out or not.:

    public function generate(ReportGenerateRequest $request)
  {
      $data = [];
      $data['major_vendors'] = $request->input('selected.vendors');
        $data['exec_dev_companies'] = $request->input('selected.exec_dev_companies');
        $data['food_science'] = $request->input('selected.food_science');
        $data['cals_interns'] = $request->input('selected.cals_interns');
        $data['cheesemakers'] = $request->input('selected.cheesemakers');
        $data['med_companies'] = $request->input('selected.med_companies');
    $data['vet_clients'] = $request->input('vet_clients');
      $data['report_title'] = $request->input('report_title');
      //dd($data);
      $data['report_type'] = $request->input('report_type');
      $data['district'] = $request->input('district');
      $data['current_year'] = $data['year'] = $request->input('current_year');
      $data['ordinal'] = $request->input('ordinal');
      $data['fy'] = $request->input('fy');
      $data['trans_total'] = $request->input('trans_total');
      $data['alumni'] = $request->input('alumni');
      $data['employees'] = $request->input('employees');
      $data['students'] = $request->input('students');
      $data['mds'] = $request->input('mds');
      $data['nurses'] = $request->input('nurses');
      $data['pharms'] = $request->input('pharms');
      $data['engineers'] = $request->input('engineers');
      $data['vets'] = $request->input('vets');
        $data['includeVetClients'] = $request->input('includeVetClients');
      $data['vet_clients'] = $request->input('vet_clients');
      //$data['total'] = 0;
      $data['financial_aid'] = $request->input('financial_aid');
      $data['promises'] = $request->input('promises');
      $data['loan_aid'] = $request->input('loan_aid');
      $data['extension'] = $request->input('extension');
      $data['execdev_companies_total'] = $request->input('execdev_companies_total');
      $data['med_companies_total'] = $request->input('med_companies_total');

      $report_name = $request->input('report_file_name');


      $html = view('reports.pdf-report', $data)->render();
        $pdfPath = storage_path('app/public/report.pdf');
        Browsershot::html($html)
        ->setNodeBinary(config('constants.node_bin').'node')
        ->setNpmBinary(config('constants.node_bin').'npm')
        ->showBackground()
        ->setOption('newHeadless', true)
        ->savePdf($pdfPath);

        return inertia('Reports/Success', [
          'pdfUrl' => route('auth.report.pdf'),
          'message' =>session('message')
        ]);

 //return redirect()->route('auth.report.success')->with('message', 'Report generated successfully.');
}
piljac1's avatar
piljac1
Best Answer
Level 28

@pweil Wait, if I understand correctly, you need to make a post request to your edit route and your edit route then renders an inertia page (without relying on a redirection)?

That's probably your issue then. POST/PUT/DELETE requests should never return an Inertia page, it should either return JSON in the case of an API or, in your case, a redirection to another GET route that will render the Inertia page. It makes sense that it would currently crash because Laravel validation's redirects to url()->previous() (if the request doesn't expect a JSON response). In your case Inertia doesn't expect JSON, so a redirection to url()->previous() is made and since it's a POST only route, it crashes.

What would most likely fix your problem would be to have an update route (either POST, PATCH or PUT) which would update the report data and then redirect to a edit route (GET) which would render your Inertia page (form). When you post the edit form to your generate route and you have validation errors, it will redirect back to the edit route and will render your form correctly.

1 like
dcranmer's avatar

@piljac1 Of course! I suspected that there wasn't something quite right about following the original request sequence (which does work in a standard Laravel setup), but just couldn't wrap my head around it. Doing a POST request and then a redirect doesn't feel exactly intuitive or natural, at least until you understand why. And then it took a little thinking about how to pass the data (I put it in the session). Whew, that was a a bit of a mind-bender for me, but a great lesson. Thanks for clearing this up for me! Much appreciated!

piljac1's avatar

@pweil Glad I could help :) However, even in a "regular" server-side rendered (with Blade) Laravel setup, it is still good practice to have POST/PUT/PATCH/DELETE requests redirect to a GET route that displays the data (in the case of non-API routes of course). To be honest, I don't even know how you didn't encounter the same issue with a regular setup.

dcranmer's avatar

@piljac1 Yeah, I don't know why. That section of the app was all coded by a former colleague; when she left, it fell in my lap. After mulling it over, I'm considering changing the "two form" approach to a single form. That would simplify things and make them a bit more straightforward.

Please or to participate in this conversation.