When creating custom mail templates in Shopware via database migrations, you may notice that the template appears in the admin panel but without translations. This issue is usually caused by UUID handling and Doctrine DBAL insert types. In this tutorial, we’ll walk through the problem, show you the root cause, and provide the exact fix.
Introduction
In Shopware 6, adding custom mail templates is a common task when building plugins. Usually, you create a migration that inserts rows into the mail_template_type
, mail_template
, and their translation tables.
But what if the template appears in the admin panel with an empty name and missing translations? That’s exactly what happened to me, and the solution turned out to be less obvious than expected.
The Symptom
After running the migration, a new mail template type showed up in the Settings → Email Templates list.
However, instead of the expected names, it looked like this:
-
Template type exists
-
No translation text shown
-
Admin only displays empty labels
When I dumped the values being inserted, the language_id
values already looked like proper binary UUIDs:
So the issue wasn’t about missing UUID conversion.
The Root Cause
Shopware stores all IDs (including language_id
, mail_template_type_id
, and mail_template_id
) as BINARY(16).
When you call Doctrine DBAL’s $connection->insert()
, the library automatically tries to convert parameters. If you pass a raw binary UUID without specifying the parameter type, DBAL often treats it as a string.
That means your mail_template_type_id
in the translation table does not match byte-for-byte the ID in mail_template_type
. From the database’s perspective, these are completely different values, so Shopware cannot join the records.
The Fix: Specify Binary Parameter Types
The fix is to explicitly tell DBAL which fields are binary. You can do that using the third parameter of insert()
.
Example for inserting the mail template type:
$connection->insert( MailTemplateTypeDefinition::ENTITY_NAME, [ 'id' => $typeId, 'technical_name' => 'test-plugin.ticket.created', 'available_entities' => json_encode(['salesChannel' => 'sales_channel'], JSON_THROW_ON_ERROR), 'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT), ], [ 'id' => \Doctrine\DBAL\ParameterType::BINARY, ] );
And when inserting translations:
$connection->insert( MailTemplateTypeTranslationDefinition::ENTITY_NAME, [ 'mail_template_type_id' => $typeId, 'language_id' => $langEnId, 'name' => 'Ticket created!', 'created_at' => (new \DateTime())->format(Defaults::STORAGE_DATE_TIME_FORMAT), ], [ 'mail_template_type_id' => \Doctrine\DBAL\ParameterType::BINARY, 'language_id' => \Doctrine\DBAL\ParameterType::BINARY, ] );
Do the same for mail_template
and mail_template_translation
.
Why This Works
By explicitly setting ParameterType::BINARY
, you ensure that DBAL inserts the values as raw 16-byte binary instead of misinterpreted strings. That way, all references between template types, templates, and translations line up correctly.
Once you rerun the migration, you’ll see your template and all translations properly displayed in the Shopware admin panel.
Conclusion
If your custom mail templates appear in the Shopware admin but without translations, the culprit is most likely improper handling of binary UUIDs.
Always remember:
-
Shopware IDs are stored as
BINARY(16)
-
Doctrine DBAL needs explicit type hints when inserting binary values
-
Use
ParameterType::BINARY
for IDs and foreign keys
This little detail can save you hours of debugging when working with migrations in Shopware.