Como crear un exportador de JSON para contenido personalizado en PHP en WordPress

Contents

Introducción

En este artículo encontrarás un tutorial completo y detallado para crear un exportador de JSON de contenido personalizado en WordPress usando PHP. Cubriremos desde la estructura básica de un plugin o snippet, hasta ejemplos prácticos para exportar entradas personalizadas (Custom Post Types), metadatos, taxonomías y archivos adjuntos. También veremos variantes: exportador desde el admin (descarga directa), endpoint REST y estrategias para manejar exportaciones grandes (streaming y paginación).

Enfoques posibles

  • Admin export (descarga directa): añadir una página en el panel de administración que genere y entregue el archivo JSON para descarga.
  • REST API endpoint: registrar una ruta REST que devuelva el JSON (útil para integraciones automáticas).
  • WP-CLI: exportador CLI para tareas programadas o procesos en servidor (no cubierto en profundidad aquí).

Requisitos y buenas prácticas

  • Usar un plugin propio o un mu-plugin en lugar de poner código en el theme cuando sea algo funcional.
  • Comprobar permisos y usar nonces para solicitudes desde el admin.
  • Sanitizar entradas y escapar salidas.
  • Soportar paginación/streaming para evitar alcanzar límites de memoria.
  • Usar JSON_PRETTY_PRINT y JSON_UNESCAPED_UNICODE para legibilidad y soporte de caracteres.

Estructura de archivos sugerida

mi-exportador-json/
mi-exportador-json/mi-exportador-json.php
mi-exportador-json/includes/class-exporter.php

1) Ejemplo: Registrar un Custom Post Type (opcional)

Si necesitas un CPT para probar el exportador, aquí tienes cómo registrarlo desde un plugin o functions.php:

lt?php
// Registra un CPT simple para pruebas
function me_register_cpt_book() {
    labels = array(
        name => Libros,
        singular_name => Libro,
    )
    args = array(
        labels             =gt labels,
        public             =gt true,
        has_archive        =gt true,
        show_in_rest       =gt true,
        supports           =gt array(title,editor,thumbnail,custom-fields),
        rewrite            =gt array(slug =gt libros),
    )
    register_post_type(libro, args)
}
add_action(init, me_register_cpt_book)
?gt

2) Exportador desde el admin (descarga directa)

Este ejemplo crea una página en el menú de administración desde la que se puede seleccionar el CPT y rango de fechas, genera el JSON y fuerza la descarga.

Archivo principal del plugin (mi-exportador-json.php)

lt?php
/
Plugin Name: MI Exportador JSON
Description: Exporta contenido personalizado a JSON.
Version: 1.0
Author: Ejemplo
/

if ( ! defined( ABSPATH ) ) {
    exit
}

require_once plugin_dir_path( __FILE__ ) . includes/class-exporter.php

add_action(plugins_loaded, function(){
    ME_Exporter::init()
})
?gt

Clase exportadora (includes/class-exporter.php)

La clase añade el menú, renderiza el formulario y procesa la exportación con verificación de permisos y nonce.

lt?php
class ME_Exporter {

    public static function init() {
        add_action(admin_menu, array(__CLASS__, add_admin_menu))
        add_action(admin_post_me_export_json, array(__CLASS__, handle_export))
    }

    public static function add_admin_menu() {
        add_management_page(
            Exportar JSON,
            Exportar JSON,
            export, // capability recomendada
            me-export-json,
            array(__CLASS__, render_admin_page)
        )
    }

    public static function render_admin_page() {
        // Obtener post types públicos
        post_types = get_post_types( array(public => true), objects )
        ?>
        ltdiv class=wrapgt
            lth2gtExportar contenido a JSONlt/h2gt
            ltform method=post action=gt
                ltinput type=hidden name=action value=me_export_jsongt
                
                lttable class=form-tablegt
                    lttrgt
                        ltthgtltlabelgtTipo de contenidolt/labelgtlt/thgt
                        lttdgt
                            ltselect name=post_typegt
                                lt?php foreach(post_types as pt): ?gt
                                    ltoption value=lt?php echo esc_attr(pt->name) ?gtgtlt?php echo esc_html(pt->label) ?gtlt/optiongt
                                lt?php endforeach ?gt
                            lt/selectgt
                        lt/tdgt
                    lt/trgt
                    lttrgt
                        ltthgtltlabelgtFecha desdelt/labelgtlt/thgt
                        lttdgtltinput type=date name=date_from /gtlt/tdgt
                    lt/trgt
                    lttrgt
                        ltthgtltlabelgtFecha hastalt/labelgtlt/thgt
                        lttdgtltinput type=date name=date_to /gtlt/tdgt
                    lt/trgt
                lt/tablegt
                ltp class=submitgtltbutton class=button button-primarygtGenerar JSONlt/buttongtlt/pgt
            lt/formgt
        lt/divgt
        lt?php
    }

    public static function handle_export() {
        // Permisos
        if ( ! current_user_can(export) ) {
            wp_die(No tiene permisos para exportar.)
        }

        // Nonce
        if ( ! isset(_POST[me_export_json_nonce])  ! wp_verify_nonce(_POST[me_export_json_nonce], me_export_json_action) ) {
            wp_die(Nonce no válido.)
        }

        post_type = isset(_POST[post_type]) ? sanitize_text_field(_POST[post_type]) : post
        date_from = ! empty(_POST[date_from]) ? sanitize_text_field(_POST[date_from]) : 
        date_to   = ! empty(_POST[date_to]) ? sanitize_text_field(_POST[date_to]) : 

        // Preparar query
        args = array(
            post_type => post_type,
            posts_per_page => -1,
            post_status => any,
            date_query => array(),
        )

        if ( date_from ) {
            args[date_query][] = array( after => date_from )
        }
        if ( date_to ) {
            args[date_query][] = array( before => date_to )
        }

        query = new WP_Query( args )

        items = array()
        foreach ( query->posts as post ) {
            items[] = self::export_post_to_array( post )
        }

        filename = sprintf(%s-%s.json, post_type, date(Y-m-d_His))

        // Headers para descarga
        header(Content-Description: File Transfer)
        header(Content-Type: application/json charset=utf-8)
        header(Content-Disposition: attachment filename= . filename . )
        header(Expires: 0)
        header(Cache-Control: must-revalidate)
        header(Pragma: public)

        echo wp_json_encode(items, JSON_PRETTY_PRINT  JSON_UNESCAPED_UNICODE)
        exit
    }

    private static function export_post_to_array( post ) {
        // Datos básicos
        data = array(
            ID => post->ID,
            post_title => get_the_title(post),
            post_content => apply_filters(the_content, post->post_content),
            post_excerpt => post->post_excerpt,
            post_status => post->post_status,
            post_type => post->post_type,
            post_date => get_the_date(c, post),
            post_modified => get_the_modified_date(c, post),
            permalink => get_permalink(post),
        )

        // Meta (todos los metadatos, limpiar claves que empiezan con _ no públicas si se desea)
        meta = get_post_meta( post->ID )
        foreach ( meta as key => value ) {
            // Si se desea excluir metadatos internos:
            if ( strpos(key, _) === 0 ) {
                continue
            }
            data[meta][key] = maybe_unserialize(value)
        }

        // Taxonomías asociadas
        taxonomies = get_object_taxonomies( post->post_type )
        foreach ( taxonomies as tax ) {
            terms = get_the_terms( post->ID, tax )
            if ( is_wp_error(terms)  empty(terms) ) {
                data[terms][tax] = array()
            } else {
                data[terms][tax] = wp_list_pluck(terms, name)
            }
        }

        // Imagen destacada (url)
        if ( has_post_thumbnail(post) ) {
            thumb_id = get_post_thumbnail_id(post)
            data[thumbnail] = wp_get_attachment_url(thumb_id)
        } else {
            data[thumbnail] = null
        }

        return data
    }
}
?gt

Notas sobre este enfoque

  • Para exportaciones pequeñas o moderadas es suficiente. Para sitios con miles de posts puede agotar memoria.
  • Si necesitas incluir campos repetibles/ACF, usa get_post_meta para las claves correspondientes o las funciones ACF apropiadas.

3) Exportador vía REST API

Registrar una ruta REST es útil para integraciones. Este endpoint devolverá un JSON con las entradas filtradas.

lt?php
add_action(rest_api_init, function() {
    register_rest_route(me/v1, /export/(?P[a-zA-Z0-9-_] ), array(
        methods  =gt GET,
        callback =gt me_rest_export,
        permission_callback =gt function( request ) {
            // Requiere autenticación y capacidad export (o personalizar)
            return current_user_can(export)
        }
    ))
})

function me_rest_export( request ) {
    post_type = request->get_param(post_type)
    date_from = request->get_param(date_from)
    date_to   = request->get_param(date_to)

    args = array(
        post_type => post_type,
        posts_per_page => -1,
        post_status => any,
    )

    if ( date_from  date_to ) {
        date_query = array()
        if ( date_from ) date_query[] = array(after => date_from)
        if ( date_to )   date_query[] = array(before => date_to)
        args[date_query] = date_query
    }

    q = new WP_Query(args)
    items = array()
    foreach(q->posts as post) {
        items[] = ME_Exporter::export_post_to_array(post) // reutiliza la función
    }

    return rest_ensure_response(items)
}
?gt

Consideraciones

  • El endpoint devuelve JSON en el body. Para forzar descarga desde cliente, se puede solicitar y luego crear un archivo local con JavaScript o realizar una llamada desde servidor.
  • Controlar permisos: aquí se ha exigido current_user_can(export), pero puedes restringirlo más.

4) Exportaciones grandes: streaming y paginación

Para evitar memoria insuficiente, conviene enviar el JSON en streaming por bloques (chunked) o exportar por páginas. A continuación un ejemplo sencillo de streaming que imprime una lista JSON elemento a elemento.

lt?php
// Exportador streaming (ejecutar desde admin_post o handler propio)
function me_stream_export( post_type = post ) {
    if ( ! current_user_can(export) ) {
        wp_die(No autorizado)
    }

    page = 1
    per_page = 100 // ajustar según memoria/tiempo
    first = true

    // Headers
    header(Content-Type: application/json charset=utf-8)
    header(Content-Disposition: attachment filename=export-stream- . post_type . - . date(Ymd_His) . .json)
    // Desactivar buffer
    if ( function_exists(apache_setenv) ) {
        @apache_setenv(no-gzip, 1)
    }
    @ini_set(zlib.output_compression, Off)
    @ini_set(output_buffering, Off)
    @ini_set(max_execution_time, 0)
    echo [

    while ( true ) {
        args = array(
            post_type => post_type,
            posts_per_page => per_page,
            paged => page,
            post_status => any,
            fields => ids,
        )
        q = new WP_Query(args)
        if ( empty(q->posts) ) {
            break
        }

        foreach ( q->posts as post_id ) {
            post = get_post(post_id)
            item = ME_Exporter::export_post_to_array(post) // o función equivalente
            if ( ! first ) {
                echo ,
            }
            echo wp_json_encode(item, JSON_UNESCAPED_UNICODE)
            flush()
            first = false
        }

        if ( q->max_num_pages lt= page ) {
            break
        }
        page  
    }

    echo ]
    exit
}
?gt

Consejos

  • Evitar usar posts_per_page =gt -1 en sitios grandes.
  • Usar flush() para enviar datos al cliente por partes.
  • Incrementar tiempo de ejecución y memoria si es necesario, pero preferir paginar.

5) Incluir archivos adjuntos (media)

Normalmente se incluye la URL del attachment (wp_get_attachment_url). Si necesitas incluir el archivo en base64, ten en cuenta el alto consumo de memoria y tráfico.

// Dentro de export_post_to_array (ejemplo para incluir attachment como base64 - usar con precaución)
attachments = get_attached_media(, post->ID)
data[attachments] = array()
foreach (attachments as att) {
    url = wp_get_attachment_url(att-gtID)
    file = get_attached_file(att-gtID)
    base64 = null
    if ( file_exists(file)  filesize(file) lt 1024  1024  2 ) { // limitar a 2MB
        content = file_get_contents(file)
        base64 = data: . get_post_mime_type(att-gtID) . base64, . base64_encode(content)
    }
    data[attachments][] = array(
        ID =gt att-gtID,
        url =gt url,
        mime =gt get_post_mime_type(att-gtID),
        base64 =gt base64,
    )
}

6) Exportar campos personalizados y ACF

Para ACF puedes usar get_field() si ACF está activo, o get_post_meta() directamente. Ejemplo:

// Dentro de export_post_to_array
if ( function_exists(get_field) ) {
    data[acf] = array(
        precio =gt get_field(precio, post->ID),
        autor  =gt get_field(autor, post->ID),
    )
} else {
    data[meta][precio] = get_post_meta(post->ID, precio, true)
}

7) Seguridad y permisos

  1. Capability: usar capacidades adecuadas como export o manage_options según el caso.
  2. Nonce: siempre verificar nonce para solicitudes desde formularios del admin.
  3. Sanitización: sanear parámetros GET/POST (sanitize_text_field, intval, etc.).
  4. Validar datos sensibles: no exportar metadatos internos (_edit_lock, etc.) a menos que sea necesario.

8) Pruebas y depuración

  • Probar con distintos tamaños de dataset.
  • Verificar que las URLs de medios sean accesibles y que los permisos de archivos en servidor sean correctos.
  • Comprobar que las fechas, taxonomías y metadatos lleguen en el formato esperado.

9) Ejemplos de consumo del endpoint desde JavaScript (opcional)

Si quieres descargar desde el navegador el JSON devuelto por el endpoint REST y forzar descarga:

// Ejemplo básico para descargar el JSON desde el front (requiere autenticación si el endpoint la requiere).
fetch(/wp-json/me/v1/export/libro, {
    credentials: include
})
.then(resp =gt resp.blob())
.then(blob =gt {
    const url = window.URL.createObjectURL(blob)
    const a = document.createElement(a)
    a.href = url
    a.download = export-libro.json
    document.body.appendChild(a)
    a.click()
    a.remove()
    window.URL.revokeObjectURL(url)
})

10) Consideraciones finales

Este conjunto de patrones te permite implementar un exportador de JSON robusto y adaptable. Para la mayoría de proyectos conviene:

  • Implementar primero la versión admin (rápida de desarrollar).
  • Si necesitas integraciones automáticas, añadir un endpoint REST con permisos adecuados.
  • Para sitios grandes, priorizar paginación/streaming y limitar el tamaño de los archivos adjuntos incluidos en base64.

Con esto dispones de las herramientas y ejemplos necesarios para crear un exportador de JSON para contenido personalizado en WordPress usando PHP, seguro y escalable.



Acepto donaciones de BAT's mediante el navegador Brave 🙂



Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *