Skip to content
Back to skills

Laravel Request

ASecurity

Create Form Requests with validation rules, authorization, and custom error messages in Laravel 11+.

  • 4 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 12, 2026
testinggophpbashtestinggitapidatabase

Works with

  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned September 12, 2026

npx -y skills add aibot88/sec_skill_store --skill laravel-request --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Laravel Request?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Laravel Request
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/aibot88-laravel-request/badge)](https://www.skillsdirectory.com/skills/aibot88-laravel-request)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: laravel-request
description: Create Form Requests with validation rules, authorization, and custom error messages in Laravel 11+.
keywords: request, validation, form request, rules, authorize
---

# Laravel Request Skill

## Cuando Activar

- Usuario dice: "create request", "validation", "form request"
- Usuario necesita validar datos
- Usuario menciona rules o authorization

## Antes de Generar

**SIEMPRE pregunta:**

```
¿Qué necesitás validar?

1. Tipo de operación:
   - Store (crear) → campos requeridos
   - Update (actualizar) → campos opcionales
   
2. Campos:
   - ¿Cuáles son los campos?
   - ¿Tipos de datos? (string, int, enum, file)
   - ¿Validaciones especiales? (unique, exists, regex)

3. Authorization:
   - ¿Necesitás authorization? (policies)
   
4. Custom rules:
   - ¿Validaciones custom?

Contame para generar el request completo.
```

## Template - Store Request

```php
<?php

namespace App\Http\Requests;

use App\Enums\{{Model}}Status;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class Store{{Model}}Request extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        // Check if user has permission
        return $this->user()->can('create', {{Model}}::class);
        
        // Or simply:
        // return true;
    }

    /**
     * Get the validation rules that apply to the request.
     */
    public function rules(): array
    {
        return [
            'name' => ['required', 'string', 'max:255'],
            'description' => ['nullable', 'string', 'max:1000'],
            'status' => ['required', Rule::enum({{Model}}Status::class)],
            'category_id' => ['required', 'integer', 'exists:categories,id'],
            'tags' => ['nullable', 'array'],
            'tags.*' => ['integer', 'exists:tags,id'],
            'image' => ['nullable', 'image', 'max:2048'], // 2MB max
            'is_featured' => ['boolean'],
            'price' => ['required', 'numeric', 'min:0', 'max:999999.99'],
            'published_at' => ['nullable', 'date', 'after:now'],
        ];
    }

    /**
     * Get custom attributes for validator errors.
     */
    public function attributes(): array
    {
        return [
            'name' => 'nombre',
            'description' => 'descripción',
            'status' => 'estado',
            'category_id' => 'categoría',
            'tags' => 'etiquetas',
            'image' => 'imagen',
            'is_featured' => 'destacado',
            'price' => 'precio',
            'published_at' => 'fecha de publicación',
        ];
    }

    /**
     * Get the error messages for the defined validation rules.
     */
    public function messages(): array
    {
        return [
            'name.required' => 'El nombre es obligatorio.',
            'name.max' => 'El nombre no puede exceder :max caracteres.',
            'status.required' => 'El estado es obligatorio.',
            'category_id.exists' => 'La categoría seleccionada no existe.',
            'price.min' => 'El precio debe ser mayor o igual a :min.',
            'image.image' => 'El archivo debe ser una imagen.',
            'image.max' => 'La imagen no puede exceder :max KB.',
            'published_at.after' => 'La fecha de publicación debe ser futura.',
        ];
    }

    /**
     * Prepare the data for validation.
     */
    protected function prepareForValidation(): void
    {
        $this->merge([
            'is_featured' => $this->boolean('is_featured'),
        ]);
    }

    /**
     * Get validated data with additional transformations.
     */
    public function validatedData(): array
    {
        $validated = $this->validated();

        // Transform data if needed
        if (isset($validated['price'])) {
            $validated['price'] = (float) $validated['price'];
        }

        return $validated;
    }
}
```

## Template - Update Request

```php
<?php

namespace App\Http\Requests;

use App\Enums\{{Model}}Status;
use App\Models\{{Model}};
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

class Update{{Model}}Request extends FormRequest
{
    /**
     * Determine if the user is authorized to make this request.
     */
    public function authorize(): bool
    {
        ${{model}} = $this->route('{{model}}');
        
        return $this->user()->can('update', ${{model}});
    }

    /**
     * Get the validation rules that apply to the request.
     */
    public function rules(): array
    {
        ${{model}} = $this->route('{{model}}');

        return [
            'name' => ['sometimes', 'string', 'max:255'],
            'description' => ['nullable', 'string', 'max:1000'],
            'status' => ['sometimes', Rule::enum({{Model}}Status::class)],
            'category_id' => ['sometimes', 'integer', 'exists:categories,id'],
            'tags' => ['nullable', 'array'],
            'tags.*' => ['integer', 'exists:tags,id'],
            'image' => ['nullable', 'image', 'max:2048'],
            'is_featured' => ['boolean'],
            'price' => ['sometimes', 'numeric', 'min:0', 'max:999999.99'],
        ];
    }

    /**
     * Get custom attributes for validator errors.
     */
    public function attributes(): array
    {
        return [
            'name' => 'nombre',
            'description' => 'descripción',
            'status' => 'estado',
            'category_id' => 'categoría',
            'price' => 'precio',
        ];
    }

    /**
     * Get the error messages for the defined validation rules.
     */
    public function messages(): array
    {
        return [
            'name.max' => 'El nombre no puede exceder :max caracteres.',
            'category_id.exists' => 'La categoría seleccionada no existe.',
            'price.min' => 'El precio debe ser mayor o igual a :min.',
            'image.max' => 'La imagen no puede exceder :max KB.',
        ];
    }
}
```

## Template - Custom Rule

```php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class ValidateSlug implements ValidationRule
{
    /**
     * Run the validation rule.
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        if (! preg_match('/^[a-z0-9]+(?:-[a-z0-9]+)*$/', $value)) {
            $fail('El :attribute debe ser un slug válido (solo letras minúsculas, números y guiones).');
        }
    }
}
```

```php
<?php

namespace App\Rules;

use Closure;
use Illuminate\Contracts\Validation\ValidationRule;

class MaxWords implements ValidationRule
{
    public function __construct(
        private readonly int $maxWords
    ) {}

    /**
     * Run the validation rule.
     */
    public function validate(string $attribute, mixed $value, Closure $fail): void
    {
        $wordCount = str_word_count($value);
        
        if ($wordCount > $this->maxWords) {
            $fail("El :attribute no puede exceder {$this->maxWords} palabras.");
        }
    }
}
```

## Validaciones Comunes

### Strings

```php
'name' => ['required', 'string', 'min:3', 'max:255'],
'slug' => ['required', 'string', 'alpha_dash', 'unique:{{table}},slug'],
'email' => ['required', 'email:rfc,dns', 'unique:users'],
'password' => ['required', 'string', 'min:8', 'confirmed'],
'url' => ['nullable', 'url', 'active_url'],
```

### Numbers

```php
'age' => ['required', 'integer', 'min:18', 'max:120'],
'price' => ['required', 'numeric', 'between:0.01,999999.99'],
'quantity' => ['required', 'integer', 'gt:0'],
'discount' => ['nullable', 'numeric', 'min:0', 'max:100'],
```

### Dates

```php
'birth_date' => ['required', 'date', 'before:today'],
'start_date' => ['required', 'date', 'after:now'],
'end_date' => ['required', 'date', 'after:start_date'],
'published_at' => ['nullable', 'date_format:Y-m-d H:i:s'],
```

### Files

```php
'avatar' => ['nullable', 'image', 'mimes:jpg,png', 'max:2048'],
'document' => ['required', 'file', 'mimes:pdf,doc,docx', 'max:10240'],
'video' => ['nullable', 'mimetypes:video/mp4', 'max:51200'], // 50MB
```

### Arrays

```php
'tags' => ['required', 'array', 'min:1', 'max:5'],
'tags.*' => ['integer', 'exists:tags,id'],
'items' => ['required', 'array'],
'items.*.name' => ['required', 'string'],
'items.*.quantity' => ['required', 'integer', 'min:1'],
```

### Enums

```php
'status' => ['required', Rule::enum({{Model}}Status::class)],
'role' => ['required', Rule::in(['admin', 'user', 'guest'])],
```

### Relationships

```php
'user_id' => ['required', 'integer', 'exists:users,id'],
'category_id' => ['nullable', 'exists:categories,id,deleted_at,NULL'], // No soft deleted
```

### Conditional Rules

```php
public function rules(): array
{
    return [
        'type' => ['required', Rule::in(['physical', 'digital'])],
        'weight' => [
            Rule::requiredIf($this->input('type') === 'physical'),
            'nullable',
            'numeric',
        ],
        'download_url' => [
            Rule::requiredIf($this->input('type') === 'digital'),
            'nullable',
            'url',
        ],
    ];
}
```

### Unique with Exceptions

```php
// Store: unique
'email' => ['required', 'email', 'unique:users'],

// Update: unique except current
public function rules(): array
{
    $user = $this->route('user');
    
    return [
        'email' => [
            'required',
            'email',
            Rule::unique('users')->ignore($user->id),
        ],
    ];
}
```

## Template - Request Test

```php
<?php

use App\Http\Requests\Store{{Model}}Request;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

describe('Store{{Model}}Request', function () {
    it('validates required fields', function () {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/{{models}}', []);
        
        $response->assertUnprocessable()
            ->assertJsonValidationErrors(['name', 'status', 'price']);
    });

    it('validates string max length', function () {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/{{models}}', [
                'name' => str_repeat('a', 256), // Exceeds max:255
            ]);
        
        $response->assertUnprocessable()
            ->assertJsonValidationErrors(['name']);
    });

    it('validates enum values', function () {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/{{models}}', [
                'status' => 'invalid_status',
            ]);
        
        $response->assertUnprocessable()
            ->assertJsonValidationErrors(['status']);
    });

    it('validates relationship existence', function () {
        $user = User::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/{{models}}', [
                'category_id' => 999, // Non-existent
            ]);
        
        $response->assertUnprocessable()
            ->assertJsonValidationErrors(['category_id']);
    });

    it('validates image file', function () {
        $user = User::factory()->create();
        
        Storage::fake('public');
        
        $response = $this->actingAs($user)
            ->postJson('/api/{{models}}', [
                'image' => UploadedFile::fake()->create('document.pdf'), // Not an image
            ]);
        
        $response->assertUnprocessable()
            ->assertJsonValidationErrors(['image']);
    });

    it('passes with valid data', function () {
        $user = User::factory()->create();
        $category = Category::factory()->create();
        
        $response = $this->actingAs($user)
            ->postJson('/api/{{models}}', [
                'name' => 'Test {{Model}}',
                'description' => 'Test description',
                'status' => '{{Model}}Status::ACTIVE->value,
                'category_id' => $category->id,
                'price' => 99.99,
            ]);
        
        $response->assertCreated();
    });
});
```

## Checklist de Generación

- ✅ authorize() method
- ✅ rules() con todas las validaciones
- ✅ attributes() en español
- ✅ messages() personalizados
- ✅ prepareForValidation() si necesario
- ✅ Custom rules si aplican
- ✅ Conditional rules
- ✅ Tests incluidos
- ✅ PHPDoc comments
- ✅ Type hints

## Comandos Artisan

```bash
# Generar form request
php artisan make:request Store{{Model}}Request

# Generar custom rule
php artisan make:rule ValidateSlug

# Generar test
php artisan make:test Store{{Model}}RequestTest --pest
```

## Decisiones

| Campo | Validación |
|-------|------------|
| Texto corto | string, max:255 |
| Texto largo | string, max:1000 |
| Email | email:rfc,dns |
| Password | min:8, confirmed |
| Número | numeric, min, max |
| Fecha | date, after, before |
| File | file, mimes, max |
| Enum | Rule::enum() |
| Unique | Rule::unique() |
| Exists | exists:table,column |

Files in this skill

  • SKILL.md12.8 KB
  • source.json612 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.