Wer schon einmal ein JSON-LD Schema.org „von Hand“ in einen Artikel eingefügt hat, kennt das eigentliche Problem: Nach 30 Veröffentlichungen ist es inkonsistent, unvollständig, und niemand traut sich, es zu pflegen.

Der Bedarf / Der Anwendungsfall

Strukturierte Daten (JSON-LD) helfen Google und anderen Suchmaschinen, Inhalte genau zu verstehen: Artikeltyp, Autor, Veröffentlichungsdatum, Hauptbild, "Über uns"-Eintrag, FAQ usw. WordPressSie verfügen bereits über viele verlässliche Informationen (Titel, Auszug, Titelbild). Die Lücke besteht in der „Bedeutung“: Themen, Entitäten, Absichten und manchmal das Recht tippe Schema.org (Artikel vs. TechArtikel vs. Nachrichtenartikel usw.).

KI ist hier nützlich, um ein Ergebnis zu erzeugen semantische Schicht basierend auf dem Inhalt, ohne Ob Sie nun 10 Minuten pro Artikel für die Auswahl von Schlüsselwörtern, Entitäten oder „Über uns“-Abschnitten aufwenden – meiner Erfahrung nach ist dies besonders gewinnbringend bei:

  • Blog-Techniken (WordPress, dev, data): KI extrahiert Technologien, Versionen und Konzepte gut.
  • Redaktionelle Websites bei vielen Bearbeitern: Standardisierung der Auszeichnungssprache ohne Schulung aller auf Schema.org.
  • „Inhalte“ von E-Commerce-Websites (Ratgeber, Vergleiche): Artikel anreichern, ohne die Produktbeschreibungen zu verändern.

Am Ende werden Sie wissen, wie man ein/eine Plugin (WordPress 6.9.4 / PHP 8.1+ kompatibel) welche:

  • generiert über eine KI-API (Aufruf) ein JSON-LD Schema.org pro Artikel. wp_remote_post())
  • speichert die Antwort im Cache (Transients API)
  • Es wird nur bei Bedarf neu generiert (Veröffentlichung/Aktualisierung).
  • injiziert das JSON-LD in das <head> Vorderseite
  • Es behandelt Fehler (Zeitüberschreitungen, Kontingentüberschreitungen, ungültiges JSON) mit sauberen Fallback-Lösungen.

Kurze Zusammenfassung

  • Wir generieren ein JSON-LD per Post über KI, dann wir Geschäfte in Post-Meta (und wir fügen ein vorübergehend (zusätzlich zur Vermeidung wiederholter Anrufe).
  • Der API-Schlüssel befindet sich in wp-config.php define(), niemals in einer dauerhaften Form.
  • Wir nennen die KI-API mit wp_remote_post() + Timeout + Fehlerbehandlung.
  • Wir zwingen die KI, eine Antwort zurückzusenden. Strenges JSON (und wir validieren es auf der PHP-Seite).
  • Wir injizieren das JSON-LD-Skript über wp_head (Vorderseite) und wir vermeiden den Verwaltungsaufwand.
  • Wir fügen einen REST-Endpunkt nur für Administratoren hinzu für regenerieren auf Anfrage (praktisch in der Qualitätssicherung).

Wann sollte man KI dafür einsetzen?

Setzen Sie KI ein, wenn Sie tatsächlich eine semantische Anreicherung benötigen und nicht nur, um „überall ‚Artikel‘ einzufügen“. Gute Anwendungsfälle:

  • Langer Inhalt (1000+ Wörter) wobei die Extraktion von Entitäten (Marken, Werkzeuge, Konzepte) Präzision bringt.
  • Unvollständige Taxonomien (unzuverlässige Kategorien/Tags) und Sie wünschen sich übersichtlichere „Über uns/Erwähnungen“-Felder.
  • Team schreiben bei heterogenen Stilen: KI normalisiert.
  • SEO-Migration (Neues Design, neues SEO-Plugin): Sie können ein konsistentes Schema generieren, ohne Beiträge neu schreiben zu müssen.

Ich habe oft einen Vorteil auf Seiten gesehen, auf denen der WordPress-Snippet leer ist und die Autoren häufig wechseln: KI erzeugt eine konsistente Beschreibung, die den zufälligen „Snippet“ vermeidet.

Wann man KI NICHT einsetzen sollte

Verzichten Sie auf KI, wenn Ihr System rein mechanisch und bereits deterministisch ist.

  • Schaufenster Bei 10 Seiten: entweder manuell oder mithilfe eines SEO-Plugins.
  • Einfache Diagramme (Organisation, Website, BreadcrumbList) werden bereits von Ihrem SEO-Plugin verwaltet.
  • Sensibler Inhalt (Gesundheit, Recht) wenn man sich auf KI verlässt, um Eigenschaften zu „erfinden“. Hierbei muss KI extraktiv bleiben, nicht kreativ.
  • Knappes Budget und eine große Anzahl von Beiträgen, die täglich veröffentlicht werden: Die API-Kosten können steigen, wenn Sie zu oft neu generieren.

Ein klassisches Anti-Pattern ist der Aufruf der KI bei jedem Seitenaufruf. Dies führt zu Timeouts, unnötigen Kosten und, bei schlecht geschütztem Code, mitunter zu leeren Seiten.

Voraussetzungen

Zielumgebung: WordPress 6.9.4 (April 2026) und PHP 8.1+.

API-Schlüssel und Speicher

Sie können OpenAI, Anthropic, Mistral oder Google verwenden. Ich werde ein Beispiel mit OpenAI (API-Antworten) bereitstellen, da es im strikten JSON-Bereich sehr stabil ist. Die Struktur des Plugins ermöglicht jedoch einen einfachen Austausch des Anbieters.

Speichern Sie den Schlüssel in wp-config.php (oder besser noch, eine Umgebungsvariable, die von Ihrem Hosting-Anbieter eingefügt wird). Beispiel:

/**
 * Clé API IA (ne jamais commiter ce fichier).
 * Idéalement, utilisez une variable d'environnement et fallback sur define().
 */
define('BPCAB_AI_OPENAI_API_KEY', 'REMPPLACEZ-MOI');

PHP-Erweiterungen

  • cURL (oft aktiviert) oder allow_url_fopen (WordPress verwendet Requests, welches auf cURL angewiesen ist, falls verfügbar).
  • JSON (Standard).

Nützliche offizielle Quellen

Lösungsarchitektur

Vom Plugin verwendeter Textstrom:

WordPress-Editor (save_post) → Datenaufbereitung (Titel, Inhalt, Auszug, Bild, Autor) → wp_remote_post() an die AI-API → JSON-Antwort → Validierung/Bereinigung → Beitragsmetadaten + temporäre Speicherung → Frontend (wp_head) fügt Daten ein …

Warum dieser Workflow in der Produktion gut funktioniert

  • Generation zum Zeitpunkt der Rettung (oder auf Anfrage), nicht auf dem Display: Sie blockieren das Front-End-Rendering nicht, wenn die KI-API langsam ist.
  • Cache : Ein kurzer Übergangsvorgang verhindert, dass sich die Regenerierungsschleife wiederholt, wenn ein Redakteur 5 Mal auf „Aktualisieren“ klickt.
  • Post-Meta : persistent, exportierbar und versionierbar (falls Sie über ein Staging-System verfügen).
  • JSON-Validierung Falls die KI fehlerhaften Text oder JSON zurückgibt, wird nichts eingefügt (Fallback).

Wichtiger Hinweis: SEO-Plugins und Duplikate

Yoast, Rank Math, SEOPress usw. verwenden bereits JSON-LDs. Wenn Sie eigene hinzufügen, riskieren Sie Folgendes:

  • Duplikate (zwei Article)
  • Unstimmigkeiten (zwei Autoren, zwei Bilder)

Die von mir empfohlene Strategie: injizieren Sie ein „komplementäres“ Schema (z.B. about, mentions, keywords, audience) in einem einzigen Article dass Sie die Kontrolle darüber haben, oder erzeugen Sie andernfalls eine @graph sauber. Der folgende Code generiert ein @graph Minimal und vermeidet eine „Neuerfindung“ der Organisation/Website.

Der vollständige Code – Schritt für Schritt

Ich rate Ihnen, es hineinzulegen Mu-Plugin Wenn es auch bei Themenänderungen und versehentlicher Deaktivierung erhalten bleiben soll. Ansonsten ein Standard-Plugin.

Schritt 1 – Minimale Plugin-Struktur

Erstellen Sie eine Datei: wp-content/mu-plugins/bpcab-ai-schema.php (Erstellen Sie den Ordner gegebenenfalls).

<?php
/**
 * Plugin Name: BPCAB AI Schema (JSON-LD)
 * Description: Génère et injecte des données structurées Schema.org via IA par article.
 * Version: 1.0.0
 * Requires at least: 6.9
 * Requires PHP: 8.1
 *
 * Conseil : placez ce fichier en mu-plugin pour éviter la désactivation accidentelle.
 */

if (!defined('ABSPATH')) {
	exit;
}

Schritt 2 – Konstanten, Optionen und Schutzmaßnahmen

Wir sichern das von Anfang an ab: Existiert der Schlüssel nicht, wird gar nichts unternommen. Ich habe schon oft erlebt, dass Websites wiederholt 401-Fehler zurückgaben, weil der Code trotz fehlendem Schlüssel immer wieder versuchte, darauf zuzugreifen.

/**
 * Retourne la clé API OpenAI depuis wp-config.php.
 */
function bpcab_ai_schema_get_openai_key(): string {
	if (defined('BPCAB_AI_OPENAI_API_KEY') && is_string(BPCAB_AI_OPENAI_API_KEY) && BPCAB_AI_OPENAI_API_KEY !== '') {
		return BPCAB_AI_OPENAI_API_KEY;
	}
	return '';
}

/**
 * Petite liste de post types autorisés.
 * Ajustez selon votre site (ex: 'post', 'page', 'guide', etc.).
 */
function bpcab_ai_schema_allowed_post_types(): array {
	return array('post');
}

Schritt 3 – Extrahieren von „zuverlässigen“ WordPress-Daten

KI sollte keine Datumsangaben, Autoren oder URLs erfinden. Wir übernehmen diese von WordPress und bitten die KI anschließend lediglich um semantische Anreicherung.

/**
 * Construit un paquet de données "source of truth" depuis WordPress.
 * On évite d'envoyer des données inutiles (coût + confidentialité).
 */
function bpcab_ai_schema_build_post_payload(int $post_id): array {
	$post = get_post($post_id);
	if (!$post) {
		return array();
	}

	$title   = get_the_title($post);
	$content = $post->post_content;

	// Option : limiter la taille envoyée à l'API (coût + latence).
	// Ici, on garde le contenu brut, mais vous pouvez préférer wp_strip_all_tags().
	$content_plain = wp_strip_all_tags($content);
	$content_plain = mb_substr($content_plain, 0, 12000); // garde-fou

	$excerpt = has_excerpt($post) ? $post->post_excerpt : wp_trim_words($content_plain, 55, '…');

	$author_id = (int) $post->post_author;
	$author_name = $author_id ? get_the_author_meta('display_name', $author_id) : '';

	$permalink = get_permalink($post);
	$published = get_the_date(DATE_W3C, $post);
	$modified  = get_the_modified_date(DATE_W3C, $post);

	$image_id = get_post_thumbnail_id($post);
	$image_url = '';
	if ($image_id) {
		$image = wp_get_attachment_image_src($image_id, 'full');
		if (is_array($image) && !empty($image[0])) {
			$image_url = $image[0];
		}
	}

	return array(
		'post_id'      => $post_id,
		'post_type'    => $post->post_type,
		'title'        => $title,
		'excerpt'      => $excerpt,
		'content'      => $content_plain,
		'permalink'    => $permalink,
		'datePublished'=> $published,
		'dateModified' => $modified,
		'authorName'   => $author_name,
		'image'        => $image_url,
		'language'     => get_bloginfo('language'),
	);
}

Schritt 4 – KI-Aufforderung „strenges JSON“ + API-Aufruf über wp_remote_post()

Das Problem, das die meisten Implementierungen zum Scheitern bringt: Die KI gibt Text um das JSON herum oder nicht konforme Felder zurück. Wir erzwingen ein striktes Format und validieren es anschließend.

Beispiel mit OpenAI (Antwortendpunkt). Offizielle API-Referenz: OpenAI Responses API.

/**
 * Appelle OpenAI pour générer un JSON Schema.org (ou un fragment) basé sur le contenu.
 * Retourne un tableau PHP (décodé) ou WP_Error.
 */
function bpcab_ai_schema_call_openai(array $payload) {
	$api_key = bpcab_ai_schema_get_openai_key();
	if ($api_key === '') {
		return new WP_Error('bpcab_no_api_key', 'Clé API OpenAI manquante (BPCAB_AI_OPENAI_API_KEY).');
	}

	// Prompt : on demande un JSON STRICT, sans texte.
	$system = "Vous êtes un assistant spécialisé en SEO technique. Vous produisez uniquement du JSON strict, sans commentaire ni markdown.";
	$user = array(
		"Objectif: Générer un JSON-LD Schema.org pour un article WordPress.n"
		. "Contraintes:n"
		. "- Répondre uniquement avec un objet JSON valide.n"
		. "- Ne pas inventer d'URL, de dates, d'auteur.n"
		. "- Utiliser EXACTEMENT les valeurs fournies pour headline, url, datePublished, dateModified, author.name, image.n"
		. "- Ajouter des champs sémantiques utiles: keywords (array), about (array of Thing), mentions (array of Thing), articleSection (string), inLanguage.n"
		. "- Type recommandé: Article (ou TechArticle si le texte est technique).n"
		. "- Produire un JSON-LD avec @context et @graph.n"
		. "- Limiter keywords à 12 max. about/mentions: 8 max chacun.n"
		. "- Ne pas inclure Organization/WebSite si vous n'avez pas les données.nn"
		. "Données fiables (à utiliser telles quelles):n"
		. wp_json_encode(array(
			"headline" => $payload['title'] ?? '',
			"description" => $payload['excerpt'] ?? '',
			"url" => $payload['permalink'] ?? '',
			"datePublished" => $payload['datePublished'] ?? '',
			"dateModified" => $payload['dateModified'] ?? '',
			"authorName" => $payload['authorName'] ?? '',
			"image" => $payload['image'] ?? '',
			"inLanguage" => $payload['language'] ?? 'fr-FR',
		)) . "nn"
		. "Contenu (extrait):n"
		. ($payload['content'] ?? '')
	);

	$body = array(
		'model' => 'gpt-4.1-mini',
		'input' => array(
			array('role' => 'system', 'content' => $system),
			array('role' => 'user', 'content' => $user),
		),
		// Paramètres prudents : on veut du factuel, pas de créativité.
		'temperature' => 0.2,
		'max_output_tokens' => 900,
		// Demande explicite de sortie JSON. Selon l'API, ce champ peut évoluer.
		// Si OpenAI change, gardez la validation JSON côté PHP comme filet de sécurité.
		'text' => array('format' => array('type' => 'json_object')),
	);

	$args = array(
		'headers' => array(
			'Authorization' => 'Bearer ' . $api_key,
			'Content-Type'  => 'application/json',
		),
		'body' => wp_json_encode($body),
		'timeout' => 20, // évitez 60s : en front, c'est mort. Ici on est en save_post, mais restons raisonnables.
	);

	$response = wp_remote_post('https://api.openai.com/v1/responses', $args);

	if (is_wp_error($response)) {
		return $response;
	}

	$code = (int) wp_remote_retrieve_response_code($response);
	$raw  = wp_remote_retrieve_body($response);

	if ($code < 200 || $code >= 300) {
		return new WP_Error('bpcab_openai_http_error', 'Erreur HTTP OpenAI: ' . $code, array('body' => $raw));
	}

	$data = json_decode($raw, true);
	if (!is_array($data)) {
		return new WP_Error('bpcab_openai_bad_json', 'Réponse OpenAI non JSON (impossible à décoder).', array('body' => $raw));
	}

	// Selon le format de Responses API, le texte peut être dans output[...].
	// On essaie d'extraire un bloc texte puis de décoder ce JSON.
	$json_text = '';

	// Extraction robuste (évite de dépendre d'un seul chemin).
	if (!empty($data['output']) && is_array($data['output'])) {
		foreach ($data['output'] as $item) {
			if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
				continue;
			}
			foreach ($item['content'] as $content_item) {
				if (is_array($content_item) && ($content_item['type'] ?? '') === 'output_text' && isset($content_item['text'])) {
					$json_text .= $content_item['text'];
				}
			}
		}
	}

	$json_text = trim($json_text);
	if ($json_text === '') {
		// Fallback : parfois l'API peut renvoyer directement un champ text.
		if (isset($data['text']) && is_string($data['text'])) {
			$json_text = trim($data['text']);
		}
	}

	if ($json_text === '') {
		return new WP_Error('bpcab_openai_empty_output', 'Sortie OpenAI vide ou non trouvée.', array('body' => $raw));
	}

	$schema = json_decode($json_text, true);
	if (!is_array($schema)) {
		return new WP_Error('bpcab_schema_not_json', 'Le contenu généré n’est pas un JSON valide.', array('generated' => $json_text));
	}

	return $schema;
}

Schritt 5 – JSON-LD-Validierung und -Bereinigung

JSON wird nicht wie HTML „bereinigt“. Der richtige Ansatz besteht darin, die minimale Struktur zu validieren, alles Gefährliche (Skripte) zu entfernen und es zur Anzeigezeit korrekt zu kodieren.

Häufige Fehlerquelle: Verwendung wp_kses_post() bei einer JSON-Datei. Dadurch werden die Anführungszeichen aufgehoben und das JSON wird ungültig. Hier validieren wir es als Array, dann wp_json_encode().

/**
 * Validation minimale du schéma.
 * On vérifie @context et @graph. On peut être plus strict selon vos besoins.
 */
function bpcab_ai_schema_validate(array $schema) {
	if (!isset($schema['@context']) || !is_string($schema['@context'])) {
		return new WP_Error('bpcab_schema_missing_context', 'Schema invalide: @context manquant.');
	}
	if (!isset($schema['@graph']) || !is_array($schema['@graph'])) {
		return new WP_Error('bpcab_schema_missing_graph', 'Schema invalide: @graph manquant.');
	}

	// Protection basique : on refuse toute tentative d'injection de balises.
	$encoded = wp_json_encode($schema);
	if ($encoded === false) {
		return new WP_Error('bpcab_schema_encode_failed', 'Impossible d’encoder le schéma en JSON.');
	}
	if (stripos($encoded, '<script') !== false || stripos($encoded, '</script') !== false) {
		return new WP_Error('bpcab_schema_script_detected', 'Contenu suspect détecté dans le schéma.');
	}

	return true;
}

/**
 * Nettoyage "pragmatique" : on limite certaines longueurs et on force des types.
 */
function bpcab_ai_schema_normalize(array $schema): array {
	// Limite de taille pour éviter un JSON-LD énorme (performance + crawl).
	$max_graph_items = 12;
	if (isset($schema['@graph']) && is_array($schema['@graph']) && count($schema['@graph']) > $max_graph_items) {
		$schema['@graph'] = array_slice($schema['@graph'], 0, $max_graph_items);
	}

	return $schema;
}

Schritt 6 – temporärer Cache + Post-Meta-Speicher

Wir kombinieren zwei Ebenen:

  • Post-Meta (persistent) für Frontdisplay
  • vorübergehend (kurz) um eine zu schnelle Regeneration zu vermeiden
/**
 * Clés de stockage.
 */
function bpcab_ai_schema_meta_key(): string {
	return '_bpcab_ai_schema_jsonld';
}
function bpcab_ai_schema_transient_key(int $post_id): string {
	return 'bpcab_ai_schema_lock_' . $post_id;
}

/**
 * Génère et stocke le schéma pour un post.
 */
function bpcab_ai_schema_generate_for_post(int $post_id) {
	$payload = bpcab_ai_schema_build_post_payload($post_id);
	if (empty($payload)) {
		return new WP_Error('bpcab_no_payload', 'Payload vide, post introuvable ?');
	}

	// Lock anti-boucle (ex: autosave + update en rafale).
	if (get_transient(bpcab_ai_schema_transient_key($post_id))) {
		return new WP_Error('bpcab_locked', 'Génération déjà en cours ou trop récente (lock transient).');
	}
	set_transient(bpcab_ai_schema_transient_key($post_id), 1, 2 * MINUTE_IN_SECONDS);

	$schema = bpcab_ai_schema_call_openai($payload);
	if (is_wp_error($schema)) {
		return $schema;
	}

	$valid = bpcab_ai_schema_validate($schema);
	if (is_wp_error($valid)) {
		return $valid;
	}

	$schema = bpcab_ai_schema_normalize($schema);

	// Stockage en post meta (tableau encodé JSON).
	$json = wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
	if ($json === false) {
		return new WP_Error('bpcab_encode_failed', 'Encodage JSON final impossible.');
	}

	update_post_meta($post_id, bpcab_ai_schema_meta_key(), $json);

	// On relâche le lock un peu plus tôt si tout s'est bien passé.
	delete_transient(bpcab_ai_schema_transient_key($post_id));

	return true;
}

Schritt 7 — save_post einbinden (ohne den Editor zu beschädigen)

Der falsche Hook oder die falsche Bedingung, und schon wird die KI bei automatischen Speichervorgängen, Revisionen oder Elementor-Vorschauen aufgerufen. Das sehe ich ständig.

/**
 * Déclenchement à la sauvegarde.
 */
function bpcab_ai_schema_on_save_post(int $post_id, WP_Post $post, bool $update): void {
	// Éviter autosave, révisions, et contexte non pertinent.
	if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
		return;
	}

	// Éviter l'exécution sur les types non autorisés.
	if (!in_array($post->post_type, bpcab_ai_schema_allowed_post_types(), true)) {
		return;
	}

	// Éviter les brouillons: souvent le contenu est incomplet.
	// Ajustez selon votre workflow.
	if ($post->post_status !== 'publish') {
		return;
	}

	// Option : ne régénérer que si le contenu/titre a changé.
	// Ici, on régénère à chaque update publié (simple et fiable).
	$result = bpcab_ai_schema_generate_for_post($post_id);

	// On log en debug uniquement.
	if (is_wp_error($result) && defined('WP_DEBUG') && WP_DEBUG) {
		error_log('[BPCAB AI Schema] save_post error: ' . $result->get_error_code() . ' - ' . $result->get_error_message());
	}
}
add_action('save_post', 'bpcab_ai_schema_on_save_post', 20, 3);

Schritt 8 – Einfügen des JSON-LD in wp_head

Wir injizieren nur am Frontend, auf den einzelnen Knoten, und nur dann, wenn der Meta-Knoten existiert. Hier finden keine KI-Aufrufe statt.

/**
 * Injecte le JSON-LD dans le head.
 */
function bpcab_ai_schema_print_jsonld(): void {
	if (is_admin()) {
		return;
	}
	if (!is_singular(bpcab_ai_schema_allowed_post_types())) {
		return;
	}

	$post_id = get_queried_object_id();
	if (!$post_id) {
		return;
	}

	$json = get_post_meta($post_id, bpcab_ai_schema_meta_key(), true);
	if (!is_string($json) || $json === '') {
		return;
	}

	// Vérification finale : JSON valide.
	$decoded = json_decode($json, true);
	if (!is_array($decoded)) {
		return;
	}

	// Encodage propre pour éviter les surprises.
	$out = wp_json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
	if ($out === false) {
		return;
	}

	echo "<script type="application/ld+json">n";
	echo $out;
	echo "n</script>n";
}
add_action('wp_head', 'bpcab_ai_schema_print_jsonld', 99);

Schritt 9 – REST-Endpunkt für die bedarfsgesteuerte Regeneration (nur für Administratoren)

Das ist besonders nützlich, wenn ein Redakteur meldet, dass das Schema nicht angezeigt wird, und man es neu generieren möchte, ohne den Beitrag erneut zu speichern (und ohne Zugriff auf den Code zu gewähren). Wir schützen es mit Berechtigungen und einer Nonce.

/**
 * Enregistre une route REST pour régénérer le schéma.
 */
function bpcab_ai_schema_register_rest_route(): void {
	register_rest_route('bpcab/v1', '/schema/regenerate/(?P<id>d+)', array(
		'methods' => 'POST',
		'permission_callback' => function (WP_REST_Request $request) {
			// Nonce REST standard: X-WP-Nonce (wp_create_nonce('wp_rest')).
			if (!is_user_logged_in()) {
				return false;
			}
			return current_user_can('edit_posts');
		},
		'callback' => function (WP_REST_Request $request) {
			$post_id = (int) $request['id'];
			if ($post_id <= 0) {
				return new WP_REST_Response(array('ok' => false, 'error' => 'ID invalide'), 400);
			}

			$post = get_post($post_id);
			if (!$post) {
				return new WP_REST_Response(array('ok' => false, 'error' => 'Post introuvable'), 404);
			}

			if (!current_user_can('edit_post', $post_id)) {
				return new WP_REST_Response(array('ok' => false, 'error' => 'Accès refusé'), 403);
			}

			$result = bpcab_ai_schema_generate_for_post($post_id);
			if (is_wp_error($result)) {
				return new WP_REST_Response(array(
					'ok' => false,
					'error' => $result->get_error_message(),
					'code' => $result->get_error_code(),
					'data' => $result->get_error_data(),
				), 500);
			}

			return new WP_REST_Response(array('ok' => true), 200);
		},
	));
}
add_action('rest_api_init', 'bpcab_ai_schema_register_rest_route');

Der vollständige kompilierte Code

Kopieren Sie diese Datei unverändert und fügen Sie sie ein in wp-content/mu-plugins/bpcab-ai-schema.phpFügen Sie dann die Konstante hinzu. wp-config.phpTesten Sie dies nicht zuerst in der Produktionsumgebung ohne vorherige Datensicherung: Eine vergessene Klammer und die Website ist offline. leerer Bildschirm.

<?php
/**
 * Plugin Name: BPCAB AI Schema (JSON-LD)
 * Description: Génère et injecte des données structurées Schema.org via IA par article.
 * Version: 1.0.0
 * Requires at least: 6.9
 * Requires PHP: 8.1
 */

if (!defined('ABSPATH')) {
	exit;
}

function bpcab_ai_schema_get_openai_key(): string {
	if (defined('BPCAB_AI_OPENAI_API_KEY') && is_string(BPCAB_AI_OPENAI_API_KEY) && BPCAB_AI_OPENAI_API_KEY !== '') {
		return BPCAB_AI_OPENAI_API_KEY;
	}
	return '';
}

function bpcab_ai_schema_allowed_post_types(): array {
	return array('post');
}

function bpcab_ai_schema_meta_key(): string {
	return '_bpcab_ai_schema_jsonld';
}

function bpcab_ai_schema_transient_key(int $post_id): string {
	return 'bpcab_ai_schema_lock_' . $post_id;
}

function bpcab_ai_schema_build_post_payload(int $post_id): array {
	$post = get_post($post_id);
	if (!$post) {
		return array();
	}

	$title   = get_the_title($post);
	$content = $post->post_content;

	$content_plain = wp_strip_all_tags($content);
	$content_plain = mb_substr($content_plain, 0, 12000);

	$excerpt = has_excerpt($post) ? $post->post_excerpt : wp_trim_words($content_plain, 55, '…');

	$author_id = (int) $post->post_author;
	$author_name = $author_id ? get_the_author_meta('display_name', $author_id) : '';

	$permalink = get_permalink($post);
	$published = get_the_date(DATE_W3C, $post);
	$modified  = get_the_modified_date(DATE_W3C, $post);

	$image_id = get_post_thumbnail_id($post);
	$image_url = '';
	if ($image_id) {
		$image = wp_get_attachment_image_src($image_id, 'full');
		if (is_array($image) && !empty($image[0])) {
			$image_url = $image[0];
		}
	}

	return array(
		'post_id'       => $post_id,
		'post_type'     => $post->post_type,
		'title'         => $title,
		'excerpt'       => $excerpt,
		'content'       => $content_plain,
		'permalink'     => $permalink,
		'datePublished' => $published,
		'dateModified'  => $modified,
		'authorName'    => $author_name,
		'image'         => $image_url,
		'language'      => get_bloginfo('language'),
	);
}

function bpcab_ai_schema_call_openai(array $payload) {
	$api_key = bpcab_ai_schema_get_openai_key();
	if ($api_key === '') {
		return new WP_Error('bpcab_no_api_key', 'Clé API OpenAI manquante (BPCAB_AI_OPENAI_API_KEY).');
	}

	$system = "Vous êtes un assistant spécialisé en SEO technique. Vous produisez uniquement du JSON strict, sans commentaire ni markdown.";
	$user = array(
		"Objectif: Générer un JSON-LD Schema.org pour un article WordPress.n"
		. "Contraintes:n"
		. "- Répondre uniquement avec un objet JSON valide.n"
		. "- Ne pas inventer d'URL, de dates, d'auteur.n"
		. "- Utiliser EXACTEMENT les valeurs fournies pour headline, url, datePublished, dateModified, author.name, image.n"
		. "- Ajouter des champs sémantiques utiles: keywords (array), about (array of Thing), mentions (array of Thing), articleSection (string), inLanguage.n"
		. "- Type recommandé: Article (ou TechArticle si le texte est technique).n"
		. "- Produire un JSON-LD avec @context et @graph.n"
		. "- Limiter keywords à 12 max. about/mentions: 8 max chacun.n"
		. "- Ne pas inclure Organization/WebSite si vous n'avez pas les données.nn"
		. "Données fiables (à utiliser telles quelles):n"
		. wp_json_encode(array(
			"headline" => $payload['title'] ?? '',
			"description" => $payload['excerpt'] ?? '',
			"url" => $payload['permalink'] ?? '',
			"datePublished" => $payload['datePublished'] ?? '',
			"dateModified" => $payload['dateModified'] ?? '',
			"authorName" => $payload['authorName'] ?? '',
			"image" => $payload['image'] ?? '',
			"inLanguage" => $payload['language'] ?? 'fr-FR',
		)) . "nn"
		. "Contenu (extrait):n"
		. ($payload['content'] ?? '')
	);

	$body = array(
		'model' => 'gpt-4.1-mini',
		'input' => array(
			array('role' => 'system', 'content' => $system),
			array('role' => 'user', 'content' => $user),
		),
		'temperature' => 0.2,
		'max_output_tokens' => 900,
		'text' => array('format' => array('type' => 'json_object')),
	);

	$args = array(
		'headers' => array(
			'Authorization' => 'Bearer ' . $api_key,
			'Content-Type'  => 'application/json',
		),
		'body' => wp_json_encode($body),
		'timeout' => 20,
	);

	$response = wp_remote_post('https://api.openai.com/v1/responses', $args);
	if (is_wp_error($response)) {
		return $response;
	}

	$code = (int) wp_remote_retrieve_response_code($response);
	$raw  = wp_remote_retrieve_body($response);

	if ($code < 200 || $code >= 300) {
		return new WP_Error('bpcab_openai_http_error', 'Erreur HTTP OpenAI: ' . $code, array('body' => $raw));
	}

	$data = json_decode($raw, true);
	if (!is_array($data)) {
		return new WP_Error('bpcab_openai_bad_json', 'Réponse OpenAI non JSON (impossible à décoder).', array('body' => $raw));
	}

	$json_text = '';
	if (!empty($data['output']) && is_array($data['output'])) {
		foreach ($data['output'] as $item) {
			if (!is_array($item) || empty($item['content']) || !is_array($item['content'])) {
				continue;
			}
			foreach ($item['content'] as $content_item) {
				if (is_array($content_item) && ($content_item['type'] ?? '') === 'output_text' && isset($content_item['text'])) {
					$json_text .= $content_item['text'];
				}
			}
		}
	}
	$json_text = trim($json_text);
	if ($json_text === '' && isset($data['text']) && is_string($data['text'])) {
		$json_text = trim($data['text']);
	}

	if ($json_text === '') {
		return new WP_Error('bpcab_openai_empty_output', 'Sortie OpenAI vide ou non trouvée.', array('body' => $raw));
	}

	$schema = json_decode($json_text, true);
	if (!is_array($schema)) {
		return new WP_Error('bpcab_schema_not_json', 'Le contenu généré n’est pas un JSON valide.', array('generated' => $json_text));
	}

	return $schema;
}

function bpcab_ai_schema_validate(array $schema) {
	if (!isset($schema['@context']) || !is_string($schema['@context'])) {
		return new WP_Error('bpcab_schema_missing_context', 'Schema invalide: @context manquant.');
	}
	if (!isset($schema['@graph']) || !is_array($schema['@graph'])) {
		return new WP_Error('bpcab_schema_missing_graph', 'Schema invalide: @graph manquant.');
	}

	$encoded = wp_json_encode($schema);
	if ($encoded === false) {
		return new WP_Error('bpcab_schema_encode_failed', 'Impossible d’encoder le schéma en JSON.');
	}
	if (stripos($encoded, '<script') !== false || stripos($encoded, '</script') !== false) {
		return new WP_Error('bpcab_schema_script_detected', 'Contenu suspect détecté dans le schéma.');
	}

	return true;
}

function bpcab_ai_schema_normalize(array $schema): array {
	$max_graph_items = 12;
	if (isset($schema['@graph']) && is_array($schema['@graph']) && count($schema['@graph']) > $max_graph_items) {
		$schema['@graph'] = array_slice($schema['@graph'], 0, $max_graph_items);
	}
	return $schema;
}

function bpcab_ai_schema_generate_for_post(int $post_id) {
	$payload = bpcab_ai_schema_build_post_payload($post_id);
	if (empty($payload)) {
		return new WP_Error('bpcab_no_payload', 'Payload vide, post introuvable ?');
	}

	if (get_transient(bpcab_ai_schema_transient_key($post_id))) {
		return new WP_Error('bpcab_locked', 'Génération déjà en cours ou trop récente (lock transient).');
	}
	set_transient(bpcab_ai_schema_transient_key($post_id), 1, 2 * MINUTE_IN_SECONDS);

	$schema = bpcab_ai_schema_call_openai($payload);
	if (is_wp_error($schema)) {
		return $schema;
	}

	$valid = bpcab_ai_schema_validate($schema);
	if (is_wp_error($valid)) {
		return $valid;
	}

	$schema = bpcab_ai_schema_normalize($schema);

	$json = wp_json_encode($schema, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
	if ($json === false) {
		return new WP_Error('bpcab_encode_failed', 'Encodage JSON final impossible.');
	}

	update_post_meta($post_id, bpcab_ai_schema_meta_key(), $json);
	delete_transient(bpcab_ai_schema_transient_key($post_id));

	return true;
}

function bpcab_ai_schema_on_save_post(int $post_id, WP_Post $post, bool $update): void {
	if (wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)) {
		return;
	}
	if (!in_array($post->post_type, bpcab_ai_schema_allowed_post_types(), true)) {
		return;
	}
	if ($post->post_status !== 'publish') {
		return;
	}

	$result = bpcab_ai_schema_generate_for_post($post_id);
	if (is_wp_error($result) && defined('WP_DEBUG') && WP_DEBUG) {
		error_log('[BPCAB AI Schema] save_post error: ' . $result->get_error_code() . ' - ' . $result->get_error_message());
	}
}
add_action('save_post', 'bpcab_ai_schema_on_save_post', 20, 3);

function bpcab_ai_schema_print_jsonld(): void {
	if (is_admin()) {
		return;
	}
	if (!is_singular(bpcab_ai_schema_allowed_post_types())) {
		return;
	}

	$post_id = get_queried_object_id();
	if (!$post_id) {
		return;
	}

	$json = get_post_meta($post_id, bpcab_ai_schema_meta_key(), true);
	if (!is_string($json) || $json === '') {
		return;
	}

	$decoded = json_decode($json, true);
	if (!is_array($decoded)) {
		return;
	}

	$out = wp_json_encode($decoded, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
	if ($out === false) {
		return;
	}

	echo "<script type="application/ld+json">n";
	echo $out;
	echo "n</script>n";
}
add_action('wp_head', 'bpcab_ai_schema_print_jsonld', 99);

function bpcab_ai_schema_register_rest_route(): void {
	register_rest_route('bpcab/v1', '/schema/regenerate/(?P<id>d+)', array(
		'methods' => 'POST',
		'permission_callback' => function (WP_REST_Request $request) {
			if (!is_user_logged_in()) {
				return false;
			}
			return current_user_can('edit_posts');
		},
		'callback' => function (WP_REST_Request $request) {
			$post_id = (int) $request['id'];
			if ($post_id <= 0) {
				return new WP_REST_Response(array('ok' => false, 'error' => 'ID invalide'), 400);
			}

			$post = get_post($post_id);
			if (!$post) {
				return new WP_REST_Response(array('ok' => false, 'error' => 'Post introuvable'), 404);
			}

			if (!current_user_can('edit_post', $post_id)) {
				return new WP_REST_Response(array('ok' => false, 'error' => 'Accès refusé'), 403);
			}

			$result = bpcab_ai_schema_generate_for_post($post_id);
			if (is_wp_error($result)) {
				return new WP_REST_Response(array(
					'ok' => false,
					'error' => $result->get_error_message(),
					'code' => $result->get_error_code(),
					'data' => $result->get_error_data(),
				), 500);
			}

			return new WP_REST_Response(array('ok' => true), 200);
		},
	));
}
add_action('rest_api_init', 'bpcab_ai_schema_register_rest_route');

Code-Erklärung

Warum in den Beitragsmetadaten speichern?

Die Post-Metadaten gewährleisten einen stabilen Zustand. Selbst wenn die KI-API ausfällt, wird Ihr JSON-LD weiterhin ausgeliefert. Und mit einem Seitencache (z. B. Varnish oder einem Cache-Plugin) lassen sich Schwankungen vermeiden.

Warum eine zusätzliche, vorübergehende "Sperre"?

Auf Websites, die Elementor oder Divi verwenden, kann eine „Aktualisieren“-Aktion mehrere Speichervorgänge auslösen (automatisches Speichern, Revision, Aktualisierung). Selbst wenn man nach automatischem Speichern/Revision filtert, habe ich doppelte Aufrufe durch Plugins beobachtet, die den Beitrag erneut speichern. Der temporäre Speicher verhindert diese doppelte Abrechnung.

Warum ist die Validierung absichtlich minimal?

Schema.org ist sehr umfangreich. Wenn man zu streng validiert, zerstört man nützliche Verbesserungen (z. B. about en Thing vs DefinedTermHier überprüfen wir lediglich die Invarianten (@context, @graph) und wir lehnen verdächtige Inhalte ab.

Warum verwenden wir wp_kses_post() nicht für JSON?

wp_kses_post() ist ein HTML-Filter. Bei Anwendung auf JSON werden Zeichen beschädigt und das JSON wird ungültig. Stattdessen verwenden wir ein PHP-Array, überprüfen dessen Struktur und kodieren es anschließend mit wp_json_encode().

Realistische Fehler, die ich oft sehe

  • Der Code wurde in functions.php eingefügt. Beim Aktualisieren eines übergeordneten Themes geht der Code verloren. Verwenden Sie das mu-Plugin.
  • Das Semikolon vergessen in wp-config.php Après define() → Sofortiger fataler Fehler.
  • Unangemessener Haken (ZB the_content) → KI-Aufruf zum Rendern → Latenz + Kosten.
  • Produktionstests ohne Einschränkung des Beitragstyps → werden 2000 Beiträge gleichzeitig über eine Speicherschleife neu generiert.

API-Kosten und Optimierung

Die Kosten hängen vom Modell und der Größe des gesendeten Inhalts ab. Bei einem Limit von 12.000 Zeichen (was oft 2000–3000 Wörtern ohne HTML entspricht) handelt es sich um eine moderate Anfrage.

Realistische Schätzung (Größenordnung)

  • 1 Artikel = 1 KI-Aufruf zur Veröffentlichung + 1 Aufruf pro wesentlichem Update.
  • Bei einer Veröffentlichung von 30 Artikeln pro Monat und einer durchschnittlichen Aktualisierung jedes Artikels 2 Mal: ​​~90 Anrufe pro Monat.

Genaue Preisinformationen finden Sie auf den offiziellen Seiten (Preisänderungen vorbehalten). OpenAI: OpenAI-Preise.

Optimierungen, die tatsächlich funktionieren

  • Reduziere den Input : Senden Sie den Auszug + die H2/H3-Überschriften anstelle des gesamten Inhalts (falls Ihr Inhalt sehr lang ist).
  • „Mini“-Modell : mehr als ausreichend, um Schlüsselwörter/Über uns/Erwähnungen zu extrahieren.
  • Bedingte Regeneration : Vergleiche einen Hash des Inhalts (Beitragsmetadaten) und generiere nur dann neu, wenn sich der Hash ändert.
  • Batch offline (WP-CLI) für Migrationen anstelle von Bulk save_post.

Erweiterte Varianten und Anwendungsfälle

Variante 1 — nur neu generieren, wenn sich der Inhalt (Hash) geändert hat

Um zu vermeiden, dass Gebühren anfallen, wenn jemand ein Komma im Titel korrigiert, sollte ein Hash gespeichert werden.

function bpcab_ai_schema_hash_meta_key(): string {
	return '_bpcab_ai_schema_content_hash';
}

function bpcab_ai_schema_should_regenerate(int $post_id, array $payload): bool {
	$hash = hash('sha256', ($payload['title'] ?? '') . '|' . ($payload['excerpt'] ?? '') . '|' . ($payload['content'] ?? ''));
	$old  = get_post_meta($post_id, bpcab_ai_schema_hash_meta_key(), true);

	if (!is_string($old) || $old === '') {
		update_post_meta($post_id, bpcab_ai_schema_hash_meta_key(), $hash);
		return true;
	}

	if (!hash_equals($old, $hash)) {
		update_post_meta($post_id, bpcab_ai_schema_hash_meta_key(), $hash);
		return true;
	}

	return false;
}

Variante 2 – Kompatibilität mit Divi 5 / Elementor / Avada

Diese Baukästen speichern die Inhalte oft in post_content mit internen Shortcodes/JSON. Wenn Sie dies unverändert an die KI senden, kann diese Artefakte extrahieren.

  • Divi 5 Manchmal sieht man auch innere Strukturen. wp_strip_all_tags() Hilfe, aber nicht immer.
  • Elementor Ein Teil des Inhalts befindet sich in den Metadaten (Elementor-Daten). Die endgültige Darstellung ist originalgetreuer als die Rohversion.
  • Avada Fusion Builder Shortcodes, gleiches Problem.

Zwei Ansätze:

  • „Sicherer“ Ansatz (empfohlen): Nur den sichtbaren Text extrahieren über the_content Filtern, dann Tags entfernen.
  • „Schneller“ Ansatz : halten post_content und den Lärm akzeptieren.

„Sichere“ Version (achten Sie darauf, dies nicht in einer Schleife über Listen auszuführen, sondern für save_post verwenden):

function bpcab_ai_schema_get_rendered_text(WP_Post $post): string {
	// Applique les filtres (shortcodes, blocs, builders) pour obtenir un HTML proche du front.
	$html = apply_filters('the_content', $post->post_content);

	// Supprime scripts/styles éventuels.
	$html = preg_replace('#<scriptb[^>]*>.*?</script>#is', '', $html ?? '');
	$html = preg_replace('#<styleb[^>]*>.*?</style>#is', '', $html ?? '');

	$text = wp_strip_all_tags($html);
	return mb_substr($text, 0, 12000);
}

Option 3 – FAQ-Seite hinzufügen, falls der Artikel einen FAQ-Bereich enthält

Wenn Ihre Artikel häufig mit „FAQ“ enden, kann KI Frage-Antwort-Paare erkennen. Seien Sie jedoch vorsichtig: Eine künstlich erstellte FAQ birgt SEO- und redaktionelle Risiken. Ich empfehle, eine FAQ nur dann zu generieren, wenn der Inhalt bereits explizite Fragen enthält.

Sie können der Eingabeaufforderung eine Einschränkung hinzufügen: „Nur Fragen extrahieren, die bereits wortwörtlich vorhanden sind.“

Sicherheit und bewährte Verfahren

Den Schlüssel niemals auf der Clientseite preisgeben.

Vermeiden Sie unbedingt JavaScript-Aufrufe vom Browser an die KI-API. Der Schlüssel wird dadurch offengelegt (Entwicklertools, Quellcode, Protokolle). Hier läuft alles über PHP. wp_remote_post().

Ratenbegrenzung

Die temporäre „Sperre“ ist ein Anfang. Wenn Sie eine Website mit mehreren Autoren haben, fügen Sie am REST-Endpunkt ein Ratenlimit pro Benutzer hinzu (z. B. eine temporäre Sperre pro Benutzer-ID).

Eingabevalidierung

Es darf nicht zugelassen werden, dass ein Benutzer beliebigen Text über einen unkontrollierten REST-Parameter an die KI sendet. Der Endpunkt nimmt hierfür einen post_id und die Nutzdaten von WordPress neu erstellt.

DSGVO / Datenschutz

  • Senden Sie keine unnötigen personenbezogenen Daten (E-Mail-Adressen, IP-Adressen, private Felder).
  • Vermeiden Sie das Versenden von Kommentaren oder Formularen ohne klare Rechtsgrundlage.
  • Dokumentieren Sie den Unterauftragnehmer (OpenAI/Anthropic/etc.) gegebenenfalls in Ihrem Register und Ihrer Datenschutzerklärung.

Cache-Kompatibilität

Wenn Sie einen aggressiven Seitencache verwenden, wird das JSON-LD über wp_head Es wird wie alles andere zwischengespeichert. Das ist so beabsichtigt. Die Gefahr besteht darin, die Metadaten neu zu generieren und zu vergessen, den Cache (Plugin-Cache/CDN) zu leeren. In diesem Fall wird das alte Schema stundenlang angezeigt.

Wie man testet und Fehler behebt

1) Zuerst einen lokalen Test durchführen

ermöglichen WP_DEBUG et WP_DEBUG_LOG in wp-config.php. Referenz : Debuggen in WordPress.

define('WP_DEBUG', true);
define('WP_DEBUG_LOG', true);

2) Überprüfen Sie, ob die JSON-LD-Ausgabe korrekt ist.

  • Öffne eine Artikelseite.
  • Sehen Sie sich den Quellcode an.
  • Suche application/ld+json.

3) Testen Sie den REST-Endpunkt (Regenerierung)

Über Ihren Browser (als Administrator angemeldet) können Sie die Konsole per `fetch` oder über `curl` mit der Nonce aufrufen. Beispiel für einen `curl`-Aufruf (wenn Sie die Nonce im Admin-Panel abrufen):

curl -X POST "https://example.com/wp-json/bpcab/v1/schema/regenerate/123" 
  -H "X-WP-Nonce: VOTRE_NONCE" 
  -H "Content-Type: application/json"

4) JSON-LD validieren

Nutzen Sie den Schema.org-Validator oder Tools zum Testen von Rich Results. Ich gebe Ihnen keinen Link zu einem „SEO-Blog“, sondern strukturierte Verweise:

Wenn es nicht funktioniert

Wenn es zu einem Fehler kommt, liegt es fast immer an einem der folgenden Punkte: Schlüssel, Kontingent, ungültiges JSON oder Hook wird zu oft ausgelöst.

Symptom Mögliche Ursache Überprüfung Lösung
Kein JSON-LD-Skript im Quellcode Leere Metadaten (werden nie generiert) oder post_status ≠ veröffentlichen Schau dir die Meta an _bpcab_ai_schema_jsonld (über ein Debugging-Plugin) Veröffentlichen Sie den Artikel und generieren Sie ihn anschließend über den REST-Endpunkt neu.
Protokolle: HTTP 401 / 403 Fehlender/falscher API-Schlüssel WP_DEBUG_LOG, Fehlercode in debug.log Richtig BPCAB_AI_OPENAI_API_KEY in wp-config.php
Protokolle: Zeitüberschreitung Langsame API / Hosting-Anbieter blockiert ausgehende Anfragen Testen Sie ein wp_remote_get() zu einer öffentlichen Website Erhöhen Sie die Anzahl leicht timeoutÜberprüfen Sie Ihre Firewall-Einstellungen und erlauben Sie den Zugriff auf api.openai.com.
Fehler: „Der generierte Inhalt ist kein gültiges JSON.“ Die KI gab Text um das JSON herum zurück. Inspektion generated im Fehler (Debug) Gestalte die Aufforderung strenger, behalte sie bei text.formatTemperatur senken
Zwei Diagramme. Artikel auf der Seite. Das SEO-Plugin fügt bereits Artikel ein HTML-Quelltext: Suche nach mehreren "@type":"Article" Ändern Sie Ihr Schema auf „Fragment“ oder deaktivieren Sie gegebenenfalls die Artikelausgabe des SEO-Plugins.

Spezifische WordPress-Fallen

  • Code-Snippet durch ein Snippet-Plugin beschädigt Manche Plugins ändern die Ladefolge. Durch die Verwendung von mu-plugins verringern Sie dieses Risiko.
  • PHP-Version zu alt Wenn die Website noch mit PHP 7.x läuft, treten Typfehler auf. Verwenden Sie stattdessen PHP 8.1 oder höher.
  • Priorität des Hakens Wenn ein anderes Plugin den Inhalt nach Ihrem `save_post`-Aufruf ändert, kann Ihr Schema veraltet sein. Passen Sie die Priorität an (20 → 30) oder generieren Sie es über den Endpunkt neu.

Ressourcen

FAQ

Belohnt Google automatisch KI-generierte strukturierte Daten?

Nein. Auszeichnungssprachen erleichtern zwar die Interpretation, aber wenn der Inhalt nicht konsistent ist, bringt das keinen Nutzen. Der eigentliche Gewinn liegt in der Konsistenz und die Genauigkeit im großen Maßstab.

Ist es riskant, vom Modell erzeugtes JSON-LD einzufügen?

Ja, wenn man die KI erfinden lässt. Deshalb erzwingt der Code die Verwendung von WordPress-Werten für kritische Felder und validiert die Struktur vor dem Einfügen.

Kann ich stattdessen Anthropic oder Mistral verwenden?

Ja. Die Architektur soll beibehalten werden: strikte JSON-Eingabeaufforderung, wp_remote_post()Validierung json_decode()Es ändert sich lediglich das Anfrage-/Antwortformat.

Warum nicht ein vollständiges Organisations-/Website-/BreadcrumbList-Schema generieren?

Da diese Elemente oft bereits von einem SEO-Plugin verwaltet werden und seitenweit gelten (nicht pro Artikel), führt die Vermischung zweier Quellen zu Inkonsistenzen.

Wie lassen sich Duplikate mit Yoast/Rank Math/SEOPress vermeiden?

Zwei realistische Optionen:

  • Generieren Sie ein Schema, das den Artikel nicht dupliziert (z. B. ein DefinedTermSet oder Thing verwandt)
  • Deaktivieren Sie die Schemaausgabe des SEO-Plugins (falls diese Option vorhanden ist) und lassen Sie Ihr Plugin die Artikelverwaltung übernehmen.

Kann ich das Schema für die Seiten (Beitragstyp Seite) generieren?

Ja, aber erzwingen Sie nicht die Option „Artikel“. Auf manchen Seiten können Sie die KI bitten, zwischen verschiedenen Formaten zu wählen. WebPage, AboutPage, ContactPageHinzufügen page in bpcab_ai_schema_allowed_post_types() und die Eingabeaufforderung anpassen.

Warum wird mein Diagramm nicht auf einer Elementor-Seite angezeigt?

Oft liegt es daran, dass Sie eine Vorschau oder eine Vorlage testen, nicht eine is_singular() Klassisch. Testen Sie die endgültige öffentliche URL und prüfen Sie anschließend den Quelltext. Wenn der Inhalt auf der Seite `post_content` leer ist, verwenden Sie die Variante „gerenderter Text“. apply_filters('the_content', ...).

Kann ich das Diagramm zur Überprüfung im Admin-Panel anzeigen lassen?

Ja. Fügen Sie eine schreibgeschützte Metabox hinzu, die das JSON anzeigt. Vermeiden Sie es, dieses Feld bearbeitbar zu machen, da Sie sonst die Kontrolle über die Validierung verlieren.

Was soll ich tun, wenn die API manchmal ungültiges JSON zurückgibt?

Senken temperatureVerbessern Sie die Eingabeaufforderung („Antworten Sie nur mit einem JSON-Objekt“) und belassen Sie die Validierung auf der PHP-Seite. Im Produktivbetrieb bevorzuge ich „kein Schema“ gegenüber einem fehlerhaften Schema.

Wie migriert man eine alte Website mit 2000 Artikeln?

Vermeiden Sie es, 2000 Backups gleichzeitig zu erstellen. Fügen Sie stattdessen einen WP-CLI-Befehl oder ein Batch-Skript hinzu, das Backups in Batches (50 Einträge) mit einer Pause verarbeitet und die Ratenbegrenzung beachtet. Auf Wunsch kann ich Ihnen eine WP-CLI-Version zur Verfügung stellen, die auf … basiert. WP_CLI::add_command() Angepasst für dieses Plugin.