
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.
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.
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
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
| Tabelle | Zweck |
|---|---|
tx_semanticdelivery_channel_content | Channel-spezifische Content-Varianten |
tx_semanticdelivery_distribution_job | Job-Queue für Distributionen |
tx_semanticdelivery_schema_cache | Gecachte Schema.org-Daten |
tx_semanticdelivery_oauth_token | Verschlüsselte OAuth-Tokens je Plattform |
Page-Felder
| Feld | Typ | Beschreibung |
|---|---|---|
tx_semanticdelivery_schema_type | Select | Schema.org-Typ überschreiben (Default: Auto-Detect) |
tx_semanticdelivery_channels | CheckBox | Auslieferungskanäle (Website, AI Agent, Voice, Social Media) |
tx_semanticdelivery_distribution_enabled | Check | Distribution für diese Seite aktivieren |
tx_semanticdelivery_distribution_platforms | CheckBox | Zielplattformen (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)
| Plattform | Distributor | Auth | Limit |
|---|---|---|---|
| Bluesky | BlueskyDistributor | App Password | 300 Grapheme |
LinkedInDistributor | OAuth 2.0 Access Token | 3.000 Zeichen | |
| X/Twitter | TwitterXDistributor | Bearer Token | 280 Zeichen |
InstagramDistributor | Page Access Token | 2.200 Zeichen | |
FacebookDistributor | Page Access Token | 63.000 Zeichen |
Blog-Plattformen (Long-Form)
| Plattform | Distributor | Auth | Format |
|---|---|---|---|
| Dev.to | DevToDistributor | API Key | Markdown |
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
| Plattform | Distributor | Auth |
|---|---|---|
| Ayrshare | AyrshareDistributor | API Key |
| Buffer | BufferDistributor | Access Token |
| n8n | N8nDistributor | Webhook URL |
| Late.dev | LateDevDistributor | API Key |
| Generic Webhook | WebhookDistributor | Custom 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:
- Enable Distribution aktivieren
- Zielplattformen auswählen (nur konfigurierte sind sichtbar)
- 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):
- Preview — transformierter Inhalt pro Plattform vor Publikation
- Queue — Verwaltung wartender Distribution-Jobs
- Log — abgeschlossene und fehlgeschlagene Distributionen mit externen URLs
Eigenen Distributor ergänzen
- Klasse anlegen, die
DistributorInterfaceinClasses/Infrastructure/Distribution/implementiert - Optional einen
TransformerInterfaceinClasses/Infrastructure/Transformer/ - Credential-Mapping in
DistributionPlatformItemsProcFunc::CREDENTIAL_KEYSergänzen - Site-Setting in
settings.definitions.yamlundsettings.yamlhinzufü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
- 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 Resolvernullliefern, damit der zuständige Generator die Seite übernimmt. - Slug-Heuristik — nur sprachstabile Index-/Section-Patterns.
- Hero-Content-Block-Hinweis — der erste passende CType gewinnt.
- Keywords-Heuristik auf Basis von
pages.keywords. - Default:
Articlebei Standardseiten (doktype1 oder 2 mit Inhalten), sonstWebPage.
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-Pattern | Schema-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-CType | Familie | Schema-Typ |
|---|---|---|
moselwal_bloghero | article | BlogPosting |
moselwal_pagehero | article | Article |
moselwal_herocard | article | Article |
moselwal_teaser | article | NewsArticle |
moselwal_hero | webpage | WebPage |
moselwal_olehero | webpage | AboutPage |
moselwal_nozzlehero | webpage | WebPage |
Keywords-Heuristik
| Keyword enthält | Schema-Typ |
|---|---|
tech, technical | TechArticle |
paper, study, research | ScholarlyArticle |
report | Report |
social | SocialMediaPosting |
blog | BlogPosting |
news, press | NewsArticle |
collection, index | CollectionPage |
Backend-Override-Dropdown
Das Page-Property-Dropdown ist via --div---Separatoren gruppiert; jede Option trägt einen Tooltip für das Backend.
- Auto (Erkennung über Slug, Hero-CB, Keywords)
- Article-Familie:
Article,BlogPosting,NewsArticle,TechArticle,ScholarlyArticle,Report,SocialMediaPosting - WebPage-Familie:
WebPage,AboutPage,ContactPage,CollectionPage,ItemPage,ProfilePage,QAPage - Other:
FAQPage,Product,HowTo,Organization,LocalBusiness
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.
- Auf
bsky.appeinloggen. - Settings → Privacy and Security → App Passwords öffnen.
- Add App Password, Namen vergeben (z. B. „TYPO3 Distribution“).
- Generiertes Passwort kopieren und unter
semanticDelivery.social.bluesky.appPasswordvia%secret(BLUESKY_APP_PASSWORD)%referenzieren. Handle (z. B.yourhandle.bsky.social) direkt in YAML.
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.
- Auf
linkedin.com/developers/appsüber Create app eine App anlegen, Company Page verknüpfen, Logo hochladen. - Im Reiter Products das Produkt Share on LinkedIn aktivieren.
- Im Reiter Auth Client ID, Client Secret und Redirect URL hinterlegen.
- Authorization-Code über
www.linkedin.com/oauth/v2/authorizationmit Scopeopenid profile w_member_socialeinholen. - Code per
POSTaufwww.linkedin.com/oauth/v2/accessTokengegen Access- und Refresh-Token tauschen. - Person-ID per
GET api.linkedin.com/v2/userinfoermitteln (Feldsub). - Token,
personIdund optionalorganizationIdinsemanticDelivery.social.linkedineintragen. Wenn beide gesetzt sind, hatorganizationIdVorrang.
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.
- Auf
developer.x.com/en/portalein Project und darin eine App anlegen. - Unter User authentication settings OAuth 2.0 aktivieren.
- App-Permissions auf Read and write setzen.
- Unter Keys and tokens einen Bearer Token erzeugen.
- Token in
semanticDelivery.social.twitter.bearerTokenvia%secret(TWITTER_BEARER_TOKEN)%hinterlegen.
Auth: Long-Lived Page Access Token. Scopes: pages_manage_posts, pages_read_engagement.
- Auf
developers.facebook.comüber Create App eine Business-App anlegen und das Produkt Facebook Login hinzufügen. - Im Graph API Explorer die App auswählen, Generate Access Token klicken und
pages_manage_postserteilen. - Im Dropdown User or Page die Ziel-Page wählen — das angezeigte Token ist nun ein Page-Token.
- 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. - Page-ID via
GET graph.facebook.com/v21.0/me/accountsauslesen. accessTokenundpageIduntersemanticDelivery.social.facebooksetzen.
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.
- Account-ID ermitteln:
GET graph.facebook.com/v21.0/FACEBOOK_PAGE_ID?fields=instagram_business_account&access_token=PAGE_ACCESS_TOKEN. instagram_business_account.idaus der Antwort alsaccountIdübernehmen.- Page-Token (identisch zu Facebook) als
accessTokeninsemanticDelivery.social.instagrameintragen.
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.
- Auf
dev.toeinloggen. - Settings → Extensions öffnen.
- Unter DEV Community API Keys einen neuen Key erzeugen und kopieren.
- Key unter
semanticDelivery.blogging.devto.apiKeyvia%secret(DEVTO_API_KEY)%hinterlegen.
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.
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.