Skip to content

Operations Implementation

When to Use

Use #type operations when adding action links (Edit, Delete, etc.) to table rows in FormBase or customizing operations in ListBuilder.

Decision

Situation Choose Why
Standard dropbutton (Edit, Delete) #type operations Core rendering, theme integration
Modal dialog operations #type operations + attributes data-dialog-type attribute triggers modal
AJAX operations #type operations + use-ajax class Loads operation in AJAX callback
Prevent FOUC CSS visibility hidden + .js override Dropbutton JS takes time to load

Pattern

FormBase Operations:

$operations = [];

$operations['edit'] = [
  'title' => $this->t('Edit'),
  'url' => Url::fromRoute('mymodule.item.edit', ['id' => $item_id]),
];

$operations['delete'] = [
  'title' => $this->t('Delete'),
  'url' => Url::fromRoute('mymodule.item.delete', ['id' => $item_id]),
  'attributes' => ['data-dialog-type' => 'modal'], // Opens in modal
];

$row['operations'] = [
  '#type' => 'operations',
  '#links' => $operations,
  '#prefix' => '<div class="mymodule-dropbutton">',
  '#suffix' => '</div>',
];

$form['#attached']['library'][] = 'mymodule/admin'; // Load CSS

CSS (css/mymodule.admin.css):

/* Prevent flash of unstyled content (FOUC) */
.mymodule-dropbutton .dropbutton-wrapper {
  visibility: hidden;
  min-width: 100px;
}

/* Show after JavaScript loads */
.js .mymodule-dropbutton .dropbutton-wrapper {
  visibility: visible;
}

Library (mymodule.libraries.yml):

admin:
  css:
    theme:
      css/mymodule.admin.css: {}
  dependencies:
    - core/dropbutton

ListBuilder Operations:

public function getDefaultOperations(EntityInterface $entity) {
  $operations = parent::getDefaultOperations($entity);

  $operations['custom'] = [
    'title' => $this->t('Custom Action'),
    'url' => $entity->toUrl('custom'),
    'weight' => 10, // Controls order
  ];

  return $operations;
}

Common Mistakes

See Also