custom-validator skill
Create custom validation constraints for API Platform resources using Symfony constraints or Laravel validation rules.
- GitHub stars
- 29
- Files
- 1
- Last commit
- Oct 22, 2026
- License
- —
- Allowed tools
- —
What does the custom-validator skill do?
custom-validator creates custom validation constraints for API Platform resources using Symfony constraints or Laravel validation rules.
Summary generated from the skill's SKILL.md.
About the custom-validator skill
Developing complex business logic for API Platform resources is what this skill enables for backend developers. It provides the necessary patterns to implement custom validation constraints when standard Symfony or Laravel rules are insufficient for specific requirements. An agent should load this skill when a user needs to enforce domain-specific formats, check for uniqueness, or implement rate and plan limits during write operations.
For Symfony-based projects, the skill demonstrates how to create a Constraint class and a corresponding ConstraintValidator class to handle logic like account limit checks. It also covers class-level constraints for multi-field validation and callback validation for DTOs. For Laravel environments, it focuses on using the rules option within the ApiResource attribute, supporting arrays, closures, or FormRequest class-strings. This ensures that custom business rules correctly emit 422 ConstraintViolationList responses.
What an agent can do with it
- 1Enforce plan-based account limitsCreate a validator that prevents users from exceeding a specific number of accounts based on their current subscription plan.
Written from the skill's SKILL.md. Every use case is checked against a quote from it.
How to install the custom-validator skill
Each command copies the skill folder into the client's skills directory. Restart the client or start a new session to load it.Install the custom-validator skill in Plugin
/plugin marketplace add api-platform/skillset /plugin install api-platform@api-platform-skillset
Install the custom-validator skill in Claude Code
~/.claude/skills/custom-validator/SKILL.md
$ npx degit api-platform/skillset/skills/custom-validator ~/.claude/skills/custom-validatorInstall the custom-validator skill in Claude Code (project)
.claude/skills/custom-validator/SKILL.md
$ npx degit api-platform/skillset/skills/custom-validator .claude/skills/custom-validatorInstall the custom-validator skill in Codex
~/.agents/skills/custom-validator/SKILL.md
$ npx degit api-platform/skillset/skills/custom-validator ~/.agents/skills/custom-validatorInstall the custom-validator skill in Gemini CLI
~/.gemini/skills/custom-validator/SKILL.md
$ npx degit api-platform/skillset/skills/custom-validator ~/.gemini/skills/custom-validatorInstall the custom-validator skill in Claude.ai
npx degit api-platform/skillset/skills/custom-validator custom-validator && zip -r custom-validator.zip custom-validator # claude.ai → Customize → Skills → + → Create skill → Upload a skill → custom-validator.zip
SKILL.md
View on GitHubCustom Validation Constraints
Create custom validators when built-in Symfony constraints are insufficient.
Constraint Class
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
#[\Attribute(\Attribute::TARGET_PROPERTY | \Attribute::TARGET_METHOD | \Attribute::IS_REPEATABLE)]
class IsValidAccountLimit extends Constraint
{
public string $message = 'Account limit reached for your current plan.';
public int $limit = 50;
}
Validator Class
Convention: {ConstraintName}Validator in the same namespace.
<?php
namespace App\Validator;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class IsValidAccountLimitValidator extends ConstraintValidator
{
public function __construct(
private readonly UserHelper $userHelper,
private readonly AccountRepository $accountRepository,
) {}
public function validate(mixed $value, Constraint $constraint): void
{
if (!$constraint instanceof IsValidAccountLimit) {
throw new \InvalidArgumentException('Unexpected constraint type');
}
if (null === $value || '' === $value) {
return;
}
$user = $this->userHelper->getUser();
$count = $this->accountRepository->countByUser($user->getId());
if ($count >= $constraint->limit) {
$this->context->buildViolation($constraint->message)
->addViolation();
}
}
}
Usage on Properties
use App\Validator\IsValidAccountLimit;
use Symfony\Component\Validator\Constraints as Assert;
class Account
{
#[Assert\NotBlank(groups: ['account:write'])]
#[Assert\Email(groups: ['account:write'])]
#[IsValidAccountLimit(groups: ['account:write'])]
public ?string $address;
}
Class-Level Constraint (UniqueEntity)
For constraints that validate across multiple fields:
#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)]
class MongoDBUnique extends Constraint
{
public array $fields = [];
public string $message = 'This value is already used.';
public function getTargets(): string
{
return self::CLASS_CONSTRAINT;
}
}
Usage:
#[MongoDBUnique(fields: ['address'], groups: ['account:write', 'account:patch'])]
class Account {}
Validation Groups with Operations
Target a constraint to specific operations by passing groups: (as shown above)
and wiring those groups into each operation's validationContext — see
operations for the full per-operation setup.
Callback Validation
For quick one-off validation on DTOs:
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
class SendMessage
{
public ?string $text = null;
public ?string $html = null;
#[Assert\Callback]
public function validateContent(ExecutionContextInterface $context): void
{
if (null === $this->text && null === $this->html) {
$context->buildViolation('Either text or html content is required.')
->atPath('text')
->addViolation();
}
}
}
Laravel
Laravel does not use Symfony Constraints/ConstraintValidator. Validation is
declared with Laravel validation rules via the
rules option on #[ApiResource] or per-operation (rules can be an array, a closure,
or a FormRequest class-string). The ValidateProvider runs them before the
processor and emits the same 422 ConstraintViolationList shape.
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\Post;
#[ApiResource(rules: ['title' => 'required|min:2'])]
#[Post(rules: ['isbn' => ['required', 'string']])] // per-operation override
class Book extends Model {}
For business rules beyond built-in rules, write a Laravel custom rule (a class
implementing Illuminate\Contracts\Validation\ValidationRule, scaffold with
php artisan make:rule) or a closure rule, and reference it in rules. A
FormRequest class-string is also accepted — its authorize()/rules() run, and an
AuthorizationException maps to 403, ValidationException to 422. Validation groups
and Symfony validationContext do not apply on Laravel; scope rules per operation
instead. Partial PATCH can relax required to sometimes via
partial_patch_validation in config/api-platform.php.
Files in this skill
1custom-validator skill questions
How do I install the custom-validator skill in Claude Code?
Run npx degit api-platform/skillset/skills/custom-validator ~/.claude/skills/custom-validator. Claude Code loads skills from ~/.claude/skills (all projects) or .claude/skills (one project) and uses a skill when its description matches the task.
Which clients can use the custom-validator skill?
Any client that reads the Agent Skills format: Claude Code, Codex, Gemini CLI and claude.ai (upload as a ZIP). Scripts in the skill run only where the client allows code execution.
How do I create a custom validator in Symfony for API Platform?
You need to create a Constraint class and a corresponding Validator class following the {ConstraintName}Validator naming convention in the same namespace.