Extbase
View as MarkdownA small client-side helper for sending GET and POST requests to an Extbase controller action from your own code - e.g. an async search's load callback.
extbase.post()
Permalink to heading "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. Nested objects and arrays are flattened into bracket notation, so an action taking an object works directly:
await extbase.post(url, { demand: { city: 'Berlin' }, ids: [1, 2] });
// -> tx_docs_docs[demand][city]=Berlin, tx_docs_docs[ids][0]=1, tx_docs_docs[ids][1]=2
null/undefined values in data are skipped, and a File/Blob is sent as-is. 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] or [controller] key in its query string, data is sent unprefixed - post() works as a plain "POST as FormData" helper for non-Extbase endpoints too. If such a URL still expects the namespace, nest data under it yourself: { tx_docs_docs: { q } }.
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
Permalink to heading "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.
extbase.get()
Permalink to heading "extbase.get()"const response = await extbase.get(searchUrl, { q: filterText }, { signal });
Appends data as query parameters under the URL's Extbase argument namespace, using the same flattening as post(). The URL can be relative, as f:uri.action returns it, or absolute. Its existing query is kept untouched, cHash included - so a parameter added this way fails the cHash check (see Why a POST body) unless TYPO3 is told to ignore it, either by excluding it in your settings.php:
$GLOBALS['TYPO3_CONF_VARS']['FE']['cacheHash']['excludedParameters'][] = 'tx_docs_docs[q]';
(an exact match - prefix the entry with ^ to cover nested keys like tx_docs_docs[demand][city]), or by using post() instead.
get() and post() return the native Response and, like fetch, only reject on a network failure or an aborted signal. A non-2xx response resolves normally, so check response.ok yourself. A 422 from JsonValidationErrorsTrait carries its field messages as the JSON body.
Posting domain models
Permalink to heading "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()
Permalink to heading "extbase.getArgumentPrefix()"The lower-level piece get() and post() are 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] or [controller] key in its query string.
Reference
Permalink to heading "Reference"Sends GET and POST requests to an Extbase controller action from your own code, e.g. from the load callback of an async search. The data is nested under the Extbase argument namespace of the URL, the way f:uri.action builds it.
GETs url with data appended as query parameters under its Extbase argument namespace. The URL's own query, cHash included, is kept - so the new parameters have to be listed in FE.cacheHash.excludedParameters, or TYPO3 rejects the request.
get(url: string, data?: Record<string, unknown>, init?: RequestInit): Promise<Response>
| Name | Description | Required | Default |
|---|---|---|---|
url | stringThe URL of the action, absolute or relative. | Yes | - |
data | Record<string, unknown>The arguments. Nested objects and arrays are flattened into bracket notation. | No | - |
init | RequestInitMore options for fetch, e.g. { signal }. | No | - |
Returns Promise<Response>. The native Response. A non-2xx status resolves normally, check response.ok.
POSTs data as multipart/form-data, nested under the URL's Extbase argument namespace. A URL without one gets data unprefixed.
post(url: string, data?: Record<string, unknown>, init?: RequestInit): Promise<Response>
| Name | Description | Required | Default |
|---|---|---|---|
url | stringThe URL of the action. | Yes | - |
data | Record<string, unknown>The arguments. Nested objects and arrays are flattened into bracket notation, null and undefined are skipped, a File or Blob is sent as it is. | No | - |
init | RequestInitMore options for fetch, e.g. { signal }. | No | - |
Returns Promise<Response>. The native Response. A non-2xx status resolves normally, check response.ok.
getArgumentPrefix()
Permalink to heading "getArgumentPrefix()"Reads the Extbase argument namespace (tx_docs_docs) of a frontend action URL, from its [controller] or [action] query key. UriBuilder::uriFor() nests those under it, also for a custom pluginNamespace.
getArgumentPrefix(url: string): string | null
| Name | Description | Required | Default |
|---|---|---|---|
url | stringThe URL of the action. | Yes | - |
Returns string | null. The namespace, or null for a URL without such a key.