When defining custom content entities one can use EntityListBuilder to build a list of those entities for administrative purpose.

By default such entities are ordered by ID, however it is possible to change ordering by overriding EntityListBuilder::getEntityIds(). Nevertheless this is only ordering of the items. But how to make that table sortable (by clicking on column header to sort ascending and descending)?

4 Answers 4


This is how i sort my entityListBuilder :

i use the method load() :

  public function load() {

    $entity_query = \Drupal::service('entity.query')->get('produits_entity');
    $header = $this->buildHeader();


    $uids = $entity_query->execute();

    return $this->storage->loadMultiple($uids);

Then i build my header like this :

   * {@inheritdoc}
  public function buildHeader() {

    $header = array(
        'id' => array(
            'data' => $this->t('N° produit'),
            'field' => 'id',
            'specifier' => 'id',
            'class' => array(RESPONSIVE_PRIORITY_LOW),
        'nom' => array(
            'data' => $this->t('Nom'),
            'field' => 'name',
            'specifier' => 'name',

        'prixHT' => array(
            'data' => $this->t('Prix HT'),
            'field' => 'prixHT',
            'specifier' => 'prixHT',

        'statut' => array(
            'data' => $this->t('Actif'),
            'field' => 'actif',
            'specifier' => 'actif',


    return $header + parent::buildHeader();

you need to add in your buildHeader() 2 param for each field you want sort :

name of field and specifier

  • In the end I decided to build my base list builder around your idea, which is nicer from coder perspective than building views as Berdir suggested. Although I recommend using getEntityIds() instead of load(). Jul 6, 2016 at 20:46
  • Nice, that is almost like a tablesort integration! I agree to use getEntityIds, it's a bit less code.
    – berliner
    Mar 19, 2020 at 17:30
  • Btw, there are bugs relating to the handling of the field key, just one example: drupal.org/project/drupal/issues/2935344
    – berliner
    Mar 20, 2020 at 8:59
  • Looks like 'entity.query' service doesn't exist in Drupal 9.1, I've used instead 'entity.query.config' see below example. $entity_type = \Drupal::entityTypeManager()->getDefinition('my_entity_type'); $entity_query = \Drupal::service('entity.query.config')->get($entity_type, 'AND'); Aug 19, 2021 at 9:51

Entity query doesn't have tablesort integration. You'd have to implement the query logic yourself.

For content entities, I would strongly recommend to rely on the views integration instead. List builders are limited in functionality and mostly useful for config entities only.

  • How is that accomplished? Do you omit the annotation list_builder, how do you encapsulate a view as config within a custom module and also use *.links.task.yml, *.links.action.yml, *.routing.yml etc to string together the functionality?
    – Jason
    Jul 5, 2018 at 14:30
  • 1
    One option is that you use a basic list builder or even the default and then also provide a view as either required or optional config. The advantage of that approach is that you still have a predictable generated route name that you can rely on for local actions and so on.
    – Berdir
    Jul 6, 2018 at 10:05
  • Ah, do both! Another option could be to programmatically render the view within the list_builder callback.
    – Jason
    Jul 7, 2018 at 22:14
  • I think that would be more complicated for now good reason. Views has a overwrite-existing-route functionality on purpose, so that you can have a non-views fallback and predictable routes (although an abilty to set a route name in views would be great too). By manually calling it, you'd still have a hardcoded views dependency, it would have to be a block/embed view which has some limitations and your code would break if someone deletes that view
    – Berdir
    Jul 8, 2018 at 21:06
  • 1
    Yes, if it's the same path then views will automatically re-use the existing route.
    – Berdir
    Jul 10, 2018 at 17:22

For now, DraggableListBuilder is only available for config entities, not content entities. Take a look at the documentation for DraggableListBuilder 9.0 here. I spent about day trying to make that work until I noticed "config" in the route.

The best workaround I've come up with is to ignore EntityListBuilder and use the ContentEntityForm API instead. ContentEntityForm allows you to use the native TableDrag widget, which adds sortable handles to the form list. There's a well-documented example here.

These are the key lines of code to add to the usual buildform() function:

$form['table-row'][$id]['#attributes']['class'][] = 'draggable';
$form['table-row'][$id]['#weight'] = $row->weight->value;
$form['table-row'][$id]['weight'] = [
  '#type' => 'weight',
  '#title_display' => 'invisible',
  '#default_value' => $row->weight->value,
  '#attributes' => ['class' => ['table-sort-weight']],

If this solution is of interest, I can add a working example.


There is a sortable DraggableListBuilder available now.

use Drupal\Core\Config\Entity\DraggableListBuilder;

 * Provides a listing of user roles.
class RoleListBuilder extends DraggableListBuilder {

  public function getFormID() {
    return 'user_admin_roles_form';

  public function buildHeader() {
    $header['label'] = t('Name');
    return $header + parent::buildHeader();

  public function buildRow(EntityInterface $entity) {
    $row['label'] = $this->getLabel($entity);
    return $row + parent::buildRow($entity);


You need to make sure your config entities have a weight and label. enter image description here

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.