Como interceptar el guardado de post para generar slugs custom en PHP en WordPress

En este tutorial detallado aprenderás a interceptar el guardado de entradas (posts) en WordPress para generar slugs personalizados en PHP. Se muestran las mejores prácticas, dos enfoques comunes (filtrar antes de insertar y ajustar después de guardar), cómo evitar bucles infinitos, cómo garantizar unicidad, transliteración y ejemplos prácticos listos para usar en temas o plugins.

Contents

Por qué interceptar el guardado del post

  • Automatizar slugs basados en campos personalizados: por ejemplo generar el slug con un ACF o metadato concreto.
  • Aplicar reglas de negocio: normalizar slugs con prefijos, sufijos, añadir fecha o estructura personalizada.
  • Control multilenguaje o transliteración: generar slugs consistentes para idiomas con caracteres especiales.
  • Estandarizar SEO: aplicar un formato definido para todos los contenidos.

Hooks útiles y cuándo usarlos

Hook Cuándo Ventajas Inconvenientes
wp_insert_post_data Antes de que WordPress inserte/actualice el post en la base de datos (filtrando los datos). Evita llamadas extra a la DB ideal para modificar post_name antes del guardado. Se ejecuta tanto en inserciones como en actualizaciones hay que contemplar revisiones/autosaves.
save_post Después de que el post ha sido guardado. Acceso al ID del post y a metadatos recién guardados útil si el slug depende de campos que se guardan en el mismo proceso. Si actualizas el post dentro de este hook, debes evitar recursividad (remove_action).

Recomendaciones previas (chequeos comunes)

  • Ignorar revisiones y autosaves: wp_is_post_revision(), wp_is_post_autosave(), DOING_AUTOSAVE.
  • Comprobar el tipo de post: data[post_type] o post->post_type.
  • Comprobar capacidades si procede: current_user_can(edit_post, post_ID).
  • Evitar actuar en REST requests/AJAX si no corresponde: defined(REST_REQUEST) o defined(DOING_AJAX).
  • Usar funciones de sanitización de WP: sanitize_title_with_dashes(), remove_accents(), wp_unique_post_slug().

Método recomendado A: Modificar el slug antes de guardar (wp_insert_post_data)

Este es el método más limpio porque modifica post_name antes de que WordPress haga la inserción/actualización, evitando una segunda actualización de post.


Explicación rápida

  1. Filtramos wp_insert_post_data para intervenir antes del guardado final.
  2. Hacemos checks de revisión/autosave y tipo de post.
  3. Generamos la base del slug (campo personalizado o título) y la sanitizamos.
  4. Usamos wp_unique_post_slug para evitar colisiones.
  5. Asignamos post_name y devolvemos los datos.

Método alternativo B: Ajustar el slug después del guardado (save_post)

Útil cuando el slug depende de metadatos que se guardan en el mismo proceso y no están disponibles en wp_insert_post_data (por ejemplo ACF que guarda en save_post).

post_type ) {
        return
    }

    // Comprobar permisos: el usuario actual puede editar el post
    if ( ! current_user_can( edit_post, post_ID ) ) {
        return
    }

    // Obtener valor a partir de un campo meta guardado (ejemplo con ACF)
    mi_campo = get_post_meta( post_ID, mi_campo_slug, true )

    // Si no hay campo, fallback al título
    source = ! empty( mi_campo ) ? mi_campo : post->post_title

    // Generar slug y sanitizar
    slug_base = sanitize_title_with_dashes( source )
    unique_slug = wp_unique_post_slug( slug_base, post_ID, post->post_status, post->post_type, post->post_parent )

    // Si ya es igual, no hacer nada (evita actualización innecesaria)
    if ( unique_slug === post->post_name ) {
        return
    }

    // Evitar recursividad: remover el hook, actualizar, volver a añadir
    remove_action( save_post, mi_sluggenerador_save_post, 10 )
    wp_update_post( array(
        ID => post_ID,
        post_name => unique_slug
    ) )
    add_action( save_post, mi_sluggenerador_save_post, 10, 3 )
}
?>

Puntos clave de este enfoque

  • Se comprueba que el nuevo slug realmente cambia antes de llamar a wp_update_post.
  • Se elimina temporalmente la acción para evitar recursividad cuando se actualiza el post dentro del hook.
  • Puede ser necesario si dependes de metadatos que solo existen tras el primer guardado.

Generador de slugs avanzado (transliteración y fallback)

En entornos multilengua o con caracteres no latinos, conviene transliterar (remove_accents o iconv) y aplicar reglas adicionales (prefijos, sufijos, fecha, ID si hace falta). Ejemplo completo de helper:


Casos especiales y consideraciones

  • Bulk edits y quick-edit: ten en cuenta que el flujo de guardado difiere comprueba que tu lógica cubre estos casos.
  • REST API: si quieres interceptar guardados realizados vía REST, comprueba la constante REST_REQUEST y si necesitas actuar en este flujo.
  • Permalinks y flush: no es necesario hacer flush_rewrite_rules al cambiar slugs de posts individuales. Flush solo cuando cambian reglas de reescritura.
  • Rendimiento: evita consultas innecesarias prefiere wp_insert_post_data para no ejecutar wp_update_post extra.
  • Auditoría/Redirecciones: si cambias slugs en posts ya indexados, considera crear redirecciones 301 desde el slug antiguo al nuevo para no perder SEO.

Pruebas recomendadas antes de ponerlo en producción

  1. Probar con títulos con caracteres especiales (acentos, ñ, caracteres no latinos).
  2. Comprobar en creación, edición, quick-edit y bulk edit.
  3. Probar en REST API y en envíos desde frontend (si aplica).
  4. Probar con tipos de post personalizados y con jerarquías parent/child.
  5. Verificar que no hay bucles en logs ni cargas extra de DB.

Resumen final

La forma más limpia y eficiente generalmente es usar wp_insert_post_data para modificar post_name antes del guardado. Si dependes de metadatos que solo existen tras guardar, usa save_post con remove_action/añadir acción para evitar recursividad. Siempre sanitiza, translitera si procede y usa wp_unique_post_slug para evitar colisiones. Comprueba revisiones, autosaves y capacidades para no interferir con procesos internos de WordPress.



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 *