Statamic Laravel Webentwicklung

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.

Chris
Geschäftsführer, PHP Senior-Entwickler
Aktualisiert:
Statamic - Corporate Website.

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:

  1. Quick & Dirty – Die Daten direkt im Blade-Template laden. Geht schnell, macht aber auf Dauer Probleme.

  2. 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:

  1. Service – HTTP-Kommunikation mit der API

  2. Action – Geschäftslogik (z. B. Caching, Transformation)

  3. View Component – Datenvorbereitung für die View

  4. 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.