Tipos de contenido (CPT)
Los tipos de contenido personalizados (CPT, Custom Post Types) son una de las características más potentes de Ágora CMS. Te permiten definir estructuras de datos propias con sus propios campos — más allá del blog estándar — y exponerlas a través de la API con el mismo patrón REST que ya conoces. Con los CPTs puedes gestionar desde el panel cualquier tipo de contenido estructurado que tu sitio necesite.
Un CPT es una plantilla de contenido con campos personalizados: texto, URL, booleano, número, fecha, imagen, etc. Ejemplos de uso habituales: productos o servicios de un catálogo, casos de estudio o proyectos de portfolio, FAQs con pregunta y respuesta, testimonios de clientes, eventos con fecha y lugar, miembros del equipo con cargo y fotografía. Cualquier estructura repetitiva que necesites gestionar desde el CMS puede modelarse como un CPT.
GET /content-types — Listar tipos disponibles
Devuelve los tipos de contenido activos de una web. Úsalo para descubrir qué CPTs están disponibles, conocer su slug (que usarás en los siguientes endpoints) y obtener la definición de sus campos. Este endpoint es especialmente útil para generar código dinámico o validar el esquema de campos antes de consumir las entradas.
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
web_id | string | Sí | ID de la web |
curl "https://tu-panel.com/api/v1/content-types?web_id=abc-123" \
-H "X-API-Key: tu-api-key" Respuesta esperada 200 OK
{
"data": [
{
"id": "cpt-001",
"nombre": "Proyectos",
"slug": "proyectos",
"description": "Portfolio de proyectos",
"fields": [
{ "name": "client", "type": "text", "label": "Cliente", "required": true },
{ "name": "url", "type": "url", "label": "URL del proyecto", "required": false },
{ "name": "highlight", "type": "boolean", "label": "Destacado", "required": false }
]
}
]
}
El campo fields de cada tipo describe la estructura de los campos personalizados: su nombre de clave (name), el tipo de dato (type), la etiqueta legible para humanos (label) y si es obligatorio (required). Puedes usar esta información para validar o tipar las entradas en tu frontend. El id del tipo de contenido también está disponible aquí si lo necesitas para referencias internas, pero en las URLs de los siguientes endpoints usa siempre el slug.
GET /content-types/{type} — Entradas de un tipo
{type} es el slug del tipo de contenido (ej: proyectos, testimonios, faqs). Devuelve las entradas publicadas con paginación, igual que el endpoint de posts. Los campos personalizados aparecen en el objeto fields como pares clave-valor donde la clave coincide con el name definido en el tipo.
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
web_id | string | Sí | ID de la web |
lang | string | No | Código de idioma |
page | integer | No | Número de página. Por defecto: 1 |
limit | integer | No | Resultados por página. Por defecto: 20 |
curl "https://tu-panel.com/api/v1/content-types/proyectos?web_id=abc-123&lang=es&page=1" \
-H "X-API-Key: tu-api-key" Respuesta esperada 200 OK
{
"data": [
{
"id": "entry-001",
"titulo": "Rediseño web corporativa",
"slug": "rediseno-web-corporativa",
"status": "published",
"published_at": "2026-02-01T09:00:00Z",
"featured_image_url": "https://tu-panel.com/uploads/.../imagen.jpg",
"fields": {
"client": "Empresa XYZ",
"url": "https://empresa-xyz.com",
"highlight": true
}
}
],
"meta": { "total": 8, "page": 1, "limit": 20, "pages": 1 }
}
Los campos personalizados aparecen en el objeto fields como pares clave-valor.
Las claves coinciden con el name del campo definido en el tipo.
GET /content-types/{type}/{slug} — Entrada individual
Devuelve una entrada concreta de un CPT por su slug. Usa este endpoint en las páginas de detalle de cada entrada — portfolio individual, FAQ expandida, testimonio completo, etc. El objeto fields contiene todos los campos personalizados de esa entrada.
curl "https://tu-panel.com/api/v1/content-types/proyectos/rediseno-web-corporativa?web_id=abc-123&lang=es" \
-H "X-API-Key: tu-api-key" Respuesta esperada 200 OK
{
"data": {
"id": "entry-001",
"titulo": "Rediseño web corporativa",
"slug": "rediseno-web-corporativa",
"status": "published",
"published_at": "2026-02-01T09:00:00Z",
"featured_image_url": "https://...",
"meta_title": null,
"meta_description": null,
"fields": {
"client": "Empresa XYZ",
"url": "https://empresa-xyz.com",
"highlight": true
}
}
} Campos de respuesta (entradas)
| Campo | Tipo | Descripción |
|---|---|---|
id | UUID | Identificador único de la entrada |
titulo | string | Título de la entrada |
slug | string | URL amigable, única por tipo e idioma |
status | string | Siempre published en la API pública |
published_at | ISO 8601 | Fecha de publicación |
featured_image_url | string | null | URL de la imagen destacada |
meta_title | string | null | Título SEO (solo en endpoint individual) |
meta_description | string | null | Descripción SEO (solo en endpoint individual) |
fields | object | Campos personalizados del CPT como pares clave-valor |
Valida siempre los campos de fields antes de renderizarlos. Aunque el esquema del CPT defina un campo como requerido, puede llegar como null si la entrada fue creada antes de añadir ese campo o si la validación del panel no se completó correctamente. Un check defensivo evita errores en producción:
// Ejemplo: CPT de testimonios
const { data: testimonials } = await fetch(
`/api/v1/content-types/testimonios?web_id=${WEB_ID}`,
{ headers: { 'X-API-Key': API_KEY } }
).then(r => r.json())
// Renderizar con validación defensiva
testimonials.forEach(t => {
const autor = t.fields?.autor ?? 'Anónimo'
const cargo = t.fields?.cargo ?? ''
const texto = t.fields?.texto ?? t.titulo
const foto = t.featured_image_url ?? '/img/avatar-placeholder.jpg'
// ... renderizar
}) Casos de uso
Sección de testimonios. Crea un CPT "Testimonios" con campos autor (texto), cargo (texto), empresa (texto) y texto (textarea). Llama al endpoint desde tu componente de servidor y renderiza las tarjetas de testimonios. Los redactores añaden nuevos testimonios desde el panel sin tocar código.
Catálogo de productos o servicios. Modela cada producto como una entrada CPT con campos de precio, descripción corta, categoría de producto, URL de compra y booleano de "destacado". Filtra los destacados en el frontend con entry.fields.destacado === true.
FAQs gestionadas desde el CMS. Crea un CPT "FAQs" con campos pregunta y respuesta. El titulo de la entrada puede ser la misma pregunta. Renderiza un acordeón en el frontend que carga todas las FAQs publicadas desde la API, sin necesidad de hacer deploys para añadir nuevas preguntas.
Equipo / personas. Un CPT "Equipo" con campos cargo, linkedin, bio_corta y orden (número). Ordena por el campo orden en el cliente para controlar la posición de cada persona en la página.
Portfolio de proyectos. Usa un CPT "Proyectos" con cliente, tecnologias, url_live, url_repo y destacado. La featured_image_url sirve como imagen principal del proyecto. Genera rutas estáticas por slug para las páginas de detalle.
Consejos y buenas prácticas
- Crea un CPT por tipo de contenido semánticamente distinto. No uses un CPT genérico "Contenido" con un campo de tipo que diferencie registros — pierde toda la ventaja de los tipos bien definidos.
- Los campos del CPT vienen en el objeto
fieldscomo pares clave-valor planos. Las claves son las que definiste en el panel al crear el tipo — documenta el esquema de campos en tu propio proyecto para que otros desarrolladores del equipo lo conozcan. - Valida siempre los campos antes de renderizarlos. Pueden ser
nullaunque el campo sea requerido en el esquema — usa el operador??o comprobaciones explícitas para proporcionar valores de fallback. - El slug del tipo de contenido (
proyectos,testimonios…) forma parte de la URL del endpoint. Si renombras un CPT en el panel cambiará su slug y tendrás que actualizar todas las integraciones — hazlo con cuidado en producción. - Para tipos con pocos registros (FAQs, equipo, testimonios), carga todos sin paginación usando
limit=100. Para catálogos grandes, implementa paginación.
Errores frecuentes
| Código | Causa probable | Solución |
|---|---|---|
401 | API Key no incluida o revocada | Verifica el header X-API-Key en Ajustes → API |
404 en el listado | El slug del tipo no existe o el CPT está desactivado | Comprueba el slug exacto llamando primero a GET /content-types para ver los tipos disponibles |
404 en entrada individual | El slug de la entrada no existe, no está publicada o pertenece a otro tipo | Verifica que la entrada está publicada en el panel y que el slug es correcto |
400 | Falta el parámetro web_id | Incluye web_id en la query string de todas las peticiones |
Ver también
- Posts — para contenido editorial estándar (blog, noticias, artículos)
- Media — los campos de imagen en CPTs usan URLs del gestor de medios
- Introducción — autenticación, paginación y conceptos generales de la API