Jak eksportować dane WordPress do excel/CSV w 2026: Sposób dewelopera

Jak eksportować dane WordPress do excel/CSV w 2026: Sposób dewelopera

Ostatnio zweryfikowano: 13 października 2026
26 min czytania
Przewodnik
Full-stack developer

W 2013 roku, jeśli chciałeś wyciągnąć dane z WordPressa, kopiowałeś i wklejałeś tabele do Excela. Dziś przenoszenie danych to wymóg prawny (RODO) i konieczność biznesowa.

Niezależnie od tego, czy migrujesz zamówienia WooCommerce, czy robisz kopię danych użytkowników, eksport nie zawsze wymaga wtyczki. Musisz zrozumieć strumienie PHP i WP-CLI.

Do większości eksportów wystarczy zwykły CSV: strumieniuj wiersze przez fputcsv() przy pobieraniu w przeglądarce, pobieraj dane paczkami przez WP_Query i przechodź na WP-CLI, gdy żądanie w panelu uderzyłoby w limit pamięci albo timeout. REST API wybierz wtedy, gdy dane ma odebrać inny system, a nie człowiek z arkuszem.

#Jak wyeksportować dane z WordPressa do Excela

Trzy podejścia pokrywają prawie każde zadanie eksportu w WordPressie:

  • PHP wewnątrz WordPressa do pobierania z panelu z kontrolą uprawnień
  • WP-CLI do dużych zbiorów i zadań planowanych na serwerze
  • REST API do dashboardów, hurtowni danych, CRM-ów i aplikacji headless

Okazjonalne, małe eksporty dobrze mieszczą się w pobieraniu przez PHP. Cykliczne albo bardzo duże tabele należą do linii komend. Systemy zewnętrzne powinny pobierać JSON przez REST i budować CSV poza WordPressem, jeśli nadal potrzebują arkusza.

Dobieraj metodę do sposobu, w jaki zawodzi. Pobieranie w przeglądarce pada na timeoutach. Wtyczki padają, gdy próbują zbudować cały obiekt arkusza w jednym żądaniu. Doraźne zrzuty SQL padają, gdy po aktualizacji wtyczki zmieniają się nazwy kolumn. Wybierz drogę, która da ten sam eksport także za miesiąc, nie tylko dziś.

#Co eksportuje Narzędzia > Eksport w WordPressie

WordPress ma eksporter w rdzeniu, ale nie tworzy on arkusza. Według dokumentacji ekranu Narzędzia > Eksport pobrany plik to XML w formacie WordPress eXtended RSS (WXR) z wpisami, stronami, własnymi typami treści, komentarzami, polami własnymi, kategoriami, tagami, własnymi taksonomiami i użytkownikami. Przy wpisach i stronach możesz filtrować po kategorii, autorze, zakresie dat i statusie.

Ten plik jest przeznaczony dla Narzędzia > Import > WordPress w innej instalacji. Excel otworzy XML, ale każdy wpis ciągnie za sobą pełny HTML treści, metadane i komentarze, więc jako arkusz jest mało przydatny. WXR służy do migracji i kopii treści. CSV jest dla człowieka, który ma filtrować, sumować albo zestawiać kolumny.

Ten sam WXR powstaje na serwerze przez wp export. Komenda przyjmuje filtry --post_type, --start_date i --end_date (format RRRR-MM-DD), --post_status oraz --author, domyślnie dzieli pliki co 15 MB (--max_file_size, -1 bez limitu), a z --stdout wypisuje XML na standardowe wyjście:

wp export --dir=/tmp/exports/ --post_type=post --start_date=2026-01-01 --end_date=2026-06-30

#CSV czy plik XLSX z PhpSpreadsheet

Zacznij od CSV, chyba że ktoś ma twardy wymóg pliku .xlsx.

Mocne strony CSV

  • Łatwo go wygenerować w PHP albo WP-CLI
  • Łatwo go sprawdzić i debugować w edytorze tekstu
  • Otwiera się w Excelu, Numbers i Arkuszach Google
  • Omija biblioteki XLSX i ich koszt pamięci

Koszty XLSX

  • Format binarny z arkuszami, stylami i współdzielonymi ciągami znaków
  • Wymaga biblioteki, np. PhpSpreadsheet, albo kroku konwersji po eksporcie
  • Buduje więcej stanu w pamięci, zanim plik będzie gotowy
  • Trudniej go bezpiecznie strumieniować w długim żądaniu HTTP

Możesz być kuszony, by użyć PhpSpreadsheet do wygenerowania prawdziwych plików .xlsx. Przy dużych zbiorach to złe miejsce: plik powstaje w RAM, zanim do przeglądarki pójdzie pierwszy bajt. Excel otwiera pliki CSV natywnie, a binarny .xlsx rzadko jest potrzebny, chyba że chodzi o formuły albo formatowanie. Praktyczny wzorzec: eksportuj CSV z WordPressa, a jeśli ktoś upiera się przy XLSX, przekonwertuj go potem w Excelu, LibreOffice albo małym skryptem offline. Żądanie WordPressa zostaje lekkie.

#Eksport użytkowników WordPress do CSV w PHP

Jeśli chcesz zbudować przycisk “Pobierz raport” w kokpicie klienta, użyj natywnego fputcsv w PHP. Nigdy nie ładuj wszystkich danych do tablicy ($data[] = ...), bo wyczerpiesz limit pamięci. Zamiast tego strumieniuj dane bezpośrednio do przeglądarki. Najpierw uprawnienia i nonce, potem nagłówki, php://output, wiersz nagłówka i dane paczkami.

function wppoland_export_users_csv() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'Brak uprawnień.' );
    }
    check_admin_referer( 'wppoland_export_users' );

    header( 'Content-Type: text/csv; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename="eksport-uzytkownikow-' . gmdate( 'Y-m-d' ) . '.csv"' );

    // Otwórz strumień wyjściowy
    $fp = fopen( 'php://output', 'w' );

    // Wiersz nagłówka
    fputcsv( $fp, array( 'ID', 'Nazwa użytkownika', 'Email', 'Zarejestrowany' ), ',', '"', '' );

    // Iteruj paczkami po 100 użytkowników
    $args = array(
        'number'  => 100,
        'offset'  => 0,
        'orderby' => 'ID',
        'order'   => 'ASC',
    );

    while ( $users = get_users( $args ) ) {
        foreach ( $users as $user ) {
            fputcsv(
                $fp,
                array( $user->ID, $user->user_login, $user->user_email, $user->user_registered ),
                ',',
                '"',
                ''
            );
        }
        $args['offset'] += 100;
        if ( count( $users ) < 100 ) {
            break;
        }
    }

    fclose( $fp );
    exit;
}
add_action( 'admin_post_export_users', 'wppoland_export_users_csv' );

Formularz, który uruchamia pobieranie, musi zawierać wp_nonce_field( 'wppoland_export_users' ), żeby check_admin_referer() mógł go zweryfikować. Eksporty użytkowników i zamówień zwykle zawierają dane osobowe, więc traktuj je jak każdą inną uprzywilejowaną akcję w panelu w duchu RODO: zapisuj, kto uruchomił eksport, i trzymaj plik z dala od publicznych adresów URL.

#fputcsv w PHP 8.4 i parametr escape

Przykłady w tym przewodniku przekazują do fputcsv() pięć argumentów i to jest celowe. Podręcznik PHP podaje, że od PHP 8.4.0 poleganie na domyślnej wartości escape jest przestarzałe: parametr trzeba podać jawnie, pozycyjnie albo jako argument nazwany. Eksport uruchamiany przy każdym cronie zapełniłby log ostrzeżeniami o przestarzałym użyciu.

Ten sam podręcznik wyjaśnia, że każda wartość escape inna niż pusty ciąg może dać CSV niezgodny z RFC 4180 albo taki, który nie przetrwa drogi tam i z powrotem przez funkcje CSV w PHP. Domyślnie jest to ukośnik wsteczny, a zalecenie brzmi: ustaw "". Dlatego wszystkie wywołania tutaj używają ',', '"', ''. Podręcznik zapowiada też, że wartość domyślna zmieni się w przyszłej wersji, nie wcześniej niż w PHP 9.0, więc ustawienie jej dziś chroni przed zmianą wyniku po aktualizacji serwera.

#Eksport dużej bazy WordPress bez limitu pamięci

Typowa awaria to załadowanie zbyt wielu danych naraz. Limity pamięci PHP, max_execution_time i timeouty reverse proxy przerywają długie eksporty w panelu. Pobieraj dane paczkami przez WP_Query z posts_per_page i offset, a przy dużych tabelach lepiej stronicuj stabilnym kursorem.

// $fp to strumień otwarty jak w przykładzie z użytkownikami.
$offset = 0;
$limit  = 500;

do {
    $query = new WP_Query(
        array(
            'post_type'              => 'post',
            'post_status'            => 'publish',
            'posts_per_page'         => $limit,
            'offset'                 => $offset,
            'orderby'                => 'ID',
            'order'                  => 'ASC',
            'fields'                 => 'ids',
            'no_found_rows'          => true,
            'update_post_meta_cache' => false,
            'update_post_term_cache' => false,
        )
    );

    foreach ( $query->posts as $post_id ) {
        fputcsv( $fp, array( $post_id, get_the_title( $post_id ) ), ',', '"', '' );
    }

    $offset += $limit;
    wp_cache_flush_runtime(); // WP 6.0+: czyści tylko pamięć podręczną tego żądania
} while ( count( $query->posts ) === $limit );

Dlaczego to pomaga:

  • fields => 'ids' ogranicza budowanie obiektów
  • Wyłączone cache meta i termów nie pozwalają, by object cache rósł bez końca
  • Stały rozmiar paczki trzyma szczyt pamięci blisko jednej strony wierszy
  • Strumieniowanie przez fputcsv() nie trzyma całego CSV w RAM
  • Sortowanie rosnąco po ID sprawia, że wpis opublikowany w trakcie eksportu nie przesunie stron i nie zdubluje ani nie pominie wierszy

W pętli używaj wp_cache_flush_runtime(), a nie wp_cache_flush(). Przy trwałym object cache (Redis albo Memcached) wp_cache_flush() w połowie eksportu czyści cache całej strony. Funkcja runtime, dostępna od WordPressa 6.0, czyści tylko pamięć bieżącego żądania, a jeśli drop-in jej nie obsługuje, zwraca false i nie dotyka współdzielonego cache.

Przy bardzo dużych tabelach sam offset zwalnia, bo MySQL i tak przechodzi przez pominięte wiersze. Lepsza jest paginacja kluczem: WHERE ID > last_id ORDER BY ID ASC LIMIT N. W zadaniach CLI ten wzorzec jest wart dodatkowego kodu.

#Jak naprawić błąd memory limit exhausted przy eksporcie

Przy eksporcie setek tysięcy rekordów PHP może wyczerpać pamięć. Podniesienie limitu to doraźna łatka, przetwarzanie w małych paczkach to właściwa poprawka:

// Doraźnie: podnieś limit pamięci w kontekście panelu
wp_raise_memory_limit( 'admin' );

// Właściwie: przetwarzaj w małych paczkach
function wppoland_safe_export() {
    global $wpdb;

    $batch_size = 100;
    $offset     = 0;

    while ( true ) {
        $results = $wpdb->get_results(
            $wpdb->prepare(
                "SELECT ID, post_title, post_date FROM {$wpdb->posts} WHERE post_type = 'post' ORDER BY ID ASC LIMIT %d OFFSET %d",
                $batch_size,
                $offset
            )
        );

        if ( empty( $results ) ) {
            break;
        }

        // Zapisz wiersze do pliku...

        $offset += $batch_size;

        // Zwolnij wyniki ostatniego zapytania
        $wpdb->flush();
    }
}

wp_raise_memory_limit() podnosi limit do wartości WP_MAX_MEMORY_LIMIT zamiast wpisywać ją na sztywno w kodzie.

#Jak uniknąć timeoutu serwera przy eksporcie

// Zwiększ czas wykonania (część hostingów to blokuje)
set_time_limit( 0 );
ignore_user_abort( true );

// Albo dziel długą operację na kroki AJAX
add_action( 'wp_ajax_wppoland_long_export', 'wppoland_long_export_handler' );

function wppoland_long_export_handler() {
    check_ajax_referer( 'wppoland_long_export' );
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_send_json_error( null, 403 );
    }

    $page        = isset( $_POST['page'] ) ? absint( $_POST['page'] ) : 0;
    $total_pages = isset( $_POST['total_pages'] ) ? absint( $_POST['total_pages'] ) : 10;

    // Twoja funkcja dopisująca jedną stronę danych do pliku
    wppoland_process_export_page( $page );

    if ( $page < $total_pages ) {
        wp_send_json_success( array( 'next_page' => $page + 1 ) );
    }
    wp_send_json_success( array( 'complete' => true ) );
}

Oficjalny opis argumentów zapytania: klasa WP_Query na developer.wordpress.org.

#Jak wyeksportować wpisy do CSV przez WP-CLI

Jeśli masz 500 000 wpisów, przeglądarka zaliczy timeout. Użyj WP-CLI bezpośrednio na serwerze. Omijasz przeglądarkę i łańcuch timeoutów serwera WWW, a zadanie da się zautomatyzować cronem.

Komenda:

wp post list --post_type=product --fields=ID,post_title,post_status --format=csv > produkty.csv

Przydaje się przy:

  • dużych katalogach wpisów albo produktów
  • zrzutach zamówień WooCommerce, które w wp-admin skończyłyby się timeoutem
  • nocnych eksportach z crona do object storage albo wspólnego folderu
  • przekazaniu danych przy migracji i raportowaniu

Dokumentacja komendy: wp post list. Komenda przyjmuje --format=csv, json, yaml, ids, count albo table oraz dowolny argument WP_Query w postaci --<pole>=<wartość>. Na dużych stronach liczy się jeden szczegół: --posts_per_page ma domyślnie wartość -1, czyli wszystkie wpisy w jednym zapytaniu. Przy ogromnej tabeli stronicuj przez --posts_per_page=1000 --paged=N w pętli powłoki.

Dla użytkowników jest odpowiednik wp user list z domyślnymi polami ID, user_login, display_name, user_email, user_registered i roles. Każda komenda przyjmuje też globalny parametr --ssh=uzytkownik@host/sciezka, który uruchamia eksport na zdalnym serwerze z twojego komputera bez osobnej sesji.

Zaawansowany eksport (pola własne): Możesz napisać własną komendę WP-CLI, jeśli domyślne flagi nie wystarczą. Komenda otwiera plik, stronicuje zamówienia i zapisuje CSV na dysk, więc pamięć zostaje przewidywalna, a przerwane zadanie można wznowić od punktu kontrolnego.

// Komenda: wp wppoland export-orders
WP_CLI::add_command( 'wppoland export-orders', function ( $args, $assoc_args ) {
    $file = fopen( 'zamowienia.csv', 'w' );
    fputcsv( $file, array( 'ID zamówienia', 'Utworzone', 'Status', 'Suma', 'Klient' ), ',', '"', '' );

    $page = 1;
    do {
        $orders = wc_get_orders(
            array(
                'type'    => 'shop_order',
                'limit'   => 200,
                'paged'   => $page,
                'orderby' => 'ID',
                'order'   => 'ASC',
            )
        );

        foreach ( $orders as $order ) {
            $created = $order->get_date_created();
            fputcsv(
                $file,
                array(
                    $order->get_id(),
                    $created ? $created->date( 'c' ) : '',
                    $order->get_status(),
                    $order->get_total(),
                    $order->get_billing_email(),
                ),
                ',',
                '"',
                ''
            );
        }
        $page++;
    } while ( count( $orders ) === 200 );

    fclose( $file );
    WP_CLI::success( 'Eksport zakończony do zamowienia.csv' );
} );

limit i paged to udokumentowane parametry wc_get_orders(). Z limit => -1 komenda załadowałaby wszystkie zamówienia do pamięci przed zapisaniem pierwszego wiersza, czyli dokładnie to, czego paczki mają uniknąć.

#Komendy WP-CLI do masowego eksportu danych

#Podstawowe komendy WP-CLI do eksportu CSV

# Eksport wpisów (domyślny typ to post)
wp post list --format=csv > wszystkie-wpisy.csv

# Eksport stron
wp post list --post_type=page --format=csv > strony.csv

# Eksport użytkowników
wp user list --format=csv > uzytkownicy.csv

# Eksport z konkretnymi polami
wp post list --post_type=post --fields=ID,post_title,post_date,post_status --format=csv > wybrane-wpisy.csv

# Eksport komentarzy
wp comment list --format=csv > komentarze.csv

# Eksport z filtrowaniem
wp post list --post_status=publish --format=csv > opublikowane-wpisy.csv
wp post list --post_type=product --format=csv > produkty.csv

#Eksport wpisów z polami meta przez WP-CLI

# Eksport z metadanymi przez własny skrypt
wp eval-file export-with-meta.php

Gdzie export-with-meta.php:

<?php
// Plik: export-with-meta.php, uruchamiany przez wp eval-file
$file = fopen( 'wpisy-z-meta.csv', 'w' );
fwrite( $file, "\xEF\xBB\xBF" );
fputcsv( $file, array( 'ID', 'Tytuł', 'Autor', 'Views', 'Featured' ), ',', '"', '' );

$paged = 1;
do {
    $posts = get_posts(
        array(
            'post_type'      => 'post',
            'posts_per_page' => 500,
            'paged'          => $paged,
            'orderby'        => 'ID',
            'order'          => 'ASC',
        )
    );

    foreach ( $posts as $post ) {
        $views    = get_post_meta( $post->ID, 'views', true );
        $featured = get_post_meta( $post->ID, 'featured', true );

        fputcsv(
            $file,
            array(
                $post->ID,
                $post->post_title,
                get_the_author_meta( 'display_name', $post->post_author ),
                $views ? $views : 0,
                $featured ? 'Tak' : 'Nie',
            ),
            ',',
            '"',
            ''
        );
    }
    $paged++;
} while ( count( $posts ) === 500 );

fclose( $file );
WP_CLI::success( 'Eksport ukończony.' );

#Automatyczny eksport WP-CLI przez crontab

# Dodaj do crontab (crontab -e)
# Codziennie o 4:00, plik poza katalogiem publicznym
0 4 * * * cd /path/to/wordpress && wp post list --format=csv > /backups/posts-$(date +\%Y-\%m-\%d).csv

# Co godzinę: własny skrypt przez wp eval-file
0 * * * * cd /path/to/wordpress && wp eval-file hourly-export.php

#Komplet bazy danych przez wp db export

Czasem prośba nie dotyczy arkusza, tylko kopii wszystkiego. wp db export uruchamia mysqldump z danymi dostępowymi z wp-config.php i zapisuje plik SQL. Przez --tables wybierasz konkretne tabele, przez --exclude_tables je pomijasz, a - jako nazwa pliku wysyła zrzut na standardowe wyjście:

wp db export - --tables=wp_posts,wp_postmeta | gzip > tresci.sql.gz

Dopasuj prefiks tabel do swojej instalacji. Zrzut SQL służy do odtworzenia albo załadowania do innej bazy, nie do otwierania w Excelu. Zawiera też tabelę użytkowników z hashami haseł i często klucze API, które wtyczki trzymają w wp_options. Traktuj go jak sekret: nigdy w publicznym katalogu, nigdy jako załącznik maila.

#Jak pobrać wszystkie wpisy przez REST API w WordPressie

Jeśli budujesz dashboard w React lub aplikację headless, konsumujesz JSON, a nie CSV. Gdy odbiorcą jest program, a nie człowiek z Excelem, REST API bywa czystsze niż generowanie CSV w WordPressie.

Typowe przypadki:

  • synchronizacja treści do hurtowni danych albo narzędzia BI
  • zasilanie CRM-u użytkownikami albo leadami
  • frontendy headless, które potrzebują ustrukturyzowanych danych wpisów

WordPress REST API (/wp-json/wp/v2/posts) domyślnie stronicuje dane. Według przewodnika po paginacji per_page ma limit 100 rekordów, a każda odpowiedź niesie nagłówki X-WP-Total (liczba rekordów) i X-WP-TotalPages (liczba stron). Opis kolekcji: endpoint Posts.

Aby pobrać wszystkie dane, napisz pętlę albo rekurencyjną funkcję JS, która podąża za nagłówkiem X-WP-TotalPages, zamiast zgadywać, kiedy przestać. Uwierzytelniaj się przez Application Passwords albo przepływy OAuth obsługiwane przez hosting i szanuj limity zapytań. CSV zbuduj po stronie odbiorcy, jeśli arkusz nadal jest potrzebny. Taki podział zostawia WordPressowi kontrolę dostępu, a formatowanie pliku odbiorcy.

Nie wystawiaj uprzywilejowanych tras eksportu bez uwierzytelnienia. Traktuj endpointy eksportu jak każdą inną powierzchnię panelu bliską zapisowi, nawet jeśli tylko czytają.

#Eksport produktów WooCommerce do CSV bez wtyczki

Katalog nie wymaga kodu. Według dokumentacji importera i eksportera CSV produktów WooCommerce ma własne narzędzie do importu, eksportu i masowej aktualizacji produktów:

  1. Wejdź w Produkty > Wszystkie produkty i kliknij Eksportuj u góry strony.
  2. Wybierz kolumny albo zostaw opcję eksportu wszystkich.
  3. Filtruj po typach produktów i kategoriach albo zostaw oba pola na wszystkich.
  4. Zaznacz eksport wszystkich własnych metadanych, jeśli potrzebujesz danych WooCommerce albo innych wtyczek. Te kolumny dostają prefiks meta:.
  5. Kliknij Generuj CSV i poczekaj, aż przeglądarka pobierze plik.

Plik ma ten sam format, który czyta importer, więc nadaje się jako baza do masowych zmian cen albo stanów: eksportujesz, edytujesz w arkuszu, importujesz z powrotem. Narzędzie obejmuje katalog produktów. Zamówień nie eksportuje, o nich jest następna sekcja.

#Jak wyeksportować zamówienia WooCommerce do CSV

Eksport zamówień wygląda prosto, dopóki pozycje, zwroty i HPOS nie zmienią obrazu.

Pozycje to nie jeden wiersz. Zamówienie może mieć kilka produktów, opłat, pozycji wysyłki i podatków. Ustal z góry, czy wiersz CSV oznacza zamówienie, czy pozycję. Mieszanie obu bez jasnego schematu daje bezużyteczne arkusze.

Meta i pola własne rozdymają szerokość. Identyfikatory bramek płatności, numery przesyłek i meta wtyczek tworzą szerokie, rzadko wypełnione kolumny. Eksportuj tylko pola potrzebne w raporcie. Jeśli musisz zachować obie formy, dodaj drugi plik na pozycje.

Magazyny danych się różnią. Klasyczne wpisy shop_order i tabele High-Performance Order Storage (HPOS) odpytuje się inaczej. HPOS jest domyślnym magazynem od WooCommerce 8.2, według dokumentacji wc_get_orders(). Używaj CRUD WooCommerce (wc_get_orders() / WC_Order_Query) zamiast surowego WP_Query na shop_order, żeby eksporter przetrwał zmianę magazynu. Do filtrowania dat date_created przyjmuje zakres w postaci 2026-01-01...2026-03-31, liczony w strefie czasowej strony.

Filtry statusów mają znaczenie. Szkice, nieudane płatności, zwrócone zamówienia i kosz zawyżają albo zniekształcają raporty przychodu. Zapisz, które statusy uwzględniasz.

Dane osobowe i dostęp. Zamówienia zawierają imiona, maile i adresy. Kontrola uprawnień (manage_woocommerce albo własna capability), nonce i log audytu należą do każdego eksportera w panelu. Masowe zrzuty rób przez WP-CLI, żeby plik nigdy nie leżał w publicznie dostępnym katalogu uploads.

Wydajność. Złączenie wszystkich kluczy meta zamówienia w jednym zapytaniu SQL wygląda na szybkie, dopóki tabela nie urośnie. Stronicuj zamówienia, ładuj każde przez wc_get_order(), zapisz wiersz i porzuć obiekt. To wolniejsze na zamówienie niż jeden wielki JOIN, ale dużo stabilniejsze przy limitach pamięci.

Waluta i format. Zapisuj sumy jako zwykłe liczby. Formatowanie waluty zostaw Excelowi. Separatory tysięcy zależne od języka wewnątrz pól CSV psują import w innych narzędziach.

Pola dat. Zapisuj ISO-8601 albo znaczniki czasu Unix i formatuj dopiero w arkuszu. Mieszane lokalne formaty (03.04.2026 i 04/03/2026) powodują ciche błędy sortowania, gdy plik przekracza granice.

Zwroty i zwroty częściowe. Zdecyduj, czy zwrócone kwoty pojawiają się jako ujemne wiersze, osobne identyfikatory zwrotów czy sumy netto zamówienia. Zapisz wybór w README obok crona eksportu, żeby księgowość i programiści pracowali na tym samym arkuszu.

Poniższy kod tworzy eksport zamówień z panelu z danymi rozliczeniowymi i wysyłkowymi, według tych zasad:

function wppoland_export_woocommerce_orders_csv() {
    if ( ! current_user_can( 'manage_woocommerce' ) ) {
        wp_die( 'Brak uprawnień.' );
    }
    check_admin_referer( 'wppoland_export_orders' );

    if ( ! function_exists( 'wc_get_orders' ) ) {
        wp_die( 'WooCommerce nie jest aktywne.' );
    }

    $date_from = isset( $_GET['date_from'] ) ? sanitize_text_field( wp_unslash( $_GET['date_from'] ) ) : '';
    $date_to   = isset( $_GET['date_to'] ) ? sanitize_text_field( wp_unslash( $_GET['date_to'] ) ) : '';

    header( 'Content-Type: text/csv; charset=UTF-8' );
    header( 'Content-Disposition: attachment; filename="zamowienia-woocommerce-' . gmdate( 'Y-m-d' ) . '.csv"' );

    $fp = fopen( 'php://output', 'w' );

    // BOM dla Excela (poprawne polskie znaki)
    fwrite( $fp, "\xEF\xBB\xBF" );

    fputcsv(
        $fp,
        array( 'ID zamówienia', 'Data', 'Status', 'Email', 'Imię i nazwisko', 'Telefon', 'Adres', 'Miasto', 'Kod pocztowy', 'Kraj', 'Suma', 'Suma VAT', 'Metoda płatności', 'Metoda wysyłki', 'Uwagi' ),
        ',',
        '"',
        ''
    );

    $args = array(
        'type'    => 'shop_order',
        'limit'   => 50,
        'paged'   => 1,
        'orderby' => 'ID',
        'order'   => 'ASC',
        'status'  => array( 'completed', 'processing', 'on-hold', 'cancelled', 'refunded' ),
    );

    // Jeden parametr date_created: zakres albo pojedyncza granica
    if ( $date_from && $date_to ) {
        $args['date_created'] = $date_from . '...' . $date_to;
    } elseif ( $date_from ) {
        $args['date_created'] = '>=' . $date_from;
    } elseif ( $date_to ) {
        $args['date_created'] = '<=' . $date_to;
    }

    do {
        $orders = wc_get_orders( $args );

        foreach ( $orders as $order ) {
            $billing = $order->get_address( 'billing' );
            $created = $order->get_date_created();

            fputcsv(
                $fp,
                array(
                    $order->get_id(),
                    $created ? $created->date( 'Y-m-d H:i:s' ) : '',
                    $order->get_status(),
                    $order->get_billing_email(),
                    wppoland_csv_safe( $billing['first_name'] . ' ' . $billing['last_name'] ),
                    $billing['phone'],
                    wppoland_csv_safe( trim( $billing['address_1'] . ' ' . $billing['address_2'] ) ),
                    wppoland_csv_safe( $billing['city'] ),
                    $billing['postcode'],
                    $billing['country'],
                    $order->get_total(),
                    $order->get_total_tax(),
                    $order->get_payment_method_title(),
                    $order->get_shipping_method(),
                    wppoland_csv_safe( $order->get_customer_note() ),
                ),
                ',',
                '"',
                ''
            );
        }

        $args['paged']++;
    } while ( count( $orders ) === 50 );

    fclose( $fp );
    exit;
}
add_action( 'admin_post_wppoland_export_orders', 'wppoland_export_woocommerce_orders_csv' );

Sumy idą jako zwykłe liczby z get_total(), bez przecinka dziesiętnego: Excel w polskich ustawieniach sformatuje je sam, a inne narzędzia nie potkną się o separator. Funkcja wppoland_csv_safe() pochodzi z sekcji o wstrzykiwaniu formuł niżej.

#Formularz eksportu zamówień w panelu WooCommerce

Dodaj formularz do eksportu w panelu WooCommerce:

function wppoland_export_orders_page() {
    ?>
    <div class="wrap">
        <h1>Eksport zamówień</h1>
        <form method="get" action="<?php echo esc_url( admin_url( 'admin-post.php' ) ); ?>">
            <input type="hidden" name="action" value="wppoland_export_orders">
            <?php wp_nonce_field( 'wppoland_export_orders' ); ?>
            <table class="form-table" role="presentation">
                <tr>
                    <th scope="row"><label for="date_from">Data od</label></th>
                    <td><input type="date" id="date_from" name="date_from" /></td>
                </tr>
                <tr>
                    <th scope="row"><label for="date_to">Data do</label></th>
                    <td><input type="date" id="date_to" name="date_to" /></td>
                </tr>
            </table>
            <?php submit_button( 'Eksportuj do CSV' ); ?>
        </form>
    </div>
    <?php
}

function wppoland_add_export_menu() {
    add_submenu_page(
        'woocommerce',
        'Eksport zamówień',
        'Eksport CSV',
        'manage_woocommerce',
        'wppoland-export-orders',
        'wppoland_export_orders_page'
    );
}
add_action( 'admin_menu', 'wppoland_add_export_menu' );

#Eksport pól ACF i meta do CSV

Wiele wtyczek (np. ACF, Meta Box) przechowuje dane w niestandardowych polach. Tak wyeksportujesz je razem z wpisami:

function wppoland_export_posts_with_meta_csv() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'Brak uprawnień.' );
    }
    check_admin_referer( 'wppoland_export_meta' );

    header( 'Content-Type: text/csv; charset=UTF-8' );
    header( 'Content-Disposition: attachment; filename="wpisy-z-polami-' . gmdate( 'Y-m-d' ) . '.csv"' );

    $fp = fopen( 'php://output', 'w' );
    fwrite( $fp, "\xEF\xBB\xBF" );

    $custom_fields = array( 'views', 'featured', 'external_url', 'video_id' );
    $headers       = array_merge( array( 'ID', 'Tytuł', 'Autor', 'Data', 'Status', 'Kategoria', 'Tagi', 'Miniatura' ), $custom_fields );
    fputcsv( $fp, $headers, ',', '"', '' );

    $paged = 1;
    do {
        $query = new WP_Query(
            array(
                'post_type'      => 'post',
                'post_status'    => 'publish',
                'posts_per_page' => 100,
                'paged'          => $paged,
                'orderby'        => 'ID',
                'order'          => 'ASC',
                'no_found_rows'  => true,
            )
        );

        foreach ( $query->posts as $post ) {
            $categories = get_the_category( $post->ID );
            $tags       = get_the_tags( $post->ID );

            fputcsv(
                $fp,
                array(
                    $post->ID,
                    wppoland_csv_safe( get_the_title( $post ) ),
                    get_the_author_meta( 'display_name', $post->post_author ),
                    get_the_date( 'Y-m-d H:i:s', $post ),
                    $post->post_status,
                    implode( ', ', wp_list_pluck( $categories, 'name' ) ),
                    $tags ? implode( ', ', wp_list_pluck( $tags, 'name' ) ) : '',
                    (string) get_the_post_thumbnail_url( $post, 'full' ),
                    get_post_meta( $post->ID, 'views', true ),
                    get_post_meta( $post->ID, 'featured', true ) ? 'Tak' : 'Nie',
                    get_post_meta( $post->ID, 'external_url', true ),
                    get_post_meta( $post->ID, 'video_id', true ),
                ),
                ',',
                '"',
                ''
            );
        }
        $paged++;
    } while ( count( $query->posts ) === 100 );

    fclose( $fp );
    exit;
}
add_action( 'admin_post_wppoland_export_meta', 'wppoland_export_posts_with_meta_csv' );

Pętla kończy się, gdy strona zwróci mniej niż 100 wpisów. Konstrukcja while ( $query = new WP_Query( $args ) ) nigdy nie zwraca fałszu, bo nowy obiekt zawsze jest prawdziwy, więc bez osobnego warunku kręciłaby się w nieskończoność.

#Jak ustawić automatyczny eksport CSV przez WP-Cron

Jeśli potrzebujesz codziennej kopii danych, skonfiguruj eksport przez WordPress Cron. Plik z danymi osobowymi nie powinien trafiać do wp-content/uploads, bo ten katalog jest publicznie dostępny. Zapisuj go poza katalogiem głównym strony:

// Katalog poza public_html, np. zdefiniowany w wp-config.php:
// define( 'WPPOLAND_EXPORT_DIR', '/home/konto/exports/' );

// Rejestracja harmonogramu przy aktywacji wtyczki
register_activation_hook( __FILE__, function () {
    if ( ! wp_next_scheduled( 'wppoland_daily_export' ) ) {
        wp_schedule_event( strtotime( 'tomorrow 03:00' ), 'daily', 'wppoland_daily_export' );
    }
} );

add_action( 'wppoland_daily_export', 'wppoland_run_daily_export' );

function wppoland_run_daily_export() {
    $dir = trailingslashit( WPPOLAND_EXPORT_DIR );
    if ( ! file_exists( $dir ) ) {
        wp_mkdir_p( $dir );
    }

    $filename = $dir . 'users-' . gmdate( 'Y-m-d' ) . '.csv';

    $fp = fopen( $filename, 'w' );
    fwrite( $fp, "\xEF\xBB\xBF" );
    fputcsv( $fp, array( 'ID', 'Login', 'Email', 'Data rejestracji', 'Rola' ), ',', '"', '' );

    $args = array( 'number' => 500, 'offset' => 0, 'orderby' => 'ID', 'order' => 'ASC' );

    while ( $users = get_users( $args ) ) {
        foreach ( $users as $user ) {
            fputcsv(
                $fp,
                array( $user->ID, $user->user_login, $user->user_email, $user->user_registered, implode( ', ', $user->roles ) ),
                ',',
                '"',
                ''
            );
        }
        $args['offset'] += 500;
        if ( count( $users ) < 500 ) {
            break;
        }
    }

    fclose( $fp );

    // Powiadomienie bez załącznika i bez danych osobowych w treści
    wp_mail( get_option( 'admin_email' ), 'Codzienny eksport WordPress', 'Eksport zakończony: ' . basename( $filename ) );
}

// Wyrejestrowanie przy wyłączaniu wtyczki
register_deactivation_hook( __FILE__, function () {
    wp_clear_scheduled_hook( 'wppoland_daily_export' );
} );

strtotime( 'tomorrow 03:00' ) liczy godzinę w strefie PHP, a WordPress ustawia ją na UTC, więc w Polsce zadanie ruszy o 4:00 albo 5:00 czasu lokalnego. WP-Cron uruchamia się dopiero przy wizycie na stronie; przy małym ruchu podepnij wp cron event run --due-now pod systemowy crontab.

#Jak eksportować dane osobowe z WordPressa zgodnie z RODO

#Pseudonimizacja danych użytkowników w eksporcie CSV

Przy eksporcie danych użytkowników do analiz rozważ zastąpienie adresu email skrótem:

function wppoland_export_users_pseudonymised() {
    if ( ! current_user_can( 'manage_options' ) ) {
        wp_die( 'Brak uprawnień.' );
    }
    check_admin_referer( 'wppoland_export_users_pseudo' );

    header( 'Content-Type: text/csv; charset=utf-8' );
    header( 'Content-Disposition: attachment; filename="uzytkownicy-pseudonimizowani.csv"' );

    $fp = fopen( 'php://output', 'w' );
    fputcsv( $fp, array( 'ID', 'Hash email', 'Rola', 'Data rejestracji' ), ',', '"', '' );

    $args = array( 'number' => 100, 'offset' => 0, 'orderby' => 'ID', 'order' => 'ASC' );

    while ( $users = get_users( $args ) ) {
        foreach ( $users as $user ) {
            // HMAC z kluczem z wp-config.php zamiast gołego sha256
            $email_hash = hash_hmac( 'sha256', strtolower( $user->user_email ), wp_salt( 'auth' ) );

            fputcsv(
                $fp,
                array( $user->ID, $email_hash, implode( ', ', $user->roles ), $user->user_registered ),
                ',',
                '"',
                ''
            );
        }
        $args['offset'] += 100;
        if ( count( $users ) < 100 ) {
            break;
        }
    }

    fclose( $fp );
    exit;
}

To pseudonimizacja, nie anonimizacja. Motyw 26 RODO mówi, że dane spseudonimizowane, które przy użyciu dodatkowych informacji można przypisać osobie fizycznej, należy uznać za informacje o osobie możliwej do zidentyfikowania. Plik dalej jest daną osobową, a ID użytkownika w pierwszej kolumnie i tak prowadzi do konta. Goły sha256 z adresu email da się odwrócić słownikiem znanych adresów, dlatego przykład używa HMAC z sekretem z wp-config.php.

#Jak wyeksportować dane użytkownika na jego żądanie

Gdy prośba przychodzi od osoby, która korzysta ze swoich praw, nie potrzebujesz własnego eksportera. Dokumentacja dla twórców wtyczek opisuje narzędzie eksportu danych osobowych dodane w WordPressie 4.9.6 (Narzędzia > Eksport danych osobowych), przygotowane z myślą o przepisach takich jak RODO.

Kluczem każdego eksportu jest adres email, co obejmuje zarejestrowanych użytkowników i osoby, które zostawiły tylko komentarz. Administrator wpisuje nazwę użytkownika albo email, WordPress wysyła link potwierdzający, a po potwierdzeniu administrator pobiera ZIP albo wysyła go bezpośrednio do osoby. W środku jest strona indeksu HTML z danymi pogrupowanymi, np. komentarzami. Wtyczki, które przechowują własne dane osobowe, podpinają się filtrem wp_privacy_personal_data_exporters, żeby ich dane trafiły do tego samego pliku.

#Jak zapobiec wstrzykiwaniu formuł w wyeksportowanym CSV

CSV z WordPressa zawiera dane wpisane przez użytkowników: nazwy wyświetlane, komentarze, pola formularzy, adresy rozliczeniowe. OWASP opisuje to ryzyko jako CSV injection, czyli wstrzykiwanie formuł: pojawia się, gdy strona osadza niezaufane dane w pliku CSV. Jeśli komórka zaczyna się od =, +, - albo @, od tabulatora, powrotu karetki albo nowej linii, arkusz może przy otwarciu potraktować ją jak formułę.

OWASP zaleca, by żadna komórka nie zaczynała się od tych znaków, np. przez dodanie pojedynczego apostrofu na początku, dzięki czemu program odczyta komórkę jako tekst:

function wppoland_csv_safe( $value ) {
    $value = (string) $value;
    if ( '' !== $value && in_array( $value[0], array( '=', '+', '-', '@', "\t", "\r", "\n" ), true ) ) {
        return "'" . $value;
    }
    return $value;
}

Stosuj ją do kolumn z wolnym tekstem, nie do kwot liczonych przez WooCommerce: ujemna suma zwrotu zaczyna się od - i zamieniłaby się w tekst, a to psuje sumowanie w arkuszu.

#Wtyczki do eksportu WordPress do CSV i Excel

#Darmowe wtyczki do eksportu CSV

  1. WP All Export - eksport z interfejsem drag-and-drop
  2. Stream - logowanie aktywności użytkowników z możliwością eksportu logów

#Płatne wtyczki do eksportu z supportem

Komercyjne wtyczki z gwarancją wsparcia i aktualizacji kupisz m.in. w sklepie plogins:

  1. WP All Export Pro + WP All Import Pro - kompleksowe rozwiązanie do importu i eksportu, z dodatkiem do pól ACF
  2. WooCommerce Customer / Order / Coupon Export - eksport klientów, zamówień i kuponów WooCommerce

#Jak zainstalować WP-CLI

# Instalacja WP-CLI
curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar
chmod +x wp-cli.phar
sudo mv wp-cli.phar /usr/local/bin/wp

# Weryfikacja
wp --info

#Polskie znaki i inne błędy eksportu CSV

#Jak naprawić polskie znaki w pliku CSV

Excel w Windows zgaduje kodowanie pliku CSV. Pomoc Microsoftu potwierdza, że plik CSV w UTF-8 zapisany z BOM otwiera się normalnie. Bez BOM udokumentowana droga to Dane > Pobierz dane > Z pliku > Z pliku tekstowego/CSV, gdzie wybierasz kodowanie przed wczytaniem.

// Dodaj BOM na początku pliku, przed wierszem nagłówka
$fp = fopen( 'php://output', 'w' );
fwrite( $fp, "\xEF\xBB\xBF" );

Filtr convert.iconv.UTF-8/UTF-8 niczego tu nie naprawia, bo konwertuje UTF-8 na UTF-8. Gdy plik trafia do innego skryptu, a nie do Excela, pomiń BOM: wiele parserów czyta te trzy bajty jako część nazwy pierwszej kolumny.

#Błąd headers already sent przy eksporcie CSV

Komunikat oznacza, że przed header() poszedł już jakiś bajt: spacja przed <?php, BOM w pliku motywu, ostrzeżenie PHP albo echo z innej wtyczki. Podepnij eksport pod admin_post_*, które rusza przed wysłaniem jakiegokolwiek HTML panelu, a resztki bufora wyczyść przed nagłówkami:

// Na początku funkcji eksportu, przed header():
while ( ob_get_level() > 0 ) {
    ob_end_clean();
}

Nie wywołuj ob_end_clean() na końcu eksportu: skasowałbyś wygenerowany CSV, zanim trafi do przeglądarki.

#Dlaczego plik CSV z eksportu jest pusty

// Sprawdź uprawnienia
if ( ! current_user_can( 'manage_options' ) ) {
    wp_die( 'Brak uprawnień administratora.' );
}

// Debuguj zapytanie
error_log( 'Eksport - liczba użytkowników: ' . count( $users ) );

#Którą metodę eksportu CSV wybrać

MetodaZaletyWadyZastosowanie
PHP fputcsvNatywne, szybkie, bez zewnętrznych zależnościWymaga kodu PHPMałe i średnie zbiory, niestandardowe eksporty
WP-CLIBrak limitów żądania HTTP, przetwarzanie wsadoweWymaga dostępu SSHDuże zbiory, automatyzacja
REST APIProgramatyczny dostęp, headlessStronicowanie, JSON zamiast CSVAplikacje, integracje
Narzędzia > Eksport (WXR)W rdzeniu, bez koduXML, nie arkuszMigracja treści
WtyczkiInterfejs GUI, gotowe rozwiązaniaOgraniczenia wersji darmowychUżytkownicy nietechniczni

#Metoda eksportu a wielkość sklepu

  • Mały sklep (< 1000 zamówień): PHP + fputcsv
  • Duży sklep (> 10 000 zamówień): WP-CLI + cron
  • Wiele sklepów (franczyza): WP-CLI + skrypt bash
  • Aplikacja headless: REST API
  • Zgodność z RODO: narzędzie eksportu danych osobowych z rdzenia i pseudonimizacja w analizach
  • Automatyczna kopia: wp db export + cron + FTP/S3

#Jak bezpiecznie eksportować dane z WordPressa

  1. Weryfikuj uprawnienia i nonce przed eksportem
  2. Sanityzuj dane wejściowe (daty, filtry)
  3. Szyfruj wrażliwe eksporty przed wysłaniem
  4. Loguj eksporty na potrzeby audytu RODO
  5. Automatycznie usuwaj stare pliki z serwera

#Jak przyspieszyć eksport dużych zbiorów danych

  1. Używaj przetwarzania wsadowego
  2. Strumieniuj zamiast ładować do pamięci
  3. Optymalizuj zapytania (pobieraj tylko potrzebne kolumny)
  4. Używaj indeksów w bazie danych
  5. Cache’uj wyniki dla powtarzających się eksportów

#Lista kontrolna eksportu danych z WordPressa

  1. W WordPressie wybieraj CSV zamiast XLSX, a WXR tylko do przenoszenia treści.
  2. Strumieniuj przez fputcsv() z jawnym escape i kontrolą uprawnień przy małych pobraniach z panelu.
  3. Pobieraj paczkami przez WP_Query albo zapytania WooCommerce; wyłącz zbędne cache.
  4. Duże i cykliczne zadania przenieś do WP-CLI.
  5. Używaj REST API, gdy dane pobiera inny system.
  6. W WooCommerce ustal, co znaczy wiersz (zamówienie czy pozycja), i używaj API CRUD, żeby HPOS nie zepsuł eksportera.
  7. Neutralizuj komórki z wolnym tekstem, które zaczynają się od =, +, - albo @.

Twoje dane należą do Ciebie. Upewnij się, że zawsze możesz je wyciągnąć.

Eksport danych bywa ostatnią rzeczą, o którą ktoś dba, aż do momentu zmiany systemu albo żądania z RODO. Jeśli chcesz mieć te ścieżki przygotowane wcześniej, przygotujemy je w ramach opieki nad stroną.

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.

Jaki jest najbezpieczniejszy sposób eksportu dużych zbiorów danych z WordPressa?#
Przy bardzo dużych eksportach zwykle WP-CLI, bo omija limity wykonania żądania w przeglądarce i daje lepszą kontrolę nad pamięcią i paczkami.
Eksportować z WordPressa do CSV czy do XLSX?#
CSV jest zwykle najprostszy i najbardziej przenośny. Jeśli ktoś potrzebuje właśnie Excela, wyeksportuj CSV i otwórz go albo przekonwertuj później.
Czy da się wyeksportować zamówienia WooCommerce bez wtyczki?#
Tak. Zamówienia wyeksportujesz przez PHP z wc_get_orders(), WP-CLI albo REST API, zależnie od wielkości zbioru i tego, jak dużej kontroli potrzebujesz nad pozycjami i meta.
Jak uniknąć problemów z pamięcią i timeoutem podczas eksportu?#
Strumieniuj wiersze zamiast budować wszystko w pamięci, pobieraj rekordy paczkami, a bardzo duże eksporty przenieś do WP-CLI albo zadań w tle.
Kiedy użyć REST API zamiast pobierania CSV?#
Gdy inny system potrzebuje ustrukturyzowanego JSON na żądanie albo według harmonogramu. CSV jest dla człowieka, który potrzebuje arkusza.

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

Porozmawiajmy

Polecane artykuły