---
title: "Register a custom normalizer"
manual: "Headless Content Blocks"
version: "main"
permalink: "https://docs.typo3.org/permalink/netzbewegung/nb-headless-content-blocks:register-a-custom-normalizer@main"
source: "Howto/RegisterCustomNormalizer.rst"
rendered: "2026-09-22T01:10:25+00:00"
---

# Register a custom normalizer {#register-a-custom-normalizer}

This guide shows how to add your own value types to the JSON output — for domain objects or field types the built-in normalizers do not cover.

## When you need one {#when-you-need-one}

A normalizer claims a *value type*. You need one when a field value arrives at the end of the chain and would become `null` (see [Troubleshooting](https://docs.typo3.org/permalink/netzbewegung/nb-headless-content-blocks:troubleshooting@main)) — typically because your extension introduced a custom relation type, or you want to change the shape of an existing one (e.g. categories with more fields than the frozen `uid/pid/title` shape).

Normalizers are consulted in registration order; the first one whose `supports()` returns `true` wins. To *replace* built-in behavior, claim the same type with a higher `priority`.

## Implement the interface {#implement-the-interface}

```php
<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\Normalization;

use Netzbewegung\NbHeadlessContentBlocks\Normalization\Context;
use Netzbewegung\NbHeadlessContentBlocks\Normalization\NormalizerInterface;
use TYPO3\CMS\Core\Resource\FileReference;

final class SquareThumbnailNormalizer implements NormalizerInterface
{
    public function supports(mixed $value, Context $context): bool
    {
        // claim only what you really want to shape
        return $value instanceof FileReference && ($context->getOption('square') === true);
    }

    public function normalize(mixed $value, Context $context): mixed
    {
        // must return a JSON-compatible value (array|string|int|float|null)
        return [
            'publicUrl' => $value->getPublicUrl(),
        ];
    }
}
```

The `Context` gives you the current `TcaSchema`, the request, and the processor `options.` (TypoScript). Normalizers may recurse: call `$context->getChain()->normalize($nestedValue, $context)` for nested rich values, and `$context->buildRecord($record)` to run a related record through the full conversion (identifier mapping, transformers, event).

## Register the service {#register-the-service}

In your extension's `Configuration/Services.yaml`:

```yaml
services:
  MyVendor\MyExtension\Normalization\SquareThumbnailNormalizer:
    tags:
      - name: 'nb_headless.normalizer'
        priority: 100        # higher = earlier in the chain (default 0)
```

No other wiring needed — the `NormalizerChain` receives all tagged services via a tagged iterator.

## Built-in normalizers {#built-in-normalizers}

See [Normalizers and transformers](https://docs.typo3.org/permalink/netzbewegung/nb-headless-content-blocks:normalizers-and-transformers@main) for the built-in chain and its frozen output shapes — do not change them accidentally: the shapes are part of the [JSON contract](https://docs.typo3.org/permalink/netzbewegung/nb-headless-content-blocks:json-contract@main).
