krystal-sf/ux-metadata
krystal-sf/ux-metadata is a Laravel/PHP package for defining and managing UX metadata in your application, helping keep presentation details organized and consistent across views and components through a simple, configurable API.
Install the bundle
composer require krystal-sf/ux-metadata
If there is no Flex recipe, register it manually in config/bundles.php:
return [
// ...
KrystalSf\UxMetadata\UxMetadataBundle::class => ['all' => true],
];
Define your defaults in config/packages/ux_metadata.yaml — typically a site title suffix, default description, Open Graph image, Twitter handle, and locale. Check src/DependencyInjection/Configuration.php in the package for the exact key names before writing this file; config keys are the most likely thing to have shifted between tagged releases.
Wire the renderer into your base layout. Symfony UX bundles usually expose either a Twig function or a Twig component that emits the <title>, meta description, canonical, Open Graph, and Twitter tags in one call. Expect something like:
<head>
{{ ux_metadata() }}
...
</head>
Confirm the exact name in src/Resources/views/ or in the README — this is the one line you cannot guess.
First real use case: set a page-specific title and description from a controller action:
public function show(Product $product, MetadataManager $metadata): Response
{
$metadata
->setTitle($product->getName())
->setDescription(strip_tags($product->getSummary()))
->setOgImage($product->getHeroImageUrl());
return $this->render('product/show.html.twig', ['product' => $product]);
}
Where to look first, in order: the README, src/ root for the manager/service and any value objects, Configuration.php for config keys, and src/Resources/views/ for the template you embed in <head>. Given the package shows zero dependents, treat the README as the source of truth and pin the version you integrate against.
Default + override in the layout. Put the renderer in base.html.twig, define defaults in bundle config, and only override what a page cares about. This keeps controllers free of boilerplate and means a new page automatically gets a sane title pattern (Page Name | Site Name) rather than inheriting a stale global one.
Controller-driven metadata. Inject the metadata service into controllers (or a shared abstract controller) and set values before render(). For resource pages, derive title/description from the entity rather than hardcoding strings — this is where the bundle earns its keep compared to manually writing <meta> tags in each template.
Twig-driven metadata for template-owned pages. For marketing or CMS-style pages where the copy lives in the template, set values in a {% block %} near the top of the child template before the layout renders. Pick one convention — controller or template — per page type and stick to it, otherwise debugging "why is the title wrong" becomes a hunt across two layers.
Global rules via an event subscriber. For site-wide concerns (appending a brand suffix, setting og:site_name, defaulting an OG image per section) use a listener on kernel.controller or kernel.response rather than repeating logic. This is the cleanest extension point short of decorating the manager.
Turbo-friendly rendering. If you use Symfony UX Turbo, <head> metadata is not re-evaluated on Turbo navigations unless you handle it. Listen for turbo:load (or re-render the metadata block server-side per visit) so titles and OG tags stay correct after in-app navigation. Without this, the first page load looks right and everything after it silently inherits the original metadata.
Structured data / JSON-LD. Where the bundle supports it, feed the same value object you use for meta tags into a JSON-LD block for products, articles, and breadcrumbs. Keeping one source of truth avoids the classic mismatch between the visible description and the structured-data description.
Composition over duplication. Create a small internal helper (e.g. App\Metadata\PageMetadataFactory) that turns your domain objects into metadata calls. When the package's API changes — likely, given the maturity level — you update one class instead of every controller.
ux_metadata.yaml, run bin/console cache:clear. Silently ignored config keys are usually a typo in the key name against Configuration.php, not a bug.&amp;) is the usual symptom of pre-escaping before passing values in.turbo:load, be aware Turbo caches page previews; metadata can briefly show the previous page. If that matters, disable preview caching for affected links or ensure the metadata block is inside the cached body.composer.json, and wrap calls behind your own service so a breaking change is a one-file fix. Read the CHANGELOG between upgrades.dump() the metadata object just before render, and check the rendered <head> in a WebTestCase assertion (assertSelectorTextContains('title', ...), assertSelectorExists('meta[property="og:title"]')). Assertions catch regressions when defaults or suffix logic change.description or og:image is unset — inherit the default, omit the tag, or throw in dev. Silent omission is usually right; it prevents empty content="" attributes that some crawlers flag.<title>. If you keep a manual <title> in the layout alongside the bundle's output, you get duplicate tags. Remove the hand-rolled tags when you adopt the bundle, and grep templates for og: and twitter: to catch stragglers.How can I help you explore Laravel packages today?