惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
小众软件
小众软件
The Cloudflare Blog
博客园 - 【当耐特】
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
酷 壳 – CoolShell
酷 壳 – CoolShell
WordPress大学
WordPress大学
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
V
Visual Studio Blog
量子位
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
美团技术团队
S
SegmentFault 最新的问题
宝玉的分享
宝玉的分享
博客园 - 叶小钗
月光博客
月光博客
Apple Machine Learning Research
Apple Machine Learning Research
T
Tailwind CSS Blog
博客园 - 聂微东
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
J
Java Code Geeks
Y
Y Combinator Blog
D
Docker
Microsoft Azure Blog
Microsoft Azure Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Exporting WordPress Content to JSON for Headless and Stat...
anti Gym Clu · 2026-04-27 · via DEV Community
Cover image for Exporting WordPress Content to JSON for Headless and Static Sites

anti Gym Club

You've got years of content in WordPress. Now you want to move to a static site generator, build a headless frontend, or just back up your content in a portable format.

The WordPress REST API exists, but it requires authentication setup, pagination handling, and multiple requests. Sometimes you just want a clean JSON export.

Here's how to build one.

The Export Class

<?php
/**
 * WordPress Content Exporter
 */
class Content_Exporter {

    /**
     * Export posts based on options
     *
     * @param array $options Export options
     * @return array
     */
    public function export( $options = array() ) {
        $defaults = array(
            'post_type'      => 'post',
            'post_status'    => 'publish',
            'posts_per_page' => -1,
            'category'       => '',
            'include'        => array( 'content', 'excerpt', 'featured_image', 'taxonomies', 'meta' ),
        );

        $options = wp_parse_args( $options, $defaults );

        $args = array(
            'post_type'      => sanitize_key( $options['post_type'] ),
            'post_status'    => sanitize_key( $options['post_status'] ),
            'posts_per_page' => intval( $options['posts_per_page'] ),
            'orderby'        => 'date',
            'order'          => 'DESC',
        );

        // Category filter
        if ( ! empty( $options['category'] ) ) {
            $args['cat'] = intval( $options['category'] );
        }

        $query = new WP_Query( $args );
        $posts = array();

        if ( $query->have_posts() ) {
            while ( $query->have_posts() ) {
                $query->the_post();
                $posts[] = $this->format_post( get_post(), $options['include'] );
            }
            wp_reset_postdata();
        }

        return $posts;
    }

    /**
     * Format a single post
     */
    private function format_post( $post, $include ) {
        $data = array(
            'id'       => $post->ID,
            'title'    => $post->post_title,
            'slug'     => $post->post_name,
            'date'     => $post->post_date,
            'modified' => $post->post_modified,
            'status'   => $post->post_status,
            'type'     => $post->post_type,
            'url'      => get_permalink( $post->ID ),
            'author'   => $this->get_author( $post->post_author ),
        );

        if ( in_array( 'content', $include, true ) ) {
            $data['content'] = $post->post_content;
            $data['content_rendered'] = apply_filters( 'the_content', $post->post_content );
        }

        if ( in_array( 'excerpt', $include, true ) ) {
            $data['excerpt'] = $post->post_excerpt
                ? $post->post_excerpt
                : wp_trim_words( $post->post_content, 55 );
        }

        if ( in_array( 'featured_image', $include, true ) ) {
            $data['featured_image'] = $this->get_featured_image( $post->ID );
        }

        if ( in_array( 'taxonomies', $include, true ) ) {
            $data['taxonomies'] = $this->get_taxonomies( $post->ID, $post->post_type );
        }

        if ( in_array( 'meta', $include, true ) ) {
            $data['meta'] = $this->get_public_meta( $post->ID );
        }

        return $data;
    }

    /**
     * Get author data
     */
    private function get_author( $author_id ) {
        $user = get_userdata( $author_id );
        if ( ! $user ) {
            return null;
        }

        return array(
            'id'   => $user->ID,
            'name' => $user->display_name,
            'slug' => $user->user_nicename,
        );
    }

    /**
     * Get featured image
     */
    private function get_featured_image( $post_id ) {
        $thumbnail_id = get_post_thumbnail_id( $post_id );
        if ( ! $thumbnail_id ) {
            return null;
        }

        $image = wp_get_attachment_image_src( $thumbnail_id, 'full' );
        $alt = get_post_meta( $thumbnail_id, '_wp_attachment_image_alt', true );

        return array(
            'id'     => $thumbnail_id,
            'url'    => $image ? $image[0] : '',
            'width'  => $image ? $image[1] : 0,
            'height' => $image ? $image[2] : 0,
            'alt'    => $alt,
        );
    }

    /**
     * Get taxonomies
     */
    private function get_taxonomies( $post_id, $post_type ) {
        $taxonomies = get_object_taxonomies( $post_type, 'objects' );
        $data = array();

        foreach ( $taxonomies as $taxonomy ) {
            $terms = get_the_terms( $post_id, $taxonomy->name );
            if ( ! $terms || is_wp_error( $terms ) ) {
                continue;
            }

            $data[ $taxonomy->name ] = array_map( function( $term ) {
                return array(
                    'id'   => $term->term_id,
                    'name' => $term->name,
                    'slug' => $term->slug,
                );
            }, $terms );
        }

        return $data;
    }

    /**
     * Get public meta (exclude private _ prefixed keys)
     */
    private function get_public_meta( $post_id ) {
        $meta = get_post_meta( $post_id );
        $data = array();

        foreach ( $meta as $key => $values ) {
            // Skip private meta
            if ( strpos( $key, '_' ) === 0 ) {
                continue;
            }
            $data[ $key ] = count( $values ) === 1 ? $values[0] : $values;
        }

        return $data;
    }

    /**
     * Convert to JSON
     */
    public function to_json( $posts ) {
        return wp_json_encode(
            $posts,
            JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
        );
    }
}

Enter fullscreen mode Exit fullscreen mode

Usage

$exporter = new Content_Exporter();

// Export all posts
$posts = $exporter->export();
$json = $exporter->to_json( $posts );

// Export pages only
$pages = $exporter->export( array(
    'post_type' => 'page',
) );

// Export specific category
$tutorials = $exporter->export( array(
    'category' => 5, // category ID
) );

// Minimal export (no content, just metadata)
$minimal = $exporter->export( array(
    'include' => array( 'excerpt', 'featured_image' ),
) );

Enter fullscreen mode Exit fullscreen mode

Adding an Admin Interface

/**
 * Add export page to admin
 */
function add_export_admin_page() {
    add_management_page(
        'Export Content',
        'Export Content',
        'manage_options',
        'content-export',
        'render_export_page'
    );
}
add_action( 'admin_menu', 'add_export_admin_page' );

/**
 * Handle export
 */
function render_export_page() {
    // Handle form submission
    if ( isset( $_POST['export_nonce'] ) &&
         wp_verify_nonce( $_POST['export_nonce'], 'content_export' ) ) {

        $exporter = new Content_Exporter();

        $options = array(
            'post_type' => sanitize_key( $_POST['post_type'] ?? 'post' ),
            'include'   => array_map( 'sanitize_key', $_POST['include'] ?? array() ),
        );

        $posts = $exporter->export( $options );
        $json = $exporter->to_json( $posts );

        // Trigger download
        header( 'Content-Type: application/json' );
        header( 'Content-Disposition: attachment; filename="export-' . date('Y-m-d') . '.json"' );
        echo $json;
        exit;
    }

    // Render form
    $post_types = get_post_types( array( 'public' => true ), 'objects' );
    ?>
    <div class="wrap">
        <h1>Export Content to JSON</h1>

        <form method="post">
            <?php wp_nonce_field( 'content_export', 'export_nonce' ); ?>

            <table class="form-table">
                <tr>
                    <th>Post Type</th>
                    <td>
                        <select name="post_type">
                            <?php foreach ( $post_types as $type ) : ?>
                                <option value="<?php echo esc_attr( $type->name ); ?>">
                                    <?php echo esc_html( $type->labels->name ); ?>
                                </option>
                            <?php endforeach; ?>
                        </select>
                    </td>
                </tr>
                <tr>
                    <th>Include</th>
                    <td>
                        <label><input type="checkbox" name="include[]" value="content" checked> Content</label><br>
                        <label><input type="checkbox" name="include[]" value="excerpt" checked> Excerpt</label><br>
                        <label><input type="checkbox" name="include[]" value="featured_image" checked> Featured Image</label><br>
                        <label><input type="checkbox" name="include[]" value="taxonomies" checked> Taxonomies</label><br>
                        <label><input type="checkbox" name="include[]" value="meta"> Custom Meta</label>
                    </td>
                </tr>
            </table>

            <?php submit_button( 'Download JSON' ); ?>
        </form>
    </div>
    <?php
}

Enter fullscreen mode Exit fullscreen mode

Converting to Markdown

For static site generators like Hugo, Jekyll, or Astro, Markdown with frontmatter is often more useful:

/**
 * Convert posts array to Markdown files content
 */
public function to_markdown( $posts ) {
    $output = '';

    foreach ( $posts as $post ) {
        // Frontmatter
        $output .= "---\n";
        $output .= 'title: "\"' . str_replace( '\"', '\"', $post['title'] ) . \"\"\n\";"
        $output .= 'slug: ' . $post['slug'] . "\n";
        $output .= 'date: ' . $post['date'] . "\n";

        if ( ! empty( $post['author']['name'] ) ) {
            $output .= 'author: ' . $post['author']['name'] . "\n";
        }

        if ( ! empty( $post['featured_image']['url'] ) ) {
            $output .= 'featured_image: ' . $post['featured_image']['url'] . "\n";
        }

        if ( ! empty( $post['taxonomies']['category'] ) ) {
            $cats = array_column( $post['taxonomies']['category'], 'name' );
            $output .= 'categories: [' . implode( ', ', $cats ) . "]\n";
        }

        $output .= "---\n\n";

        // Content
        $output .= '# ' . $post['title'] . "\n\n";

        if ( ! empty( $post['content'] ) ) {
            $output .= $this->html_to_markdown( $post['content'] );
        }

        $output .= "\n\n---\n\n";
    }

    return trim( $output );
}

/**
 * Basic HTML to Markdown
 */
private function html_to_markdown( $html ) {
    // Remove WordPress block comments
    $html = preg_replace( '/<!-- \/?wp:[^>]+ -->/', '', $html );

    $replacements = array(
        '/<h1[^>]*>(.*?)<\/h1>/i'   => "# $1\n\n",
        '/<h2[^>]*>(.*?)<\/h2>/i'   => "## $1\n\n",
        '/<h3[^>]*>(.*?)<\/h3>/i'   => "### $1\n\n",
        '/<p[^>]*>(.*?)<\/p>/is'    => "$1\n\n",
        '/<strong>(.*?)<\/strong>/i' => "**$1**",
        '/<em>(.*?)<\/em>/i'        => "*$1*",
        '/<a[^>]+href=["\']([^"\']+)["\'][^>]*>(.*?)<\/a>/i' => "[$2]($1)",
        '/<ul[^>]*>(.*?)<\/ul>/is'  => "$1\n",
        '/<li[^>]*>(.*?)<\/li>/i'   => "- $1\n",
        '/<code>(.*?)<\/code>/i'    => "`$1`",
        '/<br\s*\/?>/i'             => "\n",
    );

    foreach ( $replacements as $pattern => $replacement ) {
        $html = preg_replace( $pattern, $replacement, $html );
    }

    return wp_strip_all_tags( $html );
}

Enter fullscreen mode Exit fullscreen mode

Output Example

{
  "id": 123,
  "title": "Getting Started with WordPress",
  "slug": "getting-started-wordpress",
  "date": "2026-04-15 10:30:00",
  "modified": "2026-04-18 14:22:00",
  "status": "publish",
  "type": "post",
  "url": "https://example.com/getting-started-wordpress/",
  "author": {
    "id": 1,
    "name": "John Doe",
    "slug": "john-doe"
  },
  "content": "<!-- wp:paragraph -->Raw content here...",
  "content_rendered": "<p>Rendered HTML content...</p>",
  "excerpt": "A quick introduction to WordPress...",
  "featured_image": {
    "id": 456,
    "url": "https://example.com/wp-content/uploads/image.jpg",
    "width": 1200,
    "height": 630,
    "alt": "WordPress dashboard"
  },
  "taxonomies": {
    "category": [
      { "id": 1, "name": "Tutorials", "slug": "tutorials" }
    ],
    "post_tag": [
      { "id": 5, "name": "WordPress", "slug": "wordpress" }
    ]
  }
}

Enter fullscreen mode Exit fullscreen mode

Use Cases

  • Migrating to Gatsby/Next.js export, transform, import
  • Backup portable content outside the database
  • Headless WordPress pre-generate JSON for frontend consumption
  • Static site migration export to Markdown for Hugo/Jekyll
  • Content analysis process exported JSON with external tools

ACF Support

If you use Advanced Custom Fields, add this method:

/**
 * Get ACF fields
 */
private function get_acf_fields( $post_id ) {
    if ( ! function_exists( 'get_fields' ) ) {
        return array();
    }

    $fields = get_fields( $post_id );
    return $fields ? $fields : array();
}

Enter fullscreen mode Exit fullscreen mode

Then add 'acf' to the include array option.


If you need a ready-to-use solution with admin UI, date filtering, and format options, I built Content Exporter for exactly this.

Resources: