Use AJAX in the Drupal Form API with Links (or any other element)

The use of #ajax attribute in the Drupal Form API is limited to a certain set of form elements. But what if you want to use a link to trigger your AJAX instead? It may be possible using the Drupal AJAX framework, but you'll need to figure out a way to pass the $form_state without breaking your form. There is a workaround that will allow you to use the AJAX built into the Form API with links or any other element.


The trick is to use a hidden form element, with all the needed AJAX, and jQuery .trigger() to execute the event. It's not the most ideal or cleanest solution, but it works.
Here's some code to get your started.

In your form

Create two elements: one to handle the AJAX and the other to present to the user (e.g. a hyperlink).

  // Include the JavaScript for our pseudo-trigger.
  $form['#attached']['js'][] = drupal_get_path('module', 'example') .'/example.js';
  // Create the link element.
  $form['link'] = array(    
    '#markup' => '<a href="#" id="trigger-ajax">'. t('Execute AJAX') .'</a>',    
  // Create the button.
  $form['ajax_submit'] = array(
    '#type' => 'button',
    '#value' => 'Hidden button',        
    '#ajax' => array(
      'callback' => 'example_ajax_callback',
      'wrapper' => 'container-id',
      'event' => 'click',
    // Hide the button.
    '#attributes' => array(
      'style' => array('display:none;'),      
      'class' => array(

In your JavaScript

Use jQuery to act when the hyperlink is clicked and trigger a click event on the hidden button.

(function ($) {
  Drupal.behaviors.example = {
    attach: function (context, settings) {
      // When the hyperlink is clicked...
        // Stop the link's default behavior.
        // Trigger a click even on the hidden button to initate our AJAX.
        // I prefer using a class to find the element because sometimes Drupal appends the id with the delta.
        // e.g. edit-ajax-submit--2
        $('input.hidden-button', '#container-id').trigger('click');


That's all there is to it. If you've come across a better solution, post a comment and let me know about it.