Field Value Transforms
Field value transforms run on raw database values after they are fetched and before they are handed to the GraphQL layer. They allow site-specific post-processing of individual fields without leaking the processing logic into the schema or into client code.
The primary use case is resolving TYPO3 link-handling URIs (t3://,
t3://, t3://, mailto:, tel:) that RTE-managed fields
such as tt_ store verbatim in the database. In a normal
Fluid render, lib. resolves these to real URLs during
page rendering. TypoGraph bypasses the TSFE-driven rendering pipeline, so
without a transform the raw t3:// URIs would reach the client as-is.
Configuration Structure
Transforms are configured under the typograph. key in
the site configuration, nested as <Type:
typograph:
fieldTransforms:
TypeName:
fieldName: transformName
Keys follow GraphQL naming: Type matches a type in your schema
(e.g. Content) and field matches a field on that type
(e.g. bodytext). Internally the resolver converts the field name to
the corresponding database column name via
camel, the same conversion used everywhere
else in TypoGraph.
Built-In Transforms
typolinks
Expands TYPO3 link-handling URIs in HTML href attributes into real
URLs. The following schemes are rewritten:
t3://page?uid=<id> [#<fragment>] t3://file?uid=<id> t3://file?identifier=<storage>:<path> t3://url?url=<target> t3://record?identifier=<key>&uid=<id> mailto:<address>tel:<number>
Any other scheme (plain http/https, in-page anchors, protocol-relative
URLs) is left untouched so that user-pasted external links remain valid.
Resolution uses TYPO3's
TYPO3\\, which internally
dispatches to the same link builders (Page,
File, Email, Telephone,
External, Database) used during
regular frontend rendering.
typograph:
fieldTransforms:
Content:
bodytext: typolinks
With this configuration, a row whose bodytext contains:
<p>See <a href="t3://page?uid=76#64">this page</a> for details.</p>
will be delivered to the GraphQL client as:
<p>See <a href="/path/to/page/#c64">this page</a> for details.</p>
TSFE-Related Caveats
Because TypoGraph's middleware runs before
typo3/, there is no running
Typo. Link copes with this by
lazily building a minimal TSFE from the request attributes attached by
the earlier typo3/ middleware (site, language,
routing). This is sufficient for all built-in link types.
TypoScript-driven link behaviour such as config.,
config., or custom typolink entries is
not applied, because the full TypoScript setup is never loaded. If
your site relies on any of those, configure the equivalent at a
different level (e.g. absolute URLs via Site configuration).
Full lib. processing (paragraph wrapping, <br>
handling, HTML-sanitizer integration, …) is not available from this
middleware position because it depends on the TypoScript setup array
being populated by the regular frontend pipeline. The typolinks
transform deliberately scopes itself to link rewriting for this reason.
Registering Custom Transforms
To add your own transform, implement
Digicademy\\ and
register it under a short name in Configuration/:
<?php
declare(strict_types=1);
namespace Vendor\Sitepackage\Transformer;
use Digicademy\TypoGraph\Transformer\TransformerInterface;
use Psr\Http\Message\ServerRequestInterface;
final class MyCustomTransformer implements TransformerInterface
{
public function transform(mixed $value, ServerRequestInterface $request): mixed
{
// return $value unchanged when it is not of a type you handle
if (!is_string($value)) {
return $value;
}
// …your post-processing…
return $value;
}
}
services:
_defaults:
autowire: true
autoconfigure: true
public: false
Vendor\Sitepackage\:
resource: '../Classes/*'
# Extend the TransformerRegistry provided by TypoGraph with the
# new transform under a short name. Keep the `typolinks` entry so
# the built-in transform remains available alongside yours.
Digicademy\TypoGraph\Transformer\TransformerRegistry:
arguments:
$transformers:
typolinks: '@Digicademy\TypoGraph\Transformer\TypolinksTransformer'
myCustom: '@Vendor\Sitepackage\Transformer\MyCustomTransformer'
Transforms must:
- Return the input unchanged for value types they do not handle.
- Avoid throwing for unexpected inputs. The resolver logs and swallows exceptions so that one misbehaving transform does not abort the entire GraphQL response, but logging contributes noise; prefer a defensive early return.
Error Behaviour
- If a configured transform name has no entry in the registry, the resolver logs a warning and leaves the raw value in place. This is usually a typo in site configuration or a missing DI wiring.
- If a transform throws, the resolver logs an error and leaves the raw value in place for the affected record.
- If the current request is not attached (only possible when calling
Resolverdirectly without a request), transforms are skipped entirely.Service:: process ()