agentsclimarketplace

Api filter

Skill api-platform/skillset/skills/api-filter

API Platform agent skills

Install
npx -y skills add api-platform/skillset --skill api-filter

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

2 things to look at

  • no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
  • 24 stars24 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Adds filters to API Platform collections using the canonical QueryParameter approach. Use whenever the user wants search, sorting, date ranges, boolean/enum/numeric filtering, IRI lookups, free-text search, or any 'let users filter/search/sort the list by X' request on a collection — even if they never say 'filter'. Also use when migrating legacy #[ApiFilter]/SearchFilter code.

SKILL.md

9.5 KB, as published. Nobody here has run it

Adding Filters to Collections

Declare filters with QueryParameter in the operation's parameters: array. The key is the query-string parameter exposed to clients; the filter: is the filter instance. This is the canonical approach for API Platform 4.4+ — it works with any state provider and is per-operation explicit.

use ApiPlatform\Metadata\GetCollection;
use ApiPlatform\Metadata\QueryParameter;
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;

#[GetCollection(
    parameters: [
        'status' => new QueryParameter(filter: new ExactFilter()),
    ],
)]
class Order {}

Client: GET /orders?status=shipped

#[ApiFilter] is legacy. The class-level #[ApiFilter(SearchFilter::class, ...)] attribute and the multi-strategy filters (SearchFilter, BooleanFilter, NumericFilter, OrderFilter, BackedEnumFilter) are deprecated in 4.4 and removed in 6.0. Don't teach or add them in new code. If you encounter them, the migration target is the QueryParameter + single-purpose filter set below. The mapping is in the table at the bottom.

Canonical filter set

Each filter does one thing. They live in two parallel namespaces — pick the one matching your persistence layer:

FilterPurposeORMMongoDB ODM
ExactFilterequality, multi-value (IN); booleans/ints/enums via nativeTypeApiPlatform\Doctrine\Orm\Filter\ExactFilterApiPlatform\Doctrine\Odm\Filter\ExactFilter
PartialSearchFilterLIKE %x% substring…\Orm\Filter\PartialSearchFilter…\Odm\Filter\PartialSearchFilter
ComparisonFiltergt/gte/lt/lte/ne (decorates an equality filter)…\Orm\Filter\ComparisonFilter…\Odm\Filter\ComparisonFilter
SortFilterORDER BY…\Orm\Filter\SortFilter…\Odm\Filter\SortFilter
DateFilterbefore/after date ranges…\Orm\Filter\DateFilter…\Odm\Filter\DateFilter
RangeFilterbetween/gt/lt on numbers…\Orm\Filter\RangeFilter…\Odm\Filter\RangeFilter
ExistsFilterIS NULL / IS NOT NULL…\Orm\Filter\ExistsFilter…\Odm\Filter\ExistsFilter
IriFilterrelationship lookup by IRI…\Orm\Filter\IriFilter…\Odm\Filter\IriFilter
OrFilterdecorator: switches a primary's WHERE to orWhere…\Orm\Filter\OrFilter…\Odm\Filter\OrFilter
FreeTextQueryFilterdecorator: broadcasts one value across N properties…\Orm\Filter\FreeTextQueryFilter…\Odm\Filter\FreeTextQueryFilter

ComparisonFilter and OrFilter are still marked @experimental in 4.4 — they are the intended migration target but their API may shift before stabilizing.

Common filters

Exact match

'status' => new QueryParameter(filter: new ExactFilter())

GET /orders?status=shipped. Arrays produce an IN: ?status[]=draft&status[]=sent.

Boolean / integer / enum — ExactFilter + nativeType

There is no BooleanFilter in the canonical set. Use ExactFilter and declare the native type so values are cast and documented correctly:

use Symfony\Component\TypeInfo\Type\BuiltinType;
use Symfony\Component\TypeInfo\TypeIdentifier;

'active' => new QueryParameter(
    filter: new ExactFilter(),
    nativeType: new BuiltinType(TypeIdentifier::BOOL),
),

GET /orders?active=true. Use TypeIdentifier::INT for integers; pass an enum's native type for backed enums.

Partial search (LIKE)

use ApiPlatform\Doctrine\Orm\Filter\PartialSearchFilter;

'q' => new QueryParameter(filter: new PartialSearchFilter(), property: 'title')

GET /books?q=harry. property: maps the public param name to the entity field.

Comparison (gt/gte/lt/lte/ne)

use ApiPlatform\Doctrine\Orm\Filter\ComparisonFilter;

'price' => new QueryParameter(filter: new ComparisonFilter(new ExactFilter()))

GET /products?price[gt]=100&price[lte]=500.

Date range

use ApiPlatform\Doctrine\Orm\Filter\DateFilter;

'createdAt' => new QueryParameter(filter: new DateFilter())

GET /orders?createdAt[after]=2024-01-01&createdAt[before]=2024-12-31.

Numeric range

use ApiPlatform\Doctrine\Orm\Filter\RangeFilter;

'price' => new QueryParameter(filter: new RangeFilter())

GET /products?price[between]=10..100.

Exists (null check)

use ApiPlatform\Doctrine\Orm\Filter\ExistsFilter;

'deletedAt' => new QueryParameter(filter: new ExistsFilter())

GET /orders?deletedAt[exists]=false.

Relation (IRI)

use ApiPlatform\Doctrine\Orm\Filter\IriFilter;

'author' => new QueryParameter(filter: new IriFilter())

GET /books?author=/authors/1.

Sorting — SortFilter

There is no OrderFilter in the canonical set. Use SortFilter. Two forms:

use ApiPlatform\Doctrine\Orm\Filter\SortFilter;

// Per-property named parameter
'orderName' => new QueryParameter(filter: new SortFilter(), property: 'name'),
// → GET /books?orderName=desc

// Dynamic, OrderFilter-style: one parameter, any allowed property
'order[:property]' => new QueryParameter(filter: new SortFilter()),
// → GET /books?order[name]=asc&order[createdAt]=desc

property: can traverse relations ('department.company.name' sorts across joins). Control null placement with nullsComparison:

use ApiPlatform\Doctrine\Common\Filter\OrderFilterInterface;

'orderHireDate' => new QueryParameter(
    filter: new SortFilter(nullsComparison: OrderFilterInterface::NULLS_ALWAYS_FIRST),
    property: 'hireDate',
),

Free-text across multiple fields

FreeTextQueryFilter decorates a primary and broadcasts one value to several properties; wrap with OrFilter to match any of them:

use ApiPlatform\Doctrine\Orm\Filter\FreeTextQueryFilter;
use ApiPlatform\Doctrine\Orm\Filter\OrFilter;
use ApiPlatform\Doctrine\Orm\Filter\ExactFilter;

'autocomplete' => new QueryParameter(
    filter: new FreeTextQueryFilter(new OrFilter(new ExactFilter())),
    properties: ['name', 'email', 'description'],
),

GET /users?autocomplete=alice.

Validating filter parameters

use Symfony\Component\Validator\Constraints as Assert;

'length' => new QueryParameter(
    filter: new ExactFilter(),
    constraints: [new Assert\Length(max: 100)],
)

Invalid values yield a 422 before the query runs.

Default ordering

Set a default sort directly on the operation (no parameter needed):

new GetCollection(order: ['createdAt' => 'DESC'])

Legacy → canonical migration map

Legacy (deprecated 4.4, removed 6.0)Canonical replacement
#[ApiFilter(SearchFilter::class, ['x' => 'exact'])]'x' => new QueryParameter(filter: new ExactFilter())
SearchFilter strategy partialPartialSearchFilter
BooleanFilterExactFilter + nativeType: BOOL
NumericFilterExactFilter + nativeType: INT
BackedEnumFilterExactFilter + enum nativeType
OrderFilterSortFilter
RangeFilter, DateFilter, ExistsFiltersame class — survives as drop-in, just move to QueryParameter

Laravel (Eloquent)

The QueryParameter wiring is identical, but Laravel ships its own Eloquent filter set under ApiPlatform\Laravel\Eloquent\Filter\ — these are not the Doctrine classes and the names differ. Declare them with class-level #[QueryParameter] / #[GetCollection(parameters: …)] on the model (or DTO). The key, property, properties, :property placeholder and constraints options work the same.

NeedDoctrine (above)Laravel Eloquent class
equalityExactFilterEqualsFilter
LIKE %x%PartialSearchFilterPartialSearchFilter
LIKE x% / %xStartSearchFilter / EndSearchFilterStartSearchFilter / EndSearchFilter
booleanExactFilter + nativeTypeBooleanFilter
date rangeDateFilterDateFilter (filterContext: ['include_nulls' => true] to keep nulls; default excludes)
numeric rangeRangeFilterRangeFilter
sortSortFilterOrderFilter
OROrFilterOrFilter (decorator: new OrFilter(new EqualsFilter()))
use ApiPlatform\Laravel\Eloquent\Filter\EqualsFilter;
use ApiPlatform\Laravel\Eloquent\Filter\OrderFilter;
use ApiPlatform\Metadata\ApiResource;
use ApiPlatform\Metadata\QueryParameter;

#[ApiResource]
#[QueryParameter(key: 'name', filter: EqualsFilter::class)]
#[QueryParameter(key: 'sort[:property]', filter: OrderFilter::class, properties: ['name', 'id'])]
class Book extends Model {}

Constraints are Laravel validation rules (a string/array), not Symfony constraints: new QueryParameter(key: 'name', filter: PartialSearchFilter::class, constraints: 'min:2'). Custom filters implement ApiPlatform\Laravel\Eloquent\Filter\FilterInterface (apply(Builder $builder, mixed $values, Parameter $parameter, array $context = []): Builder); scaffold with php artisan make:filter. There is no ExistsFilter, ComparisonFilter, IriFilter or FreeTextQueryFilter in the Eloquent set — nativeType on a parameter is also unused (use BooleanFilter for booleans). #[ApiFilter] is not the Laravel idiom; models declare #[QueryParameter] directly.

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.