Contents
Introduction
This article is a complete, detailed tutorial showing how to create a JSON exporter for custom content in WordPress using PHP. You will find practical code examples (for a REST endpoint, an admin export link, and a WP-CLI command), full explanations of options and edge cases, performance and security recommendations, and examples for exporting post fields, custom fields (meta), taxonomies, featured images and attachments, plus options for large-site exports and gzipped downloads. Copy the code snippets into a plugin or mu-plugin file, adapt the names and capabilities to your project, and deploy.
Prerequisites
- WordPress 4.7 (REST API included) or newer.
- Familiarity with creating a plugin or adding code to a mu-plugin.
- Knowledge of PHP, WP_Query, and WordPress functions like get_post_meta(), wp_get_post_terms(), wp_get_attachment_url().
- Access to wp-config.php and server settings if exporting very large datasets (memory, execution time).
Overview: approaches
You can provide JSON exports in three common ways — choose one or implement multiple for different consumers:
- REST API endpoint — good for programmatic consumption, supports query parameters, pagination, and standard authentication (cookies, application passwords, OAuth, etc.).
- Admin export page or admin_post handler — convenient for site admins to click a link and download a JSON file use nonces and capability checks.
- WP-CLI command — best for very large sites or automation on the server, avoids web server timeouts and memory limits.
Designing the JSON schema
Before writing code, decide what data shape you need. A recommended generic export item for a post-like object:
- ID, post_type, post_title, post_content, post_excerpt, post_status, post_date, post_modified
- author (ID, user_login, display_name)
- meta — associative array of custom fields (unserialized where appropriate)
- terms — keyed by taxonomy, with term_id, name, slug, taxonomy
- featured_image — ID and URL (optionally with metadata or base64 data if you want to embed files)
- attachments — list of attachments with ID, mime type, url, metadata (optionally base64)
Minimal schema example (JSON)
{ schema_version: 1.0, site_url: https://example.com, exported_at: 2025-09-26T12:34:56Z, data: [ { ID: 123, post_type: product, post_title: My product, post_content: ..., post_date: 2025-01-01 12:00:00, author: { ID: 2, user_login: editor, display_name: Editor Name }, meta: { _price: 29.99, sizes: [S,M,L] }, terms: { product_cat: [ {term_id: 5, name: T-shirts, slug: t-shirts, taxonomy: product_cat} ] }, featured_image: { ID: 555, url: https://example.com/wp-content/uploads/2025/01/image.jpg } } ] }
Export via REST API: full example
This REST-based exporter is flexible and supports query parameters, pagination, and include/exclude flags for meta, terms, and attachments. Add the code to a plugin file (e.g., my-json-exporter.php) or a mu-plugin.
GET, callback => my_export_rest_callback, permission_callback => function( request ) { // Customize permission: restrict to users who can export // For public exports remove or adjust this to your needs. return current_user_can(export) current_user_can(manage_options) }, )) }) / REST callback: validate params, build query and return WP_REST_Response. Query parameters supported: - post_type (string) default: post - page (int) default: 1 - per_page (int) default: 50 (limit on server recommended) - meta (bool) include postmeta - default true - terms (bool) include taxonomies - default true - attachments (bool) include attachments metadata - default false / function my_export_rest_callback( WP_REST_Request request ) { params = request->get_params() post_type = isset(params[post_type]) ? sanitize_text_field(params[post_type]) : post page = max(1, (int) (params[page] ?? 1)) per_page = max(1, min(200, (int) (params[per_page] ?? 50))) // cap 200 for safety include_meta = isset(params[meta]) ? filter_var(params[meta], FILTER_VALIDATE_BOOLEAN) : true include_terms = isset(params[terms]) ? filter_var(params[terms], FILTER_VALIDATE_BOOLEAN) : true include_attachments = isset(params[attachments]) ? filter_var(params[attachments], FILTER_VALIDATE_BOOLEAN) : false query_args = array( post_type => post_type, posts_per_page => per_page, paged => page, post_status => any, orderby => ID, order => ASC, fields => all, ) q = new WP_Query( query_args ) items = array() foreach ( q->posts as post ) { items[] = my_export_prepare_post( post, include_meta, include_terms, include_attachments ) } meta = array( total => (int) q->found_posts, pages => (int) ceil( q->found_posts / per_page ), page => (int) page, per_page => (int) per_page, ) payload = array( schema_version => 1.0, site_url => get_site_url(), exported_at => gmdate(c), data => items, _paging => meta, ) return rest_ensure_response( payload ) } / Prepare a single post for export: fields, meta, terms, attachments. / function my_export_prepare_post( post, include_meta = true, include_terms = true, include_attachments = false ) { if ( is_int( post ) ) { post = get_post( post ) } data = array( ID => (int) post->ID, post_type => post->post_type, post_title => get_the_title( post ), post_content => post->post_content, // raw content if you want rendered apply the_content filters post_excerpt => post->post_excerpt, post_status => post->post_status, post_date => post->post_date, post_modified=> post->post_modified, author => array( ID => (int) post->post_author, user_login => get_the_author_meta( user_login, post->post_author ), display_name => get_the_author_meta( display_name, post->post_author ), ), ) // Meta if ( include_meta ) { raw_meta = get_post_meta( post->ID ) meta = array() foreach ( raw_meta as meta_key => values ) { // Values is an array of values for this meta key. if ( count( values ) === 1 ) { meta[ meta_key ] = maybe_unserialize( values[0] ) } else { meta[ meta_key ] = array_map( maybe_unserialize, values ) } } data[meta] = meta } // Taxonomies / terms if ( include_terms ) { taxonomies = get_object_taxonomies( post->post_type ) terms_out = array() foreach ( taxonomies as tax ) { terms = wp_get_post_terms( post->ID, tax, array( fields => all ) ) if ( ! is_wp_error( terms ) ! empty( terms ) ) { terms_out[ tax ] = array_map( function( term ) { return array( term_id => (int) term->term_id, name => term->name, slug => term->slug, taxonomy => term->taxonomy, ) }, terms ) } } if ( ! empty( terms_out ) ) { data[terms] = terms_out } } // Featured image attachments if ( include_attachments ) { // Featured image thumb_id = get_post_thumbnail_id( post->ID ) if ( thumb_id ) { data[featured_image] = array( ID => (int) thumb_id, url => wp_get_attachment_url( thumb_id ), ) } // All attachments attached to the post attachments = get_attached_media( , post->ID ) att_out = array() foreach ( attachments as att ) { att_out[] = array( ID => (int) att->ID, filename => wp_basename( get_attached_file( att->ID ) ), url => wp_get_attachment_url( att->ID ), mime_type=> get_post_mime_type( att->ID ), metadata => wp_get_attachment_metadata( att->ID ), ) } if ( ! empty( att_out ) ) { data[attachments] = att_out } } return data } ?>
Notes about the REST approach
- Use permission_callback to enforce authentication. For public read-only export you might return true for read-only data but be careful with meta and attachments that may contain secrets.
- Limit posts_per_page to avoid memory exhaustion use pagination (page per_page) and the _paging response to iterate.
- Use JSON serialization via the REST response — WordPress will set the Content-Type and encode JSON safely.
Admin export link (download JSON file)
Add an admin submenu link that generates and streams a download to the user. This example uses admin_post handler and nonce verification.
JSON Exporter
echo echo