PHPackages                             symkit/metadata-bundle - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [API Development](/categories/api)
4. /
5. symkit/metadata-bundle

ActiveSymfony-bundle[API Development](/categories/api)

symkit/metadata-bundle
======================

A standalone Symfony bundle for managing page metadata, breadcrumbs, and JSON-LD structured data.

v0.0.3(2mo ago)05296MITPHPPHP &gt;=8.2CI passing

Since Feb 21Pushed 2mo agoCompare

[ Source](https://github.com/SymKit/metadata-bundle)[ Packagist](https://packagist.org/packages/symkit/metadata-bundle)[ RSS](/packages/symkit-metadata-bundle/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (3)Dependencies (18)Versions (5)Used By (6)

SymKit Metadata Bundle
======================

[](#symkit-metadata-bundle)

[![CI](https://github.com/SymKit/metadata-bundle/actions/workflows/ci.yml/badge.svg)](https://github.com/SymKit/metadata-bundle/actions)[![Latest Version](https://camo.githubusercontent.com/913187eeac6f0b43a6a95c54ef1de44fc0792d93a57091434996d43ae3cd5c47/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f73796d6b69742f6d657461646174612d62756e646c652e737667)](https://packagist.org/packages/symkit/metadata-bundle)[![PHPStan Level 9](https://camo.githubusercontent.com/1bc07920f0d36e55c17e1d38b1caa132cc605f51a82b388c962870b9a747b898/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d627269676874677265656e2e737667)](https://phpstan.org/)

A modern, SOLID, and extensible Symfony bundle for managing SEO metadata, OpenGraph tags, JSON-LD structured data, and breadcrumbs.

Features
--------

[](#features)

- **Decoupled Architecture**: SEO, JSON-LD, and breadcrumbs are fully independent, connected via clean contracts.
- **Feature Toggles**: Each feature (meta tags, JSON-LD, breadcrumbs) can be independently enabled/disabled.
- **Immutable PageContext**: Request-scoped, immutable value object for page metadata.
- **JSON-LD Collector**: Push-based schema system with cumulative schemas per page.
- **8 Built-in Schema DTOs**: FAQ, Article, Product, LocalBusiness, Event, HowTo, Video, Review.
- **Separate Attributes**: `#[Seo]` and `#[Breadcrumb]` for precise, independent configuration.
- **Twig Integration**: Simple functions for templates.
- **Auto Canonical URL**: Automatically uses the current request URL as `` unless overridden.
- **Robots &amp; Author**: Per-page `robots` and `author` meta tags via builder or `#[Seo]` attribute.
- **Twitter Cards**: Global `twitter:site` and `twitter:creator` via bundle configuration.
- **Worker-safe**: `ResetInterface` ensures clean state between requests (Swoole, FrankenPHP, RoadRunner).

Installation
------------

[](#installation)

### 1. Require the bundle

[](#1-require-the-bundle)

```
composer require symkit/metadata-bundle
```

### 2. Register the bundle

[](#2-register-the-bundle)

```
// config/bundles.php
return [
    Symkit\MetadataBundle\MetadataBundle::class => ['all' => true],
];
```

### 3. Configure

[](#3-configure)

```
# config/packages/symkit_metadata.yaml
symkit_metadata:
    site_info_provider: 'App\Provider\SiteInfoProvider'
    meta_tags:
        enabled: true
        title_format: '{title} | {siteName}'
        # twitter_site: '@yoursite'
        # twitter_creator: '@yourcreator'
    json_ld:
        enabled: true
    breadcrumbs:
        enabled: true
```

Implement `SiteInfoProviderInterface` in your app:

```
use Symkit\MetadataBundle\Contract\SiteInfoProviderInterface;

final readonly class SiteInfoProvider implements SiteInfoProviderInterface
{
    public function getWebsiteName(): string { return 'My Site'; }
    public function getWebsiteDescription(): ?string { return 'Site description'; }
    public function getDefaultOgImage(): ?string { return '/images/og-default.jpg'; }
    public function getFavicon(): ?string { return '/favicon.ico'; }
    public function getAppleTouchIcon(): ?string { return null; }
    public function getAndroidIcon192(): ?string { return null; }
    public function getAndroidIcon512(): ?string { return null; }
}
```

---

Configuration Reference
-----------------------

[](#configuration-reference)

All features are enabled by default and can be independently toggled:

```
symkit_metadata:
    # Service ID implementing SiteInfoProviderInterface (used by meta_tags and json_ld)
    site_info_provider: 'App\Provider\SiteInfoProvider'

    meta_tags:
        enabled: true                         # Activates SeoListener, MetaTagRenderer, SeoTwigExtension
        title_format: '{title} | {siteName}'  # Supports {title} and {siteName} placeholders
        twitter_site: '@yoursite'             # Global twitter:site meta tag (optional)
        twitter_creator: '@yourcreator'       # Global twitter:creator meta tag (optional)

    json_ld:
        enabled: true                         # Activates JsonLdCollector, JsonLdService, generators, JsonLdTwigExtension

    breadcrumbs:
        enabled: true                         # Activates BreadcrumbListener, BreadcrumbService, BreadcrumbTwigExtension
```

### Use Case Configurations

[](#use-case-configurations)

**Simple landing page (meta tags only):**

```
symkit_metadata:
    site_info_provider: 'App\Provider\SiteInfoProvider'
    json_ld:
        enabled: false
    breadcrumbs:
        enabled: false
```

**API with structured data (JSON-LD only):**

```
symkit_metadata:
    site_info_provider: 'App\Provider\SiteInfoProvider'
    meta_tags:
        enabled: false
    breadcrumbs:
        enabled: false
```

**Navigation-focused site (breadcrumbs only):**

```
symkit_metadata:
    site_info_provider: 'App\Provider\SiteInfoProvider'
    meta_tags:
        enabled: false
    json_ld:
        enabled: false
```

**Blog (meta tags + JSON-LD):**

```
symkit_metadata:
    site_info_provider: 'App\Provider\SiteInfoProvider'
    meta_tags:
        title_format: '{title} — {siteName}'
    breadcrumbs:
        enabled: false
```

**Full CMS (all features — default):**

```
symkit_metadata:
    site_info_provider: 'App\Provider\SiteInfoProvider'
```

---

Usage
-----

[](#usage)

### Controller Attributes

[](#controller-attributes)

```
use Symkit\MetadataBundle\Attribute\Seo;
use Symkit\MetadataBundle\Attribute\Breadcrumb;
use Symkit\MetadataBundle\Enum\OgType;

#[Seo(
    title: 'Blog Post Title',
    description: 'Read our latest blog post.',
    ogImage: 'https://example.com/image.jpg',
    ogType: OgType::ARTICLE,
    robots: 'index, follow',
    author: 'Jane Doe',
    canonicalUrl: 'https://example.com/blog/post',
)]
#[Breadcrumb(
    context: 'website',
    items: [['label' => 'Blog', 'route' => 'blog_index']],
)]
public function show(Post $post): Response
{
    // ...
}
```

### Manual SEO via Builder

[](#manual-seo-via-builder)

```
use Symkit\MetadataBundle\Contract\PageContextBuilderInterface;

public function __construct(
    private readonly PageContextBuilderInterface $builder,
) {}

public function action(): Response
{
    $this->builder
        ->setTitle('Dynamic Title')
        ->setDescription('Dynamic description')
        ->setOgImage('/path/to/image.jpg')
        ->setRobots('noindex, nofollow')
        ->setAuthor('Jane Doe');
    // ...
}
```

### JSON-LD: Push Schemas into the Collector

[](#json-ld-push-schemas-into-the-collector)

```
use Symkit\MetadataBundle\Contract\JsonLdCollectorInterface;
use Symkit\MetadataBundle\JsonLd\Schema\ArticleSchema;
use Symkit\MetadataBundle\JsonLd\Schema\FaqSchema;
use Symkit\MetadataBundle\JsonLd\Schema\FaqItem;

public function show(Post $post, JsonLdCollectorInterface $jsonLd): Response
{
    // Article schema
    $jsonLd->add(new ArticleSchema(
        headline: $post->getTitle(),
        author: $post->getAuthor()->getName(),
        datePublished: $post->getPublishedAt(),
    ));

    // FAQ schema (cumulative - both coexist in @graph)
    $jsonLd->add(new FaqSchema([
        new FaqItem('What is this?', 'An example.'),
        new FaqItem('How does it work?', 'Via the collector pattern.'),
    ]));
}
```

### Available Schema DTOs

[](#available-schema-dtos)

DTOSchema.org TypeKey Fields`FaqSchema` + `FaqItem`FAQPagequestion, answer`ArticleSchema`Article/BlogPosting/NewsArticleheadline, author, datePublished`ProductSchema`Productname, price, currency, brand`LocalBusinessSchema`LocalBusinessname, address, phone, geo`EventSchema`Eventname, startDate, location, offers`HowToSchema` + `HowToStep`HowToname, steps, totalTime`VideoSchema`VideoObjectname, thumbnailUrl, uploadDate`ReviewSchema`Reviewauthor, ratingValue, itemReviewedYou can also pass raw Spatie Schema objects:

```
use Spatie\SchemaOrg\Schema;

$jsonLd->add(Schema::recipe()->name('Apple Pie')->recipeIngredient(['Apples', 'Sugar']));
```

### Populators (Entity-driven)

[](#populators-entity-driven)

Implement `MetadataPopulatorInterface` for SEO and/or `JsonLdPopulatorInterface` for JSON-LD:

```
use Symkit\MetadataBundle\Contract\MetadataPopulatorInterface;
use Symkit\MetadataBundle\Contract\JsonLdPopulatorInterface;
use Symkit\MetadataBundle\Contract\PageContextBuilderInterface;
use Symkit\MetadataBundle\Contract\JsonLdCollectorInterface;

final readonly class PostPopulator implements MetadataPopulatorInterface, JsonLdPopulatorInterface
{
    public function supports(object $subject): bool
    {
        return $subject instanceof Post;
    }

    public function populateMetadata(object $subject, PageContextBuilderInterface $builder): void
    {
        $builder->setTitle($subject->getTitle());
        $builder->setDescription($subject->getExcerpt());
    }

    public function populateJsonLd(object $subject, JsonLdCollectorInterface $collector): void
    {
        $collector->add(new ArticleSchema(
            headline: $subject->getTitle(),
            author: $subject->getAuthor()->getName(),
            datePublished: $subject->getPublishedAt(),
        ));
    }
}
```

### Breadcrumb Builders

[](#breadcrumb-builders)

```
use Symkit\MetadataBundle\Contract\BreadcrumbBuilderInterface;
use Symkit\MetadataBundle\Contract\BreadcrumbServiceInterface;
use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem;

#[AsTaggedItem(index: 'website')]
final readonly class WebsiteBreadcrumbBuilder implements BreadcrumbBuilderInterface
{
    public function __construct(
        private UrlGeneratorInterface $urlGenerator,
    ) {}

    public function build(BreadcrumbServiceInterface $service): void
    {
        $service->add('Home', $this->urlGenerator->generate('app_home', [], UrlGeneratorInterface::ABSOLUTE_URL));
    }

    public function isRootRoute(string $route): bool
    {
        return $route === 'app_home';
    }
}
```

### Twig Rendering

[](#twig-rendering)

```

    {{ page_metas() }}
    {{ render_json_ld() }}

        {% for item in page_breadcrumbs() %}
            {{ item.name }}
        {% endfor %}

```

**Available Twig functions:**

FunctionFeatureDescription`page_title()`meta\_tagsCurrent page title`page_description()`meta\_tagsCurrent page description`page_site_name()`meta\_tagsSite name from SiteInfoProvider`page_site_description()`meta\_tagsSite description from SiteInfoProvider`page_default_og_image_url()`meta\_tagsOG image (page or default)`page_favicon_url()`meta\_tagsFavicon URL`page_apple_touch_icon_url()`meta\_tagsApple touch icon URL`page_android_icon_192_url()`meta\_tagsAndroid 192x192 icon URL`page_android_icon_512_url()`meta\_tagsAndroid 512x512 icon URL`page_metas()`meta\_tagsFull meta tags HTML block`render_json_ld()`json\_ldJSON-LD script tag`page_breadcrumbs()`breadcrumbsBreadcrumb items array---

Architecture
------------

[](#architecture)

```
src/
  Contract/          # Public interfaces (BC-safe API)
  Model/             # Immutable value objects (PageContext, BreadcrumbItem)
  Enum/              # OgType, TwitterCard, ArticleType
  Attribute/         # #[Seo], #[Breadcrumb]
  Builder/           # PageContextBuilder (request-scoped)
  Event/             # PageContextEvent
  Listener/          # SeoListener, BreadcrumbListener
  Renderer/          # MetaTagRenderer
  Breadcrumb/        # BreadcrumbService
  JsonLd/
    Collector/       # JsonLdCollector (push pattern)
    Generator/       # Auto generators (WebSite, WebPage, Breadcrumb)
    Schema/          # Typed DTOs (FAQ, Article, Product, etc.)
    Service/         # JsonLdService (aggregator)
    Renderer/        # JsonLdRenderer
  Twig/              # SeoTwigExtension, JsonLdTwigExtension, BreadcrumbTwigExtension

```

### Core always registered

[](#core-always-registered)

- `PageContextBuilder` / `PageContextProvider` — shared by all features
- `SiteInfoProviderInterface` — used by meta\_tags and json\_ld

### Conditionally registered

[](#conditionally-registered)

- **meta\_tags**: `SeoListener`, `MetaTagRenderer`, `SeoTwigExtension`
- **json\_ld**: `JsonLdCollector`, `JsonLdService`, `JsonLdRenderer`, generators, `JsonLdTwigExtension`
- **breadcrumbs**: `BreadcrumbService`, `BreadcrumbListener`, `BreadcrumbTwigExtension`

---

Migration from sedie/metadata-bundle
------------------------------------

[](#migration-from-sediemetadata-bundle)

If upgrading from `sedie/metadata-bundle`:

1. Update `composer.json`: replace `sedie/metadata-bundle` with `symkit/metadata-bundle`
2. Update all PHP namespaces: `Sedie\MetadataBundle` → `Symkit\MetadataBundle`
3. Update config file: `sedie_metadata.yaml` → `symkit_metadata.yaml`
4. Update config key: `sedie_metadata:` → `symkit_metadata:`
5. Update Twig namespace: `@SedieMetadata` → `@SymkitMetadata`
6. Update service tags: `sedie_metadata.*` → `symkit_metadata.*`
7. Split `#[Metadata]` attribute into `#[Seo]` and `#[Breadcrumb]`
8. Replace `MetadataManagerInterface` usage with `PageContextBuilderInterface`
9. The single `MetadataExtension` Twig extension is now 3 separate extensions (automatic, no action needed)

---

Contributing
------------

[](#contributing)

```
make install         # Install dependencies
make cs-fix          # Fix code style
make phpstan         # Static analysis (level 9)
make test            # Run tests
make quality         # Full pipeline (cs-check + phpstan + deptrac + test + infection)
make ci              # security-check + quality
```

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance83

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community15

Small or concentrated contributor base

Maturity39

Early-stage or recently created project

 Bus Factor1

Top contributor holds 87.5% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

3

Last Release

85d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/077eba6702dc23a795ee2262dff92505e3c8ead08f7cb205be80d8aae0a6b8e5?d=identicon)[sdieunidou](/maintainers/sdieunidou)

---

Top Contributors

[![sdieunidou](https://avatars.githubusercontent.com/u/570763?v=4)](https://github.com/sdieunidou "sdieunidou (7 commits)")[![google-labs-jules[bot]](https://avatars.githubusercontent.com/in/842251?v=4)](https://github.com/google-labs-jules[bot] "google-labs-jules[bot] (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/symkit-metadata-bundle/health.svg)

```
[![Health](https://phpackages.com/badges/symkit-metadata-bundle/health.svg)](https://phpackages.com/packages/symkit-metadata-bundle)
```

###  Alternatives

[sylius/sylius

E-Commerce platform for PHP, based on Symfony framework.

8.4k5.6M651](/packages/sylius-sylius)[shopware/platform

The Shopware e-commerce core

3.3k1.5M3](/packages/shopware-platform)[sulu/sulu

Core framework that implements the functionality of the Sulu content management system

1.3k1.3M152](/packages/sulu-sulu)[contao/core-bundle

Contao Open Source CMS

1231.6M2.4k](/packages/contao-core-bundle)[prestashop/prestashop

PrestaShop is an Open Source e-commerce platform, committed to providing the best shopping cart experience for both merchants and customers.

9.0k15.4k](/packages/prestashop-prestashop)[shopware/storefront

Storefront for Shopware

684.2M148](/packages/shopware-storefront)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
