Crear Tabla de Contenidos en WordPress: Código SIN Plugins

¿Todavía no usas una Tabla de Contenido (ToC) en tus blogs de WordPress? La verdad es que te recomendaría mucho que lo hagas porque es un añadido que aporta un gran valor al usuario, cosa que también suele derivar en una mejor posición SEO del sitio. Además, es una función que queda muy bien en un blog.

En la mayoría de casos, la gente suele añadir uno de los múltiples plugins especializados precisamente en la creación de Tablas de Contenido. ¿Cuál es el problema de esto? Pues lo de siempre, estos plugins tienen que hacerse de modo que cumplan demasiadas posibilidades distintas y por ello, tienes muchísimo más código del que realmente necesitas.

¿Necesitas ayuda con el desarrollo de una página o con tu estrategia de marketing digital?
¡Consulta nuestros servicios!

Por eso hoy te traigo esta solución que he estado usando en algunos proyectos. No solo te da control total, sino que además es más ligera que cualquier plugin que encuentres. Y ya sabes lo que pienso de añadir código innecesario.

Cómo Crear una Tabla de Contenidos en WordPress Sin Plugins

Lo primero es lo primero. Añade este código a tu functions.php o, si lo prefieres (y te lo recomiendo), como snippet en Code Snippets.

<?php
function tabla_contenidos_automatica($contenido) {
    if (!is_singular('post') || !in_the_loop() || !is_main_query()) {
        return $contenido;
    }

    if (strpos($contenido, '<h2') === false) {
        return $contenido;
    }

    preg_match_all('/<h2[^>]*>(.*?)<\/h2>/', $contenido, $coincidencias);

    if (empty($coincidencias[1]) || count($coincidencias[1]) < 2) {
        return $contenido;
    }

    $tabla_contenidos = '<div class="tabla-contenidos">';
    $tabla_contenidos .= '<p><strong>TABLA DE CONTENIDOS</strong></p>';
    $tabla_contenidos .= '<ul>';

    $reemplazos = [];

    foreach ($coincidencias[1] as $indice => $texto_encabezado) {
        $texto_limpio = wp_strip_all_tags($texto_encabezado);
        
        if (empty(trim($texto_limpio))) {
            continue;
        }

        $id = 'toc-' . sanitize_title($texto_limpio) . '-' . $indice;
        $enlace = '#' . $id;

        $tabla_contenidos .= '<li>';
        $tabla_contenidos .= '<a href="' . esc_url($enlace) . '">';
        $tabla_contenidos .= esc_html($texto_limpio);
        $tabla_contenidos .= '</a></li>';

        $reemplazos[] = [
            'original' => $coincidencias[0][$indice],
            'modificado' => '<h2 id="' . esc_attr($id) . '">' . $texto_encabezado . '</h2>',
        ];
    }

    $tabla_contenidos .= '</ul>';
    $tabla_contenidos .= '</div>';

    if (!empty($reemplazos)) {
        $patron_primer_h2 = '/<h2[^>]*>/';
        if (preg_match($patron_primer_h2, $contenido, $primer_coincidencia, PREG_OFFSET_CAPTURE)) {
            $posicion = $primer_coincidencia[0][1];
            $contenido = substr_replace($contenido, $tabla_contenidos, $posicion, 0);
        }

        foreach ($reemplazos as $reemplazo) {
            $contenido = str_replace($reemplazo['original'], $reemplazo['modificado'], $contenido);
        }
    }

    return $contenido;
}

add_filter('the_content', 'tabla_contenidos_automatica', 9);
?>

Ahora vamos a ver qué hace exactamente cada parte, porque no es magia (aunque lo parezca).

Explicación del Código PHP para Tablas de Contenido Automáticas

La función es un filtro de WordPress bastante estándar, pero tiene algunos detalles interesantes. Lo primero que hace es asegurarse de que estamos en el contexto correcto.

Validación del Contexto en WordPress

Mira tres cosas básicas:

  • is_singular('post'): ¿Es un post individual? Si no, fuera.
  • in_the_loop(): ¿Estamos en el loop principal? Importante para no meter la tabla en widgets.
  • is_main_query(): ¿Es la consulta principal? Más de lo mismo.

Estas comprobaciones evitan que la tabla aparezca donde no debe, como en páginas, listados o widgets. Básicamente, nos aseguramos de no romper nada.

Detección de Encabezados H2 en el Contenido

Con strpos() (string position) miramos si hay algún H2 en el contenido. Si no hay, devolvemos el contenido tal cual. ¿Para qué crear una tabla vacía?

Si hay H2, usamos preg_match_all() para capturarlos todos. La expresión regular /<h2[^>]*>(.*?)<\/h2>/ busca cada etiqueta H2 y su contenido.

Aquí hay un detalle: si hay menos de dos H2, tampoco creamos tabla. Una tabla con un solo elemento no tiene sentido, la verdad.

Generación de IDs Únicos para Navegación Interna

Para cada H2 que encontramos hacemos esto:

  1. Limpiamos el texto: wp_strip_all_tags() se carga cualquier HTML dentro del H2.
  2. ID único: sanitize_title() crea un identificador válido para URLs, más un número por si hay H2 repetidos.
  3. Enlace: Creamos un <li> con un enlace al ID.
  4. Preparamos el cambio: Guardamos el H2 original y el nuevo con ID.

El uso de sanitize_title() aquí es clave porque asegura IDs válidos.

Inserción Precisa de la Tabla de Contenidos

Aquí está el truco interesante. Con preg_match() y PREG_OFFSET_CAPTURE encontramos exactamente dónde está el primer H2. No solo el texto, sino su posición en caracteres.

Luego usamos substr_replace() para meter la tabla justo ahí. ¿Por qué substr_replace() y no otra cosa? Porque es más eficiente cuando ya sabes la posición exacta.

Finalmente, cambiamos todos los H2 originales por los que llevan ID con str_replace(). Y listo, los enlaces funcionan.

CSS Personalizado para Tablas de Contenido en WordPress

El PHP solo genera el HTML. Si quieres que tenga buen aspecto, necesitas CSS. Te dejo unos estilos básicos que puedes ajustar a tu tema.

.tabla-contenidos {
    background: #f8f9fa;
    border-left: 4px solid #0073aa;
    padding: 20px;
    margin: 25px 0;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0,0,0,0.05);
}

.tabla-contenidos > p {
    margin-top: 0;
    margin-bottom: 15px;
    color: #2c3e50;
    font-size: 1.1em;
}

.tabla-contenidos > ul {
    margin: 0;
    padding-left: 20px;
    list-style-type: none;
}

.tabla-contenidos > ul > li {
    margin-bottom: 10px;
    position: relative;
    padding-left: 15px;
}

.tabla-contenidos > ul > li::before {
    content: "•";
    color: #0073aa;
    font-size: 1.5em;
    position: absolute;
    left: 0;
    top: -2px;
}

.tabla-contenidos > ul > li > a {
    text-decoration: none;
    color: #3498db;
    font-weight: 500;
    transition: color 0.2s ease;
    display: block;
    padding: 3px 0;
}

.tabla-contenidos > ul > li > a:hover {
    color: #2980b9;
    text-decoration: underline;
}

@media (max-width: 768px) {
    .tabla-contenidos {
        padding: 15px;
        margin: 20px 0;
    }
}

Estos estilos son bastante neutrales. Si usas un framework como Bootstrap o tu tema tiene estilos muy específicos, probablemente quieras ajustar los márgenes y colores.

Preguntas Frecuentes sobre Tablas de Contenido Personalizadas

¿Cómo Incluir Encabezados H3 y H4 en la Tabla de Contenidos?

Ahora mismo solo funciona con H2. Para incluir H3, cambia la expresión regular para que busque <h3> en lugar de (o además de) <h2>. Pero ojo, si incluyes demasiados niveles la tabla puede quedar muy larga.

Compatibilidad con HTML dentro de los Encabezados

Sí, wp_strip_all_tags() se lo quita todo. Así evitamos que salgan etiquetas raras en la tabla.

Solución para Encabezados H2 Duplicados

El código añade un número al ID. Primer «Conclusión»: toc-conclusion-0. Segundo «Conclusión»: toc-conclusion-1. Cada enlace apunta a su sección correspondiente.

Personalización de la Posición de la Tabla de Contenidos

Por defecto va antes del primer H2. Puedes modificar la lógica de inserción si quieres otro comportamiento. O hacer un shortcode para colocarla manualmente.

Medidas de Seguridad en la Implementación de ToC

He incluido varias medidas de seguridad: Esto es importante sobre todo si aceptas contenido de usuarios.

  • esc_html(): Para el texto que mostramos.
  • esc_url(): Para los enlaces.
  • esc_attr(): Para los IDs.
  • sanitize_title(): Para crear IDs seguros.

Con esto evitamos problemas como XSS o inyecciones de código.

Ventajas de Usar Código Personalizado vs Plugins para ToC

Esta solución es más ligera que cualquier plugin y te da control total. Además, aprendes cómo funcionan los filtros de WordPress por dentro, que siempre viene bien.

Como siempre, prueba primero en un entorno de staging o local. Si algo no funciona, revisa que tus H2 estén bien formados y que no haya conflictos con otros plugins o código de tu tema.

¿Te ha quedado alguna duda? Pues ya sabes, a los comentarios.

¿Te gustaría implementar esto en tu web sin complicaciones?

Ofrezco un servicio de implementación técnica express: por 50€ te instalo y configuro esta solución en tu WordPress, con garantía de funcionamiento y entrega en 24–48 horas.

He probado muchos proveedores de hosting a lo largo de mi carrera y este es sin ninguna duda el que más te recomiendo de todos ellos. Además, si adquieres algún plan de hosting con mi enlace me estarás ayudando enormemente ya que me darán una comisión por ello.
Más información aquí.

Otros Posts que Pueden Interesarte:

Silver

Programador Web y Especialista en SEO Autónomo que reside en Vilafranca del Penedés. Me dedico profesionalmente al desarrollo de páginas web y a su posterior implementación de estrategias para atraer tráfico al sitio. En mis ratos libres escribo posts en blogs como este.

Deja un comentario