Create a custom link browser¶
In this tutorial we create a custom link browser and the associated backend link handler.
We create a new tab in the link browser window in the TYPO3 backend:

A custom link browser to link to GitHub issues¶
Tip
If you want to link to a record in a custom table, configure the RecordLinkBrowser. You do not need a custom link browser in that scenario.
We introduce a custom link format to store
the links in this format: t3://github?issue=123
.
This enables us to edit an existing link in the link browser or to change parts of the GitHub URI programmatically later.
And we render the new link in the frontend automatically.
Step by step to a custom link browser
1. Register the custom link browser tab in page TSconfig¶
TCEMAIN.linkHandler {
github {
handler = T3docs\Examples\LinkHandler\GitHubLinkHandler
label = LLL:EXT:examples/Resources/Private/Language/locallang_browse_links.xlf:github
displayAfter = haiku
scanBefore = url
configuration {
project = TYPO3-Documentation/TYPO3CMS-Reference-CoreApi
action = issues
}
}
}
The following options are of note here:
handler
The backend link handler that we create in step 2.
configuration
Some configuration, available to the backend link handler. This information is not available in the frontend. Therefore in the frontend rendering of the link the information must be stored in another way. In this example we hardcoded it. But you could also make it available by TypoScript Setup or as part of the link that is saved.
For a complete list of available option see Link handler configuration.
2. Create a link browser tab¶
To create a link browser tab we implement the interface
\TYPO3\CMS\Backend\LinkHandler\LinkHandlerInterface
.
All backend link handlers provided by the Core extend the abstract class
TYPO3\CMS\Backend\LinkHandler\AbstractLinkHandler
. However, this class is
marked as @internal
and therefore can be changed by the Core Team at any time.
You have the choice of implementing the LinkHandlerInterface
yourself by
having a look at the AbstractLinkHandler
for best practices or to extend
the AbstractLinkHandler
. In the latter case your code might break on
updates though.
In this tutorial, we implement the LinkHandlerInterface
directly, as it is
best practice not to rely on internal classes.
You can find the complete class in the extension EXT:examples on GitHub: GitHubLinkHandler.
We will explain some of the important methods below:
Initialization and dependencies¶
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Recordlist\Controller\AbstractLinkBrowserController;
use TYPO3Fluid\Fluid\View\ViewInterface;
class GitHubLinkHandler implements LinkHandlerInterface
{
protected $linkAttributes = ['target', 'title', 'class', 'params', 'rel'];
protected $linkParts = [];
protected $view;
protected $configuration;
public function __construct(PageRenderer $pageRenderer)
{
$this->pageRenderer = $pageRenderer;
}
public function initialize(AbstractLinkBrowserController $linkBrowser, $identifier, array $configuration)
{
$this->configuration = $configuration;
}
public function setView(\TYPO3Fluid\Fluid\View\ViewInterface $view): void
{
$this->view = $view;
}
}
For technical reasons, not all dependencies needed by the backend link handler can be acquired by Dependency injection. Therefore the following two methods are called by Core classes once the dependencies are available:
LinkHandlerInterface::initialize()
takes care of setting the
\TYPO3\CMS\Backend\Controller\AbstractLinkBrowserController
, the identifier and
the configuration information. In this example we only need the configuration,
the other parameters might be needed in different scenarios.
AbstractLinkBrowserController $linkBrowser
Is the surrounding class calling the link handler. This class stores configuration information of the complete link browser window.
string $identifier
Contains the key of the page TSconfig configuration of the link browser tab this instance renders.
array $configuration
Contains the page TSconfig configuration as array of the link browser tab this instance renders.
The method setView()
is called by the AbstractLinkBrowserController
once the view is available and contains the necessary information to render
the link browser window.
Note
setView()
is not part of the LinkHandlerInterface
and its call is an implementation detail that might be
changed in the future.
Enable dependency injection¶
Backend link handlers are called internally in the TYPO3 Core by
GeneralUtility::makeInstance()
. Therefore dependency injection needs
to be enabled by marking the class as public in the extension's
Configuration/Services.yaml
. As we keep internal states in the link
handler class (for example $linkParts
) it cannot be a singleton and must
be marked as shared: false
:
services:
_defaults:
autowire: true
autoconfigure: true
public: false
T3docs\Examples\LinkHandler\GitHubLinkHandler:
public: true
# The link handler keeps a state and can therefore be no singleton
shared: false
Render the link browser tab¶
The method LinkHandlerInterface::render()
is called when the tab should
be rendered. It registers the required JavaScript in the page renderer, assigns
variables to the view and returns the rendered HTML.
use Psr\Http\Message\ServerRequestInterface;
class GitHubLinkHandler implements LinkHandlerInterface
{
public function render(ServerRequestInterface $request): string
{
$this->pageRenderer->loadRequireJsModule('TYPO3/CMS/Examples/GitHubLinkHandler');
$this->view->getRequest()->setControllerExtensionName('examples');
$this->view->setTemplateRootPaths(['EXT:examples/Resources/Private/Templates/LinkBrowser']);
$this->view->assign('project', $this->configuration['project'] ?? '');
$this->view->assign('action', $this->configuration['action'] ?? '');
$this->view->assign('github', !empty($this->linkParts) ? $this->linkParts['url']['value'] : '');
$this->view->setTemplate('GitHub');
return '';
}
}
Set the link via JavaScript¶
When the button in the rendered form is clicked to set a link, a custom JavaScript class interprets the form data and creates the link to be stored:
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
/**
* Module: TYPO3/CMS/Examples/GitHubLinkHandler
* Github issue link interaction
*/
define(['jquery', 'TYPO3/CMS/Recordlist/LinkBrowser'], function($, LinkBrowser) {
'use strict';
/**
*
* @type {{}}
* @exports T3docs/Examples/GitHubLinkHandler
*/
var GitHubLinkHandler = {};
$(function() {
$('#lgithubform').on('submit', function(event) {
event.preventDefault();
var value = $(this).find('[name="lgithub"]').val();
if (value === 'github:') {
return;
}
if (value.indexOf('github:') === 0) {
value = value.substr(7);
}
LinkBrowser.finalizeFunction('github:' + value);
});
});
return GitHubLinkHandler;
});
It is important that the JavaScript function calls
LinkBrowser.finalizeFunction()
. Otherwise no link will be set.
Can we handle this link?¶
The method LinkHandlerInterface::canHandleLink()
is called when the
user edits an existing link in the link browser. All backend link handlers will
be called and can decide if they can handle that link. If so, they should store
the provided information to be used in rendering (for example, to fill an input
field with the old value).
class GitHubLinkHandler implements LinkHandlerInterface
{
public function canHandleLink(array $linkParts): bool
{
if (!isset($linkParts['type']) || $linkParts['type'] !== 'github') {
return false;
}
$this->linkParts = $linkParts;
return true;
}
}
Format current URL¶
The function LinkHandlerInterface::formatCurrentUrl()
is used to preview
what the link will look like in the backend, for example, in the upper part of
the link browser window.
Attention
LinkHandlerInterface::formatCurrentUrl()
is not used to render the
link in the frontend.
3. Introduce the custom link format¶
You can find the complete class in the extension EXT:examples on GitHub: GitHubLinkHandling.
Our backend link handler implementation from step 1
saves the link in the custom format t3://github?issue=123
via JavaScript.
This format is only an arbitrary string until we tell TYPO3 how to handle links
of the new format by a second class which implements the
TYPO3\CMS\Core\LinkHandling\LinkHandlingInterface
.
Note
There are two interfaces with very similar names and very different
functionality involved here. The
\TYPO3\CMS\Backend\LinkHandler\LinkHandlerInterface
renders a tab in
the link browser window in the backend. Its implementing class is commonly
called a "(backend) link handler". Classes implementing the interface
TYPO3\CMS\Core\LinkHandling\LinkHandlingInterface
handle the
introduced link format. Such a class is called a "(core) link handler".
/**
* Resolves GitHub Links
*/
class GitHubLinkHandling implements LinkHandlingInterface
{
protected $baseUrn = 't3://github';
public function asString(array $parameters): string
{
$githubIssue = (int)$parameters['issue'];
return $this->baseUrn . '?issue=' . $githubIssue;
}
public function resolveHandlerData(array $data): array
{
return [
'issue' => (int)$data['issue'],
];
}
}
The method LinkHandlingInterface::asString()
creates a string
representation from the parameter array.
LinkHandlingInterface::resolveHandlerData()
receives
the string representation of the link and creates the parameter array from it.
For convenience the parameters are already parsed and stored as key-value pairs
in an array for you. You can perform further processing here if needed.
4. Render the custom link format in the frontend¶
The link builder, a class extending the abstract class
\TYPO3\CMS\Frontend\Typolink\AbstractTypolinkBuilder
is called whenever
a link is rendered in the frontend, for example via
TypoScript .typolink
, by the
\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::typoLink
function or by the \TYPO3\CMS\Extbase\Mvc\Web\Routing\UriBuilder
.
use TYPO3\CMS\Frontend\Typolink\LinkResult;
use TYPO3\CMS\Frontend\Typolink\LinkResultInterface;
use TYPO3\CMS\Frontend\Typolink\UnableToLinkException;
/**
* Builds a TypoLink to a GitHub issue
*/
final class GitHubLinkBuilder extends AbstractTypolinkBuilder
{
private const TYPE_GITHUB = 'github';
public function build(
array &$linkDetails,
string $linkText,
string $target,
array $conf
): LinkResultInterface {
$issueId = trim($linkDetails['value']);
$issueId = ltrim($issueId, '#');
$issueId = (int) $issueId;
if ($issueId < 1) {
throw new UnableToLinkException(
'"' . $linkDetails['value'] . '" is not a valid GitHub issue number.',
// Use the Unix timestamp of the time of creation of this message
1665304602,
null,
$linkText
);
}
$url = self::URL_TEMPLATE . $issueId;
return (new LinkResult(self::TYPE_GITHUB, $url))
->withTarget($target)
->withLinkConfiguration($conf)
->withLinkText($linkText);
}
}
The link builder must be registered in ext_localconf.php
, so that
the correct link builder for the new type can be determined by the calling API:
$GLOBALS['TYPO3_CONF_VARS']['FE']['typolinkBuilder']['github'] =
\T3docs\Examples\LinkHandler\GithubLinkBuilder::class;
The function AbstractTypolinkBuilder::build()
is called with the link
configuration and data from the typolink function. If the link can be rendered,
it returns a new \TYPO3\CMS\Frontend\Typolink\LinkResultInterface
. The
actual rendering of the link depends on the context the link is rendered in
(for example HTML or JSON).
If the link cannot be built it should throw a
\TYPO3\CMS\Frontend\Typolink\UnableToLinkException
.
Attention
The configuration from the page TSconfig configuration (step 1) is not available in the frontend. Therefore the information which repository to use must be stored in another way. In this example we hardcoded it. But you could also make it available by TypoScript setup or as part of the link format that is saved.