# Customizing your grids

<div data-full-width="false"><figure><img src="/files/LgYo6f7m6JFOIaJPS8jH" alt="Overview of an admin dashboard"><figcaption></figcaption></figure></div>

Based on the grid generated by default, our goal here is to obtain a nicely customized grid with autocomplete filters and more!

<figure><img src="/files/IxEll0tkvc6JsXRAKSS1" alt="Overview of an admin dashboard"><figcaption></figcaption></figure>

Let's imagine we have the following grid.

{% code title="src/Grid/TalkGrid.php" lineNumbers="true" %}

```php
<?php

namespace App\Grid;

use App\Entity\Talk;
use Sylius\Bundle\GridBundle\Builder\Action\CreateAction;
use Sylius\Bundle\GridBundle\Builder\Action\DeleteAction;
use Sylius\Bundle\GridBundle\Builder\Action\ShowAction;
use Sylius\Bundle\GridBundle\Builder\Action\UpdateAction;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\BulkActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\ItemActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\MainActionGroup;
use Sylius\Bundle\GridBundle\Builder\Field\DateTimeField;
use Sylius\Bundle\GridBundle\Builder\Field\StringField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Component\Grid\Attribute\AsGrid;

#[AsGrid(
    name: 'app_talk',
    resourceClass: Talk::class,
)]
final class TalkGrid extends AbstractGrid
{
    public function __construct()
    {
        // TODO inject services if required
    }

    public function __invoke(GridBuilderInterface $gridBuilder): void
    {
        $gridBuilder
            // see https://github.com/Sylius/SyliusGridBundle/blob/master/docs/field_types.md
            ->withFields(
                StringField::create('title')
                    ->setLabel('Title')
                    ->setSortable(true),
                StringField::create('description')
                    ->setLabel('Description')
                    ->setSortable(true),
                DateTimeField::create('startsAt')
                    ->setLabel('StartsAt'),
                DateTimeField::create('endsAt')
                    ->setLabel('EndsAt'),
                StringField::create('track')
                    ->setLabel('Track')
                    ->setPath('track.value')
                    ->setSortable(true),    
            )
            ->addActionGroup(
                MainActionGroup::create(
                    CreateAction::create(),
                )
            )
            ->addActionGroup(
                ItemActionGroup::create(
                    // ShowAction::create(),
                    UpdateAction::create(),
                    DeleteAction::create()
                )
            )
            ->addActionGroup(
                BulkActionGroup::create(
                    DeleteAction::create()
                )
            )
        ;
    }
}
```

{% endcode %}

## Fields

<div data-full-width="false"><figure><img src="/files/76KwYEw7vUoRgnrK0dXd" alt="Overview of an admin dashboard"><figcaption></figcaption></figure></div>

Let's clean up our grid and remove unnecessary fields.

{% code title="src/Grid/TalkGrid.php" lineNumbers="true" %}

```php
<?php

namespace App\Grid;

use App\Entity\Talk;
use Sylius\Bundle\GridBundle\Builder\Action\CreateAction;
use Sylius\Bundle\GridBundle\Builder\Action\DeleteAction;
use Sylius\Bundle\GridBundle\Builder\Action\ShowAction;
use Sylius\Bundle\GridBundle\Builder\Action\UpdateAction;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\BulkActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\ItemActionGroup;
use Sylius\Bundle\GridBundle\Builder\ActionGroup\MainActionGroup;
use Sylius\Bundle\GridBundle\Builder\Field\DateTimeField;
use Sylius\Bundle\GridBundle\Builder\Field\StringField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Component\Grid\Attribute\AsGrid;

#[AsGrid(
    name: 'app_talk',
    resourceClass: Talk::class,
)]
final class TalkGrid extends AbstractGrid
{
    public function __invoke(GridBuilderInterface $gridBuilder): void
    {
        $gridBuilder
            ->withFields(
                StringField::create('title')
                    ->setLabel('Title')
                    ->setSortable(true),
                DateTimeField::create('startsAt')
                    ->setLabel('StartsAt'),
            )
            ->addActionGroup(
                MainActionGroup::create(
                    CreateAction::create(),
                )
            )
            ->addActionGroup(
                ItemActionGroup::create(
                    // ShowAction::create(),
                    UpdateAction::create(),
                    DeleteAction::create()
                )
            )
            ->addActionGroup(
                BulkActionGroup::create(
                    DeleteAction::create()
                )
            )
        ;
    }
}
```

{% endcode %}

We have removed the `description`, `endsAt` and `track` grid fields.

<div data-full-width="false"><figure><img src="/files/76KwYEw7vUoRgnrK0dXd" alt="Overview of an admin dashboard"><figcaption></figcaption></figure></div>

## Adding the speaker avatar using Twig field

<div data-full-width="false"><figure><img src="/files/bgvaZ28UmXM7PkREvwxt" alt="Overview of an admin dashboard"><figcaption></figcaption></figure></div>

Now, let's add the speaker avatar into our talk grid.

```php
<?php

namespace App\Grid;

use Sylius\Bundle\GridBundle\Builder\Field\TwigField;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Component\Grid\Attribute\AsGrid;
// ...

#[AsGrid(
    name: 'app_talk',
    resourceClass: Talk::class,
)]
final class TalkGrid extends AbstractGrid
{
    // ...

    public function __invoke(GridBuilderInterface $gridBuilder): void
    {
        $gridBuilder
            ->withFields(
                TwigField::create('avatar', 'talk/grid/field/speaker_avatar.html.twig')
                    ->setPath('.')
                    ->setLabel('app.ui.avatar'),
            )
            // ...
        ;
    }

    // ...
}
```

{% code title="templates/talk/grid/speaker\_avatar.html.twig" lineNumbers="true" %}

```php
{{ avatar.default(avatar_path, 'img-thumbnail') }}
```

{% endcode %}

## Filters

### Adding an autocomplete filter

<div data-full-width="false"><figure><img src="/files/hD3VprldZiPN2wwbiY3w" alt="Overview of an admin dashboard"><figcaption></figcaption></figure></div>

We'd like to filter our talks by a specific speaker.

So, let's start by creating a FormType following the [Symfony UX Autocomplete Documentation](https://symfony.com/bundles/ux-autocomplete/current/index.html#usage-in-a-form-with-ajax)

{% code title="src/Form/SpeakerAutocompleteType.php" lineNumbers="true" %}

```php
declare(strict_types=1);

namespace App\Form;

use App\Entity\Speaker;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;

#[AsEntityAutocompleteField(
    alias: 'app_admin_speaker',
    route: 'ux_entity_autocomplete_admin',
)]
final class SpeakerAutocompleteType extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'class' => Speaker::class,
            'choice_label' => 'fullName',
        ]);
    }

    public function getParent(): string
    {
        return BaseEntityAutocompleteType::class;
    }
}
```

{% endcode %}

Now we need to create our custom Grid filter.

{% code title="src/Grid/Filter/SpeakerFilter.php" lineNumbers="true" %}

```php
<?php

declare(strict_types=1);

namespace App\Grid\Filter;

use App\Form\SpeakerAutocompleteType;
use Sylius\Component\Grid\Attribute\AsFilter;
use Sylius\Component\Grid\Data\DataSourceInterface;
use Sylius\Component\Grid\Filter\EntityFilter;
use Sylius\Component\Grid\Filtering\FilterInterface;

#[AsFilter(
    formType: SpeakerAutocompleteType::class,
    template: '@SyliusBootstrapAdminUi/shared/grid/filter/select.html.twig',
)]
final class SpeakerFilter implements FilterInterface
{
    public function __construct(
        private readonly EntityFilter $entityFilter,
    ) {
    }

    public function apply(DataSourceInterface $dataSource, string $name, mixed $data, array $options): void
    {
        // We simply reuse the logic of the built-in EntityFilter provided by the Sylius Grid package.
        $this->entityFilter->apply($dataSource, $name, $data, $options);
    }
}
```

{% endcode %}

Then, we add our `SpeakerFilter` to our grid.

{% code title="src/Grid/TalkGrid.php" lineNumbers="true" %}

```php
<?php

declare(strict_types=1);

namespace App\Grid;

use App\Grid\Filter\SpeakerFilter;
use Sylius\Bundle\GridBundle\Builder\Filter\Filter;
use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface;
use Sylius\Bundle\GridBundle\Grid\AbstractGrid;
use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface;
use Sylius\Component\Grid\Attribute\AsGrid;
// ...

#[AsGrid(
    name: 'app_talk',
    resourceClass: Talk::class,
)]
final class TalkGrid extends AbstractGrid
{
    // ...

    public function __invoke(GridBuilderInterface $gridBuilder): void
    {
        $gridBuilder
            ->withFilters(
                Filter::create(name: 'speaker', type: SpeakerFilter::class)
                    ->setLabel('app.ui.speaker')
                    ->setOptions(['fields' => ['speaker.id']])
            );
            
            // ...
    }

    // ...
}
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://stack.sylius.com/cookbook/admin_panel/grids.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
