Pola niestandardowe (custom fields) to jedna z najbardziej obciążonych pracą funkcji WordPressa w projektach agencyjnych. Pozwalają dołączyć do wpisu, strony albo CPT dodatkowe dane: lokalizację wydarzenia, identyfikator ERP, podpis eksperta, status oferty - wszystko jako wiersze w wp_postmeta.
Ten przewodnik idzie od get_post_meta() przez escapowanie, metaboxy, ACF, cache i rejestrację meta pod REST/Gutenberg. Bez frameworka „na zapas”: najpierw natywne API, potem warstwa wygody.
Więcej o profesjonalnym rozwoju WordPress na WPPoland.
Podstawy: get_post_meta()
Funkcja get_post_meta() to fundament odczytu:
// Pojedyncza wartość
$value = get_post_meta( $post_id, 'meta_key', true );
// Tablica wartości pod tym samym kluczem
$values = get_post_meta( $post_id, 'meta_key', false );
// Wszystkie metadane wpisu
$all_meta = get_post_meta( $post_id );Parametry
| Parametr | Typ | Opis |
|---|---|---|
$post_id | int | ID wpisu |
$key | string | Nazwa klucza meta |
$single | bool | true = jedna wartość, false = tablica |
Trzeci argument jest źródłem klasycznych bugów. Przy $single = true pusta meta zwraca pusty string. Przy false dostajesz tablicę - nawet z jednym elementem. Kod, który robi if ( $value ) na tablicy, zachowuje się inaczej niż na stringu.
Prefiksuj klucze nazwą projektu (wppoland_event_city), żeby nie kolidować z wtyczkami. Unikaj generycznych price, date, city bez namespace - wcześniej czy później WooCommerce albo SEO plugin zajmie tę samą nazwę.
Wyświetlanie w motywie
W pętli WordPressa
<?php if ( have_posts() ) : while ( have_posts() ) : the_post(); ?>
<h2><?php the_title(); ?></h2>
<?php
$sku = get_post_meta( get_the_ID(), 'wppoland_sku', true );
if ( $sku ) :
?>
<p class="sku"><?php echo esc_html( $sku ); ?></p>
<?php endif; ?>
<?php endwhile; endif; ?>Poza pętlą
$post_id = 42;
$author_name = get_post_meta( $post_id, 'custom_author', true );
echo esc_html( $author_name );Na archiwach CPT nie odwołuj się do globalnego $post „na ślepo” poza pętlą - bierz ID z get_queried_object_id() albo z argumentu szablonu. To częsty powód „leci meta z poprzedniego wpisu”.
Bezpieczeństwo: zawsze escapuj dane
Nigdy nie wypuszczaj surowych metadanych do HTML:
// Źle - podatne na XSS
echo get_post_meta( $post_id, 'user_input', true );
// Dobrze
echo esc_html( get_post_meta( $post_id, 'user_input', true ) );
// URL
echo esc_url( get_post_meta( $post_id, 'website_url', true ) );
// Atrybut HTML
echo esc_attr( get_post_meta( $post_id, 'css_class', true ) );Reguła kciuka: kontekst decyduje o funkcji. Tekst widoczny - esc_html. Atrybut - esc_attr. URL - esc_url. HTML z zaufanego źródła redakcyjnego - wp_kses_post. Meta wypełniane przez front-end formularz traktuj jak nieufne zawsze, nawet jeśli „to tylko intranet”.
Po stronie zapisu: sanitize_text_field, absint, sanitize_email, ewentualnie własny callback w register_post_meta. Nie polegaj na tym, że metabox „jest tylko w adminie” - REST i bulk edit też zapisują.
Zapisywanie pól niestandardowych
Programowo
update_post_meta( $post_id, 'meta_key', 'meta_value' );
add_post_meta( $post_id, 'meta_key', 'meta_value' );
delete_post_meta( $post_id, 'meta_key' );update_post_meta tworzy wiersz, jeśli nie istnieje. add_post_meta z domyślnym $unique = false pozwala na wiele wartości pod jednym kluczem - świadoma decyzja, nie wypadek.
Metabox w edytorze
function add_sku_metabox() {
add_meta_box(
'product_sku_box',
'Kod katalogowy',
'render_sku_metabox',
'post',
'side'
);
}
add_action( 'add_meta_boxes', 'add_sku_metabox' );
function render_sku_metabox( $post ) {
wp_nonce_field( 'save_sku', 'sku_nonce' );
$sku = get_post_meta( $post->ID, 'wppoland_sku', true );
?>
<label for="wppoland_sku">SKU:</label>
<input type="text" id="wppoland_sku" name="wppoland_sku"
value="<?php echo esc_attr( $sku ); ?>">
<?php
}
function save_sku_metabox( $post_id ) {
if ( ! isset( $_POST['sku_nonce'] ) ||
! wp_verify_nonce( $_POST['sku_nonce'], 'save_sku' ) ) {
return;
}
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
if ( ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
if ( isset( $_POST['wppoland_sku'] ) ) {
update_post_meta(
$post_id,
'wppoland_sku',
sanitize_text_field( wp_unslash( $_POST['wppoland_sku'] ) )
);
}
}
add_action( 'save_post', 'save_sku_metabox' );Nonce, capability, autosave - trzy rzeczy, które w tutorialach giną, a na audycie bezpieczeństwa wychodzą jako pierwsze. Dopisz też wp_unslash przed sanitizacją POST.
Praca z ACF
ACF upraszcza UI i typy pól (repeater, flexible, relationship):
$sku = get_field( 'wppoland_sku' );
$image = get_field( 'hero_image' );
the_field( 'product_description' );
$value = get_field( 'field_name', $post_id );Pamiętaj, że ACF i tak siedzi na postmeta (plus własne tabele w nowszych wariantach storage). Do zapytań meta_query często i tak odwołujesz się do kluczy jak w natywnym API. Przy dużych listingach sprawdzaj, czy nie wołasz get_field w pętli bez wcześniejszego update_meta_cache / bez Query, które prefetchuje meta.
Jeśli motyw ma działać po wyłączeniu ACF, trzymaj warstwę fallback: function_exists( 'get_field' ) ? get_field(...) : get_post_meta(...).
Wydajność i cache
WordPress przy standardowym WP_Query woła update_meta_cache() i ładuje meta wpisów hurtowo. Wielokrotne get_post_meta() na tym samym ID w jednym requeście nie generuje kolejnych SELECT-ów - o ile object cache działa.
Antywzorzec: w pętli 50 kart odpalasz osobne WP_Query zależne od meta. Lepiej jeden query z meta_query albo dwa etapy (IDs, potem get_posts).
$args = [
'post_type' => 'product',
'meta_query' => [
[
'key' => 'wppoland_sku',
'value' => 'A',
'compare' => 'LIKE',
],
],
];
$query = new WP_Query( $args );Unikaj sortowania po meta na wielkich zbiorach bez indeksu - MySQL nie lubi ORDER BY po meta_value na setkach tysięcy wierszy. Czasem lepsza jest denormalizacja do taksonomii albo własnej tabeli.
Autoload nie dotyczy postmeta, ale jeśli kopiujesz wzorce z get_option, nie włączaj autoload na opcjach „bo wygodniej” - to osobna klasa problemów TTFB.
Integracja z Gutenbergiem i REST
Żeby meta była widoczna w edytorze bloków i REST API, zarejestruj ją:
register_post_meta( 'post', 'wppoland_sku', [
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'auth_callback' => function () {
return current_user_can( 'edit_posts' );
},
] );Bez show_in_rest blok JS nie zobaczy pola. Bez auth_callback możesz przypadkiem otworzyć zapis meta szerszej grupie niż chciałeś. type musi zgadzać się z realnymi danymi - number vs string w JSON to źródło cichych bugów w sidebarze.
Dla pól złożonych (obiekty, listy) ustaw show_in_rest z schematem; inaczej Gutenberg dostanie płaski string i „zgubi” strukturę.
Typowe błędy z audytów
- Wyświetlanie meta bez
esc_*. - Zapis z
$_POSTbez nonce i capability. - Ten sam klucz meta używany przez dwie wtyczki.
get_fieldw pętli archiwum bez cache / bez batch meta.- Założenie, że
$single = truezawsze zwraca string (serializowane tablice po starych importach). - Brak
register_post_metaprzy budowie bloku - „działa w PHP, nie działa w edytorze”.
Migracja i nazewnictwo kluczy
Gdy przejmujesz stary motyw, zrób inwentaryzację kluczy: SELECT meta_key, COUNT(*) FROM wp_postmeta GROUP BY meta_key ORDER BY COUNT(*) DESC LIMIT 50. Od razu widać, które prefiksy są „twoje”, a które zostały po wtyczkach usuniętych lata temu. Prefiks projektu (wppoland_, client_) chroni przed kolizją z ACF albo WooCommerce.
Przy migracji z ACF na natywne register_post_meta nie kopiuj ślepo field_xxxxx. Zostaw czytelny klucz biznesowy i, jeśli trzeba, jednorazowy skrypt mapujący stare ID na nowe. Staging najpierw: porównaj liczbę wpisów z niepustym meta przed i po. Jeśli eksportujesz CSV dla redakcji, trzymaj osobną kolumnę na ID posta - tytuł nie jest unikalny.
W multisite pamiętaj, że get_post_meta jest zawsze w kontekście bieżącego bloga. Przełączanie switch_to_blog wokół odczytu meta bez restore_current_blog w finally to klasyczny sposób na „losowe” dane z innego sklepu w sieci.
Checklist przed merge
- Każde wyjście ma
esc_html,esc_attralbowp_kses_postzgodnie z kontekstem. - Formularze zapisu mają nonce i
current_user_can. - Meta używana w REST ma
show_in_restiauth_callback. - Brak
ORDER BY meta_valuena dużych archiwach bez uzasadnienia i indeksu. - Stare klucze po wtyczkach są albo udokumentowane, albo usunięte skryptem cleanup na staging.
Serializacja i stare importy
get_post_meta( $id, $key, true ) potrafi zwrócić tablicę, jeśli w bazie leży zserializowany PHP array z migracji sprzed lat. Kod zakładający string wywali notice albo wypisze „Array”. Przed esc_html sprawdź typ albo normalizuj przy zapisie.
Unikaj ręcznego serialize() / unserialize() na danych z użytkownika. WordPress sam serializuje złożone wartości w meta; Ty podajesz tablicę do update_post_meta. Przy odczycie z zewnętrznych systemów mapuj pola na skalarne klucze zamiast pakować cały wiersz do jednego blobu - łatwiej potem filtrować meta_query.
Dla pól wrażliwych (tokeny integracji, wewnętrzne ID) trzymaj show_in_rest wyłączone albo ogranicz schema tak, by REST ich nie ekspozyował publicznie. Front motywu może czytać je w PHP przy renderze, bez wystawiania na /wp-json/.
Testuj zapis meta jako użytkownik z rolą edytora, nie tylko jako administrator. Metabox albo grupa ACF powinna mieć jednozdaniowy opis dla redakcji: skąd brać wartość, czy pole jest wymagane, co się stanie na froncie gdy puste. W szablonie obsługuj stan pusty świadomie - ukryj sekcję albo pokaż fallback - zamiast renderować puste znaczniki.
Podsumowanie
Pola niestandardowe są fundamentem CPT i integracji. Natywne get_post_meta() wystarcza daleko; ACF dokłada UX. Trzy zasady zostają te same: escapuj wyjście, sanityzuj wejście, korzystaj z cache meta zamiast wymyślać własne SELECT-y w pętli. Dobrze nazwany klucz i świadomy register_post_meta oszczędzają tygodnie debugowania przy kolejnym redesignie albo migracji edytora. Jeśli redakcja nie wie, do czego służy pole, pole nie istnieje w praktyce - niezależnie od tego, jak poprawny jest kod szablonu. Krótka ściągawka w Confluence albo w stronie „Wsparcie” w wp-admin robi więcej niż kolejny tooltip w ACF.
Jeśli przejmujesz motyw z setkami luźnych kluczy meta albo migrujesz z ACF na natywne bloki, da się to uporządkować w ramach opieki developerskiej. Napisz przez formularz kontaktowy z krótkim opisem CPT i tego, czy meta ma być edytowalna w Gutenbergu.






