Creating links in TableSelect rows

cn flag

I've created a module which defines a Form, but when I try to add a column of links, it is empty when I set the '#type' to 'link'. I am testing this on Drupal 9.3.x.

On the surface, this is the same as Add a link to a tableselect row, but the answer posted there does not contain the full working code, and I'm fairly sure the solution in my context is going to differ from the one posted there.

This is the full code of the class which extends FormBase.

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;

class MyForm extends FormBase {

  public function buildForm(array $form, \Drupal\Core\Form\FormStateInterface $form_state) {
    // Build the fileNameOptions
    $privatePath = 'private://webform/upload/';
    $linkPath = '/system/files/webform/upload/';
    $templatePath = \Drupal::service('file_system')->realpath($privatePath);
    $fileList = glob($templatePath.'/*');
    $fileNameOptions = [];
    $pathLength = strlen($templatePath);
    foreach ($fileList as $filePath) {
      $fileName = substr($filePath, $pathLength + 1);
      $fileNameOptions[$fileName] = [
        'filename' => $fileName,
        'link' => [ 
          'data' => [
            '#type' => 'link',
            '#url' => $linkPath . $fileName,
            '#title' => $fileName,

    //Build the tableSelect.
    $form['templates'] = [
      '#type' => 'tableselect',
      '#header' => ['filename' => 'File name', 'link' => 'Link'],
      '#options' => $fileNameOptions,
      '#empty' => t('No files were found.'),
      '#weight' => 40,
      '#js_select' => FALSE,

    return $form;


   * {@inheritdoc}
  public function getFormId() {
    return 'my_form';

   * {@inheritdoc}
  public function submitForm(array &$form, FormStateInterface $form_state) {
    parent::submitForm($form, $form_state);


For some context, this form is attempting to show the admin a list of private files uploaded from webforms, plus links enabling them to inspect each file. The intent is to use the tableselect to allow the admin to delete unwanted files. I understand that there are security implications, and that this form should only be accessible to trusted admins. Showing the list of files works, and I'm fairly sure I can get the file deletion working. I have a webform which displays the links correctly, but it would be a better user experience if the deletion form also allows the admin to view the files through links.

If I change the '#type' of the link field from 'link' to 'textfield' or 'textarea', the column contains the elements I would expect - the title followed by a textfield or textarea. When I change the '#type' back to 'link' (as shown in the code above), the column is completely empty. When I view the page source, that column contains empty <td> tags.


It's exactly the same as if I change the '#type' to 'foo'. This implies to me that the 'link' field type is not available in the context of this form. To test this, I tried to adapt How do I get all available form field types? to the Drupal 9 context.

$all_types = \Drupal::moduleHandler()->invokeAll('hook_elements');

However, hook_elements() doesn't seem to be used any more, so this doesn't work.

Form and render elements shows 'link' as a valid render element in Drupal 9.4.x, so my understanding is that it should be available in forms created by a custom module.

CommentAdminOverview::buildForm() shows a very similar use of '#type' => 'link'.

My reading of the existing answer is that the problem there was that the column was not added to the '#header' array (not visible in the code posted).

  1. Have I missed some simple detail in the code I've posted above, which causes 'link' fields to be empty, while changing the '#type' to 'textfield' produces the expected result?
  2. Is there some way to verify the set of valid '#type' strings in my context?
  3. If the 'link' field type is not available in my context, how can I make it available?

Nothing is added to the Drupal log when I load this form.


Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.