netzbewegung/nb-headless-content-blocks

Connects together EXT:headless and EXT:content_block

Maintainers

Package info

github.com/Netzbewegung-Backend/nb_headless_content_blocks

Language:Shell

Type:typo3-cms-extension

pkg:composer/netzbewegung/nb-headless-content-blocks

Transparency log

Statistics

Installs: 3 711

Dependents: 1

Suggesters: 0

Stars: 4

Open Issues: 1

0.0.26 2026-07-23 14:10 UTC

README

TYPO3 compatibility TYPO3 compatibility

TYPO3 Extension EXT:nb_headless_content_blocks

Connects together EXT:headless (friendsoftypo3/headless) and EXT:content_blocks (friendsoftypo3/content-blocks)

TYPO3 Installation

Install extension using composer

composer require netzbewegung/nb_headless_content_blocks

and then, include Site Set "Headless Content Blocks", and you are ready to go.

Features

  • Converts all complex objects into an array without extra configuration
  • Richtext fields are automaticly converted via parseFunc($value, null, '< lib.parseFunc_RTE')
  • Additional thumbnails can be created via headless.php per Content Block
  • Support for EXT:container

Custom Configuration per Content Block Type

Create headless.php inside each Content Block.

your_extension/ContentBlocks/ContentElements/your-content-block-element/headless.php

<?php

use TYPO3\CMS\Fluid\ViewHelpers\Uri\ImageViewHelper;

$generateThumbnail = function (array $arguments): string {
    if (array_key_exists('absolute', $arguments) === false) {
        $arguments['absolute'] = true;
    }

    $imageViewHelper = new ImageViewHelper();

    foreach ($imageViewHelper->prepareArguments() as $argumentKey => $argumentDefinition) {
        if (array_key_exists($argumentKey, $arguments) === false) {
            $arguments[$argumentKey] = $argumentDefinition->getDefaultValue();
        }
    }

    $imageViewHelper->setArguments($arguments);

    return $imageViewHelper->initializeArgumentsAndRender();

};

foreach ($data['items'] ?? [] as $itemKey => $item) {

    if ($item['image']) {
        $image = $item['image'];

        $data['items'][$itemKey]['image']['thumbnails'] = [
            'mobile' => $generateThumbnail(['src' => $image['id'], 'treatIdAsReference' => true, 'width' => 320]),
            'desktop' => $generateThumbnail(['src' => $image['id'], 'treatIdAsReference' => true, 'width' => 800]),
        ];
    }
}

return $data;

Additional Data via Sub DataProcessing

tt_content.vendor_yourcontentblockelement.fields.data.dataProcessing.10 {
    dataProcessing {
        10 = menu
        10 {
            levels = 2
            as = navigation
        }
    }
}

Custom Configuration per FieldType/FieldName/TCA-Configuration in ArrayRecursiveToArray via PSR-14 Event

Create a PSR-14 Event Listener in your extension. Use $event->setProcessedValue($yourModifiedValueForThisField) to handle the value for specific fields. Any unhandled fields will fall back to the default processing.

<?php

declare(strict_types=1);

namespace MyVendor\MyExtension\EventListener;

use Netzbewegung\NbHeadlessContentBlocks\Event\ModifyArrayRecursiveToArrayEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;

#[AsEventListener()]
final class MyCustomListener {
    public function __invoke(ModifyArrayRecursiveToArrayEvent $event): void {
        // Example: Custom handling by field name (tt_content.tx_my_vendor_field_name)
        if ($event->getKey() === 'tx_my_vendor_field_name') {
            $value = $event->getValue();
            // Do your custom processing here
            $processedValue = strtoupper($value);
            // $processedValue The value which is returned for this field in json response
            $event->setProcessedValue($processedValue);
        }

        // Example: Custom handling for all text fields
        if ($event->getTcaFieldDefinition()->fieldType instanceof \TYPO3\CMS\ContentBlocks\FieldType\TextFieldType) {
            $value = $event->getValue();
            // Do your custom processing here
            $processedValue = strtoupper($value);
            // $processedValue The value which is returned for this field in json response
            $event->setProcessedValue($processedValue);
        }

        // Example: Custom handling for a specific field which have custom field type
        if ($event->getTcaFieldDefinition()->fieldType instanceof \MyVendor\MyExtension\MyCustomFieldType) {
            $value = $event->getValue();
            // Do your custom processing here
            $processedValue = strtoupper($value);
            // $processedValue The value which is returned for this field in json response
            $event->setProcessedValue($processedValue);
        }
    }
}

Custom Configuration for EXT:container (b13/container)

TypoScript Setup

left/right parallel to data

lib.content.select.where = colPos NOT IN (201, 202)

tt_content.b13_2_columns_container =< lib.contentElement
tt_content.b13_2_columns_container {
    fields {
        left = TEXT
        left {
            dataProcessing {
                10 = nb-container-json 
                10 {
                    colPos = 201
                    as = left
                }
            }
        }
        right = TEXT
        right {
            dataProcessing {
                10 = nb-container-json 
                10 {
                    colPos = 202
                    as = right
                }
            }
        }
    }
}

left/right inside data (via Sub DataProcessing)

lib.content.select.where = colPos NOT IN (201, 202)

tt_content.b13_2_columns_container.fields.data.dataProcessing.10 {
    dataProcessing {
        10 = nb-container-json 
        10 {
            colPos = 201
            as = left
        }

        20 = nb-container-json 
        20 {
            colPos = 202
            as = right
        }
    }
}

Development

Setup

ddev start
ddev composer install
touch .Build/public/FIRST_INSTALL
ddev launch

chmod +x ./Build/Scripts/runTests.sh

Dependencies are installed into .Build/vendor (TYPO3 web root: .Build/public).

Testing

Unit and functional tests are based on the TYPO3 Testing Framework.

# Unit tests
Build/Scripts/runTests.sh -s unit

# Functional tests (sqlite by default, -d mariadb|mysql|postgres possible)
Build/Scripts/runTests.sh -s functional

# PHPStan
Build/Scripts/runTests.sh -s phpstan

# Coding guidelines (php-cs-fixer)
Build/Scripts/runTests.sh -s cgl

# Specific PHP version
Build/Scripts/runTests.sh -s unit -p 8.4

Test structure

Tests/
├── Fixtures/Extensions/test_nb_headless_content_blocks/   # Content Block fixtures
├── Unit/                                                   # Unit tests (no TYPO3 context)
└── Functional/                                             # Functional tests (isolated TYPO3 instance)

IDE: temp directories from indexing

Be sure to exclude the temp directories from indexing in your IDE before starting the tests. Functional tests create isolated TYPO3 instances in typo3temp/var/tests, the DDEV web container uses .Build/public/typo3temp:

.Build/public/typo3temp

PhpStorm: Right-click the directories → Mark Directory asExcluded (or SettingsDirectories → add both to Excluded files).

VS Code (.vscode/settings.json):

{
    "files.exclude": {
        "**/.Build/public/typo3temp": true
    },
    "search.exclude": {
        "**/.Build/public/typo3temp": true
    },
    "files.watcherExclude": {
        "**/.Build/public/typo3temp/**": true
    }
}