daddl3/seo-tool-bundle

All in One Symfony Seo Bundle

Maintainers

Package info

gitlab.com/daddl3/SeoToolBundle

Issues

Type:symfony-bundle

pkg:composer/daddl3/seo-tool-bundle

Statistics

Installs: 12

Dependents: 0

Suggesters: 0

Stars: 0

1.0.0 2026-04-01 02:12 UTC

This package is auto-updated.

Last update: 2026-04-01 00:14:04 UTC


README

All-in-One Symfony SEO Bundle — Meta Tags, Open Graph, Schema.org, Breadcrumbs, Google Tag Manager, Meta Pixel, and full Profiler support.

Requirements: PHP 8.5+, Symfony 8.x

Installation

composer require daddl3/seo-tool-bundle

Symfony Flex registers the bundle automatically. If not:

// config/bundles.php
return [
    Daddl3\SeoToolBundle\SeoBundle::class => ['all' => true],
];

Configuration

All options in config/packages/seo.yaml:

seo:

    # Schema.org — inject JSON-LD into <head>
    schema:
        enabled: true
        cache_ttl: 604800   # Cache in seconds (default: 1 week)

    # Meta Tags — global default values
    meta_tags:
        title: 'My Website'
        description: 'Default description'
        keywords: ['symfony', 'seo']

    # Open Graph — global default values
    open_graph:
        title: 'My Website'
        description: 'Default description'
        sitename: 'My Website'
        url: 'https://example.com'
        type: 'website'

    # Google Tag Manager
    google_tag_manager:
        enabled: true
        tag_manager_id: 'GTM-XXXXXX'

    # Meta Pixel (Facebook)
    meta_pixel:
        enabled: true
        pixel_id: '1234567890'

Features

Meta Tags

In the Controller

use Daddl3\SeoToolBundle\Metas\MetaTagsManagerInterface;

class ProductController extends AbstractController
{
    public function show(MetaTagsManagerInterface $meta): Response
    {
        $meta
            ->setTitle('Product Name')
            ->setDescription('Short product description')
            ->setKeywords(['product', 'shop', 'symfony'])
            ->setCanonical('https://example.com/product')
            ->setRobots(['index', 'follow'])
            ->setAuthor('John Doe')
            ->setViewPort('width=device-width, initial-scale=1')
            ->setCharacterEncoding('UTF-8')
            ->setCopyright('2025 Example Inc.')
            ->setContentType('text/html; charset=UTF-8')
            ->setContentSecurityPolicy("default-src 'self'")
            ->setSubject('E-Commerce')
            ->setXUACompatible()
            ->setCustomMetaTag('theme-color', '#ffffff')
        ;

        return $this->render('product/show.html.twig');
    }
}

In a Twig Template

{{ meta_tags(
    title: 'Product Name',
    description: 'Short description',
    keywords: ['product', 'shop'],
    canonical: 'https://example.com/product',
    robots: ['index', 'follow']
) }}

Values from the controller and the template are merged. Template values override controller values.

Include in the Base Template

<head>
    {{ meta_tags() }}
</head>

Output

<meta charset="UTF-8" />
<title>Product Name</title>
<meta name="description" content="Short product description" />
<meta name="keywords" content="product, shop, symfony" />
<meta name="robots" content="index, follow" />
<link rel="canonical" href="https://example.com/product" />
<meta name="author" content="John Doe" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

All Available Methods

MethodDescription
setTitle(string)Page title (<title>)
setDescription(string)Meta description
setKeywords(array)Meta keywords
setCanonical(string)Canonical URL
setRobots(array)Robots directives (index, follow, noindex, …)
setAuthor(string)Author
setViewPort(string)Viewport
setCharacterEncoding(string)Character encoding (default: UTF-8)
setCopyright(string)Copyright notice
setSubject(string)Page subject
setContentType(string)Content type
setContentSecurityPolicy(string)CSP header
setAlternate(string, string)Alternate link (href, media)
setXUACompatible()IE compatibility mode (IE=edge)
setDefaultStyle(string)Default stylesheet
setCustomMetaTag(string, string)Custom meta tag (name, content)

Open Graph

In the Controller

use Daddl3\SeoToolBundle\OpenGraph\OpenGraphManagerInterface;

class ArticleController extends AbstractController
{
    public function show(OpenGraphManagerInterface $og): Response
    {
        $og
            ->setTitle('Article Title')
            ->setDescription('Description for social media')
            ->setImage('https://example.com/image.jpg')
            ->setImageAltText('Image description')
            ->setUrl('https://example.com/article')
            ->setType('article')
            ->setLocale('en_US')
            ->setAlternateLocale('de_DE')
            ->setSiteName('My Website')
        ;

        return $this->render('article/show.html.twig');
    }
}

In a Twig Template

{{ open_graph(
    title: 'Article Title',
    description: 'Description for social media',
    imageUrl: 'https://example.com/image.jpg',
    url: 'https://example.com/article',
    type: 'article'
) }}

Include in the Base Template

<head>
    {{ open_graph() }}
</head>

Output

<meta property="og:title" content="Article Title" />
<meta property="og:description" content="Description for social media" />
<meta property="og:image" content="https://example.com/image.jpg" />
<meta property="og:url" content="https://example.com/article" />
<meta property="og:type" content="article" />
<meta property="og:site_name" content="My Website" />

OG Image

Extended image metadata with og:image:* tags.

Controller:

use Daddl3\SeoToolBundle\OpenGraph\OGImageManagerInterface;

public function show(OGImageManagerInterface $ogImage): Response
{
    $ogImage
        ->setUrl('https://example.com/og.jpg')
        ->setSecureUrl('https://example.com/og.jpg')
        ->setType('image/jpeg')
        ->setWidth('1200')
        ->setHeight('630')
        ->setAlt('Image description')
    ;
}

Twig:

{{ og_image(
    url: 'https://example.com/og.jpg',
    type: 'image/jpeg',
    width: '1200',
    height: '630',
    alt: 'Image description'
) }}

Output:

<meta property="og:image" content="https://example.com/og.jpg" />
<meta property="og:image:secure_url" content="https://example.com/og.jpg" />
<meta property="og:image:type" content="image/jpeg" />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<meta property="og:image:alt" content="Image description" />

OG Video

Controller:

use Daddl3\SeoToolBundle\OpenGraph\OGVideoManagerInterface;

public function show(OGVideoManagerInterface $ogVideo): Response
{
    $ogVideo
        ->setUrl('https://example.com/video.mp4')
        ->setSecureUrl('https://example.com/video.mp4')
        ->setType('video/mp4')
        ->setWidth('1280')
        ->setHeight('720')
    ;
}

Twig:

{{ og_video(url: 'https://example.com/video.mp4', type: 'video/mp4', width: '1280', height: '720') }}

Output:

<meta property="og:video" content="https://example.com/video.mp4" />
<meta property="og:video:secure_url" content="https://example.com/video.mp4" />
<meta property="og:video:type" content="video/mp4" />
<meta property="og:video:width" content="1280" />
<meta property="og:video:height" content="720" />

OG Audio

Controller:

use Daddl3\SeoToolBundle\OpenGraph\OGAudioManagerInterface;

public function show(OGAudioManagerInterface $ogAudio): Response
{
    $ogAudio
        ->setUrl('https://example.com/audio.mp3')
        ->setSecureUrl('https://example.com/audio.mp3')
        ->setType('audio/mpeg')
    ;
}

Twig:

{{ og_audio(url: 'https://example.com/audio.mp3', type: 'audio/mpeg') }}

Output:

<meta property="og:audio" content="https://example.com/audio.mp3" />
<meta property="og:audio:secure_url" content="https://example.com/audio.mp3" />
<meta property="og:audio:type" content="audio/mpeg" />

OG Article

Controller:

use Daddl3\SeoToolBundle\OpenGraph\OGArticleManagerInterface;

public function show(OGArticleManagerInterface $ogArticle): Response
{
    $ogArticle
        ->setPublishedTime(new \DateTime('2025-01-15'))
        ->setModifiedTime(new \DateTime('2025-06-01'))
        ->setAuthor('John Doe')
        ->setSection('Technology')
        ->setTags(['symfony', 'php', 'seo'])
    ;
}

Twig:

{{ og_article(
    author: 'John Doe',
    section: 'Technology',
    tags: ['symfony', 'php', 'seo']
) }}

Output:

<meta property="article:published_time" content="..." />
<meta property="article:modified_time" content="..." />
<meta property="article:author" content="John Doe" />
<meta property="article:section" content="Technology" />
<meta property="article:tag" content="symfony" />
<meta property="article:tag" content="php" />
<meta property="article:tag" content="seo" />

Twitter Cards

Twitter Cards are added via the OpenGraphManager:

$og->addTwitterCardProperty('card', 'summary_large_image');
$og->addTwitterCardProperty('site', '@myWebsite');
$og->addTwitterCardProperty('creator', '@author');
$og->addTwitterCardProperty('title', 'Article Title');
$og->addTwitterCardProperty('description', 'Description for Twitter');

Output:

<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:site" content="@myWebsite" />
<meta property="twitter:creator" content="@author" />

Structured Properties

For og:video:*, og:audio:*, and og:image:* inline in the OG block:

$og->addStructuredProperty('video', 'secure_url', 'https://example.com/video.mp4');
$og->addStructuredProperty('image', 'width', '1200');

Output:

<meta property="og:video:secure_url" content="https://example.com/video.mp4" />
<meta property="og:image:width" content="1200" />

For music:

$og->addMusicProperty('musician', 'https://example.com/artist');

Schema.org (Rich Results)

First enable it in the configuration:

seo:
    schema:
        enabled: true
        cache_ttl: 604800

The schema is automatically injected as <script type="application/ld+json"> into the <head>.

In the Controller

use Daddl3\SeoToolBundle\Schema\SchemaInterface;

class ArticleController extends AbstractController
{
    public function show(SchemaInterface $schema): Response
    {
        $article = $schema->article()
            ->name('My Article')
            ->setDescription('Article description')
            ->url('https://example.com/article')
        ;

        $schema->render($article);

        return $this->render('article/show.html.twig');
    }
}

Output

<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "Article",
    "name": "My Article",
    "description": "Article description",
    "url": "https://example.com/article"
}
</script>

Nested Objects

$person = $schema->person()
    ->name('John Doe')
    ->url('https://example.com/john')
;

$org = $schema->organization()
    ->name('Example Inc.')
    ->url('https://example.com')
;

$schema->render(
    $schema->article()
        ->name('My Article')
        ->setDescription('...')
);

Supported Schema Types

CategoryTypes
Thingthing(), person(), organization(), place(), event(), action()
CreativeWorkcreativeWork(), article(), blogPosting(), socialMediaPosting(), blog(), course(), thesis(), website(), mediaObject(), imageObject(), textObject()
WebPagewebPage(), aboutPage(), contactPage(), faqPage(), itemPage(), checkoutPage(), profilePage(), searchResultPage(), collectionPage(), mediaGallery(), imageGallery(), webPageElement()
Organizationorganization(), localBusiness(), library(), radioStation(), travelAgency()
Placeplace(), administrativeArea(), country()
IntangiblejobPosting(), occupation(), breadcrumbList(), listItem(), itemList(), definedTerm(), specialty(), speakableSpecification(), monetaryAmount(), propertyValue(), offer()
Otherservice(), audience(), postalAddress(), educationOccupationalCredential()

Breadcrumb

In the Controller

use Daddl3\SeoToolBundle\Breadcrumb\BreadcrumbManagerInterface;

class BlogController extends AbstractController
{
    public function show(BreadcrumbManagerInterface $breadcrumb): Response
    {
        $breadcrumb
            ->addItem('Home', 'https://example.com')
            ->addItem('Blog', 'https://example.com/blog')
            ->addItem('My Article')   // last item without URL = active
        ;

        return $this->render('blog/show.html.twig');
    }
}

Render in the Template

{{ seo_breadcrumb() }}

Output (Bootstrap)

<ol class="breadcrumb">
    <li class="breadcrumb-item"><a href="https://example.com">Home</a></li>
    <li class="breadcrumb-item"><a href="https://example.com/blog">Blog</a></li>
    <li class="breadcrumb-item active">My Article</li>
</ol>

Customize CSS Classes

$breadcrumb->setOptions([
    'list_class'      => 'breadcrumb',
    'list_item_class' => 'breadcrumb-item',
    'active_item'     => 'active',
]);

Google Tag Manager

Configuration (automatically on all pages)

seo:
    google_tag_manager:
        enabled: true
        tag_manager_id: 'GTM-XXXXXX'

Automatically injects the GTM code into <head> and <body>.

Programmatically per Request

use Daddl3\SeoToolBundle\Seo\GoogleTagManager\TagManagerInterface;

public function index(TagManagerInterface $tagManager): Response
{
    $tagManager->enableGoogleTagManager('GTM-XXXXXX');

    return $this->render('index.html.twig');
}

Meta Pixel (Facebook)

Configuration (automatically on all pages)

seo:
    meta_pixel:
        enabled: true
        pixel_id: '1234567890'

Programmatically per Request

use Daddl3\SeoToolBundle\Seo\MetaPixel\MetaPixelInterface;

public function index(MetaPixelInterface $metaPixel): Response
{
    $metaPixel->enableMetaPixel('1234567890');

    return $this->render('index.html.twig');
}

Symfony Profiler

A full SEO profiler is available in dev mode.

Toolbar shows the status of all features at a glance:

FeatureGreenYellowRed
Meta Tagssetnot set
Open Graphsetnot set
Twitter Cardssetnot set
OG Image / Video / Audio / Articlesetnot set
Schema.orgtype namenot set
Breadcrumbnumber of items0 items
Google GTMGTM IDnot configured
Meta PixelPixel IDnot configured

Profiler panel shows for each page:

  • Overview of all features with status
  • All set meta tags in detail
  • Open Graph tags (base, Twitter Cards, Structured Properties, Music)
  • Separate panels for OG Image, Video, Audio, Article Manager
  • Schema.org JSON-LD preview
  • Breadcrumb items as a table
  • Google Tag Manager ID
  • Meta Pixel ID