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://page, t3://file, t3://url, mailto:, tel:) that RTE-managed fields such as tt_content.bodytext store verbatim in the database. In a normal Fluid render, lib.parseFunc_RTE 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.fieldTransforms key in the site configuration, nested as <TypeName>: <fieldName>: <transformName>:

config/sites/<site-identifier>/config.yaml
typograph:
  fieldTransforms:
    TypeName:
      fieldName: transformName
Copied!

Keys follow GraphQL naming: TypeName matches a type in your schema (e.g. Content) and fieldName matches a field on that type (e.g. bodytext). Internally the resolver converts the field name to the corresponding database column name via camelCaseToLowerCaseUnderscored, the same conversion used everywhere else in TypoGraph.

Built-In Transforms 

TSFE-Related Caveats 

Because TypoGraph's middleware runs before typo3/cms-frontend/page-resolver, there is no running TypoScriptFrontendController. LinkFactory copes with this by lazily building a minimal TSFE from the request attributes attached by the earlier typo3/cms-frontend/site middleware (site, language, routing). This is sufficient for all built-in link types.

TypoScript-driven link behaviour such as config.linkVars, config.forceAbsoluteUrls, or custom typolinkBuilder 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.parseFunc_RTE 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\\TypoGraph\\Transformer\\TransformerInterface and register it under a short name in Configuration/Services.yaml:

Classes/Transformer/MyCustomTransformer.php
<?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;
    }
}
Copied!
Configuration/Services.yaml (sitepackage)
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'
Copied!

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 ResolverService::process() directly without a request), transforms are skipped entirely.