Wyciąganie listy postów z kategorii w WordPress

Wyciąganie listy postów z kategorii w WordPress

Ostatnio zweryfikowano: 12 października 2026
13 min czytania
Poradnik
500+ projektów WP
Full-stack developer

Jednym z najczęstszych zadań, przed którymi staje programista WordPress, jest wyświetlenie listy wpisów z określonej kategorii w niestandardowym miejscu, na stronie głównej, w sidebarze, czy w specjalnej sekcji “Polecane”. Choć WordPress oferuje wbudowane widżety, profesjonalne wdrożenia wymagają pełnej kontroli nad kodem HTML i wydajnością zapytania.

W tym poradniku przejdziemy przez kolejne metody osiągnięcia tego celu: od najprostszej funkcji get_posts, przez potężną klasę WP_Query, aż po zmianę archiwum przez pre_get_posts, REST API i WP-CLI. Dowiesz się również, jak robić to wydajnie, aby nie “zabić” swojej bazy danych.

#Jak WordPress przechowuje kategorie

Zanim wybierzesz argument zapytania, warto wiedzieć, gdzie leżą dane:

  • Kategorie to wbudowana taksonomia WordPressa o nazwie category.
  • Każdy wpis może należeć do wielu kategorii.
  • Kategorie są hierarchiczne, z relacjami rodzic i dziecko.
  • Dane kategorii trafiają do tabel wp_terms i wp_term_taxonomy.
  • Powiązania wpisów z kategoriami leżą w wp_term_relationships.

Gdy znasz tylko slug, zamień go raz na ID: get_category_by_slug( 'news' ) zwraca obiekt termu, a get_cat_ID( 'News' ) przyjmuje nazwę wyświetlaną i zwraca 0, gdy kategoria nie istnieje.

#Jak pobrać wpisy z kategorii przez get_posts()

Jeśli potrzebujesz po prostu tablicy wpisów, by wyświetlić je w prostej pętli foreach, funkcja get_posts() jest Twoim najlepszym przyjacielem. Jest to nakładka na WP_Query z domyślnymi ustawieniami „bezpiecznymi” dla większości zastosowań.

#Przykład get_posts() z kategorią news

Załóżmy, że chcemy wyświetlić 5 ostatnich wpisów z kategorii o slugu news.

<?php
$args = array(
    'posts_per_page'   => 5,
    'category_name'    => 'news',
    'orderby'          => 'date',
    'order'            => 'DESC',
    'post_type'        => 'post',
    'post_status'      => 'publish',
);

$posts_array = get_posts( $args );

if ( ! empty( $posts_array ) ) : ?>
    <ul class="news-list">
        <?php foreach ( $posts_array as $post ) : setup_postdata( $post ); ?>
            <li>
                <a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
                <span class="date"><?php echo get_the_date(); ?></span>
            </li>
        <?php endforeach;
        wp_reset_postdata(); // Ważne!
        ?>
    </ul>
<?php else : ?>
    <p>Brak wpisów w tej kategorii.</p>
<?php endif; ?>

Kluczowe parametry:

  • posts_per_page: Liczba wpisów (ustaw -1 dla wszystkich, ale uważaj na wydajność!).
  • category_name: Slug kategorii (możesz też użyć category__in z ID kategorii).
  • wp_reset_postdata(): Niezbędne na końcu, aby pętla nie nadpisała globalnego obiektu $post dla reszty strony.

#Czym get_posts różni się od WP_Query

get_posts() to cienka nakładka, która buduje WP_Query z własnymi wartościami domyślnymi. Kod funkcji pokazuje cztery różnice istotne przy listach z kategorii:

  • numberposts ma domyślnie wartość 5, więc wywołanie bez limitu bez ostrzeżenia zwraca pięć postów.
  • post_status ma domyślnie wartość publish, a suppress_filters wartość true, więc posts_where i podobne filtry SQL z wtyczek się nie wykonują. Akcja pre_get_posts nadal się uruchamia.
  • ignore_sticky_posts i no_found_rows są zawsze wymuszone na true. Na get_posts() nie zbudujesz numerowanej paginacji, bo łączna liczba wyników nigdy nie jest liczona.
  • include ustawia posts_per_page na liczbę przekazanych ID i nadpisuje Twój limit.

Jeszcze jedna pułapka dotyczy wywołania get_posts() wewnątrz funkcji albo callbacku shortcodu. setup_postdata() przygotowuje tagi szablonu, ale the_title() i the_permalink() nadal czytają globalną zmienną $post. Zadeklaruj global $post; przed pętlą foreach ( $posts as $post ), tak jak robią to przykłady w dokumentacji funkcji, i zakończ ją wywołaniem wp_reset_postdata(). Bez tej deklaracji każdy element listy wypisze tytuł strony, na której stoi shortcode.

#Jak używać WP_Query do listy wpisów z kategorii

Dla zaawansowanych zastosowań, gdzie potrzebujesz paginacji (stronicowania), złożonych filtrów (np. wpisy z kategorii X, ale z wykluczeniem tagu Y) lub niestandardowych pól (Meta Query), WP_Query jest standardem. To właśnie ta klasa napędza główną pętlę WordPressa.

#Przykład pętli WP_Query dla kategorii

<?php
$query_args = array(
    'cat'            => 12, // ID kategorii
    'posts_per_page' => 3,
    'ignore_sticky_posts' => 1, // Ignoruj przyklejone wpisy
);

$the_query = new WP_Query( $query_args );

// Sprawdź czy są wyniki
if ( $the_query->have_posts() ) : ?>
    <div class="custom-category-feed">
        <?php while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
            <article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
                <h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
                <div class="excerpt">
                    <?php the_excerpt(); ?>
                </div>
            </article>
        <?php endwhile; ?>
        <?php wp_reset_postdata(); ?>
    </div>
<?php else : ?>
    <p>Niestety, nic nie znaleziono.</p>
<?php endif; ?>

Dlaczego WP_Query? Pozwala na skomplikowane zapytania “Taxonomy Query”. Możesz np. pobrać wpisy, które są w kategorii “Sport” ORAZ mają tag “Piłka nożna”, ale “Kolor” (custom taxonomy) NIE jest “Czerwony”.

'tax_query' => array(
    'relation' => 'AND',
    array(
        'taxonomy' => 'category',
        'field'    => 'slug',
        'terms'    => array( 'sport' ),
    ),
    array(
        'taxonomy' => 'post_tag',
        'field'    => 'slug',
        'terms'    => array( 'pilka-nozna' ),
    ),
),

#Paginacja zapytania o kategorię na statycznej stronie głównej

Własna pętla paginuje przez paged, które odczytujesz funkcją get_query_var( 'paged' ). Wyjątkiem jest szablon strony ustawiony jako statyczna strona główna: tam WordPress zapisuje numer strony w zmiennej page, dlatego dokumentacja WP_Query każe czytać get_query_var( 'page' ). Przy złej zmiennej każde kliknięcie “dalej” pokazuje znowu pierwszą stronę.

$paged = ( is_front_page() && is_page() ) ? get_query_var( 'page' ) : get_query_var( 'paged' );
$paged = max( 1, (int) $paged );

$query = new WP_Query( array(
    'category_name'  => 'aktualnosci',
    'posts_per_page' => 10,
    'paged'          => $paged,
) );

#Które parametry kategorii uwzględniają kategorie podrzędne

To samo ID kategorii zwraca różne listy postów w zależności od interfejsu, przez który je odpytujesz. Ten szczegół stoi za większością zgłoszeń typu “na liście brakuje postów”, a reguły są zapisane w kodzie rdzenia, nie w ustawieniach:

InterfejsArgumentPosty z kategorii podrzędnych
WP_Querycatuwzględnione
WP_Querycategory_nameuwzględnione
WP_Querycategory__in, category__and, category__not_inpominięte
WP_Querytax_queryuwzględnione, chyba że include_children ma wartość false
get_posts()categoryuwzględnione (to alias cat)
REST APIcategories=5pominięte
REST APIcategories[terms]=5&categories[include_children]=trueuwzględnione
Blok Query Loopfiltr kategoriipominięte
WP-CLI--cat, --category_nameuwzględnione (trafiają prosto do WP_Query)

Wiersze WP_Query wynikają z metody WP_Query::parse_tax_query(): cat zamienia się w tax query z include_children ustawionym na true, a category__in, category__and i category__not_in w tax query z include_children ustawionym na false. Dokumentacja WP_Query mówi to samo w przykładach: category__in nie pokazuje postów z kategorii podrzędnych. Wiersz get_posts() wynika z jej dokumentacji, która opisuje category jako alias cat. Wiersze REST API pochodzą z kontrolera postów, gdzie include_children ma domyślnie wartość false i jest dostępne tylko dla taksonomii hierarchicznych. Wiersz Query Loop pochodzi z funkcji build_query_vars_from_query_block() w pliku blocks.php rdzenia, która dla każdego filtra taksonomii wpisuje na sztywno include_children => false.

Skutek praktyczny: szablon PHP z cat => 5 i frontend headless wywołujący /wp/v2/posts?categories=5 mogą pokazać różne posty dla tej samej kategorii nadrzędnej. Zdecyduj, jakiego zachowania potrzebujesz, i wybierz argument, który je daje, zamiast zakładać, że oba się zgadzają.

#Jak wykluczyć kategorię z zapytania WordPress

Wykluczanie działa na dwa sposoby, a każdy inaczej traktuje kategorie podrzędne. Ujemne ID w cat wyklucza kategorię razem z jej dziećmi, bo rdzeń buduje tax query NOT IN z include_children ustawionym na true. category__not_in wyklucza tylko wskazane termy, więc posty przypisane do kategorii podrzędnej nadal się pojawiają.

// Wyklucza kategorię 12 i wszystkie jej dzieci
$args = array( 'cat' => '-12' );

// Wyklucza tylko posty przypisane bezpośrednio do 12 lub 34
$args = array( 'category__not_in' => array( 12, 34 ) );

Żeby usunąć kategorię ze strony głównej bloga, zmień główne zapytanie zamiast uruchamiać drugą pętlę. Dokumentacja pre_get_posts zaleca dokładnie takie warunki:

add_action( 'pre_get_posts', function ( $query ) {
    if ( ! is_admin() && $query->is_main_query() && $query->is_home() ) {
        $query->set( 'cat', '-12' );
    }
} );

#Jak zmienić archiwum kategorii przez pre_get_posts

Gdy chcesz zmienić, które wpisy pojawiają się na stronach archiwum kategorii, użyj akcji pre_get_posts zamiast drugiego zapytania w szablonie.

function modify_category_queries( $query ) {
    // Tylko archiwa kategorii w głównym zapytaniu
    if ( $query->is_category() && $query->is_main_query() && ! is_admin() ) {
        // 20 wpisów na stronę zamiast wartości domyślnej
        $query->set( 'posts_per_page', 20 );
    }
}
add_action( 'pre_get_posts', 'modify_category_queries' );

W callbacku używaj metod obiektu zamiast samodzielnych tagów warunkowych ($query->is_category(), $query->is_main_query()). Funkcje bez obiektu sprawdzają globalne $wp_query, a nie zapytanie przekazane do hooka, o czym dokumentacja hooka ostrzega wprost.

#Dlaczego query_posts nie nadaje się do filtrowania po kategorii

Starsze poradniki wstawiają query_posts( 'cat=5' ) na początek szablonu. Docblock funkcji query_posts() w rdzeniu mówi, że całkowicie nadpisuje ona główne zapytanie, nie jest przeznaczona dla wtyczek ani motywów i w większości przypadków należy ją zastąpić akcją pre_get_posts. Ciało funkcji to jedna linia, która przypisuje nowy WP_Query do globalnego $wp_query. Szablon wykonuje się po głównym zapytaniu, więc wywołanie powtarza pracę bazy danych i podmienia obiekt, z którego czytają paginacja i tagi warunkowe. Każde zadanie z kategoriami, które używało query_posts(), rozwiązuje albo pre_get_posts (zmiana archiwum), albo osobny WP_Query (druga lista).

Filtry kategorii łączą się też z polami własnymi. Lista wydarzeń, która pokazuje tylko nadchodzące terminy z dwóch kategorii, łączy category__in z meta_query i sortuje po polu daty:

$args = array(
    'posts_per_page' => 10,
    'category__in'   => array( get_cat_ID( 'Konferencje' ), get_cat_ID( 'Warsztaty' ) ),
    'meta_key'       => 'data_wydarzenia',
    'orderby'        => 'meta_value',
    'order'          => 'ASC',
    'meta_query'     => array(
        array(
            'key'     => 'data_wydarzenia',
            'value'   => current_time( 'Y-m-d' ),
            'compare' => '>=',
            'type'    => 'DATE',
        ),
    ),
);

get_cat_ID() przyjmuje nazwę kategorii, nie slug, i zwraca 0, gdy nic nie pasuje. Zero w category__in nie pasuje do żadnego posta, więc sprawdź ID przed uruchomieniem zapytania na produkcyjnej stronie.

#Jak nie przeciążyć bazy przy liście postów z kategorii

Wywołanie WP_Query to zapytanie do bazy danych. Jeśli masz ich 10 na stronie głównej, strona zwolni.

  1. Cache’owanie: Używaj Transient API, aby zapisać wyniki zapytania na np. godzinę.
    $cache_key = 'news_posts_cache';
    $posts_query = get_transient( $cache_key );
    
    if ( false === $posts_query ) {
        $query       = new WP_Query( $args );
        $posts_query = $query->posts; // tablica WP_Post, nie cały obiekt zapytania
        set_transient( $cache_key, $posts_query, HOUR_IN_SECONDS );
    }
  2. Ograniczanie pól: Jeśli potrzebujesz tylko ID wpisów, użyj 'fields' => 'ids', to znacznie przyspieszy zapytanie SQL.
  3. no_found_rows: Jeśli nie potrzebujesz paginacji (np. wyświetlasz tylko 3 ostatnie wpisy w stopce), dodaj 'no_found_rows' => true. WordPress nie będzie wtedy liczył wszystkich pasujących wpisów w bazie, co jest bardzo kosztowną operacją.

Argument fields przyjmuje dokładnie trzy wartości, zgodnie z dokumentacją WP_Query: all (domyślnie), ids oraz id=>parent. Każda inna wartość, także tablica nazw kolumn, bez ostrzeżenia zwraca pełne obiekty postów. Dwa kolejne przełączniki oszczędzają pracę przy listach, które wypisują tylko tytuł i link:

$args = array(
    'category_name'          => 'aktualnosci',
    'posts_per_page'         => 5,
    'no_found_rows'          => true,  // bez SQL_CALC_FOUND_ROWS, paginacja niepotrzebna
    'update_post_meta_cache' => false, // pola własne nie są wypisywane
    'update_post_term_cache' => false, // termy nie są wypisywane
);

no_found_rows wyłącza liczenie wszystkich pasujących wierszy, których WordPress potrzebuje tylko do paginacji. Obie flagi cache mają w rdzeniu domyślnie wartość true; dokumentacja mówi, że zwykle nie trzeba ich ruszać, ale ich wyłączenie oszczędza dodatkowe zapytania o meta i termy, gdy lista tych danych nie czyta. Zostaw je włączone, gdy pętla wypisuje kategorie, tagi lub pola własne, bo załadowanie cache jednym zapytaniem jest tańsze niż jedno zapytanie na każdy post.

#Jak wyświetlić wpisy z kategorii w motywie blokowym

W motywach blokowych, domyślnym typie motywu od czasu, gdy Twenty Twenty-Two trafił do WordPress 5.9, często w ogóle nie potrzebujesz PHP. Blok pętli zapytań (Query Loop) filtrowany po kategorii renderuje listę z poziomu edytora: wstaw pętlę zapytań, otwórz ustawienia bloku i w sekcji filtrów dodaj wybraną kategorię.

Filtr kategorii w bloku pasuje tylko do zaznaczonych termów. Rdzeń zamienia go na tax query z include_children ustawionym na false, więc wybór kategorii nadrzędnej nie wciąga postów przypisanych wyłącznie do jej dzieci; dodaj do filtra także kategorie podrzędne.

#Jak pobrać wpisy z kategorii przez REST API

Dla frontendów headless albo zewnętrznej integracji WordPress udostępnia wpisy z kategorii przez REST API bez własnego endpointu:

GET /wp-json/wp/v2/posts?categories=5&per_page=10&_fields=id,title,link,excerpt

Aby zamienić slug na ID, którego oczekuje endpoint, najpierw odpytaj /wp-json/wp/v2/categories?slug=news. categories_exclude odfiltrowuje term, a _embed dociąga obrazki wyróżniające i nazwy termów w jednym żądaniu.

O tym, czy lista headless jest kompletna, decydują trzy szczegóły z podręcznika REST API:

  • Kategorie podrzędne wymagają formy obiektowej. categories=5 pasuje tylko do termu 5. Żeby uwzględnić jego dzieci, wyślij categories[terms]=5&categories[include_children]=true. Ten sam obiekt przyjmuje categories[operator]=AND, gdy post musi mieć wszystkie wskazane termy.
  • Strona ma limit 100 elementów. Przewodnik po paginacji ogranicza per_page do wartości od 1 do 100. Odczytaj nagłówki odpowiedzi X-WP-Total i X-WP-TotalPages i pobieraj page=2, page=3, aż zbierzesz wszystko.
  • _fields obsługuje klucze zagnieżdżone. Od WordPress 5.3 parametr _fields przyjmuje ścieżki takie jak meta.data_wydarzenia, więc lista może pobrać jeden zarejestrowany klucz meta bez całego obiektu meta. WordPress pomija wtedy wyliczanie pól, o które nie prosisz, co skraca też czas odpowiedzi.

#Jak wylistować posty z kategorii przez WP-CLI

Przy audytach, migracjach i jednorazowych eksportach wp post list uruchamia ten sam WP_Query z wiersza poleceń. Każda opcja --<pole>=<wartość> trafia do WP_Query jako argument, więc reguły z tabeli powyżej działają bez zmian:

# ID termu i liczba postów w każdej kategorii
wp term list category --fields=term_id,name,slug,count

# Opublikowane posty z "aktualnosci" i jej kategorii podrzędnych, jako CSV
wp post list --category_name=aktualnosci --post_status=publish --fields=ID,post_title,post_date --format=csv

# Same ID, posty przypisane bezpośrednio do kategorii 12 (bez dzieci)
wp post list --category__in=12 --post_status=publish --format=ids

# Ile postów pokaże szablon dla kategorii 12 i jej dzieci
wp post list --cat=12 --post_status=publish --format=count

Dwie wartości domyślne różnią się od pętli w szablonie. Kod polecenia ustawia posts_per_page na -1 i post_status na any, więc bez --post_status=publish wynik zawiera szkice, posty oczekujące i prywatne. Domyślne kolumny to ID, post_title, post_name, post_date i post_status, a --format przyjmuje table, csv, ids, json, count i yaml. Porównanie --format=count dla --cat i --category__in przy tym samym ID szybko pokazuje, ile postów siedzi wyłącznie w kategoriach podrzędnych.

#Jak debugować zapytanie kategorii zwracające niewłaściwe wpisy

Gdy zapytanie zwraca za dużo, za mało albo nieoczekiwane wpisy, obejrzyj SQL, który faktycznie się wykonał, zamiast zgadywać po argumentach. Wtyczka Query Monitor pokazuje każde zapytanie na stronie, także to wygenerowane przez Twoją pętlę. Bez wtyczki wystarczy zapisać sparsowane zapytanie w logu:

$query = new WP_Query( $args );
// Dokładny SQL, który WordPress zbudował z $args
error_log( $query->request );

Za większością niespodzianek stoją dwie przyczyny: uwzględnianie kategorii podrzędnych (patrz tabela wyżej) oraz filtr pre_get_posts z wtyczki lub motywu, który zmienia główne zapytanie. Jeśli SQL wygląda poprawnie, a wynik jest pusty, sprawdź, czy do termu naprawdę są przypisane opublikowane wpisy w bieżącym języku, o czym łatwo zapomnieć przy WPML czy Polylang.

#Najczęstsze błędy przy liście wpisów z kategorii

  1. Brak resetowania danych posta: zawsze wywołuj wp_reset_postdata() po niestandardowych pętlach
  2. Odpytywanie przy każdym ładowaniu strony: używaj cachowania dla kosztownych zapytań
  3. Brak sprawdzenia istnienia postów: zawsze weryfikuj have_posts() przed pętlą
  4. Nieprawidłowa modyfikacja głównego zapytania: używaj pre_get_posts zamiast tworzenia nowych zapytań na stronach archiwum
  5. Ignorowanie paginacji: pamiętaj o obsłudze paginacji dla dużych archiwów kategorii

#Podsumowanie: jak wyciągnąć listę postów z kategorii

Wybór metody zależy od celu:

  • Użyj get_posts() do prostych list w sidebarach czy stopce
  • Użyj WP_Query dla głównych sekcji contentowych, z paginacją i złożonym filtrowaniem.
  • Pamiętaj o wp_reset_postdata() i cache’owaniu wyników.

Dobre opanowanie tych zapytań to fundament pracy każdego developera WordPress. Gotowa wtyczka typu “Category Posts Widget” wystarczy do prostej listy, a własny kod daje pełną kontrolę nad HTML-em i nad samym zapytaniem.

Następny krok

Przekuj artykuł w realne wdrożenie

Pod tym wpisem dokładam linki, które domykają intencję użytkownika i prowadzą dalej w strukturze serwisu.

Chcesz wdrożyć ten temat na swojej stronie?

Jeśli chcesz przełożyć wiedzę z artykułu na działającą stronę, sklep albo przebudowę serwisu, przygotuję konkretny zakres prac.

Powiązany klaster

Sprawdź inne usługi WordPress i bazę wiedzy

Wzmocnij swój biznes dzięki profesjonalnemu wsparciu technicznemu w kluczowych obszarach ekosystemu WordPress.

FAQ do artykułu

Często zadawane pytania

Najważniejsze odpowiedzi, które pomagają wdrożyć temat w praktyce.

SEO-readyGEO-readyAEO-ready3 Q&A
Kiedy użyć get_posts(), a kiedy WP_Query?#
get_posts() wystarczy do prostych list, na przykład w sidebarze czy stopce. WP_Query wybieraj wtedy, gdy potrzebujesz większej kontroli, złożonych filtrów albo paginacji.
Jak nie zabić bazy danych takimi listami?#
Najważniejsze jest ograniczenie liczby rekordów, używanie cache i unikanie kosztownych zapytań tam, gdzie wystarczy prostsza pętla lub same ID.
Czy warto używać gotowych widgetów do list wpisów?#
Przy prostych wdrożeniach można, ale własny kod zwykle daje lepszą kontrolę nad HTML-em, wydajnością i logiką zapytania.

Potrzebujesz FAQ dopasowanego do branży i rynku? Przygotujemy wersję pod Twoje cele biznesowe.

Porozmawiajmy

Polecane artykuły