Skip to main content

Extbase

A small client-side helper for POSTing to an Extbase controller action from your own code - e.g. an async search's load callback.

extbase.post()

import { extbase } from 'fluid-primitives';

const response = await extbase.post(searchUrl, { q: filterText }, { signal });

Bracket-prefixes data under the URL's own Extbase argument namespace - the same tx_{extension}_{plugin}[...] convention f:uri.action embeds in the URL it builds - and sends it as a real multipart/form-data POST body, the same shape Extbase's argument mapper expects from an ordinary HTML form. null/undefined values in data are skipped. init is spread first, so it can extend the request (e.g. { signal }, to make it abortable) without overriding the method/body post() sets.

For a URL that doesn't carry an [action]/[controller] pair in its query string, data is sent unprefixed - post() works as a plain "POST as FormData" helper for non-Extbase endpoints too.

On the PHP side, a plain typed action argument receives the value the same way regardless of whether it arrives via GET or POST:

public function searchAction(string $q = ''): ResponseInterface
{
    // ...
}

Why a POST body

A frontend action URL built with f:uri.action/f:uri.link carries a cHash computed from the arguments known at build time. Appending a query parameter to that URL afterward changes its parameter set without changing the hash, and TYPO3 rejects the mismatch with a 404. cHash only governs the GET query string, so a POST body is unaffected by it.

Posting domain models

Extbase's mass-assignment protection (__trustedProperties) applies to PersistentObjectConverter, the type converter used for persisted entities and value objects (classes extending AbstractEntity/AbstractDomainObject/AbstractValueObject). A plain PHP class that isn't one of those is mapped with the more permissive ObjectConverter, which has no such gate - extbase.post() maps onto an argument shaped like that without any extra configuration.

For an argument that is a persisted entity, the properties Extbase is allowed to set are configured directly on the controller, independently of any client-submitted token:

use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;

protected function initializeCreateAction(): void
{
    $this->arguments->getArgument('demand')
        ->getPropertyMappingConfiguration()
        ->allowProperties('q', 'page') // or ->allowAllProperties()
        ->setTypeConverterOption(
            PersistentObjectConverter::class,
            PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED,
            true,
        );
}

The service that generates a real __trustedProperties token (MvcPropertyMappingConfigurationService) is marked internal and isn't part of TYPO3's public API, so it isn't something extbase.post() generates or relies on.

extbase.getArgumentPrefix()

The lower-level piece post() is built on, for reading the prefix without sending a request:

extbase.getArgumentPrefix('https://example.com/?tx_docs_docs[action]=search&tx_docs_docs[controller]=CitySearch');
// -> 'tx_docs_docs'

Returns null for a URL with no [action]/[controller] pair in its query string.