Eliminar enlaces Markdown en PHP conservando el texto visible y el alt de las imágenes
Introducción
A veces quieres obtener texto plano desde Markdown, pero no quieres perder las partes legibles para humanos.
Si tu entrada contiene [Laravel docs](https://laravel.com) o , una regex ingenua de "eliminar todo lo que esté entre paréntesis" normalmente destruye el texto útil junto con la URL. Para previews, indexación de búsqueda, pipelines de scraping y preprocesamiento para IA, ese tradeoff no conviene.
Lo que normalmente quieres es esto:
- conservar el texto visible de los enlaces normales
- conservar el alt de las imágenes
- eliminar enlaces vacíos e imágenes vacías
- manejar correctamente los casos donde una imagen está anidada dentro de un enlace
En este artículo vamos a recorrer una utilidad pequeña en PHP que hace exactamente eso.
El Resultado Que Buscamos
Estas son las transformaciones que queremos:
[Laravel docs](https://laravel.com)
=> Laravel docs

=> Diagrama de arquitectura
[](/post/system-design)
=> Diagrama de arquitectura
[](/internal-link)
=> ""

=> ""Ese comportamiento es especialmente útil cuando Markdown es solo un formato intermedio y tu objetivo real es obtener texto plano limpio.
La Utilidad
Esta es la implementación principal:
class MarkdownUtil
{
public static function removeLinks($markdown)
{
$patternWithImageAltText = '/!\[(.*?)\]\((.*?)\)/s';
$patternWithLinkText = '/\[(.*?)\]\((?![^\[]*?\!\[)(.*?)\)/s';
$markdown = preg_replace('/!\[\]\((.*?)\)/', '', $markdown);
$markdown = preg_replace_callback($patternWithImageAltText, function ($matches) {
return $matches[1];
}, $markdown);
$markdown = preg_replace_callback($patternWithLinkText, function ($matches) {
return $matches[1];
}, $markdown);
$markdown = preg_replace('/\[\]\((.*?)\)/', '', $markdown);
return $markdown;
}
}El método es corto, pero el orden de reemplazo importa más que las regex por sí solas.
Por Qué Importa el Orden
Esta utilidad funciona como un pipeline de cuatro etapas:
- eliminar imágenes vacías
- convertir imágenes a su alt
- convertir enlaces normales a su texto visible
- eliminar enlaces vacíos
Esa secuencia evita que Markdown anidado se interprete de forma incorrecta.
Considera esta entrada:
[](/post/system-design)Si procesas los enlaces primero, corres el riesgo de tratar toda la imagen interna como si fuera texto genérico del enlace. Al procesar primero las imágenes, la entrada se convierte en:
[Diagrama de arquitectura](/post/system-design)Después de eso, el pase de enlaces normales puede reducirlo de forma segura a:
Diagrama de arquitecturaEsa es la idea principal del diseño: simplificar el Markdown por etapas en lugar de intentar resolver todo con una sola regex gigante.
El Patrón Para Imágenes
La regex para imágenes es:
$patternWithImageAltText = '/!\[(.*?)\]\((.*?)\)/s';Captura:
- el alt dentro de
![ ... ] - el destino de la imagen dentro de
( ... )
El callback devuelve solo el primer grupo capturado:
return $matches[1];Así que esto:
se convierte en:
Diagrama de arquitecturaEl modificador /s hace que el patrón sea más tolerante con Markdown multilínea, porque . también puede coincidir con saltos de línea. Eso ayuda cuando el texto del enlace, el texto alternativo o el destino se parten en varias líneas.
El Patrón Para Enlaces
La regex para enlaces normales es:
$patternWithLinkText = '/\[(.*?)\]\((?![^\[]*?\!\[)(.*?)\)/s';La parte importante es el negative lookahead:
(?![^\[]*?\!\[)Eso ayuda a que el patrón no consuma contenido con forma de imagen como si fuera el cuerpo de un enlace normal. En la práctica, vuelve más seguro el pase de enlaces frente a Markdown anidado.
Igual que con las imágenes, el callback devuelve solo el texto legible:
return $matches[1];Así que esto:
[Laravel docs](https://laravel.com)se convierte en:
Laravel docsEliminando Nodos Markdown Vacíos
La utilidad elimina explícitamente dos formas que no aportan texto legible:
$markdown = preg_replace('/!\[\]\((.*?)\)/', '', $markdown);
$markdown = preg_replace('/\[\]\((.*?)\)/', '', $markdown);Eso cubre:
- imágenes vacías como
 - enlaces vacíos como
[](/internal-link)
Es un detalle pequeño pero importante. Si los mantienes, tu salida puede terminar con placeholders inútiles o con puntuación suelta.
Casos de Uso Reales
Este tipo de utilidad sirve cuando Markdown no es tu formato final:
- convertir HTML scrapeado a Markdown y luego comprimirlo como texto plano
- construir pipelines de búsqueda o indexación que deban ignorar URLs crudas
- generar previews de contenido donde el texto visible importa más que el destino del enlace
- limpiar entrada para IA o para resúmenes antes de enviarla a otro paso del pipeline
Un flujo especialmente práctico es:
- convertir HTML a Markdown
- reemplazar enlaces e imágenes por su texto visible
- comprimir espacios
- guardar el resultado como texto plano para pasos posteriores
Así conservas el contenido legible mientras eliminas ruido de enlaces, URLs de tracking e imágenes embebidas.
Casos de Prueba Que Vale la Pena Mantener
El valor de esta utilidad no está solo en la regex. Está en el conjunto de edge cases cubiertos por las pruebas.
Conviene cubrir:
- enlaces normales con texto
- enlaces con query strings y caracteres especiales
- imágenes con alt
- imágenes con alt vacío
- imágenes anidadas dentro de enlaces
- múltiples enlaces e imágenes en el mismo string
- entrada vacía
- Markdown multilínea con imágenes base64
Un ejemplo representativo es este caso con imagen base64:
$text = <<<'EOT'
Hello,
the email was verified. 
EOT;
$expected = <<<'EOT'
Hello,
the email was verified. image
EOT;Esa prueba demuestra que la utilidad no está limitada a imágenes con rutas URL cortas.
Dónde la Regex Es Suficiente y Dónde No
Esta es una solución pragmática basada en regex, no un parser completo de Markdown.
No pretende cubrir todas las variantes de Markdown. Por ejemplo, los enlaces por referencia como [docs][1], los autolinks como <https://example.com>, los casos con corchetes escapados y algunas URLs con paréntesis pueden quedar sin cambios o producir una salida imperfecta.
Eso es una fortaleza cuando:
- controlas la forma del Markdown
- necesitas un helper liviano y sin dependencias
- te importa una transformación puntual y no el cumplimiento total de CommonMark
Se vuelve una opción más débil cuando:
- la entrada puede traer Markdown muy irregular
- necesitas preservar formato complejo y profundamente anidado
- debes soportar todos los edge cases del estándar Markdown
Para pipelines controlados dentro de una aplicación, sin embargo, este tradeoff suele ser el correcto. Una utilidad enfocada con regex es más fácil de mantener, más fácil de probar y mucho más fácil de explicar.
Conclusión
La idea útil aquí no es "usar regex sobre Markdown" en abstracto. Es "usar una transformación por etapas con intención explícita".
Esta utilidad funciona porque define una política clara:
- el texto visible de los enlaces se queda
- el alt de las imágenes se queda
- los nodos vacíos desaparecen
- los casos anidados se simplifican en el orden correcto
Si necesitas obtener texto plano limpio desde Markdown en PHP, esa política te da una solución compacta y lista para producción sin tener que traer un parser completo.
