Writing records from the frontend
Everything about reading records applies to plugins that only display data. As soon as a plugin writes — a registration form, a comment, a submitted conference proposal — the language question changes shape: not "which records do I get back", but "what language does the record I just created belong to".
New records are created in the default language
When Extbase persists a new object it fills in the language fields itself:
- The language field is set to
0, the default language, unless the object already carries a language of its own. - The translation parent field is set to
0.
A record submitted by a visitor browsing the Polish version of a site is therefore stored as a default-language record, not as a Polish one. Setting the language explicitly on the object before persisting it stores the record in that language:
<?php
namespace MyVendor\MyExtension\Controller;
use MyVendor\MyExtension\Domain\Model\Conference;
use MyVendor\MyExtension\Domain\Repository\ConferenceRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Core\Context\Context;
use TYPO3\CMS\Extbase\DomainObject\AbstractDomainObject;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
class ConferenceController extends ActionController
{
public function __construct(
protected readonly ConferenceRepository $conferenceRepository,
protected readonly Context $context,
) {}
public function createAction(Conference $conference): ResponseInterface
{
$languageId = $this->context
->getPropertyFromAspect('language', 'contentId');
$conference->_setProperty(
AbstractDomainObject::PROPERTY_LANGUAGE_UID,
$languageId,
);
$this->conferenceRepository->add($conference);
return $this->redirect('list');
}
}
That is enough to file a record under a language. It is not enough to make it a translation.
Extbase does not create translations
A translation is a record that points at the record it translates, through the
translation parent field. Extbase always writes
0
into that field when
it creates a record, and never writes anything else into it.
The consequence is that Extbase can create a record in a language, but it cannot create a record that is a translation of another record. Two records written this way — one with the language field set to English, one to Polish — remain two unrelated records. Nothing connects them, and no overlay will ever combine them.
Editing an existing translation does work: when a translated record is fetched and modified, Extbase writes the changes back to the translated record rather than to its default-language original.
What this means for a multilingual site
On a site using fallback, records without a translation are not
shown. Records created through a frontend form are default-language records
without a translation. A visitor who submits a conference proposal on the
Polish site will therefore not see it on the Polish site afterwards — it
exists, but the language configuration hides it until somebody actually relates
it to its default language parent and so translates it.
On fallback the same record stays visible in every language, because
untranslated records show through from the default language.
When to use something else
Extbase persistence is a good fit for frontend writes when the object is structurally simple: a single record, no relations to create alongside it, and no translation involved. Registration forms, contact requests and comments are all comfortably within that.
Beyond it, other tools are a better fit:
- The DataHandler creates real translations, maintains relations and respects workspaces and record history. Anything that has to produce a translated record belongs here.
- typo3/cms-form covers form-shaped work — building, validating and processing forms — without hand-writing a controller for it.
- A mixture is often the right answer: Extbase for reading and for the simple write, the DataHandler for the step that creates or updates a translation.
Relations are where frontend writing most often outgrows Extbase.
Extbase can persist relations of an object it creates, but it does so with the same limitation as everywhere else: the related records are written as default-language records too, and no translation relationship is established between them and anything else. An object graph created from a frontend form is therefore entirely default-language, however the form was labeled. The exception is a language you set explicitly on each object before persisting it, which is the developer's responsibility. Even then, Extbase alone cannot create a valid translation record connected to its default language record.
Where a frontend form has to create or modify a translated object together with its relations, the DataHandler is the appropriate tool, rather than Extbase persistence with corrections applied afterwards.