Mit Statamic individuelle Datenquellen integrieren
Statamic bringt von Haus aus ein mächtiges Content-Management mit. Doch was, wenn wir Produktdaten aus einer externen API oder projektspezifische Datensätze aus der eigenen Datenbank in unseren Page-Builder einbinden wollen?
In diesem Guide zeigen wir zwei Wege, wie sich individuelle Datenquellen in Statamic-Sektionen integrieren lassen – und warum wir nur einen davon empfehlen. Dazu nutzen wir das neue Dictionary-Fieldset, View Components und unseren Action-Ansatz.
Das Beispiel liegt als öffentliches Beispiel-Repository bereit.
Worum geht es?
Statamic verwaltet Inhalte über Flat Files oder Eloquent – das ist elegant und für die meisten Anwendungsfälle völlig ausreichend. In der Praxis begegnen uns aber regelmäßig Szenarien, in denen wir zusätzliche Datenquellen in den Page-Builder integrieren müssen: Produkte aus einem Shop-System, Angebote aus einer internen Datenbank, Stellenanzeigen von einer Recruiting-API – die Liste ist lang.
Die Frage ist dann: Wie bekomme ich diese Daten sauber in meine Statamic-Sektion, ohne mir die Architektur zu versauen?
Grundsätzlich gibt es zwei Wege:
Quick & Dirty – Die Daten direkt im Blade-Template laden. Geht schnell, macht aber auf Dauer Probleme.
View Components – Die Datenlogik in eine Laravel View Component auslagern. Unser empfohlener Ansatz, weil die Views frei von Logik bleiben.
Dazu schauen wir uns an, wie uns Statamics relativ neues Dictionary-Fieldset dabei hilft, im Control Panel eine Auswahl aus externen Daten anzubieten – ganz ohne eigenen Fieldtype. Und wir setzen auf unseren bewährten Action-Ansatz sowie unser Laramate Support Package, um die Implementierung sauber zu strukturieren.
Das Beispiel-Repository
Alle Code-Beispiele in diesem Guide stammen aus unserem öffentlichen Beispiel-Repository. Dort lässt sich das Ganze komplett nachvollziehen und als Ausgangspunkt für eigene Projekte nutzen:
github.com/Laramate/statamic-custom-data-example
Das Repo zeigt eine Statamic-Installation mit Page-Builder, die Produktdaten aus einer externen API lädt. Die API liefert Produkte und Produktkategorien – ein typisches Szenario, das sich auf viele ähnliche Anwendungsfälle übertragen lässt.
Das Dictionary-Fieldset: Externe Daten im Control Panel
Bevor wir die Daten im Frontend ausgeben, brauchen wir eine Möglichkeit, dem Redakteur im Statamic Control Panel eine Auswahl anzubieten. In unserem Beispiel soll der Redakteur eine Produktkategorie auswählen können, nach der dann gefiltert wird.
Früher hätte man dafür einen eigenen Fieldtype entwickeln müssen – mit eigenem Vue-Component im Frontend und einer PHP-Klasse im Backend. Das ist aufwändig und für viele Fälle schlicht überdimensioniert.
Statamic bietet mit dem Dictionary-Fieldtype eine elegantere Lösung: Man definiert eine PHP-Klasse, die Daten aus einer beliebigen Quelle liefert, und Statamic stellt diese automatisch als Auswahlfeld im Control Panel dar.
Die Dictionary-Klasse
Unsere ProductCategories Klasse erweitert Statamics BasicDictionary und definiert, welches Feld als Wert und welches als Label genutzt wird:
<?php
namespace App\Dictionaries;
use App\Actions\GetProductCategoriesAction;
use Statamic\Dictionaries\BasicDictionary;
class ProductCategories extends BasicDictionary
{
protected string $valueKey = 'slug';
protected string $labelKey = 'name';
protected function getItems(): array
{
return new GetProductCategoriesAction()->handle();
}
}Language:php
Das Dictionary ruft keine API direkt auf – stattdessen delegiert es die Arbeit an eine Action-Klasse. Das hat handfeste Vorteile:
Trennung der Verantwortlichkeiten: Das Dictionary kümmert sich um die Darstellung im Control Panel, die Action um die Datenbeschaffung.
Wiederverwendbarkeit: Die gleiche Action lässt sich an anderen Stellen im Projekt nutzen – in einem Controller, einem Artisan-Command oder einem Livewire-Component.
Testbarkeit: Die Action lässt sich isoliert testen, ohne das Dictionary oder das Control Panel starten zu müssen.
Einbindung im Fieldset
Im Page-Builder-Fieldset referenzieren wir unser Dictionary dann ganz einfach als Feldtyp:
product_category:
field:
dictionary: product_categories
placeholder: 'Filter a product category'
clearable: true
type: dictionary
display: 'Product Categories'
max_items: 1Language:yaml
Im Control Panel erscheint jetzt ein Dropdown mit allen Produktkategorien – gefüllt aus der API, ohne eine einzige Zeile JavaScript.
Service und Actions: Saubere API-Kommunikation
Für die Kommunikation mit der externen API setzen wir auf eine klare Schichtentrennung: Ein Service kümmert sich um die HTTP-Verbindung und stellt Methoden für die einzelnen API-Endpunkte bereit. Actions nutzen den Service, um konkrete Geschäftslogik abzubilden.
Wer tiefer in unseren Action-Ansatz einsteigen möchte, findet dazu unseren ausführlichen Beitrag: Actions – Mehr Ordnung in der Laravel Business-Logik.
Der Service
Der ProductApiService kapselt die gesamte HTTP-Kommunikation. Er konfiguriert den Client einmalig und bietet dedizierte Methoden für jeden Endpunkt:
<?php
namespace App\Services;
use Illuminate\Http\Client\PendingRequest;
use Illuminate\Http\Client\Response;
use Illuminate\Support\Facades\Http;
class ProductApiService
{
public function __construct(
public string $baseUrl = 'https://api.escuelajs.co/api/v1/'
)
{
// Get your API Credentials from config
}
public function http(): PendingRequest
{
return Http::baseUrl($this->baseUrl)
->acceptJson()
->asJson();
}
public function getProducts(?string $category = null): Response
{
return $this->http()->get("/products?categorySlug={$category}");
}
public function getCategories(): Response
{
return $this->http()->get('/categories');
}
}Language:php
In einem echten Projekt würden die API-Credentials natürlich aus der Config kommen – im Beispiel halten wir es bewusst simpel.
Die Actions
Unsere Actions erweitern die abstrakte Action Klasse aus dem laramate/support Package. Jede Action hat genau eine Aufgabe:
Produkte laden:
<?php
namespace App\Actions;
use App\Services\ProductApiService;
use Laramate\Support\Tasks\Action;
class GetProductsAction extends Action
{
public function __construct(public ?string $category = null) {}
public function handle(): array
{
$productsResponse = new ProductApiService()->getProducts($this->category);
return $productsResponse->json();
}
}Language:php
Produktkategorien laden (mit Caching):
<?php
namespace App\Actions;
use App\Services\ProductApiService;
use Exception;
use Illuminate\Support\Facades\Cache;
use Laramate\Support\Tasks\Action;
class GetProductCategoriesAction extends Action
{
public function handle(): array
{
if (Cache::has('product_categories')) {
return Cache::get('product_categories');
}
$productCategoryResponse = new ProductApiService()->getCategories();
if ($productCategoryResponse->successful()) {
$categories = $productCategoryResponse->json();
Cache::put('product_categories', $categories, 60);
return $categories;
}
throw new Exception('Unable to query product categories');
}
}Language:php
Die GetProductCategoriesAction zeigt ein wichtiges Muster: Die Kategorien werden gecacht, damit das Control Panel nicht bei jedem Seitenaufruf die API abfragt. Das Dictionary nutzt diese Action – und profitiert automatisch vom Caching.
Weg 1: Quick & Dirty – Logik im Template
Der schnellste Weg, externe Daten in eine Statamic-Sektion zu bekommen, ist die direkte Abfrage im Blade-Template. Im Grunde öffnet man einen @php-Block und ruft die Action auf:
@php
$products = new App\Actions\GetProductsAction($product_category)->handle();
@endphp
<section class="py-12 px-4 sm:px-6 lg:px-8 bg-gray-950 rounded-2xl">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@foreach($products as $product)
<x-product-card :product="$product"></x-product-card>
@endforeach
</div>
</section>Language:html
Das funktioniert – keine Frage. Aber es gibt gute Gründe, warum wir diesen Ansatz nicht empfehlen:
Logik im Template: Views sollten ausschließlich für die Darstellung zuständig sein. Sobald Geschäftslogik im Template landet, wird die Codebasis schwerer wartbar und testbar.
Fehlende Testbarkeit: Einen PHP-Block im Blade-Template kann man nicht isoliert testen. Der Test muss immer den gesamten View rendern.
Keine Separation of Concerns: Mit der Zeit sammeln sich immer mehr solcher @php-Blöcke an, und die Templates werden unübersichtlich.
Für einen schnellen Prototyp oder eine einmalige Integration mag dieser Weg vertretbar sein. Für alles andere gibt es den besseren Ansatz.
Weg 2: View Components – Unser empfohlener Ansatz
Laravel View Components sind das perfekte Werkzeug, um Logik aus Templates herauszuhalten. Die Idee: Eine PHP-Klasse bereitet die Daten vor, das zugehörige Blade-Template kümmert sich nur noch um die Darstellung.
Die View Component
<?php
namespace App\View\Components;
use App\Actions\GetProductsAction;
use Closure;
use Illuminate\Contracts\View\View;
use Illuminate\View\Component;
class ProductListing extends Component
{
public array $products;
public function __construct(public string $category)
{
$this->products = new GetProductsAction($this->category)->handle();
}
public function render(): View|Closure|string
{
return view('components.product-listing', [
'products' => $this->products,
]);
}
}Language:php
Die Klasse nimmt die ausgewählte Kategorie entgegen, ruft über die Action die Produktdaten ab und reicht sie an das Template weiter.
Das Component-Template
{{-- resources/views/components/product-listing.blade.php --}}
<section class="py-12 px-4 sm:px-6 lg:px-8 bg-gray-950 rounded-2xl">
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6">
@foreach($products as $product)
<x-product-card :product="$product"></x-product-card>
@endforeach
</div>
</section>Language:html
Kein @php-Block, keine Action-Aufrufe, keine Logik – nur sauberes HTML mit Blade-Syntax.
Die Page-Builder-Sektion
Das Template der Statamic Page-Builder-Sektion schrumpft auf eine einzige Zeile:
{{-- resources/views/page_builder/recommended_way.blade.php --}}
<x-product-listing :category="$product_category"></x-product-listing>Language:html
Die Variable $product_category kommt dabei direkt aus dem Dictionary-Feld, das der Redakteur im Control Panel befüllt hat. Die View Component übernimmt den Rest.
Im Vergleich: Warum View Components der bessere Weg sind
Stellen wir die beiden Ansätze direkt gegenüber:
Quick & Dirty:
Logik und Darstellung vermischt
Nicht isoliert testbar
Bei wachsendem Projekt schnell unübersichtlich
View Components:
Klare Trennung von Logik und Darstellung
Isoliert testbar
Wiederverwendbar in anderen Teilen der Anwendung
Native Laravel-Funktionalität – kein extra Package nötig
In Kombination mit dem Action-Pattern und dem Service ergibt sich eine Architektur, bei der jede Schicht genau eine Verantwortung hat:
Service – HTTP-Kommunikation mit der API
Action – Geschäftslogik (z. B. Caching, Transformation)
View Component – Datenvorbereitung für die View
Blade Template – Reine Darstellung
Der Page-Builder im Überblick
Im Fieldset des Page-Builders definieren wir beide Varianten als eigene Sektionen. So lässt sich im Beispiel-Repo direkt vergleichen, wie sich die Ansätze unterscheiden:
title: 'Page Builder'
fields:
-
handle: sections
field:
type: replicator
display: Sections
sets:
custom_data:
display: 'Custom Data'
sets:
recommended_way:
display: 'Recommended Way'
fields:
-
handle: product_category
field:
dictionary: product_categories
placeholder: 'Filter a product category'
clearable: true
type: dictionary
display: 'Product Categories'
max_items: 1
quick_and_dirty:
display: 'Quick And Dirty'
fields:
-
handle: product_category
field:
dictionary: product_categories
placeholder: 'Filter a product category'
clearable: true
type: dictionary
display: 'Product Categories'
max_items: 1Language:yaml
Beide Sektionen nutzen dasselbe Dictionary für die Kategorieauswahl. Der Unterschied liegt ausschließlich in der Art, wie die Daten im Frontend verarbeitet werden.
Fazit
Individuelle Datenquellen im Statamic Page-Builder zu integrieren, ist kein Hexenwerk. Mit den richtigen Werkzeugen – Dictionary-Fieldset, View Components und dem Action-Pattern – bleibt die Architektur sauber und die Codebasis wartbar.
Das Dictionary-Fieldset erspart uns den Aufwand eines eigenen Fieldtypes und gibt uns trotzdem die Flexibilität, beliebige Datenquellen im Control Panel anzubieten. Und mit View Components stellen wir sicher, dass die Templates genau das tun, wofür sie da sind: darstellen, nicht berechnen.
Im Beispiel-Repository lässt sich das Ganze in Aktion sehen. Einfach klonen, installieren und ausprobieren.