Benutzerdefinierte Felder, in der WordPress-Architektur präzise als Post Meta bezeichnet, bilden das technische Fundament, das WordPress von einem einfachen Blog-System in ein vollwertiges Enterprise-Content-Management-System transformiert hat. Ob es sich um strukturierte Produktdaten, Immobilienmerkmale wie Zimmeranzahl und Wohnfläche, Veranstaltungsdaten oder externe API-Referenzen handelt: Die Metadata API erlaubt es Entwicklern, beliebige zusätzliche Datensätze an Beiträge, Seiten und Custom Post Types anzuhängen.
Trotz der weiten Verbreitung von Komfort-Plugins wie Advanced Custom Fields (ACF) oder Meta Box ist das fundierte Verständnis der nativen WordPress Metadata API für professionelle Software-Entwickler unerlässlich. Nur wer die Funktionsweise von get_post_meta(), die Mechanismen der Datenbanktabelle wp_postmeta und die Performance-Auswirkungen von meta_query versteht, kann performante und wartbare Lösungen erstellen.
In diesem Entwickler-Leitfaden analysieren wir die Mechanik der Metadaten-Verwaltung in PHP, beleuchten Sicherheits- und Escaping-Standards und diskutieren skalierbare Architekturmuster.
Wie wp_postmeta Custom Fields speichert
WordPress speichert benutzerdefinierte Felder in einer dedizierten, vertikalen Tabelle namens wp_postmeta. Die Struktur ist bewusst generisch und besteht aus lediglich vier Spalten:
meta_id: Der Primärschlüssel (Primary Key, bigint) der Zeile.post_id: Der Fremdschlüssel (Foreign Key, bigint), der auf den Eintrag inwp_postsverweist.meta_key: Der Name bzw. Bezeichner des Feldes als Zeichenkette (varchar).meta_value: Der eigentliche Inhalt des Feldes (longtext).
-- Beispielhafter Blick auf einen Eintrag in wp_postmeta
SELECT * FROM wp_postmeta WHERE post_id = 42 AND meta_key = 'produkt_preis';Custom Fields mit Unterstrich verstecken (Protected Keys)
Wenn Sie einem Bezeichner einen führenden Unterstrich voranstellen, beispielsweise _interner_status oder _sync_timestamp, betrachtet WordPress diesen Schlüssel als geschützt. Diese Felder werden in der Standard-Meta-Box des WordPress-Redaktionsbereichs automatisch ausgeblendet. Dadurch wird verhindert, dass Redakteure versehentlich systemkritische Daten überschreiben, die von Plugins oder automatisierten Hintergrundprozessen verwaltet werden.
get_post_meta() in WordPress richtig verwenden
Die universelle Schnittstelle zum Lesen von Post-Metadaten ist die Funktion get_post_meta():
mixed get_post_meta( int $post_id, string $key = '', bool $single = false )Wann get_post_meta() ein Array statt eines Strings liefert
Ein häufiger Stolperstein liegt im Parameter $single. In WordPress kann ein Meta-Schlüssel für denselben Beitrag theoretisch mehrfach mit unterschiedlichen Werten existieren (beispielsweise mehrere Telefonnummern zu einem Standort).
$single = true: Gibt den ersten gefundenen Wert als skalaren Datentyp (String, Integer oder entpacktes Array bei serialisierten Daten) zurück. Existiert der Schlüssel nicht, wird ein leerer String""zurückgegeben.$single = false(Standardwert): Gibt stets ein Array zurück, das alle Werte enthält. Existiert kein Eintrag, ist das Array leer[].
<?php
// Innerhalb des The Loop
$post_id = get_the_ID();
// Einen Einzelwert auslesen
$anmeldefrist = get_post_meta( $post_id, 'event_anmeldefrist', true );
if ( ! empty( $anmeldefrist ) ) {
echo '<p class="frist-hinweis">';
echo 'Anmeldeschluss: ' . esc_html( $anmeldefrist );
echo '</p>';
}Wenn Sie den Parameter $key komplett weglassen (get_post_meta( $post_id )), liefert WordPress ein mehrdimensionales Array aller vorhandenen Metadaten für den Beitrag. WordPress lädt diesen gesamten Satz beim ersten Zugriff in den internen Objekt-Cache (wp_cache), sodass nachfolgende Aufrufe von get_post_meta() für denselben Beitrag keine erneute Datenbankabfrage mehr auslösen.
Was get_post_meta() für Zahlen, Booleans und fehlende Schlüssel liefert
Alles, was in wp_postmeta liegt, kommt als Text zurück, sofern es nicht serialisiert wurde. Der Docblock von get_post_meta() im WordPress-Kern listet die Umwandlungen: false kommt als leerer String zurück, true als '1', Ganzzahlen und Fließkommazahlen als Strings. Arrays und Objekte behalten ihren Typ, weil WordPress sie beim Speichern serialisiert und beim Lesen wieder entpackt.
Grenzfälle aus demselben Docblock: Eine ungültige Beitrags-ID (nicht numerisch, null oder negativ) liefert false. Eine gültige ID ohne den Schlüssel liefert '' bei $single = true und ein leeres Array bei $single = false. Ein mit register_meta() registrierter default (seit WordPress 5.5) wird nie gespeichert und kommt deshalb im registrierten Typ zurück.
update_post_meta( $post_id, 'wppoland_bestand', 0 );
$bestand = get_post_meta( $post_id, 'wppoland_bestand', true );
var_dump( $bestand ); // string(1) "0"
var_dump( 0 === $bestand ); // bool(false)
$bestand = (int) $bestand; // vor Rechnung oder strengem Vergleich castenEin strenger Vergleich mit einer Ganzzahl trifft einen gespeicherten Wert nie. Casten Sie mit (int), (float) oder absint(), bevor Sie rechnen, und vergleichen Sie mit '0' === $value, wenn die Null eine Bedeutung hat.
Wie man prüft, ob ein Custom Field existiert
Ein leerer String aus get_post_meta() verrät nicht, ob der Schlüssel leer gespeichert oder nie gespeichert wurde. metadata_exists() (seit WordPress 3.3) beantwortet genau diese Frage und liest denselben Meta-Cache, erzeugt also keine zusätzliche Abfrage, wenn die Meta des Beitrags schon geladen ist:
if ( metadata_exists( 'post', $post_id, 'wppoland_sku' ) ) {
$sku = get_post_meta( $post_id, 'wppoland_sku', true );
// Der Schlüssel existiert, auch wenn der Wert ein leerer String ist.
}Die Funktion durchläuft zuerst den Filter get_post_metadata, wie der Quelltext in meta.php zeigt. Ein Plugin, das Meta-Lesezugriffe abfängt, kann die Antwort also ändern. Nutzen Sie die Funktion, wenn „leer“ und „nicht gesetzt“ unterschiedlich behandelt werden müssen, etwa wenn die Redaktion ein Feld bewusst leert und das Template nicht auf einen Standardwert zurückfallen soll.
get_post_meta() versus the_meta()
the_meta() ist seit WordPress 6.0.2 veraltet. Der Aufruf löst _deprecated_function() mit get_post_meta() als Ersatz aus, was bei aktivem WP_DEBUG einen Hinweis erzeugt. Die Funktion gibt im Loop eine fertige HTML-Liste (<ul class='post-meta'>) mit allen Custom Fields des aktuellen Beitrags aus. Sie maskiert Schlüssel und Werte mit esc_html() und überspringt geschützte Schlüssel mit Unterstrich, hat aber keinen Parameter für einen einzelnen Schlüssel und keine Trennung zwischen Daten und Markup. So steht es im Quelltext von post-template.php und in der Dokumentation zu the_meta(): Für kontrollierte Ausgabe im Theme nutzen Sie get_post_meta() (oder eigene Wrapper) und entscheiden selbst über HTML und Escape-Funktion.
<?php
// Veraltet: gibt alle nicht geschützten Meta-Keys als Liste aus
the_meta();
// Explizit und sicher
$interne_ref = get_post_meta( get_the_ID(), 'interne_referenz', true );
if ( $interne_ref ) {
echo '<p class="ref">' . esc_html( $interne_ref ) . '</p>';
}Reservieren Sie the_meta() für alte Themes, die sie noch aufrufen, oder für eine Debug-Ansicht auf Staging. Archive, Single-Templates für Custom Post Types und Karten-Partials sollten immer explizite Schlüssel lesen. So vermeiden Sie, dass interne Meta (ERP-IDs, Sync-Tokens, Redaktionsnotizen) ohne Prefix und ohne register_post_meta-Restriktionen auf der öffentlichen Seite landen.
Custom Fields in WordPress sicher ausgeben
Ein Grundsatz moderner Softwareentwicklung lautet: Vertraue niemals Daten aus der Datenbank ohne Validierung. Selbst Metadaten, die nur von Redakteuren gepflegt werden, müssen bei der Ausgabe stets kontextbezogen maskiert werden, um Cross-Site-Scripting-Angriffe (XSS) zu verhindern.
Die richtigen Escaping-Funktionen in PHP
Der Kontext bestimmt die Funktion. Sichtbarer Text - esc_html. HTML-Attribut - esc_attr. URL - esc_url. Vertrauenswürdiges redaktionelles HTML - wp_kses_post. Meta aus Front-End-Formularen (Anfrage, Bewerbung, Upload) behandeln Sie immer als unzuverlässig, auch wenn die Oberfläche nur intern genutzt wird.
<?php
$sku = get_post_meta( get_the_ID(), 'artikel_sku', true );
$datenblatt_url = get_post_meta( get_the_ID(), 'dokument_download_url', true );
$beschreibung = get_post_meta( get_the_ID(), 'produkt_kurzbeschreibung', true );
?>
<div class="produkt-metadaten">
<span class="sku-etikett">
<?php echo esc_html( $sku ); ?>
</span>
<?php if ( ! empty( $datenblatt_url ) ) : ?>
<a href="<?php echo esc_url( $datenblatt_url ); ?>" class="button-download" target="_blank" rel="noopener noreferrer">
Datenblatt herunterladen
</a>
<?php endif; ?>
<div class="produkt-auszug">
<?php echo wp_kses_post( $beschreibung ); ?>
</div>
</div>Beim Speichern greifen sanitize_text_field, absint, sanitize_email oder ein eigener Callback in register_post_meta. Verlassen Sie sich nicht darauf, dass eine Meta-Box „nur im Admin“ existiert - REST-API und Bulk-Edit schreiben ebenfalls. Ein Feld „interne Notizen“ ohne Escape im Frontend ist in Audits ein klassischer XSS-Vektor.
Eigene Meta-Boxen im WordPress-Admin registrieren
Wenn Sie ohne Drittanbieter-Plugins saubere Eingabemasken für Autoren bereitstellen möchten, ist die Kombination aus add_meta_box und dem Hook save_post der kanonische Weg:
<?php
declare(strict_types=1);
/**
* Registriert eine benutzerdefinierte Meta-Box im Beitragseditor.
*/
function wppoland_add_buch_meta_box(): void {
add_meta_box(
'wppoland_buch_daten',
__( 'Buch-Spezifikationen', 'wppoland' ),
'wppoland_render_buch_meta_box',
'post',
'side',
'default'
);
}
add_action( 'add_meta_boxes', 'wppoland_add_buch_meta_box' );
/**
* Rendert die Eingabefelder und ein Sicherheits-Nonce.
*/
function wppoland_render_buch_meta_box( WP_Post $post ): void {
wp_nonce_field( 'wppoland_save_buch_data', 'wppoland_buch_nonce' );
$isbn = get_post_meta( $post->ID, '_buch_isbn', true );
?>
<p>
<label for="wppoland_isbn_field"><?php esc_html_e( 'ISBN-Nummer:', 'wppoland' ); ?></label>
<input type="text" id="wppoland_isbn_field" name="wppoland_buch_isbn" value="<?php echo esc_attr( $isbn ); ?>" class="widefat" />
</p>
<?php
}
/**
* Speichert und bereinigt die eingegebenen Daten sicher.
*/
function wppoland_save_buch_meta( int $post_id ): void {
// 1. Nonce prüfen
if ( ! isset( $_POST['wppoland_buch_nonce'] ) || ! wp_verify_nonce( $_POST['wppoland_buch_nonce'], 'wppoland_save_buch_data' ) ) {
return;
}
// 2. Autosave und Berechtigungen prüfen
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
// 3. Sanitizing und Persistierung
if ( isset( $_POST['wppoland_buch_isbn'] ) ) {
$saubere_isbn = sanitize_text_field( wp_unslash( $_POST['wppoland_buch_isbn'] ) );
update_post_meta( $post_id, '_buch_isbn', $saubere_isbn );
}
}
add_action( 'save_post', 'wppoland_save_buch_meta' );Advanced Custom Fields: get_field() richtig einsetzen
ACF vereinfacht die Eingabeoberfläche und komplexe Feldtypen (Repeater, Flexible Content, Relationship). In vielen deutschsprachigen Redaktionen ist das ACF-Panel der Vertrag zwischen Entwickler und Redaktion: Der Code liest get_field(), die Content-Teams rühren kein PHP an.
<?php
$sku = get_field( 'wppoland_sku' );
$image = get_field( 'hero_bild' );
// Explizite Post-ID, z. B. außerhalb des Loops
$value = get_field( 'field_name', $post_id );Bei Beiträgen speichert ACF die Werte weiterhin in postmeta. meta_query-Abfragen referenzieren oft dieselben Schlüssel wie die native API. Bei großen Listen vermeiden Sie get_field in jeder Karte ohne Prefetch (update_meta_cache bzw. eine WP_Query, die Meta bereits lädt).
Wenn das Theme auch ohne ACF lauffähig bleiben soll, halten Sie eine Fallback-Schicht bereit:
<?php
$sku = function_exists( 'get_field' )
? get_field( 'wppoland_sku', $post_id )
: get_post_meta( $post_id, 'wppoland_sku', true );Bei Bildfeldern kann get_field je nach Return-Format ID, URL oder Array liefern. Stimmen Sie das Format mit dem Template ab, bevor Sie wp_get_attachment_image oder esc_url aufrufen. Die offizielle Referenz: get_field() in den ACF Docs.
Ist die Ausgabe von the_field() in ACF maskiert
Die Dokumentation zu get_field() verlangt, zurückgegebene Werte vor der Ausgabe mit einer passenden Funktion zu maskieren, weil ACF nicht weiß, wo Sie sie verwenden. Bei echo get_field() liegt das Escaping also bei Ihnen.
the_field() verhält sich seit ACF 6.2.7 anders. Laut ACF-Leitfaden zum HTML-Escaping läuft die Ausgabe des ACF-Shortcodes ab 6.2.5 durch wp_kses(), ab 6.2.7 gilt das auch für the_field() und the_sub_field(). Die Release Notes zum Sicherheitsupdate 6.2.5 weisen darauf hin, dass dabei unsicheres HTML wie Skripte oder iframes aus Textarea- und WYSIWYG-Feldern entfernt wird. Seit ACF 6.2.6 akzeptiert get_field() außerdem ein viertes Argument, $escape_html, das einen maskierten Wert liefert und $format_value = true voraussetzt.
<?php
// Von ACF maskiert (wp_kses) seit 6.2.7
the_field( 'event_zusammenfassung' );
// Nicht maskiert: Sie wählen die Funktion für den Kontext
echo esc_html( get_field( 'event_zusammenfassung' ) );
// Seit 6.2.6: maskierten Wert vom Feldtyp anfordern
echo get_field( 'event_zusammenfassung', false, true, true );Wenn nach einem ACF-Update ein Embed in einem WYSIWYG-Feld verschwunden ist, ist das meist die Ursache. Entscheiden Sie pro Feld, ob das HTML vertrauenswürdig ist, und geben Sie es mit wp_kses_post() oder einer eigenen Liste erlaubter Tags aus, statt das Escaping global abzuschalten.
Individuelle Felder im Block-Editor einblenden
Die native Box für Custom Fields gibt es auch im Block-Editor, sie ist aber standardmäßig ausgeblendet, solange sie noch nicht benutzt wurde. Die WordPress.org-Dokumentation zu Custom Fields beschreibt den Weg. Mit deutscher Oberfläche stammen die Bezeichnungen aus dem offiziellen Sprachpaket: Beitrag zuerst speichern, das Menü Optionen öffnen (die drei Punkte in der oberen Werkzeugleiste), Voreinstellungen wählen, den Tab Allgemein öffnen, im Abschnitt Erweitert die Option Individuelle Felder aktivieren und auf Anzeigen und Seite neu laden klicken. Die Box erscheint unter dem Inhalt; mit Neu eingeben und Individuelles Feld hinzufügen legen Sie Schlüssel und Wert an.
Zwei Dinge blenden Felder in dieser Box aus. Schlüssel mit führendem Unterstrich sind geschützt und erscheinen nie in der Liste. Und ACF entfernt die Box standardmäßig: Die ACF-Referenz der Einstellungen führt remove_wp_meta_box (seit ACF 5.6.0, Standard true). So holen Sie die native Box bei aktivem ACF zurück:
<?php
add_filter( 'acf/settings/remove_wp_meta_box', '__return_false' );Das hilft beim Debuggen von Importen auf Staging. In Produktion bearbeiten Redakteure, die Rohschlüssel neben ACF-Feldern sehen, denselben Wert gern an zwei Stellen, deshalb bleibt die Box dort aus.
Warum meta_query WordPress langsam macht
Die Klasse WP_Query bietet den Parameter meta_query, um Beiträge anhand ihrer Metadaten zu filtern:
// ACHTUNG: Skaliert schlecht bei vielen Beiträgen!
$query = new WP_Query( [
'post_type' => 'produkt',
'meta_query' => [
[
'key' => 'produkt_kategorie_code',
'value' => 'ELEKTRONIK',
'compare' => '=',
],
],
] );Warum meta_query Full Table Scans auslöst
In MySQL besitzt die Tabelle wp_postmeta keinen kombinierten Index über meta_key und meta_value. Da die Spalte meta_value als longtext definiert ist, kann MySQL keine vollen Index-Scans durchführen.
Jede zusätzliche Bedingung in einer meta_query erfordert einen separaten SQL-JOIN auf die Tabelle wp_postmeta. Bei Websites mit Hunderttausenden von Zeilen führt dies zu massiven Festplatten-Lesezugriffen (Full Table Scans) und explodierenden Antwortzeiten.
meta_query Performance in WordPress verbessern
- Taxonomien für Klassifizierungen nutzen: Wenn Sie Inhalte kategorisieren, filtern oder zusammenfassen wollen, erstellen Sie stets eine Custom Taxonomy. Die Tabellen
wp_term_taxonomyundwp_term_relationshipssind exakt für relationale Filterungen optimiert. - Post Meta nur für Einzeldaten verwenden: Nutzen Sie Custom Fields primär für Attribute, die direkt auf der Einzelansicht dargestellt werden und nicht als universeller Suchfilter dienen.
- Persistente Caching-Schichten: Verwenden Sie Redis Object Cache, um häufig gelesene Meta-Objekte im Arbeitsspeicher vorzuhalten.
Object Cache und update_meta_cache
Bei einer normalen WP_Query ruft WordPress update_meta_cache() auf und lädt die Meta der gefundenen Posts in einem Rutsch. Mehrere get_post_meta()-Aufrufe auf dieselbe ID im selben Request erzeugen keine zusätzlichen SELECTs - sofern der Object Cache funktioniert (Redis oder Memcached im Hosting, sonst zumindest der In-Memory-Cache des Requests).
Eigene SELECTs auf wp_postmeta im Template-Loop sind deshalb fast immer unnötig und gefährlich: Sie umgehen den Cache und multiplizieren die Query-Last mit der Anzahl der Karten. Wenn Sie Meta vorab brauchen, holen Sie IDs über die Query und lassen den Kern den Cache befüllen, statt manuell pro Zeile zu selektieren.
Post Meta im Block-Theme mit render_block ausgeben
In Block-Themes (Full Site Editing) wandert viel Darstellung aus dem klassischen single.php in HTML-Templates und den Filter render_block. Meta wird weiterhin in PHP gelesen; geändert hat sich der Injektionspunkt.
<?php
add_filter( 'render_block', 'wppoland_sku_nach_titel', 10, 2 );
function wppoland_sku_nach_titel( string $block_content, array $block ): string {
if ( ( $block['blockName'] ?? '' ) !== 'core/post-title' ) {
return $block_content;
}
$sku = get_post_meta( get_the_ID(), 'wppoland_sku', true );
if ( ! $sku ) {
return $block_content;
}
$extra = '<p class="sku">' . esc_html( $sku ) . '</p>';
return $block_content . $extra;
}Sauberer, wenn die Meta mit show_in_rest registriert ist: Block Bindings (ab WordPress 6.5) koppeln Core-Block-Attribute an Post Meta ohne Ad-hoc-Filter. Bindings ersetzen aber kein serverseitiges Escaping für HTML, das Sie selbst schreiben. Details zum Hook: render_block.
Post Meta mit register_post_meta() und Block Bindings nutzen
Mit der Weiterentwicklung von WordPress zu modernen Block-Themes und headless Architekturen reicht das bloße Lesen per PHP nicht mehr aus. Oft müssen benutzerdefinierte Felder im Gutenberg-Editor verfügbar sein oder direkt über die REST-API und das Block-Bindings-System an HTML-Blöcke gekoppelt werden.
Custom Field mit register_post_meta() registrieren
Die Funktion register_post_meta() registriert ein Metadatenfeld formal im WordPress-Kern. Dadurch weiß WordPress, welchen Datentyp das Feld besitzt, wie es validiert wird und ob es in der REST-API exponiert werden soll:
<?php
declare(strict_types=1);
/**
* Registriert Metadaten für die REST-API und den Block-Editor.
*/
function wppoland_register_produkt_meta(): void {
register_post_meta( 'post', 'produkt_sku', [
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'auth_callback' => function() {
return current_user_can( 'edit_posts' );
},
'sanitize_callback' => 'sanitize_text_field',
] );
register_post_meta( 'post', 'produkt_technische_daten', [
'show_in_rest' => [
'schema' => [
'type' => 'object',
'properties' => [
'gewicht' => [ 'type' => 'number' ],
'material'=> [ 'type' => 'string' ],
],
],
],
'single' => true,
'type' => 'object',
'auth_callback' => function() {
return current_user_can( 'edit_posts' );
},
] );
}
add_action( 'init', 'wppoland_register_produkt_meta' );Ohne show_in_rest sieht das Block-JavaScript das Feld nicht. Ohne auth_callback öffnen Sie Schreibrechte möglicherweise weiter als beabsichtigt. Der type muss zu den realen Daten passen - number versus String im JSON erzeugt stille Bugs in der Sidebar. Für zusammengesetzte Felder (Objekte, Listen) konfigurieren Sie show_in_rest mit Schema; sonst bekommt Gutenberg einen flachen String und „verliert“ die Struktur. Sensible Werte (Gateway-Tokens, interne Abrechnungs-IDs) bleiben mit show_in_rest ausgeschaltet - das Theme kann sie in PHP beim Render lesen, ohne sie unter /wp-json/ zu publizieren.
Sobald 'show_in_rest' => true gesetzt ist, taucht das Feld im REST-API-Endpunkt unter /wp/v2/posts/{id} im Schlüssel meta auf:
{
"id": 128,
"title": { "rendered": "Industrie-Sensor X100" },
"meta": {
"produkt_sku": "SKU-99482-DE",
"produkt_technische_daten": {
"gewicht": 14.5,
"material": "Edelstahl V4A"
}
}
}Custom Field per Block Bindings in Gutenberg anzeigen (ab WordPress 6.5)
Seit WordPress 6.5 können Sie registrierte Metadatenfelder direkt mit nativen Blöcken verbinden, ohne eine einzige Zeile React-Code schreiben zu müssen. Dies geschieht deklarativ in den Block-Attributen:
<!-- wp:paragraph {
"metadata":{
"bindings":{
"content":{
"source":"core/post-meta",
"args":{"key":"produkt_sku"}
}
}
}
} -->
<p>Platzhalter für SKU im Editor</p>
<!-- /wp:paragraph -->Der WordPress-Kern ersetzt den Inhalt des Absatzes beim Rendern automatisch durch den aktuellen Wert aus get_post_meta().
Wichtig ist der Schlüsselname. Die Quelle core/post-meta liefert in vier Fällen nichts: Der Schlüssel ist nicht mit show_in_rest registriert, der Schlüssel ist geschützt (er beginnt mit einem Unterstrich), der Beitrag ist nicht öffentlich und der Besucher darf ihn nicht lesen, oder der Beitrag ist passwortgeschützt. Ein Schlüssel wie _produkt_sku erscheint deshalb nie über ein Binding, auch wenn er registriert ist. Für alles, was Sie binden wollen, verwenden Sie Schlüssel ohne Unterstrich. Bindings ersetzen außerdem kein serverseitiges Escaping für HTML, das Sie selbst schreiben.
Welche Blöcke Block Bindings unterstützen
Die Liste der bindbaren Attribute steht im Kern und ist mit jeder Version gewachsen, wie die Release-Branches von wordpress-develop zeigen:
| WordPress-Version | Bindbare Blöcke und Attribute |
|---|---|
| 6.5 bis 6.8 | Absatz (content), Überschrift (content), Bild (id, url, title, alt), Button (url, text, linkTarget, rel) |
| 6.9 | zusätzlich Bild-caption, Beitragsdatum (datetime), Navigationslink und Untermenü (url) |
| 7.1 | zusätzlich Listenelement (content) |
Quellen: class-wp-block.php im Branch 6.5 und block-bindings.php im Branch 7.1.
Eigene Block-Bindings-Quelle registrieren
Wenn der Wert aufbereitet werden muss (formatierter Preis, Datum im Format der Website), registrieren Sie in PHP eine eigene Quelle mit register_block_bindings_source() im Hook init. Der Callback erhält die Binding-Argumente, die Block-Instanz und den Attributnamen, wie der Docblock in block-bindings.php beschreibt:
<?php
add_action( 'init', function () {
register_block_bindings_source( 'wppoland/formatted-price', [
'label' => __( 'Formatierter Preis', 'wppoland' ),
'uses_context' => [ 'postId' ],
'get_value_callback' => function ( array $source_args, $block_instance ) {
$post_id = $block_instance->context['postId'] ?? get_the_ID();
$raw = get_post_meta( $post_id, $source_args['key'] ?? '', true );
return '' === $raw ? null : number_format_i18n( (float) $raw, 2 );
},
] );
} );Im Block-Markup steht dann "source": "wppoland/formatted-price" mit dem Meta-Schlüssel in args. Der Quellname braucht ein Namespace-Präfix wie wppoland/ und darf nur Kleinbuchstaben, Ziffern, Bindestriche und Schrägstriche enthalten.
Warum Custom Fields bei einem Custom Post Type nicht in der REST-API erscheinen
Bei einem Custom Post Type gibt es eine weitere Bedingung, die im Docblock von register_meta() steht: Der Post Type muss Support für Custom Fields deklarieren, sonst fehlt die registrierte Meta in der REST-Antwort.
<?php
register_post_type( 'veranstaltung', [
'public' => true,
'show_in_rest' => true,
'supports' => [ 'title', 'editor', 'custom-fields' ],
] );
register_post_meta( 'veranstaltung', 'wppoland_ort', [
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function () {
return current_user_can( 'edit_posts' );
},
] );Derselbe Docblock nennt zwei neuere Argumente. revisions_enabled (WordPress 6.4) speichert Meta mit den Beitragsrevisionen und funktioniert nur für den Objekttyp post. label (WordPress 6.7) gibt dem Schlüssel einen lesbaren Namen.
Häufige Fehler bei Custom Fields in WordPress
- Meta ohne
esc_html,esc_attroderesc_urlausgeben. meta_queryals Ersatz für Taxonomien auf großen Archiven.get_fieldin jeder Archiv-Karte ohne Batch-Meta / Cache.the_meta()in Produktion und dadurch interne Schlüssel auf der öffentlichen Seite.- Block bauen ohne
register_post_meta- „funktioniert in PHP, scheitert im Editor“. - Angenommen,
get_post_meta( ..., true )liefert immer einen String: nach Migrationen kann ein serialisiertes Array zurückkommen; voresc_htmlden Typ prüfen oder beim Speichern normalisieren. - Einen geschützten
_schluesselper Block Bindings anbinden und sich wundern, dass nichts erscheint. - Meta an einem Custom Post Type ohne
custom-fields-Support registrieren und sie dann in der REST-Antwort vermissen.
Meta-Schlüssel benennen und von ACF migrieren
Wenn Sie ein altes Theme übernehmen, inventarisieren Sie die Schlüssel: SELECT meta_key, COUNT(*) FROM wp_postmeta GROUP BY meta_key ORDER BY COUNT(*) DESC LIMIT 50. Sie sehen sofort, welche Präfixe zum Projekt gehören und welche von längst entfernten Plugins übrig sind. Ein Projektpräfix (wppoland_, kunde_) schützt vor Kollisionen mit ACF oder WooCommerce.
Bei der Migration von ACF zu nativem register_post_meta kopieren Sie Verweise wie field_xxxxx nicht blind. Behalten Sie einen lesbaren fachlichen Schlüssel und, falls nötig, ein einmaliges Skript, das alte IDs auf neue abbildet. Zuerst auf Staging: Vergleichen Sie die Zahl der Beiträge mit nicht leerer Meta vorher und nachher.
In Multisite arbeitet get_post_meta immer im aktuellen Blog. switch_to_blog um Meta-Lesezugriffe herum ohne restore_current_blog in einem finally-Block ist ein klassischer Weg, Daten einer anderen Website im Netzwerk zu vermischen.
Warum get_post_meta trotz single true ein Array liefert
get_post_meta( $id, $key, true ) kann ein Array liefern, wenn in der Datenbank ein serialisiertes PHP-Array aus einer alten Migration liegt. Code, der einen String erwartet, wirft Notices oder gibt das Wort „Array“ aus. Prüfen Sie vor esc_html den Typ oder normalisieren Sie beim Speichern.
Vermeiden Sie manuelles serialize() und unserialize() auf Benutzerdaten. WordPress serialisiert zusammengesetzte Meta-Werte selbst; Sie übergeben das Array an update_post_meta. Bei Integrationen mit externen Systemen bilden Sie Felder auf skalare Schlüssel ab statt auf einen einzigen Blob, dann bleibt meta_query später nutzbar.
Testen Sie das Speichern von Meta als Redakteur, nicht nur als Administrator. Eine Meta-Box oder ACF-Gruppe sollte einen Hilfetext in einem Satz tragen: woher der Wert kommt, ob er Pflicht ist und was das Frontend zeigt, wenn er leer ist. Im Template behandeln Sie den leeren Zustand bewusst (Abschnitt ausblenden oder Fallback zeigen), statt leere Tags zu rendern.
Checkliste für Custom Fields vor dem Livegang
- Jede Ausgabe nutzt
esc_html,esc_attr,esc_urloderwp_kses_postpassend zum Kontext. - Speicher-Handler prüfen Nonce und
current_user_can. - Meta für die REST-API hat
show_in_restundauth_callback, Custom Post Types deklarierencustom-fields. - Kein
ORDER BY meta_valueauf großen Archiven ohne Begründung und Index. - Alte Plugin-Schlüssel sind dokumentiert oder per Cleanup-Skript auf Staging getestet entfernt.
- Kein Produktions-Template ruft
the_meta()für öffentliche Inhalte auf. - Per Block Bindings gebundene Schlüssel haben keinen Unterstrich.
Unterschied zwischen get_post_meta() und get_field() von ACF
get_post_meta() ist die native Funktion des WordPress-Kerns. Sie liefert die reinen Rohdaten ohne Overhead. get_field() stammt aus dem Plugin Advanced Custom Fields und wendet automatische Formatierungen, Rückgabewerte (z. B. Bild-Objekte statt IDs) und Beziehungstransformationen an.
Arrays als Custom Field speichern
Ja. Wenn Sie ein PHP-Array an update_post_meta() übergeben, serialisiert WordPress die Daten automatisch (serialize()) und entpackt sie bei get_post_meta(..., true) wieder transparent. Beachten Sie jedoch, dass serialisierte Daten in SQL-Abfragen nicht gezielt durchsucht werden können.
Metadaten beim Löschen eines Beitrags
Ja. Wenn ein Beitrag über wp_delete_post() endgültig gelöscht wird, bereinigt WordPress alle verknüpften Zeilen in der Tabelle wp_postmeta automatisch.
Ein sauber strukturierter Umgang mit Custom Fields sichert nicht nur die Zukunftssicherheit Ihrer Website, sondern verhindert langsame Ladezeiten im redaktionellen Alltag. Wenn Sie komplexe Datenmodelle, Headless-APIs oder High-Performance-Architekturen planen, stehe ich Ihnen als Experte für WordPress-Entwicklung und Metadaten-Architektur beratend zur Seite.







