Drei parallele matt-schwarze Kanalrohre auf gebürstetem Stahl-Rail, jedes Rohr mit dezentem Farb-Marker am Ende, weiches Tageslicht.
Extension · moselwal/semantic-delivery · MIT

semantic-delivery — ein Inhalt, elf Distributoren.

Schema.org-Anreicherung und Multichannel-Distribution für TYPO3 14. Sechs native Plattform-Adapter (LinkedIn, X/Twitter, Instagram, Facebook, Bluesky, Dev.to) plus fünf Proxy-Adapter (Ayrshare, Buffer, n8n, Late.dev, Webhook). Per-Page-Plattform-Auswahl, Backend-Modul, CLI-Commands. MIT-Lizenz.

Die Realität

Multichannel-Distribution endet meist im Copy-Paste-Loop.

Mit semantic-delivery

  • Auto-Generation von Schema.org-JSON-LD aus Content-Modellen
  • Channel-Transformer pro Kanal (Web/AI/Voice/Social) mit Zeichenlimit-Awareness
  • Direkt-Distribution auf 6 Plattformen, Proxy-Distribution auf 5 weiteren
  • OAuth-Tokens AES-256-CBC verschlüsselt im Backend, mit Refresh-Flow
  • Dev.to-Cross-Posting mit Canonical-URL-Attribution
  • Podcast-Episoden-Detection mit automatischer Job-Erstellung
  • Per-Page-Plattform-Auswahl im Backend — nur konfigurierte Plattformen sichtbar

Bisher

  • Manuelle Anpassung pro Kanal mit jeweils eigenen Zeichenlimits
  • JSON-LD-Schreibarbeit im Template
  • Keine Channel-Detection im Frontend (Web vs AI Agent vs Voice)
  • OAuth-Token-Verwaltung als Side-Project mit Klartext-Speicher
  • Cross-Posting in Blog-Plattformen ohne Canonical-Anbindung
  • Podcast-Episoden-Detection als Cron-Bastelei

Vier Grundbausteine

Channel-Detection-Middleware

Erkennt anhand Headers / User-Agent / Accept, ob die Anfrage von einem AI-Agent, Voice-Assistant, Social-Crawler oder Browser kommt — und liefert das passende Format.

Distributor-Architektur

Auto-Discovery via Symfony-DI. Eigene Distributors implementieren DistributorInterface und tauchen automatisch im Page-Editor auf, sobald die zugehörigen Site-Credentials konfiguriert sind.

Channel-Transformer

Pro Kanal ein eigener Transformer: Web/AI Agent/Voice/Social Media. Kürzung auf Plattform-Limits (z.B. 280 Zeichen X, 300 Graphemes Bluesky), Tonality-Anpassung, Asset-Auswahl.

Schema.org-Anreicherung

JSON-LD wird automatisch aus dem Content-Modell generiert: Article, BlogPosting, NewsArticle, FAQPage, Product, HowTo, Organization, LocalBusiness. Auto-Detect mit manuellem Override pro Seite.

Verfügbarkeit: Coming soon — öffentliche Veröffentlichung in Vorbereitung

Die öffentliche Bereitstellung als Composer-Paket wird derzeit vorbereitet. Wenn Sie den Baustein bereits in Ihrer TYPO3-Plattform einsetzen möchten, sprechen Sie uns über das Kontaktformular an — wir liefern aktuell im Rahmen von Plattform-Engagements aus.

02 — Plattformen

Elf Distributoren in drei Kategorien

Sechs Plattformen werden direkt über deren native APIs bedient (Direkt-Distribution), eine davon ist eine Long-Form-Blog-Plattform mit Canonical-Attribution. Fünf weitere laufen via etablierten Proxy-Diensten — wenn Sie z.B. ohnehin Buffer oder Ayrshare einsetzen.

Proxy-Dienste

  • Ayrshare — API Key
  • Buffer — Access Token
  • n8n — Webhook-URL
  • Late.dev — API Key
  • Generic Webhook — Custom URL

Long-Form Blog

  • Dev.to — API Key · Markdown-Format

Setzt automatisch eine Canonical-URL zurück zur Original-TYPO3-Seite — keine SEO-Strafe für Cross-Posting.

Social Media (Short-Form)

  • Bluesky — App Password · 300 Graphemes
  • LinkedIn — OAuth 2.0 · 3.000 Zeichen
  • X / Twitter — Bearer Token · 280 Zeichen
  • Instagram — Page Access Token · 2.200 Zeichen
  • Facebook — Page Access Token · 63.000 Zeichen
03 — Backend & Operations

Backend, CLI und Operations

semantic-delivery wird ohne Backend-Erweiterungen geliefert, die niemand bedient — das Backend-Modul ist von vornherein redaktionsbereit, die CLI-Commands sind cron-tauglich, der OAuth-Service speichert Tokens AES-256-CBC verschlüsselt.

DDD + Tests

4-Layer-DDD (Domain / Application / Infrastructure / Presentation), strikt enforced via deptrac. PHPStan Level 8, PER-CS3x0, PHPUnit Unit + Functional Tests. Vier Datenbank-Tabellen (channel_content, distribution_job, schema_cache, oauth_token).

OAuth-Token-Service

Für Plattformen mit OAuth 2.0 + Refresh: Tokens werden mit AES-256-CBC verschlüsselt im Backend gespeichert (Encryption-Key des TYPO3). API für Storage, Lookup, Refresh und Expiry-Check ist im Service-Container.

CLI-Commands

semantic-delivery:detect-episodes erkennt neue Podcast-Episoden und legt Distribution-Jobs an (mit --dry-run und --auto-publish). semantic-delivery:process-jobs arbeitet die Queue ab.

Backend-Modul

Unter Content → Semantic Delivery (Admin-Zugriff): Preview pro Plattform vor Publish, Job-Queue-Verwaltung, Log mit externen URLs der erfolgreichen Distributionen. Pro Seite eigener „Semantic Delivery“-Tab mit Plattform-Checkbox-Liste.

Datenbank- und Seiten-Felder

Tabellen

TabelleZweck
tx_semanticdelivery_channel_contentChannel-spezifische Content-Varianten
tx_semanticdelivery_distribution_jobJob-Queue für Distributionen
tx_semanticdelivery_schema_cacheGecachte Schema.org-Daten
tx_semanticdelivery_oauth_tokenVerschlüsselte OAuth-Tokens je Plattform

Page-Felder

FeldTypBeschreibung
tx_semanticdelivery_schema_typeSelectSchema.org-Typ überschreiben (Default: Auto-Detect)
tx_semanticdelivery_channelsCheckBoxAuslieferungskanäle (Website, AI Agent, Voice, Social Media)
tx_semanticdelivery_distribution_enabledCheckDistribution für diese Seite aktivieren
tx_semanticdelivery_distribution_platformsCheckBoxZielplattformen (dynamisch gefüllt)

Der Plattform-Selector zeigt nur Plattformen, für die in der aktuellen Site Credentials hinterlegt sind. Redakteur:innen können so kein Ziel auswählen, das fehlschlagen würde.

Unterstützte Plattformen

Social Media (Short-Form)

PlattformDistributorAuthLimit
BlueskyBlueskyDistributorApp Password300 Grapheme
LinkedInLinkedInDistributorOAuth 2.0 Access Token3.000 Zeichen
X/TwitterTwitterXDistributorBearer Token280 Zeichen
InstagramInstagramDistributorPage Access Token2.200 Zeichen
FacebookFacebookDistributorPage Access Token63.000 Zeichen

Blog-Plattformen (Long-Form)

PlattformDistributorAuthFormat
Dev.toDevToDistributorAPI KeyMarkdown

Blog-Distributoren setzen automatisch ein Canonical zurück auf die TYPO3-Originalseite. Hinweis: Medium gibt keine neuen API-Tokens mehr aus; der MediumDistributor wurde entfernt.

Proxy-Dienste

PlattformDistributorAuth
AyrshareAyrshareDistributorAPI Key
BufferBufferDistributorAccess Token
n8nN8nDistributorWebhook URL
Late.devLateDevDistributorAPI Key
Generic WebhookWebhookDistributorCustom URL

Setup

1. Distribution in den Site Settings aktivieren

 

semanticDelivery:
  channels:
    socialMedia:
      enabled: true
  distribution:
    enabled: true
    defaultDistributor: 'bluesky'

 

2. Plattform-Credentials konfigurieren

Credentials liegen pro Site in config/sites/<name>/config.yaml. Sensible Tokens nutzen die %secret()%-Syntax (siehe moselwal/secret-resolver), öffentliche Identifier landen direkt im YAML.

Nur Plattformen mit gefülltem Primär-Credential erscheinen im Backend-Editor. Plattformen ohne Credential sind ausgeblendet — fehlerhafte Auswahl ist somit ausgeschlossen.

3. Datenbankschema aktualisieren

 

vendor/bin/typo3 database:updateschema

 

4. Plattformen pro Seite auswählen

Im TYPO3-Backend hat jede Seite einen Tab Semantic Delivery:

  1. Enable Distribution aktivieren
  2. Zielplattformen auswählen (nur konfigurierte sind sichtbar)
  3. Speichern und publizieren

Das Distributionssystem respektiert diese Auswahl — Seiten gehen nur an die markierten Plattformen.

CLI, Backend-Modul und Erweiterung

CLI-Befehle

 

# Neue Podcast-Episoden erkennen
vendor/bin/typo3 semantic-delivery:detect-episodes --dry-run
vendor/bin/typo3 semantic-delivery:detect-episodes
vendor/bin/typo3 semantic-delivery:detect-episodes --auto-publish

# Pending-Jobs verarbeiten
vendor/bin/typo3 semantic-delivery:process-jobs

 

Empfohlener Cron

 

*/15 * * * * cd /app && vendor/bin/typo3 semantic-delivery:detect-episodes
*/5  * * * * cd /app && vendor/bin/typo3 semantic-delivery:process-jobs

 

Backend-Modul

Verfügbar unter Content → Semantic Delivery (Admin-Zugriff):

Eigenen Distributor ergänzen

  1. Klasse anlegen, die DistributorInterface in Classes/Infrastructure/Distribution/ implementiert
  2. Optional einen TransformerInterface in Classes/Infrastructure/Transformer/
  3. Credential-Mapping in DistributionPlatformItemsProcFunc::CREDENTIAL_KEYS ergänzen
  4. Site-Setting in settings.definitions.yaml und settings.yaml hinzufügen

Sobald Credentials hinterlegt sind, taucht die Plattform automatisch im Page-Editor auf. OAuth-Tokens werden über den OAuthTokenService verwaltet und at-rest mit AES-256-CBC unter dem TYPO3 Encryption Key verschlüsselt.

Quellcode & Doku

TYPO3 Extension Repository

Nicht im offiziellen TER — die öffentliche Distribution über Composer wird vorbereitet (coming soon).

Composer-Package

Veröffentlichung als moselwal/semantic-delivery in Vorbereitung. Coming soon.

Repository

Quellcode und Issue-Tracker werden mit der öffentlichen Veröffentlichung freigeschaltet. Coming soon.

Mirror

Öffentlicher Mirror und Pull-Request-Workflow folgen mit der Veröffentlichung. Coming soon.

Schema.org-Resolver-Logik

Der SchemaTypeResolverService entscheidet pro Seite, ob ein Dokument der Article- oder WebPage-Familie ausgegeben wird. ArticleSchemaGenerator (Priority 50) und WebPageSchemaGenerator (Priority 45) delegieren beide ihr canHandle() an den Resolver, sodass nie beide Familien gleichzeitig erscheinen.

Auflösungs-Reihenfolge

  1. Manueller Override über pages.tx_semanticdelivery_schema_type. Werte aus Article- oder WebPage-Familie werden direkt übernommen. Andere Werte (z. B. Product, FAQPage) lassen den Resolver null liefern, damit der zuständige Generator die Seite übernimmt.
  2. Slug-Heuristik — nur sprachstabile Index-/Section-Patterns.
  3. Hero-Content-Block-Hinweis — der erste passende CType gewinnt.
  4. Keywords-Heuristik auf Basis von pages.keywords.
  5. Default: Article bei Standardseiten (doktype 1 oder 2 mit Inhalten), sonst WebPage.

Sprachspezifische Patterns wie /kontakt, /ueber-uns, /about, /contact, /team, /profil werden bewusst nicht automatisch erkannt — hier setzen Sie den Override im Backend.

Slug-Heuristik

Slug-PatternSchema-Typ
/blog (Root)CollectionPage
/blog/<sub>BlogPosting
/news, /press, /presse (Root)CollectionPage
/news/<sub>, /press/<sub>, /presse/<sub>NewsArticle
/portfolio, /referenz* (Root)CollectionPage
/portfolio/<sub>, /referenz*/<sub>ItemPage

Hero-CType-Mapping

Hero-CTypeFamilieSchema-Typ
moselwal_blogheroarticleBlogPosting
moselwal_pageheroarticleArticle
moselwal_herocardarticleArticle
moselwal_teaserarticleNewsArticle
moselwal_herowebpageWebPage
moselwal_oleherowebpageAboutPage
moselwal_nozzleherowebpageWebPage

Keywords-Heuristik

Keyword enthältSchema-Typ
tech, technicalTechArticle
paper, study, researchScholarlyArticle
reportReport
socialSocialMediaPosting
blogBlogPosting
news, pressNewsArticle
collection, indexCollectionPage

Backend-Override-Dropdown

Das Page-Property-Dropdown ist via --div---Separatoren gruppiert; jede Option trägt einen Tooltip für das Backend.

Werte außerhalb der Article-/WebPage-Familien geben die Seite an den jeweils zuständigen Generator (FAQ, Product, Organization …) ab. Der Resolver liefert ebenfalls null, wenn die Seite FAQ-, Service-, Product-, Podcast- oder PackagesGrid-CTypes enthält oder unterhalb von /leistungen, /impressum, /datenschutz, /karriere oder /podcasts liegt.

Plattform-Setup-Guides

Schritt-für-Schritt-Einstieg pro Plattform. Sensible Tokens werden über %secret()% aufgelöst, öffentliche Identifier (Handle, Page-ID, Account-ID) stehen direkt in der YAML.

Bluesky

Auth: App Password. Token läuft nicht ab und kann jederzeit in den App-Passwords-Einstellungen widerrufen werden.

  1. Auf bsky.app einloggen.
  2. Settings → Privacy and Security → App Passwords öffnen.
  3. Add App Password, Namen vergeben (z. B. „TYPO3 Distribution“).
  4. Generiertes Passwort kopieren und unter semanticDelivery.social.bluesky.appPassword via %secret(BLUESKY_APP_PASSWORD)% referenzieren. Handle (z. B. yourhandle.bsky.social) direkt in YAML.

LinkedIn

Auth: OAuth 2.0 Access Token. Modi: w_member_social (privat) oder w_organization_social (Company Page). Tokens laufen nach 60 Tagen ab; Refresh manuell oder per OAuthTokenService.

  1. Auf linkedin.com/developers/apps über Create app eine App anlegen, Company Page verknüpfen, Logo hochladen.
  2. Im Reiter Products das Produkt Share on LinkedIn aktivieren.
  3. Im Reiter Auth Client ID, Client Secret und Redirect URL hinterlegen.
  4. Authorization-Code über www.linkedin.com/oauth/v2/authorization mit Scope openid profile w_member_social einholen.
  5. Code per POST auf www.linkedin.com/oauth/v2/accessToken gegen Access- und Refresh-Token tauschen.
  6. Person-ID per GET api.linkedin.com/v2/userinfo ermitteln (Feld sub).
  7. Token, personId und optional organizationId in semanticDelivery.social.linkedin eintragen. Wenn beide gesetzt sind, hat organizationId Vorrang.

Detail-Doku im README.

X / Twitter

Auth: OAuth 2.0 Bearer Token. Scopes: tweet.read, tweet.write, users.read. Für tweet.write ist mindestens Basic-Access erforderlich.

  1. Auf developer.x.com/en/portal ein Project und darin eine App anlegen.
  2. Unter User authentication settings OAuth 2.0 aktivieren.
  3. App-Permissions auf Read and write setzen.
  4. Unter Keys and tokens einen Bearer Token erzeugen.
  5. Token in semanticDelivery.social.twitter.bearerToken via %secret(TWITTER_BEARER_TOKEN)% hinterlegen.

Facebook

Auth: Long-Lived Page Access Token. Scopes: pages_manage_posts, pages_read_engagement.

  1. Auf developers.facebook.com über Create App eine Business-App anlegen und das Produkt Facebook Login hinzufügen.
  2. Im Graph API Explorer die App auswählen, Generate Access Token klicken und pages_manage_posts erteilen.
  3. Im Dropdown User or Page die Ziel-Page wählen — das angezeigte Token ist nun ein Page-Token.
  4. Token gegen Long-Lived-Token tauschen: GET graph.facebook.com/v21.0/oauth/access_token?grant_type=fb_exchange_token&client_id=APP_ID&client_secret=APP_SECRET&fb_exchange_token=SHORT_LIVED_TOKEN.
  5. Page-ID via GET graph.facebook.com/v21.0/me/accounts auslesen.
  6. accessToken und pageId unter semanticDelivery.social.facebook setzen.

Instagram

Auth: Instagram Graph API über Facebook-Page-Token. Scopes: instagram_basic, instagram_content_publish. Voraussetzung: Facebook-Page mit verknüpftem Instagram-Business- oder Creator-Konto. Reine Text-Posts werden von der Graph API nicht unterstützt.

  1. Account-ID ermitteln: GET graph.facebook.com/v21.0/FACEBOOK_PAGE_ID?fields=instagram_business_account&access_token=PAGE_ACCESS_TOKEN.
  2. instagram_business_account.id aus der Antwort als accountId übernehmen.
  3. Page-Token (identisch zu Facebook) als accessToken in semanticDelivery.social.instagram eintragen.

Dev.to

Auth: API-Key, kein OAuth. Token läuft nicht ab. Posts werden als Markdown publiziert, kanonische URL zeigt zurück auf die TYPO3-Seite.

  1. Auf dev.to einloggen.
  2. Settings → Extensions öffnen.
  3. Unter DEV Community API Keys einen neuen Key erzeugen und kopieren.
  4. Key unter semanticDelivery.blogging.devto.apiKey via %secret(DEVTO_API_KEY)% hinterlegen.
Nächster Schritt

Eigene Plattform fehlt?

Auto-Discovery macht das Anlegen eines neuen Distributors zu einer Frage von zwei Klassen — Implement DistributorInterface, optional einen Transformer, Credential-Mapping in DistributionPlatformItemsProcFunc::CREDENTIAL_KEYS ergänzen, fertig. Wenn Sie das selbst machen wollen: Doku im Repo. Wenn wir es bauen sollen: sprechen Sie uns an.

Custom-Distributor besprechen

Oder direkt schreiben: kontakt@moselwal.de

Setzen wir ein bei …

Dieses Paket trägt die Multi-Channel-Distribution in AI-Ready CMS und AI-Ready Commerce. In der betreuten Variante läuft es als Teil von AI-Ready CMS as a Service.