Custom Route Implementation
When to Use
Use custom AJAX routes when you need AJAX endpoints outside Form API: autocomplete, search, content loading, or API-style endpoints. Always implement the
nojsfallback for JavaScript-disabled environments.
Decision
| At this step... | If... | Then... |
|---|---|---|
| Route definition | Needs graceful degradation | Use {ajax} parameter with 'nojs' default |
| Access control | Complex permissions | Use _custom_access callback instead of _permission |
| Response type | Multiple commands needed | Use AjaxResponse with multiple addCommand() calls |
| Response type | Single element update | Return render array directly from callback |
Pattern
Route definition:
# my_module.routing.yml
my_module.ajax_content:
path: '/my-module/ajax/content/{type}/{ajax}'
defaults:
_controller: '\Drupal\my_module\Controller\AjaxController::getContent'
ajax: 'nojs'
requirements:
_permission: 'access content'
type: '[a-z]+'
ajax: 'nojs|ajax'
Controller:
class AjaxController extends ControllerBase {
public function getContent($type, $ajax = 'nojs') {
$content = $this->buildContent($type);
if ($ajax === 'ajax') {
$response = new AjaxResponse();
$response->addCommand(new ReplaceCommand('#content-wrapper', $content));
return $response;
}
// Non-AJAX fallback
return ['#theme' => 'my_template', '#content' => $content];
}
}
Frontend link:
$build['link'] = [
'#type' => 'link',
'#title' => t('Load Content'),
'#url' => Url::fromRoute('my_module.ajax_content', ['type' => 'example', 'ajax' => 'nojs']),
'#attributes' => ['class' => ['use-ajax']], // Drupal auto-handles AJAX
'#attached' => ['library' => ['core/drupal.ajax']],
];
Reference: core/modules/system/tests/modules/ajax_test/ajax_test.routing.yml
Common Mistakes
- Wrong: Not providing non-AJAX fallback → Right: Breaks for users without JavaScript; always handle
nojspath - Wrong: Wrong route parameter pattern → Right:
{ajax}requirement must benojs|ajaxexactly - Wrong: Forgetting to replace
/nojs/with/ajax/in custom JavaScript → Right: AJAX never triggers; useuse-ajaxclass to let Drupal handle it - Wrong: Not using ControllerBase → Right: Missing helper methods like
t(),entityTypeManager(), etc. - Wrong: Missing access control → Right: Security vulnerability; always define
_permissionor_custom_access