← Back to list

⚡ Add Filters to Your Symfony API in 3 Minutes Flat

Stop writing the same filter for every endpoint. There’s a better way.

Ismaile ABDALLAH · 2026-03-04 21:00 · 43 claps · 3.1 min read
#symfony #symfony-bundle #php #github
Open on Medium ↗
Wiki topics: FT · Fine-tuning & Adaptation 🔓 · Open Source

⚡ Add Filters to Your Symfony API in 3 Minutes Flat

Stop writing the same filter for every endpoint. There’s a better way.

Photo by sehoon ye on Unsplash

Photo by sehoon ye on Unsplash

😩 The Problem We All Know Too Well

You’re building a REST API with Symfony. Your client needs to filter users by name, status, age range, email domain… You know the drill.

So you start writing this:

public function list(Request $request): JsonResponse
{
    $qb = $this->userRepository->createQueryBuilder('u');

    if ($request->query->has('firstname')) {
        $qb->andWhere('u.firstname = :firstname')
            ->setParameter('firstname', $request->query->get('firstname'));
    }

    if ($request->query->has('status')) {
        $qb->andWhere('u.status = :status')
            ->setParameter('status', $request->query->get('status'));
    }

    if ($request->query->has('min_age')) {
        $qb->andWhere('u.age >= :min_age')
            ->setParameter('min_age', $request->query->get('min_age'));
    }

    // ... 50 more lines of this 😭
}

For every. single. endpoint. Just copy-paste spaghetti that grows with every new requirement.

🎯 What if You Could Do This Instead?

#[Route('/api/users', methods: ['GET'])]
#[ApiFilter(name: 'firstname', allowedTypes: [FilterType::Eq->value, FilterType::Like->value])]
#[ApiFilter(name: 'status', enumClass: UserStatus::class)]
#[ApiFilter(name: 'age', allowedTypes: [FilterType::Gte->value, FilterType::Lte->value])]
#[ApiFilter(name: 'deleted_at', allowedTypes: [FilterType::IsNull->value])]
public function list(Filters $filters): JsonResponse
{
    $users = $this->userRepository->findByFilters($filters);

    return $this->json($users);
}

That’s it. No manual parsing. No parameter binding. No validation code. Just declare what you allow, and the bundle handles the rest.

📦 Meet isma/api-filters-bundle

**isma/api-filters-bundle** is a Symfony bundle that:

  1. 🏷️ Reads #[ApiFilter] attributes from your controller methods
  2. Parses & validates query string filters automatically
  3. 🔧 Applies them to your Doctrine QueryBuilder.

It ships with multiple filters types and is fully extensible.

🚀 Setup in 3 Minutes

Minute 1 — Install

composer require isma/api-filters-bundle

Minute 2 — Declare Your Filters

Add #[ApiFilter] attributes to your controller action:

use Isma\ApiFiltersBundle\Attribute\ApiFilter;
use Isma\ApiFiltersBundle\ValueObject\Filters;
use Isma\ApiFiltersBundle\ValueObject\FilterType;

final class ProductController
{
    #[Route('/api/products', methods: ['GET'])]
    #[ApiFilter(name: 'name', allowedTypes: [FilterType::Like->value, FilterType::StartWith->value])]
    #[ApiFilter(name: 'price', allowedTypes: [FilterType::Gte->value, FilterType::Lte->value])]
    #[ApiFilter(name: 'category', allowedTypes: [FilterType::Eq->value])]
    #[ApiFilter(name: 'discontinued_at', allowedTypes: [FilterType::IsNull->value])]
    public function list(Filters $filters): JsonResponse
    {
        // $filters is automatically resolved from the query string 🪄
    }
}

The Filters object is injected via Symfony's ValueResolverInterface — no manual Request parsing needed.

Minute 3 — Apply to Your Query

use Isma\ApiFiltersBundle\Filter\FilterApplierInterface;

final class ProductRepository
{
    public function __construct(
        private FilterApplierInterface $filterApplier,
    ) {}

    public function findByFilters(Filters $filters): array
    {
        $qb = $this->createQueryBuilder('p');

        $this->filterApplier->apply($qb, $filters, [
            'name'             => 'p.name',
            'price'            => 'p.price',
            'category'         => 'p.category',
            'discontinued_at'  => 'p.discontinuedAt',
        ]);

        return $qb->getQuery()->getResult();
    }
}

The mapping array ('name' => 'p.name') connects the public filter names from the URL to your actual Doctrine columns. That's your security layer — only mapped fields can be filtered.

Now hit your API:

GET /api/products?filters[name][like]=keyboard
GET /api/products?filters[price][gte]=50&filters[price][lte]=200
GET /api/products?filters[category][eq]=electronics

⏱️ Three minutes. No extra code. Completely validated.

Each filter type lives in its own class. Want to add an x-between filter? Simply create it:

use Doctrine\ORM\QueryBuilder;
use Isma\ApiFiltersBundle\Filter\FilterStrategyInterface;

final class BetweenFilterStrategy implements FilterStrategyInterface
{
    public function getType(): string
    {
        return 'x-between';
    }

    public function apply(
        QueryBuilder $queryBuilder,
        string $column,
        mixed $value,
        string $parameterName,
    ): void {
        $queryBuilder
            ->andWhere(sprintf(
                '%s BETWEEN :%s_min AND :%s_max',
                $column,
                $parameterName,
                $parameterName,
            ))
            ->setParameter($parameterName . '_min', $value[0])
            ->setParameter($parameterName . '_max', $value[1]);
    }
}

That’s all. ✅ No service registration. ✅ No YAML. ✅ No XML.

It’s automatically discovered via Symfony autoconfiguration.

Example request:

GET /api/products?filters[price][x-between][]=100&filters[price][x-between][]=500

✍️ Conclusion

If you’re building a custom API with Symfony and don’t need the full API Platform ecosystem, this bundle is for you. Lightweight, declarative, and free from unnecessary complexity — just filtering, nothing more.

The project is open source and contributions are welcome. Whether it’s a new filter, a bug fix, or an improvement idea, feel free to open an issue or submit a PR on GitHub. Every contribution matters.

If this article helped you, give it a 👏 and follow me for more Symfony tips!


메타데이터
post_id
35f1d3e71c05
slug
add-filters-to-your-symfony-api-in-3-minutes-flat-35f1d3e71c05
url
https://medium.com/@ZeroCool001/add-filters-to-your-symfony-api-in-3-minutes-flat-35f1d3e71c05
canonical_url
https://medium.com/@ZeroCool001/add-filters-to-your-symfony-api-in-3-minutes-flat-35f1d3e71c05
author_url
https://medium.com/@ZeroCool001
status
ok
fetched_at
2026-07-30 16:09:00