
Symfony:api Platform Filters Skill
- 427 installs
- 190 repo stars
- Updated August 6, 2026
- makfly/superpowers-symfony
Add and tune API Platform collection filters for search, ordering, and scoped queries when building Symfony REST list endpoints with predictable client query params.
About
Symfony API Platform filters skill guides implementation of SearchFilter, OrderFilter, and related API Platform filter types in Symfony backends. It helps teams expose safe, documented query parameters for list endpoints without hand-rolling repetitive repository filtering logic.
- SearchFilter setup
- OrderFilter configuration
- scoped collection queries
- query param conventions
- API Platform filter services
Symfony:Api Platform Filters by the numbers
- 427 all-time installs (skills.sh)
- Ranked #19 of 68 PHP & Laravel skills by installs in the Skillselion catalog
- Data as of Aug 11, 2026 (Skillselion catalog sync)
npx skills add https://github.com/makfly/superpowers-symfony --skill symfonyapi-platform-filtersAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 427 |
|---|---|
| repo stars | ★ 190 |
| Last updated | August 6, 2026 |
| Repository | makfly/superpowers-symfony ↗ |
What it does
Add and tune API Platform collection filters for search, ordering, and scoped queries when building Symfony REST list endpoints with predictable client query params.
Files
Api Platform Filters (Symfony)
Use when
- Designing or evolving API Platform contracts and operations.
- Aligning serialization, validation, and security behavior.
Default workflow
1. Define operation-level contract and payload boundaries. 2. Implement resource/DTO/provider/processor changes with explicit mapping. 2. Apply operation-specific validation and security constraints. 2. Validate functional behavior across happy and negative paths.
Guardrails
- Keep API contract explicit and version-aware.
- Avoid exposing internal entity fields implicitly.
- Prevent drift between docs and actual serialization.
Progressive disclosure
- Use this file for execution posture and risk controls.
- Open references when deep implementation details are needed.
Output contract
- API artifacts changed (resource/DTO/provider/processor).
- Contract/security decisions and rationale.
- Functional verification results.
References
reference.mddocs/complexity-tiers.md
Reference
API Platform Filters
Built-in Filters
Search Filter
<?php
// src/Entity/Product.php
use ApiPlatform\Metadata\ApiFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
#[ApiResource]
#[ApiFilter(SearchFilter::class, properties: [
'name' => 'partial', // LIKE %value%
'description' => 'partial',
'sku' => 'exact', // = value
'category.name' => 'exact', // Related entity
])]
class Product
{
// ...
}Search strategies:
exact: Exact match (=)partial: Contains (LIKE %value%)start: Starts with (LIKE value%)end: Ends with (LIKE %value)word_start: Word starts with
Usage:
GET /api/products?name=phone
GET /api/products?category.name=electronicsDate Filter
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
#[ApiFilter(DateFilter::class, properties: ['createdAt', 'updatedAt'])]
class Product
{
#[ORM\Column]
private \DateTimeImmutable $createdAt;
}Usage:
GET /api/products?createdAt[after]=2024-01-01
GET /api/products?createdAt[before]=2024-12-31
GET /api/products?createdAt[strictly_after]=2024-01-01
GET /api/products?createdAt[strictly_before]=2024-12-31Range Filter
use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;
#[ApiFilter(RangeFilter::class, properties: ['price', 'stock'])]
class Product
{
#[ORM\Column]
private int $price;
#[ORM\Column]
private int $stock;
}Usage:
GET /api/products?price[gte]=1000&price[lte]=5000
GET /api/products?stock[gt]=0Operators: lt, lte, gt, gte, between
Boolean Filter
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
#[ApiFilter(BooleanFilter::class, properties: ['isActive', 'isFeatured'])]
class Product
{
#[ORM\Column]
private bool $isActive = true;
}Usage:
GET /api/products?isActive=true
GET /api/products?isFeatured=1Order Filter
use ApiPlatform\Doctrine\Orm\Filter\OrderFilter;
#[ApiFilter(OrderFilter::class, properties: [
'name' => 'ASC',
'price',
'createdAt',
])]
class Product
{
// ...
}Usage:
GET /api/products?order[price]=desc
GET /api/products?order[createdAt]=asc&order[name]=ascExists Filter
use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;
#[ApiFilter(ExistsFilter::class, properties: ['deletedAt', 'description'])]
class Product
{
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $deletedAt = null;
}Usage:
GET /api/products?exists[deletedAt]=false # Not deleted
GET /api/products?exists[description]=true # Has descriptionCustom Filters
Simple Custom Filter
<?php
// src/Filter/ActiveProductFilter.php
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
final class ActiveProductFilter extends AbstractFilter
{
protected function filterProperty(
string $property,
mixed $value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
if ($property !== 'active') {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
$paramName = $queryNameGenerator->generateParameterName('active');
$queryBuilder
->andWhere(sprintf('%s.isActive = :%s', $alias, $paramName))
->andWhere(sprintf('%s.deletedAt IS NULL', $alias))
->setParameter($paramName, filter_var($value, FILTER_VALIDATE_BOOLEAN));
}
public function getDescription(string $resourceClass): array
{
return [
'active' => [
'property' => 'active',
'type' => 'bool',
'required' => false,
'description' => 'Filter active products (not deleted)',
'openapi' => [
'example' => 'true',
],
],
];
}
}Usage:
#[ApiResource]
#[ApiFilter(ActiveProductFilter::class)]
class Product { /* ... */ }Filter with Multiple Properties
<?php
// src/Filter/PriceRangeFilter.php
namespace App\Filter;
use ApiPlatform\Doctrine\Orm\Filter\AbstractFilter;
use ApiPlatform\Doctrine\Orm\Util\QueryNameGeneratorInterface;
use ApiPlatform\Metadata\Operation;
use Doctrine\ORM\QueryBuilder;
final class PriceRangeFilter extends AbstractFilter
{
protected function filterProperty(
string $property,
mixed $value,
QueryBuilder $queryBuilder,
QueryNameGeneratorInterface $queryNameGenerator,
string $resourceClass,
?Operation $operation = null,
array $context = []
): void {
if ($property !== 'priceRange') {
return;
}
$alias = $queryBuilder->getRootAliases()[0];
$ranges = [
'budget' => [0, 5000],
'mid' => [5000, 20000],
'premium' => [20000, 50000],
'luxury' => [50000, null],
];
if (!isset($ranges[$value])) {
return;
}
[$min, $max] = $ranges[$value];
$minParam = $queryNameGenerator->generateParameterName('minPrice');
$queryBuilder
->andWhere(sprintf('%s.price >= :%s', $alias, $minParam))
->setParameter($minParam, $min);
if ($max !== null) {
$maxParam = $queryNameGenerator->generateParameterName('maxPrice');
$queryBuilder
->andWhere(sprintf('%s.price < :%s', $alias, $maxParam))
->setParameter($maxParam, $max);
}
}
public function getDescription(string $resourceClass): array
{
return [
'priceRange' => [
'property' => 'priceRange',
'type' => 'string',
'required' => false,
'description' => 'Filter by price range',
'openapi' => [
'enum' => ['budget', 'mid', 'premium', 'luxury'],
],
],
];
}
}Filter Groups
Apply multiple filters per operation:
#[ApiResource(
operations: [
new GetCollection(
filters: [
SearchFilter::class,
OrderFilter::class,
ActiveProductFilter::class,
]
),
]
)]
class Product { /* ... */ }Database Indexing
Always index filtered columns:
#[ORM\Entity]
#[ORM\Index(columns: ['name'], name: 'idx_product_name')]
#[ORM\Index(columns: ['price'], name: 'idx_product_price')]
#[ORM\Index(columns: ['created_at'], name: 'idx_product_created')]
#[ORM\Index(columns: ['is_active', 'deleted_at'], name: 'idx_product_active')]
class Product
{
// ...
}Best Practices
1. Index filtered columns for performance 2. Limit searchable properties - don't expose everything 3. Use exact for IDs and foreign keys 4. Use partial sparingly - it prevents index usage 5. Document filters with OpenAPI descriptions 6. Validate filter values in custom filters
Skill Operating Checklist
Design checklist
- Confirm operation boundaries and invariants first.
- Minimize scope while preserving contract correctness.
- Test both happy path and negative path behavior.
Validation commands
- ./vendor/bin/phpunit --filter=Api
- ./vendor/bin/phpstan analyse
- php bin/console debug:router
Failure modes to test
- Invalid payload or forbidden actor.
- Boundary values / not-found cases.
- Retry or partial-failure behavior for async flows.