Del gestor a JavaScript: leer datos por API

Hasta ahora WordPress hacía todo: leía la base de datos y generaba el HTML con PHP.

La API REST de WordPress entrega el contenido en JSON: /wp-json/wp/v2/<tipo>.

fetch + await + respuesta.json() para pedir los datos; ?_embed para la imagen y los términos.

Crea el HTML con el DOM, con estados de carga y error, y reutiliza tu filtro.

Base de datos → API → JSON → DOM: el recorrido completo de los datos.

  • Hasta ahora WordPress hacía todo: leía la base de datos y generaba el HTML con PHP. Pero también puede entregar los datos en bruto, en JSON, para que tu JavaScript los muestre como quiera. Así se une todo: base de datos → API → JSON (lección Ficheros y formatos de datos) → DOM.
  • Una API es una "puerta" para que un programa pida datos a otro. Las API web funcionan con direcciones: pides una URL y en lugar de una página recibes JSON.
  • WordPress trae una, la API REST. Prueba en el navegador (con tu sitio de Studio abierto; cambia la dirección por la tuya):
  • http://localhost:8881/wp-json/wp/v2/pages http://localhost:8881/wp-json/wp/v2/proyecto http://localhost:8881/wp-json/wp/v2/disciplina
  • Verás un array de objetos JSON: uno por página, por proyecto o por disciplina. Cada tipo de contenido aparece en la API solo si activaste Mostrar en la API REST al crearlo (lección Tipos de contenido personalizados (CPT UI)). Firefox muestra el JSON ordenado; en Chrome, marca Dar formato (Pretty-print).

Introducción

Hasta ahora WordPress hacía todo: leía la base de datos y generaba el HTML con PHP. Pero también puede entregar los datos en bruto, en JSON, para que tu JavaScript los muestre como quiera. Así se une todo: base de datos → API → JSON (lección Ficheros y formatos de datos) → DOM.

Qué es una API

Una API es una "puerta" para que un programa pida datos a otro. Las API web funcionan con direcciones: pides una URL y en lugar de una página recibes JSON.

WordPress trae una, la API REST. Prueba en el navegador (con tu sitio de Studio abierto; cambia la dirección por la tuya):

http://localhost:8881/wp-json/wp/v2/pages http://localhost:8881/wp-json/wp/v2/proyecto http://localhost:8881/wp-json/wp/v2/disciplina

Verás un array de objetos JSON: uno por página, por proyecto o por disciplina. Cada tipo de contenido aparece en la API solo si activaste Mostrar en la API REST al crearlo (lección Tipos de contenido personalizados (CPT UI)). Firefox muestra el JSON ordenado; en Chrome, marca Dar formato (Pretty-print).

Qué trae cada proyecto

Un proyecto en la API tiene, entre otros, estos campos:

{ "id": 42, "slug": "identidad-cafe-lento", "link": "http://localhost:8881/proyecto/identidad-cafe-lento/", "title": { "rendered": "Identidad Café Lento" }, "excerpt": { "rendered": "<p>Marca y envases para una cafetería de barrio.</p>\n" }, "featured_media": 57, "disciplina": [3, 5] }

title.rendered y excerpt.rendered vienen en HTML (con etiquetas y entidades como &#8211;). disciplina trae los id de sus términos, no sus nombres. featured_media es el id de la imagen, no su dirección.

Para no hacer más peticiones, añade ?_embed: la respuesta incluye la imagen y los términos dentro de _embedded. Otros parámetros útiles: per_page=20, orderby=title&order=asc, disciplina=3 (solo los de esa disciplina).

Pedir los datos con fetch

fetch pide una URL y devuelve una promesa: el resultado llega un poco después. Con async y await se escribe como si fuera en orden:

const API = "/wp-json/wp/v2/proyecto?_embed&per_page=50"; async function cargarProyectos() { const respuesta = await fetch(API); if (!respuesta.ok) { throw new Error(`Error ${respuesta.status}`); } return respuesta.json(); }

await espera a que llegue la respuesta; solo se puede usar dentro de una función async (o en un módulo). respuesta.ok es false si hubo un error (404, 500…). respuesta.json() convierte el JSON en objetos de JavaScript.

Mostrar los proyectos

function aTexto(html) { // Convierte el HTML de WordPress en texto sin etiquetas ni entidades return new DOMParser().parseFromString(html, "text/html").body.textContent.trim(); } function crearTarjeta(proyecto) { const tarjeta = document.createElement("article"); tarjeta.className = "tarjeta"; const terminos = proyecto._embedded?.["wp:term"]?.flat() ?? []; tarjeta.dataset.categoria = terminos.map((t) => t.slug).join(" "); const imagen = proyecto._embedded?.["wp:featuredmedia"]?.[0]; if (imagen) { const img = document.createElement("img"); img.src = imagen.source_url; img.alt = imagen.alt_text; tarjeta.append(img); } const titulo = document.createElement("h3"); const enlace = document.createElement("a"); enlace.href = proyecto.link; enlace.textContent = aTexto(proyecto.title.rendered); titulo.append(enlace); const resumen = document.createElement("p"); resumen.textContent = aTexto(proyecto.excerpt.rendered); tarjeta.append(titulo, resumen); return tarjeta; } async function iniciar() { const galeria = document.querySelector("#galeria"); galeria.textContent = "Cargando proyectos…"; try { const proyectos = await cargarProyectos(); galeria.textContent = ""; proyectos.forEach((p) => galeria.append(crearTarjeta(p))); } catch (error) { galeria.textContent = "No se han podido cargar los proyectos."; console.error(error); } } iniciar();

Fíjate en:

?. y ??: si un proyecto no tiene imagen o disciplinas, no da error. aTexto: el título y el extracto vienen en HTML; con textContent evitas insertar HTML ajeno (lección El DOM). Estados de carga y de error: la persona sabe qué pasa si la red va lenta o falla. data-categoria con los slugs de las disciplinas: una función filtrar como la de la lección Eventos sirve casi sin cambios (usa includes si hay varias).

Dónde poner el código

La forma más sencilla, y sin problemas de permisos, es dentro del propio WordPress: en la página Portafolio, un bloque HTML personalizado con el contenedor, los botones y el <script>. Como la página y la API están en el mismo sitio, basta con la ruta /wp-json/....

Si el JavaScript está en otra dirección (tu portfolio publicado en Netlify, por ejemplo), la API tiene que ser accesible desde Internet: un sitio de Studio en tu ordenador no lo es. Además, el navegador aplica la política CORS, que decide qué webs pueden pedir datos a otra; si ves un error de CORS en la consola, el servidor no permite esa petición.

Solo lectura y datos públicos

Sin iniciar sesión, la API solo lee contenido publicado: no puede crear ni borrar nada ni ver borradores. Para escribir hace falta autenticación, fuera del alcance de esta lección. No pongas nunca contraseñas ni claves en el JavaScript de una página: cualquiera puede leerlo.

Practica

Abre en el navegador /wp-json/wp/v2/proyecto?_embed de tu sitio y localiza el título, la imagen y las disciplinas de un proyecto. En una página de prueba, con un bloque HTML personalizado, carga los proyectos y muestra sus títulos en una lista. Añade las tarjetas completas y los botones de filtro por disciplina. Desactiva la red en las DevTools (Network → Offline) y comprueba el mensaje de error.

Errores frecuentes

404 en /wp-json/wp/v2/proyecto: el tipo no tiene activado Mostrar en la API REST o su slug es otro (mira /wp-json/wp/v2/types). Usar await fuera de una función async. Mostrar title.rendered con innerHTML o ver &#8211; en pantalla. Olvidar ?_embed y no encontrar la imagen ni los nombres de las disciplinas. No contemplar el caso sin datos o con error de red.

WordPress
36

Del gestor a JavaScript: leer datos por API

Hasta ahora WordPress hacía todo: leía la base de datos y generaba el HTML con PHP.

Código del tema: fetch("/wp-json/wp/v2/…")

📘 Teoría

Introducción

Hasta ahora WordPress hacía todo: leía la base de datos y generaba el HTML con PHP. Pero también puede entregar los datos en bruto, en JSON, para que tu JavaScript los muestre como quiera. Así se une todo: base de datos → API → JSON (lección Ficheros y formatos de datos) → DOM.

Qué es una API

Una API es una "puerta" para que un programa pida datos a otro. Las API web funcionan con direcciones: pides una URL y en lugar de una página recibes JSON.

WordPress trae una, la API REST. Prueba en el navegador (con tu sitio de Studio abierto; cambia la dirección por la tuya):

http://localhost:8881/wp-json/wp/v2/pages
http://localhost:8881/wp-json/wp/v2/proyecto
http://localhost:8881/wp-json/wp/v2/disciplina

Verás un array de objetos JSON: uno por página, por proyecto o por disciplina. Cada tipo de contenido aparece en la API solo si activaste Mostrar en la API REST al crearlo (lección Tipos de contenido personalizados (CPT UI)). Firefox muestra el JSON ordenado; en Chrome, marca Dar formato (Pretty-print).

Qué trae cada proyecto

Un proyecto en la API tiene, entre otros, estos campos:

{
  "id": 42,
  "slug": "identidad-cafe-lento",
  "link": "http://localhost:8881/proyecto/identidad-cafe-lento/",
  "title": { "rendered": "Identidad Café Lento" },
  "excerpt": { "rendered": "<p>Marca y envases para una cafetería de barrio.</p>\n" },
  "featured_media": 57,
  "disciplina": [3, 5]
}
  • title.rendered y excerpt.rendered vienen en HTML (con etiquetas y entidades como &#8211;).
  • disciplina trae los id de sus términos, no sus nombres.
  • featured_media es el id de la imagen, no su dirección.

Para no hacer más peticiones, añade ?_embed: la respuesta incluye la imagen y los términos dentro de _embedded. Otros parámetros útiles: per_page=20, orderby=title&order=asc, disciplina=3 (solo los de esa disciplina).

Pedir los datos con fetch

fetch pide una URL y devuelve una promesa: el resultado llega un poco después. Con async y await se escribe como si fuera en orden:

const API = "/wp-json/wp/v2/proyecto?_embed&per_page=50";

async function cargarProyectos() {
  const respuesta = await fetch(API);
  if (!respuesta.ok) {
    throw new Error(`Error ${respuesta.status}`);
  }
  return respuesta.json();
}
  • await espera a que llegue la respuesta; solo se puede usar dentro de una función async (o en un módulo).
  • respuesta.ok es false si hubo un error (404, 500…).
  • respuesta.json() convierte el JSON en objetos de JavaScript.

Mostrar los proyectos

function aTexto(html) {
  // Convierte el HTML de WordPress en texto sin etiquetas ni entidades
  return new DOMParser().parseFromString(html, "text/html").body.textContent.trim();
}

function crearTarjeta(proyecto) {
  const tarjeta = document.createElement("article");
  tarjeta.className = "tarjeta";

  const terminos = proyecto._embedded?.["wp:term"]?.flat() ?? [];
  tarjeta.dataset.categoria = terminos.map((t) => t.slug).join(" ");

  const imagen = proyecto._embedded?.["wp:featuredmedia"]?.[0];
  if (imagen) {
    const img = document.createElement("img");
    img.src = imagen.source_url;
    img.alt = imagen.alt_text;
    tarjeta.append(img);
  }

  const titulo = document.createElement("h3");
  const enlace = document.createElement("a");
  enlace.href = proyecto.link;
  enlace.textContent = aTexto(proyecto.title.rendered);
  titulo.append(enlace);

  const resumen = document.createElement("p");
  resumen.textContent = aTexto(proyecto.excerpt.rendered);

  tarjeta.append(titulo, resumen);
  return tarjeta;
}

async function iniciar() {
  const galeria = document.querySelector("#galeria");
  galeria.textContent = "Cargando proyectos…";
  try {
    const proyectos = await cargarProyectos();
    galeria.textContent = "";
    proyectos.forEach((p) => galeria.append(crearTarjeta(p)));
  } catch (error) {
    galeria.textContent = "No se han podido cargar los proyectos.";
    console.error(error);
  }
}

iniciar();

Fíjate en:

  • ?. y ??: si un proyecto no tiene imagen o disciplinas, no da error.
  • aTexto: el título y el extracto vienen en HTML; con textContent evitas insertar HTML ajeno (lección El DOM).
  • Estados de carga y de error: la persona sabe qué pasa si la red va lenta o falla.
  • data-categoria con los slugs de las disciplinas: una función filtrar como la de la lección Eventos sirve casi sin cambios (usa includes si hay varias).

Dónde poner el código

La forma más sencilla, y sin problemas de permisos, es dentro del propio WordPress: en la página Portafolio, un bloque HTML personalizado con el contenedor, los botones y el <script>. Como la página y la API están en el mismo sitio, basta con la ruta /wp-json/....

Si el JavaScript está en otra dirección (tu portfolio publicado en Netlify, por ejemplo), la API tiene que ser accesible desde Internet: un sitio de Studio en tu ordenador no lo es. Además, el navegador aplica la política CORS, que decide qué webs pueden pedir datos a otra; si ves un error de CORS en la consola, el servidor no permite esa petición.

Solo lectura y datos públicos

Sin iniciar sesión, la API solo lee contenido publicado: no puede crear ni borrar nada ni ver borradores. Para escribir hace falta autenticación, fuera del alcance de esta lección. No pongas nunca contraseñas ni claves en el JavaScript de una página: cualquiera puede leerlo.

Practica

  1. Abre en el navegador /wp-json/wp/v2/proyecto?_embed de tu sitio y localiza el título, la imagen y las disciplinas de un proyecto.
  2. En una página de prueba, con un bloque HTML personalizado, carga los proyectos y muestra sus títulos en una lista.
  3. Añade las tarjetas completas y los botones de filtro por disciplina.
  4. Desactiva la red en las DevTools (Network → Offline) y comprueba el mensaje de error.

Errores frecuentes

  • 404 en /wp-json/wp/v2/proyecto: el tipo no tiene activado Mostrar en la API REST o su slug es otro (mira /wp-json/wp/v2/types).
  • Usar await fuera de una función async.
  • Mostrar title.rendered con innerHTML o ver &#8211; en pantalla.
  • Olvidar ?_embed y no encontrar la imagen ni los nombres de las disciplinas.
  • No contemplar el caso sin datos o con error de red.

¿Qué es esto?

Soy Cristian Eslava y a veces hago webs para procrastinar yo y vosotros. culTest

La hice en febrero de 2026 para facilitar el aprendizaje de mis alumnos. La idea es aprender desarrollo web practicando y que el proyecto siga creciendo con nuevos temas, tests y retos.

Está inspirada en MDN, W3Schools, CodePen, Manz y muchos otros sitios de documentación sobre desarrollo web. Quería combinar teoría útil, ejemplos ejecutables, retos y el sistema de tests que ya tenía en culTest. culTest

Si te gustó, si no te gustó o si quieres escribirme, puedes hacerlo en cristianeslava@gmail.com