WordPress 7.0 „Armstrong” został wydany 20 maja 2026 roku i dla programistów wtyczek najważniejsza zmiana nie polega na tym, że „WordPress ma AI”. Ważniejsze jest coś mniej efektownego, ale praktycznie dużo mocniejszego: Core dostał wspólną warstwę do rozmowy z modelami AI, a plugin nie musi już sam sklejać SDK OpenAI, Anthropic czy Google, własnego ekranu na API key i osobnej obsługi odpowiedzi.
To nie jest jednak magiczny przycisk. WordPress 7.0 nie uruchamia zapytań AI sam z siebie. Administrator musi mieć skonfigurowanego providera w Settings → Connectors, a sama funkcja wtyczki powinna najpierw sprawdzić, czy dana operacja jest obsługiwana. Core daje infrastrukturę, nie darmowy budżet na tokeny i nie gwarancję, że na każdym hostingu zadziała ten sam model.
Pierwsze zapytanie przez wp_ai_client_prompt() w PHP
Najprostszy przypadek wygląda kusząco krótko:
$text = wp_ai_client_prompt( 'Napisz jednozdaniowy opis wtyczki do SEO.' )
->generate_text();
if ( is_wp_error( $text ) ) {
return;
}
echo esc_html( $text );
I technicznie to działa: wp_ai_client_prompt() jest głównym punktem wejścia do generowania treści AI w WordPressie, zwraca fluent builder i pozwala zakończyć łańcuch metodą typu generate_text(). Generator zwraca wynik albo WP_Error, zgodnie z konwencjami WordPressa.
W kodzie produkcyjnym taki minimalizm szybko się mści. Lepiej od razu przyjąć trzy zasady:
- nie zakładaj, że provider AI jest skonfigurowany;
- nie wyświetlaj UI, zanim sprawdzisz obsługę funkcji;
- wymuszaj format odpowiedzi, jeżeli wynik ma trafić do pola, meta danych albo automatyzacji.
Praktyczny przykład: generowanie meta description dla wpisu. Kod jest zgodny z PHP 7.4, bo WordPress 7.0 podnosi minimalną wersję PHP właśnie do 7.4, więc nie używamy tu nowszych konstrukcji języka, które odetną część instalacji.
/**
* Generuje propozycję meta description dla wpisu.
*
* @param int $post_id ID wpisu.
* @return string|WP_Error Opis albo błąd.
*/
function acme_generate_meta_description( $post_id ) {
if ( ! function_exists( 'wp_ai_client_prompt' ) ) {
return new WP_Error(
'acme_ai_client_missing',
'AI Client nie jest dostępny na tej instalacji WordPressa.',
array( 'status' => 500 )
);
}
$post = get_post( $post_id );
if ( ! $post || 'post' !== $post->post_type ) {
return new WP_Error(
'acme_invalid_post',
'Nieprawidłowy wpis.',
array( 'status' => 400 )
);
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return new WP_Error(
'acme_forbidden',
'Brak uprawnień do pracy z tym wpisem.',
array( 'status' => 403 )
);
}
$schema = array(
'type' => 'object',
'required' => array( 'description' ),
'additionalProperties' => false,
'properties' => array(
'description' => array(
'type' => 'string',
'minLength' => 80,
'maxLength' => 170,
),
),
);
$prompt = sprintf(
"Przygotuj jedną propozycję meta description po polsku.\n\nTytuł: %s\n\nTreść:\n%s",
get_the_title( $post ),
wp_trim_words( wp_strip_all_tags( $post->post_content ), 350, '' )
);
$builder = wp_ai_client_prompt( $prompt )
->using_system_instruction(
'Jesteś redaktorem technicznym. Pisz konkretnie, bez clickbaitu, bez emoji.'
)
->using_temperature( 0.2 )
->using_max_tokens( 140 )
->as_json_response( $schema );
if ( ! $builder->is_supported_for_text_generation() ) {
return new WP_Error(
'acme_ai_text_not_supported',
'Na tej stronie nie ma skonfigurowanego modelu obsługującego generowanie tekstu.',
array( 'status' => 503 )
);
}
$json = $builder->generate_text();
if ( is_wp_error( $json ) ) {
return $json;
}
$data = json_decode( $json, true );
if ( ! is_array( $data ) || empty( $data['description'] ) ) {
return new WP_Error(
'acme_invalid_ai_response',
'Model zwrócił odpowiedź w nieoczekiwanym formacie.',
array( 'status' => 502 )
);
}
return sanitize_text_field( $data['description'] );
}
Tu są dwie decyzje, które realnie zmniejszają liczbę awarii. Pierwsza: is_supported_for_text_generation() przed wywołaniem modelu. WordPress sprawdza dostępność funkcji na podstawie konfiguracji buildera i dostępnych modeli; takie sprawdzenie nie wykonuje zapytania do AI i nie generuje kosztu. Druga: as_json_response() ze schematem, bo tekst wolny jest dobry dla człowieka, ale kiepski dla kodu, który ma zapisać pole w bazie.
Model można preferować, ale nie należy go wymuszać w logice biznesowej. using_model_preference() wskazuje kolejność preferowanych modeli, a AI Client wybiera pierwszy dostępny albo przechodzi do kompatybilnego zamiennika. To ważne przy wtyczkach dystrybuowanych publicznie: klient może mieć OpenAI, Anthropic, Google albo własny connector. Plugin ma działać mimo tej różnicy.
$result = wp_ai_client_prompt( 'Streść wpis w 5 punktach.' )
->using_temperature( 0.1 )
->using_model_preference(
'claude-sonnet-4-6',
'gemini-3.1-pro-preview',
'gpt-5.4'
)
->generate_text_result();
if ( is_wp_error( $result ) ) {
return $result;
}
// Pełny wynik zawiera m.in. dane providera, modelu i zużycie tokenów.
return rest_ensure_response( $result );
Do prostego tekstu wystarczy generate_text(). Do diagnostyki, logów, rozliczania kosztów albo panelu „co się stało” lepsze jest generate_text_result(), bo pełny obiekt wyniku zawiera metadane providera, modelu i zużycia tokenów.
Rejestrowanie własnych zdolności za pomocą pakietu @wordpress/abilities
Tu łatwo pomylić dwie warstwy. Server-side Ability rejestrujesz w PHP przez wp_register_ability(). Client-side Ability rejestrujesz w JavaScripcie przez registerAbility() z pakietu @wordpress/abilities. Dla funkcji wtyczki, która czyta lub zmienia dane w WordPressie, bezpieczniejszym punktem startu jest PHP: masz current_user_can(), walidację wejścia, WP_Error i normalny dostęp do modeli danych WordPressa.
Najpierw kategoria. Kategorie muszą być zarejestrowane przed zdolnościami i robi się to na hooku wp_abilities_api_categories_init.
add_action( 'wp_abilities_api_categories_init', 'acme_register_ability_categories' );
function acme_register_ability_categories() {
wp_register_ability_category(
'acme-seo',
array(
'label' => __( 'ACME SEO', 'acme-seo' ),
'description' => __( 'Zdolności związane z analizą i generowaniem danych SEO.', 'acme-seo' ),
)
);
}
Potem konkretna Ability. Tu nie opisujesz „AI ma dostęp do wtyczki”. Opisujesz jedną, ograniczoną operację: wejście, wyjście, uprawnienia i callback. To jest najważniejsza różnica między sensowną integracją a otwarciem furtki do chaosu.
add_action( 'wp_abilities_api_init', 'acme_register_abilities' );
function acme_register_abilities() {
wp_register_ability(
'acme-seo/generate-meta-description',
array(
'label' => __( 'Generate meta description', 'acme-seo' ),
'description' => __(
'Generates one draft SEO meta description for a specific post. Use only for editorial suggestions.',
'acme-seo'
),
'category' => 'acme-seo',
'input_schema' => array(
'type' => 'object',
'required' => array( 'post_id' ),
'additionalProperties' => false,
'properties' => array(
'post_id' => array(
'type' => 'integer',
'minimum' => 1,
),
),
),
'output_schema' => array(
'type' => 'object',
'required' => array( 'description' ),
'additionalProperties' => false,
'properties' => array(
'description' => array(
'type' => 'string',
'maxLength' => 170,
),
),
),
'execute_callback' => 'acme_ability_generate_meta_description',
'permission_callback' => 'acme_ability_can_generate_meta_description',
'meta' => array(
'annotations' => array(
'readonly' => true,
'destructive' => false,
'idempotent' => false,
),
'show_in_rest' => true,
),
)
);
}
function acme_ability_can_generate_meta_description( $input ) {
$post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0;
if ( ! $post_id ) {
return false;
}
return current_user_can( 'edit_post', $post_id );
}
function acme_ability_generate_meta_description( $input ) {
$post_id = isset( $input['post_id'] ) ? absint( $input['post_id'] ) : 0;
$description = acme_generate_meta_description( $post_id );
if ( is_wp_error( $description ) ) {
return $description;
}
return array(
'description' => $description,
);
}
W wp_register_ability() wymagane są między innymi: label, description, category, execute_callback i permission_callback. Schemat wejścia i wyjścia jest opcjonalny formalnie, ale w praktyce powinien być obowiązkowy w każdej Ability wystawianej dla AI albo automatyzacji. Bez schematu model i klient nie wiedzą dokładnie, co wolno przekazać i czego się spodziewać po wyniku.
@wordpress/abilities przydaje się po stronie edytora i panelu admina. Pakiet daje funkcje takie jak getAbilities(), getAbility(), registerAbility() i executeAbility(). W WordPressie 7.0 jest też @wordpress/core-abilities, czyli warstwa integracyjna, która pobiera server-side abilities przez REST API i rejestruje je w store @wordpress/abilities.
Przykład użycia z poziomu JavaScriptu w panelu wtyczki:
import { getAbility, executeAbility } from '@wordpress/abilities';
export async function generateMetaDescription( postId ) {
const ability = getAbility( 'acme-seo/generate-meta-description' );
if ( ! ability ) {
throw new Error( 'Ta instalacja nie udostępnia wymaganej Ability.' );
}
try {
const result = await executeAbility(
'acme-seo/generate-meta-description',
{ post_id: postId }
);
return result.description;
} catch ( error ) {
switch ( error.code ) {
case 'ability_permission_denied':
throw new Error( 'Nie masz uprawnień do wygenerowania opisu dla tego wpisu.' );
case 'ability_invalid_input':
throw new Error( 'Wtyczka przekazała nieprawidłowe dane wejściowe.' );
case 'ability_invalid_output':
throw new Error( 'Ability zwróciła dane w nieprawidłowym formacie.' );
default:
throw error;
}
}
}
Client-side Ability rejestruj tylko wtedy, gdy operacja naprawdę dzieje się w przeglądarce: nawigacja w panelu, ustawienie fokusu, wstawienie bloku w edytorze, lokalne przekształcenie zaznaczenia. Taka zdolność nadal powinna mieć kategorię, opis, callback i schematy. executeAbility() waliduje wejście i wyjście; przy błędach może zwrócić kody typu ability_invalid_input, ability_invalid_output albo ability_permission_denied.
const {
registerAbilityCategory,
registerAbility,
} = await import( '@wordpress/abilities' );
registerAbilityCategory( 'acme-editor', {
label: 'ACME Editor',
description: 'Akcje wykonywane lokalnie w edytorze.',
} );
registerAbility( {
name: 'acme-seo/focus-meta-description-field',
label: 'Focus meta description field',
description: 'Moves focus to the ACME SEO meta description input in the editor sidebar.',
category: 'acme-editor',
input_schema: {
type: 'object',
properties: {},
additionalProperties: false,
},
output_schema: {
type: 'object',
required: [ 'success' ],
properties: {
success: { type: 'boolean' },
},
additionalProperties: false,
},
callback: async () => {
const input = document.querySelector( '[data-acme-meta-description]' );
if ( ! input ) {
return { success: false };
}
input.focus();
return { success: true };
},
meta: {
annotations: {
readonly: false,
destructive: false,
idempotent: true,
},
},
} );
Jeżeli chcesz dać modelowi AI dostęp do konkretnej Ability, używasz using_abilities() na prompt builderze. To nie znaczy, że model dostaje pełny dostęp do WordPressa. WordPress konwertuje wybrane abilities na deklaracje funkcji dla modelu, a do wykonania wywołań służy WP_AI_Client_Ability_Function_Resolver, który musi dostać listę dozwolonych abilities i blokuje wywołania spoza tej listy.
W praktyce zaczynaj od read-only abilities. Najpierw niech AI czyta dane, analizuje i proponuje. Dopiero później dodawaj operacje zapisujące — i tylko z ręczną akceptacją użytkownika, logiem oraz precyzyjnym permission_callback.
Obsługa błędów, limity tokenów i asynchroniczność w WP 7.0
Najgorszy błąd w pluginie AI to wywołanie modelu w miejscu, które musi odpowiedzieć natychmiast: render frontu, save_post, masowa aktualizacja produktów, import CSV albo lista wpisów w adminie. Model bywa szybki, ale nie masz kontroli nad opóźnieniem providera, limitem konta, siecią ani konfiguracją hostingu klienta. WordPressowy wrapper ma domyślny timeout żądań AI ustawiony na 30 sekund, możliwy do zmiany filtrem wp_ai_client_default_request_timeout, ale zwiększanie timeoutu zwykle tylko przesuwa problem w czasie.
Najbezpieczniejszy priorytet wdrożenia:
- Najpierw feature detection — nie pokazuj przycisku „Generuj”, jeżeli
is_supported_for_text_generation()zwracafalse. - Potem limity wejścia — ucinaj treść, nie wysyłaj całego wpisu z komentarzami, revizjami i shortcodami.
- Dopiero potem kolejka — dla dłuższych operacji oddziel żądanie użytkownika od pracy AI.
- Na końcu preferencje modelu — model jest optymalizacją, nie fundamentem działania wtyczki.
Dla krótkich funkcji redakcyjnych, takich jak tytuł SEO, meta description, alt text czy krótki excerpt, rozsądniej ustawić niski using_max_tokens(): często 80–200 tokenów wystarczy. Dla streszczeń długich artykułów można podnieść limit, ale trzeba pilnować kosztu i czasu odpowiedzi. WordPress AI Client ma using_max_tokens(), using_temperature(), using_top_p(), using_top_k(), using_stop_sequences(), as_json_response() i inne metody konfiguracyjne, więc nie trzeba przenosić tych ustawień do osobnego SDK providera.
Przykład endpointu REST dla krótkiej operacji synchronicznej:
add_action( 'rest_api_init', 'acme_register_rest_routes' );
function acme_register_rest_routes() {
register_rest_route(
'acme-seo/v1',
'/meta-description/(?P<post_id>\d+)',
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => 'acme_rest_generate_meta_description',
'permission_callback' => 'acme_rest_can_generate_meta_description',
'args' => array(
'post_id' => array(
'required' => true,
'sanitize_callback' => 'absint',
),
),
)
);
}
function acme_rest_can_generate_meta_description( WP_REST_Request $request ) {
$post_id = absint( $request->get_param( 'post_id' ) );
return current_user_can( 'edit_post', $post_id );
}
function acme_rest_generate_meta_description( WP_REST_Request $request ) {
$post_id = absint( $request->get_param( 'post_id' ) );
$result = acme_generate_meta_description( $post_id );
if ( is_wp_error( $result ) ) {
return $result;
}
return rest_ensure_response(
array(
'description' => $result,
)
);
}
REST route rejestruj na rest_api_init, z własną przestrzenią nazw i permission_callback; WordPress traktuje brak permission_callback jako błąd definicji trasy. rest_ensure_response() pozwala zwrócić spójny obiekt odpowiedzi, a WP_Error może przejść jako odpowiedź błędu.
Dla dłuższych zadań, na przykład „wygeneruj opisy dla 300 produktów”, endpoint powinien tylko utworzyć zadanie i oddać job_id. Samą pracę przenieś do kolejki. W prostym pluginie można użyć wp_schedule_single_event(), pamiętając o ograniczeniu: zdarzenie WP-Cron uruchomi się, gdy ktoś odwiedzi stronę po zaplanowanym czasie, więc nie jest to precyzyjny worker czasu rzeczywistego.
function acme_queue_ai_job( $post_id ) {
$job_id = wp_generate_uuid4();
update_option(
'acme_ai_job_' . $job_id,
array(
'post_id' => absint( $post_id ),
'status' => 'queued',
'result' => null,
'error' => null,
),
false
);
wp_schedule_single_event(
time() + 10,
'acme_run_ai_job',
array( $job_id ),
true
);
return $job_id;
}
add_action( 'acme_run_ai_job', 'acme_run_ai_job_callback' );
function acme_run_ai_job_callback( $job_id ) {
$option_name = 'acme_ai_job_' . sanitize_key( $job_id );
$job = get_option( $option_name );
if ( ! is_array( $job ) || empty( $job['post_id'] ) ) {
return;
}
$result = acme_generate_meta_description( absint( $job['post_id'] ) );
if ( is_wp_error( $result ) ) {
$job['status'] = 'failed';
$job['error'] = array(
'code' => $result->get_error_code(),
'message' => $result->get_error_message(),
);
} else {
$job['status'] = 'done';
$job['result'] = array(
'description' => $result,
);
}
update_option( $option_name, $job, false );
}
Na stronie klienta nie rób spinnera bez końca. Po kliknięciu przycisku pokaż status: queued, running, done, failed. Jeżeli zadanie ma trwać dłużej niż pojedynczy request HTTP, użytkownik powinien móc zamknąć panel i wrócić do wyniku. To brzmi jak detal UX, ale w praktyce odróżnia funkcję AI gotową dla klienta od dema, które działa tylko na lokalnym środowisku.
Najostrzejsze reguły bezpieczeństwa:
- Nie wysyłaj danych osobowych do modelu, jeżeli funkcja nie wymaga ich do wykonania zadania.
- Nie zapisuj wyniku AI automatycznie jako opublikowanej treści; zapisuj szkic, sugestię albo pole wymagające akceptacji.
- Nie dawaj modelowi szerokiej Ability typu
update_anything. Jedna Ability powinna robić jedną rzecz. - Nie ufaj JSON-owi z modelu bez walidacji.
as_json_response()pomaga, ale po stronie PHP nadal sprawdzaj strukturę. - Nie ukrywaj błędów providera. Użytkownik musi wiedzieć, czy problemem jest brak Connectors, brak uprawnień, timeout, limit konta czy niepoprawny wynik.
FAQ
Czy wp_ai_client_prompt() działa bez instalowania zewnętrznego SDK?
Tak. Kod wtyczki korzysta z natywnego AI Clienta w WordPressie 7.0. Nadal potrzebny jest skonfigurowany provider AI, zwykle przez connector i klucz API w panelu WordPressa.
Czy WordPress 7.0 zawiera wbudowane modele AI?
Nie w sensie „lokalny model w Core”. Core daje warstwę komunikacji, Connectors i integrację z providerami. Dostawcy AI są obsługiwani przez konfigurację connectorów i provider plugins.
Kiedy używać generate_text(), a kiedy generate_text_result()?generate_text() wystarczy, gdy potrzebujesz samego tekstu. generate_text_result() wybierz wtedy, gdy chcesz zapisać metadane: model, provider, zużycie tokenów albo pełniejszy wynik diagnostyczny.
Czy Ability zarejestrowana w PHP jest automatycznie bezpieczna?
Nie. Bezpieczeństwo daje dopiero dobry permission_callback, wąski zakres działania, schemat wejścia, schemat wyjścia i brak operacji „zrób wszystko”. Samo wp_register_ability() tylko rejestruje kontrakt.
Od czego zacząć wdrożenie AI w istniejącej wtyczce?
Od jednej, czytelnej funkcji read-only: analiza wpisu, propozycja tytułu, propozycja opisu, wykrycie brakującego alt textu. Najpierw sprawdź providerów przez is_supported_for_text_generation(), potem dodaj REST endpoint albo Ability, a dopiero na końcu kolejkę dla operacji masowych.

