Flag of Ukraine
SymfonyCasts stands united with the people of Ukraine

Linking to EasyAdmin from Twig

Keep on Learning!

If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.

Start your All-Access Pass
Buy just this tutorial for $12.00

With a Subscription, click any sentence in the script to jump to that part of the video!

Login Subscribe

Let's go look at the "Show" page for a question that is not approved yet. We have a lot of buttons up here... which is fine. But what if we don't like their order? Like, Delete might make more sense as the last button instead of in the middle.

Ordering Actions

No problem! We can control this inside of configureActions(). At the bottom, after we've set up the actions, call another method - ->reorder() - and pass this the page that we want to reorder them on. In this case, it's Crud::PAGE_DETAIL. Then, very simply, add the names of the actions. We have quite a few... let's start with approve, view... and then the three built-in actions: Action::EDIT, Action::INDEX, and Action::DELETE. These are the five actions that correspond to these five buttons.

... lines 1 - 27
class QuestionCrudController extends AbstractCrudController
... lines 30 - 52
public function configureActions(Actions $actions): Actions
... lines 55 - 84
return parent::configureActions($actions)
... lines 86 - 102
->reorder(Crud::PAGE_DETAIL, [
... lines 111 - 223

Adding Action Icons to the Entire Admin

Now when we refresh... very nice! Though... I'm noticing that it looks a bit odd that some of these have icons and others don't. Let's see if we can add an icon to the Edit and Index actions across our entire admin.

If we want to modify something for all of our admin, we need to do it inside of DashboardController. As we saw earlier, to modify a built-in action, we can call the ->update() function. Pass this the page - Crud::PAGE_DETAIL - the action - Action::EDIT - and then a static function with an Action $action argument. Inside, modify and return the Action at the same time: return $action->setIcon('fa fa-edit').

... lines 1 - 24
class DashboardController extends AbstractDashboardController
... lines 27 - 100
public function configureActions(): Actions
return parent::configureActions()
... line 104
->update(Crud::PAGE_DETAIL, Action::EDIT, static function (Action $action) {
return $action->setIcon('fa fa-edit');
... lines 108 - 110
... lines 112 - 144

Let's do the same thing one more time for the index action button: use Action::PAGE_INDEX... and we'll give this fa fa-list.

... lines 1 - 100
public function configureActions(): Actions
return parent::configureActions()
... lines 104 - 107
->update(Crud::PAGE_DETAIL, Action::INDEX, static function (Action $action) {
return $action->setIcon('fa fa-list');
... lines 112 - 146

Refresh now and... I love it! We see the icons here... and if we go anywhere else - like to an Answer's detail page - the icons are here too.

At this point, we know how to generate a link to any EasyAdminBundle page. If I scroll up a bit... the key is to get the AdminUrlGenerator, and then set whatever you need on it, like the action and CRUD controller.

Go to the Homepage... and click into a question. To make life easier for admin users, I want to put an "Edit" button that takes us directly to the edit action for this specific question. So... how do we generate URLs to EasyAdmin from Twig?

Open the template for this page - templates/question/show.html.twig - and find the <h1>. Here it is. For organization, I'll wrap this in a <div> with class="d-flex justify-content-between".

... lines 1 - 4
{% block body %}
... lines 6 - 37
<div class="col">
<div class="d-flex justify-content-between">
<h1 class="q-title-show">{{ question.name }}</h1>
... lines 41 - 46
... lines 48 - 52
... lines 54 - 93
{% endblock %}

After the h1, add the link... but only for admin users. So {% if is_granted('ROLE_ADMIN') %}... and {% endif %}. Inside <a href=""> - I'll leave the href empty for a moment - with class="text-white". And inside of that, a <span class="fa fa-edit">.

... lines 1 - 4
{% block body %}
... lines 6 - 38
<div class="d-flex justify-content-between">
... lines 40 - 41
{% if is_granted('ROLE_ADMIN') %}
<a class="text-white" href="">
<span class="fa fa-edit"></span>
{% endif %}
... lines 48 - 93
{% endblock %}

Back in our browser, try that. And... hello edit link!

To generate the URL, we need to tell EasyAdmin which CRUD controller, action, and entity ID to use... all stuff we've done in PHP. In Twig, it's nearly the same thing thanks to a shortcut function called ea_url().

This gives us the AdminUrlGenerator object. And so, we can just... call the normal methods, like .setController()... passing the long controller class name. We have to use double slashes so that they don't get escaped, since we're inside of a string. Now add .setAction('edit') and .setEntityId(question.id).

... lines 1 - 41
{% if is_granted('ROLE_ADMIN') %}
<a class="text-white" href="{{ ea_url()
<span class="fa fa-edit"></span>
{% endif %}
... lines 51 - 99

It's a little weird to write this kind of code in Twig, but that's how it's done! Back over here, refresh... and try the button. Got it!

Ok team, last topic! Let's talk about how we can leverage layout panels and other goodies to organize our form into groups, rows, or even tabs on this form page.

Leave a comment!

Login or Register to join the conversation

We should definitely simplify the use of ea_url in twig. This is powerfull tool, but mainly used in one way.


Hey Kslosarz,

Any alternatives for building URLs to EA in Twig templates? :)

This is powerfull tool, but mainly used in one way

What do you mean?


Cat in space

"Houston: no signs of life"
Start the conversation!

What PHP libraries does this tutorial use?

// composer.json
    "require": {
        "php": ">=8.0.2",
        "ext-ctype": "*",
        "ext-iconv": "*",
        "composer/package-versions-deprecated": "^1.11", //
        "doctrine/doctrine-bundle": "^2.1", // 2.5.5
        "doctrine/doctrine-migrations-bundle": "^3.0", // 3.2.1
        "doctrine/orm": "^2.7", // 2.10.4
        "easycorp/easyadmin-bundle": "^4.0", // v4.0.2
        "handcraftedinthealps/goodby-csv": "^1.4", // 1.4.0
        "knplabs/knp-markdown-bundle": "dev-symfony6", // dev-symfony6
        "knplabs/knp-time-bundle": "^1.11", // 1.17.0
        "sensio/framework-extra-bundle": "^6.0", // v6.2.5
        "stof/doctrine-extensions-bundle": "^1.4", // v1.7.0
        "symfony/asset": "6.0.*", // v6.0.1
        "symfony/console": "6.0.*", // v6.0.2
        "symfony/dotenv": "6.0.*", // v6.0.2
        "symfony/flex": "^2.0.0", // v2.0.1
        "symfony/framework-bundle": "6.0.*", // v6.0.2
        "symfony/mime": "6.0.*", // v6.0.2
        "symfony/monolog-bundle": "^3.0", // v3.7.1
        "symfony/runtime": "6.0.*", // v6.0.0
        "symfony/security-bundle": "6.0.*", // v6.0.2
        "symfony/stopwatch": "6.0.*", // v6.0.0
        "symfony/twig-bundle": "6.0.*", // v6.0.1
        "symfony/ux-chartjs": "^2.0", // v2.0.1
        "symfony/webpack-encore-bundle": "^1.7", // v1.13.2
        "symfony/yaml": "6.0.*", // v6.0.2
        "twig/extra-bundle": "^2.12|^3.0", // v3.3.7
        "twig/twig": "^2.12|^3.0" // v3.3.7
    "require-dev": {
        "doctrine/doctrine-fixtures-bundle": "^3.3", // 3.4.1
        "symfony/debug-bundle": "6.0.*", // v6.0.2
        "symfony/maker-bundle": "^1.15", // v1.36.4
        "symfony/var-dumper": "6.0.*", // v6.0.2
        "symfony/web-profiler-bundle": "6.0.*", // v6.0.2
        "zenstruck/foundry": "^1.1" // v1.16.0