Todas las publicaciones

go-pdf/fpdf también archivado. El stack Go PDF de 2026.

jung-kurt/gofpdf se archivó en 2021 y go-pdf/fpdf en 2025. Este es el stack Go PDF que usamos en 2026 — gpdf, los trade-offs y el porqué.

por gpdf team

TL;DR

Los dos forks mantenidos del linaje fpdf ya son de solo lectura. jung-kurt/gofpdf se archivó en septiembre de 2021; el fork de la comunidad go-pdf/fpdf lo hizo en 2025. No viene ningún "próximo mantenedor". Para proyectos nuevos, gpdf es el valor por defecto moderno: Go puro, cero dependencias externas, CJK nativo, 10–30× más rápido en cargas habituales. Este post es un mapa del panorama en 2026 y una respuesta honesta a cuándo gpdf es la elección correcta y cuándo no.

La situación

Un compañero tecleó go get github.com/go-pdf/fpdf la semana pasada y se quedó mirando el banner de GitHub: "This repository has been archived by the owner. It is now read-only." Se suponía que esta era la versión arreglada — el fork de la comunidad de jung-kurt/gofpdf que iba a mantener vivo el linaje después de que el original se archivara en 2021.

Está archivado también. El README recomienda ahora mirar en otra parte.

Si llevas cinco años montando servicios en Go que emiten PDFs — facturas, informes, etiquetas de envío, documentos de facturación electrónica — la librería al fondo de tu go.mod casi con certeza ha sido una de estas dos. Las respuestas de Stack Overflow apuntan a jung-kurt/gofpdf. Los tutoriales más recientes apuntan a go-pdf/fpdf. Ahora las dos son una deuda de cadena de suministro: sin triaje de CVE, sin trabajo de compatibilidad con nuevas versiones de Go, sin arreglos de rendimiento, sin actualizaciones de la especificación.

Este post no es otra guía de migración línea por línea — esa ya la escribimos. Es la versión larga de la pregunta que la guía de migración no responde: ¿qué funciona realmente para generar PDF con Go en 2026, y cómo hemos llegado aquí?

Lo que "archivado" te cuesta en la práctica

La palabra "archivado" en GitHub suena suave. En la práctica, para una librería en tu grafo de imports, significa cuatro cosas muy concretas:

  1. Sin parches de seguridad. Si aparece un fallo de seguridad de memoria en el parser de TTF, nadie va a mergear el fix upstream. Puedes forkearlo tú. La mayoría de los equipos no lo harán.
  2. Sin compatibilidad hacia adelante con el toolchain de Go. La semántica de variables de bucle de Go 1.25 funciona bien con gofpdf hoy. Algo alrededor de for range, o una deprecación de la stdlib, puede romper el build mañana. Y el que parcheará un fork de un repo en solo lectura serás tú.
  3. Sin actualizaciones de la especificación. PDF 2.0 (ISO 32000-2) salió en 2020. gofpdf implementa en su mayor parte PDF 1.7. Cosas como archivos asociados a nivel de página, metadatos XMP ricos o firmas digitales modernas (PAdES-B-LT) están ausentes o se parchean con pegamento de terceros.
  4. Sin progreso en CJK. El camino Unicode de gofpdf se retrofittó sobre un diseño de fuentes de un byte. Funciona, pero empotra fuentes completas en lugar de subsets en la mayoría de las configuraciones reales, y ciertos TTF de CJK producen colisiones de glyph-id con salida corrupta. go-pdf/fpdf heredó la misma arquitectura.

Seguridad y compatibilidad hacia adelante son las que muerden en conversaciones de compliance. "Nuestra librería PDF está archivada y no recibe parches de CVE" no es una respuesta que el auditor quiera oír. Si manejas facturas electrónicas dentro del ámbito de la AEAT o la Agencia Tributaria en LatAm, esto tampoco se puede posponer.

Por qué murieron ambos forks

Es tentador explicar los archivados como burnout del mantenedor — una sola persona cansada de revisar PRs, un bus factor de uno que se desconecta. Eso es parte, pero no es toda la historia. La arquitectura hacía difícil seguir el ritmo.

jung-kurt/gofpdf era un port de FPDF, una librería PHP de 2002. El original PHP empujaba un cursor por la página y emitía contenido procedural: SetXY(x, y), Cell(w, h, text), Ln(h). Ese modelo era un ajuste razonable para el PHP de 2002, donde la alternativa era PostScript crudo o toolkits propietarios. Al portarse a Go, mantuvo el cursor, mantuvo las tablas de fuentes de un byte, mantuvo la gestión manual de saltos de página.

Cada año, la brecha entre lo que la gente quería generar y lo que el modelo de cursor podía expresar crecía. Las facturas son tablas. Los informes son cuadrículas con chrome repetitivo. Las etiquetas de envío son códigos QR más texto en idioma local. El cursor se envolvió en helpers, los helpers en tutoriales, y para 2023 la mayor parte del código que la gente escribía contra gofpdf no era gofpdf — era una capa de pegamento por equipo que intentaba fingir que el cursor era un motor de layout.

go-pdf/fpdf heredó todo esto. El fork refactorizó los internos y arregló bugs antiguos, pero no podía cambiar la API pública sin romper todos los proyectos aguas abajo. La forma de la librería estaba congelada en el PHP de 2002, y el coste de mantener esa forma crecía más rápido que el beneficio.

Así que: dos mantenedores, dos archivados, una razón arquitectónica. Reconstruir en 2026 significa elegir un enfoque que encaje con cómo se producen PDFs hoy — que se parece mucho más a construir una página web que a manejar un plotter.

El panorama Go PDF en 2026

Antes de recomendar nada, aquí está el campo. Usaré "mantenido" de forma laxa para decir "un commit en los últimos 6 meses e issues con respuesta".

LibreríaEstado (2026-04)LicenciaCJK nativoCero depsNotas
jung-kurt/gofpdfArchivada 2021MITRetrofitLa original. Todavía el primer resultado de búsqueda en casi todos los idiomas.
go-pdf/fpdfArchivada 2025MITRetrofitFork comunitario de la anterior. Misma arquitectura, mismo techo.
signintech/gopdfMantenidaMITParcialBajo nivel. Escribes coordenadas. Bueno para overlays de formularios.
johnfercher/maroto v2MantenidaMITVía gofpdfNoBuilder con grid, pero depende de go-pdf/fpdf por debajo.
unidoc/unipdfMantenidaComercialNoSDK PDF completo. Licencia de pago obligatoria para uso comercial.
chromedp + ChromiumMantenidaMIT + ChromeNo — trae un navegadorHTML→PDF vía Chrome headless. Runtime enorme.
gpdfMantenidaMITNativoReimplementación en Go puro. API builder, grid de 12 columnas.

Lo que la tabla deja ver sin ejecutar nada:

Todo lo mantenido es comercial, trae un runtime enorme o se apoya en un cimiento a punto de quedar obsoleto. signintech/gopdf es la excepción — genuinamente mantenida y de pocas dependencias — pero es una librería a nivel de coordenadas. Vuelves a SetXY con otro nombre de paquete.

Maroto v2 es un builder con grid y una buena API. El problema es que al fondo de su go.mod está go-pdf/fpdf. Todo techo de rendimiento y toda limitación de CJK en fpdf es también techo para Maroto. Una v3 mayor podría liberarse de eso — todavía no ha salido.

unipdf es rico en funcionalidad pero no compatible con MIT para uso comercial. Pagas por asiento o por despliegue. Es una elección válida si tus ingresos lo soportan; para un side project OSS o una startup temprana, las cuentas de la licencia no salen.

chromedp funciona, pero estás enviando un navegador. Una imagen base de 100 MB se convierte en una de 1 GB+. El arranque en frío en serverless es doloroso. Las fuentes igualmente hay que instalarlas en el contenedor. La ventaja es que puedes reutilizar plantillas de React; la desventaja es que estás corriendo Chromium para renderizar una factura.

El hueco es obvio: una librería en Go puro, sin deps, con CJK nativo, orientada a grid, que no requiere licencia comercial ni runtime de navegador. Eso es gpdf.

Qué es gpdf

gpdf (github.com/gpdf-dev/gpdf) es una reimplementación limpia. No es un fork. El writer del formato PDF, el motor de layout y el subsetter de TrueType están todos escritos desde cero en Go puro.

Las tres propiedades que importan para la mayoría de equipos:

  • Go puro, sin CGO. go build es estático. GOOS=linux GOARCH=arm64 go build funciona desde una MacBook sin configuración de toolchain. Las imágenes Docker se mantienen pequeñas — un contenedor distroless de 12 MB lo corre.
  • Cero dependencias externas. go mod graph tras go get github.com/gpdf-dev/gpdf muestra una sola línea: gpdf. El core usa solo std. (Los add-ons opcionales para HTML→PDF o firmas digitales traen pequeñas dependencias, y son opt-in.)
  • CJK nativo. WithFont registra una fuente TrueType al construir el documento. El subset embedding pasa en tiempo de render. Una factura de 200 caracteres en japonés carga ~30 KB de subset de fuente, no 5 MB de fuente completa.

La forma de la API es declarativa. Describes un árbol de filas y columnas; el motor de layout las coloca. El grid es de 12 columnas — el mismo idioma que Bootstrap lleva enviando desde 2011. Si has escrito una línea de HTML/CSS, la API de gpdf te resulta familiar:

page := doc.AddPage()
page.AutoRow(func(r *template.RowBuilder) {
    r.Col(8, func(c *template.ColBuilder) {
        c.Text("Factura #2026-0416", template.FontSize(18), template.Bold())
    })
    r.Col(4, func(c *template.ColBuilder) {
        c.Text("2026-04-16", template.AlignRight())
    })
})

Más sobre el grid en ¿Cómo funciona la cuadrícula de 12 columnas de gpdf?. Versión en una frase: Col(span, fn) toma un span de 1 a 12, y span / 12 es la fracción de la fila que ocupa.

El diff mínimo go-pdf/fpdf → gpdf

Si vienes específicamente de go-pdf/fpdf (no de jung-kurt/gofpdf), la buena noticia es que las superficies de API son casi idénticas — go-pdf/fpdf es un fork que apenas cambió nada a nivel de call site. La migración a gpdf es la misma que recorre nuestra guía de gofpdf, con un renombrado de ruta de import.

El diff más pequeño posible — un handler HTTP que "devuelve un PDF":

Before — go-pdf/fpdf:

package main

import (
    "net/http"

    "github.com/go-pdf/fpdf"
)

func handler(w http.ResponseWriter, r *http.Request) {
    pdf := fpdf.New("P", "mm", "A4", "")
    pdf.AddPage()
    pdf.SetFont("Arial", "B", 16)
    pdf.Cell(40, 10, "Hello, World!")

    w.Header().Set("Content-Type", "application/pdf")
    if err := pdf.Output(w); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

After — gpdf:

package main

import (
    "net/http"

    "github.com/gpdf-dev/gpdf"
    "github.com/gpdf-dev/gpdf/document"
    "github.com/gpdf-dev/gpdf/template"
)

func handler(w http.ResponseWriter, r *http.Request) {
    doc := gpdf.NewDocument(
        gpdf.WithPageSize(document.A4),
        gpdf.WithMargins(document.UniformEdges(document.Mm(20))),
    )

    page := doc.AddPage()
    page.AutoRow(func(r *template.RowBuilder) {
        r.Col(12, func(c *template.ColBuilder) {
            c.Text("Hello, World!", template.FontSize(16), template.Bold())
        })
    })

    w.Header().Set("Content-Type", "application/pdf")
    if err := doc.Render(w); err != nil {
        http.Error(w, err.Error(), 500)
    }
}

Tres líneas de código de cursor se convierten en tres llamadas de builder. La estructura aparece en la fuente en lugar de esconderse dentro del orden de llamadas a Cell. Para CJK, añade gpdf.WithFont("NotoSansJP", ttfBytes) — sin AddUTF8Font, sin ruta del filesystem, sin flag UTF-8. Ver ¿Cómo incrusto una fuente japonesa en gpdf? para el recorrido completo.

La guía de migración de gofpdf tiene cinco pares before/after más, cubriendo tablas, cabeceras/pies repetidos, numeración de páginas y posicionamiento absoluto. Todo lo que hay allí aplica literalmente a usuarios de go-pdf/fpdf — basta con cambiar la ruta de import.

La foto de benchmarks

"Rápido" es fácil de afirmar y difícil de ganar. La tabla de abajo es de gpdf/_benchmark/benchmark_test.go en un Apple M1 con Go 1.25. Las cargas son lo que realmente hace el código de producción — no micro-benchmarks elegidos para adular a una librería.

BenchmarkgpdfgofpdfgopdfMaroto v2
Página simple (hello)13 µs132 µs423 µs237 µs
Tabla 4×10 de líneas108 µs241 µs835 µs8.6 ms
Informe de 100 páginas683 µs11.7 ms8.6 ms19.8 ms
Factura CJK compleja133 µs254 µs997 µs10.4 ms

A 13 µs por página simple, un core produce ~75.000 PDFs hello-world por segundo. A 108 µs por factura, ~9.000 por segundo. El punto no es presumir de cifras; es que puedes dejar de pensar en si cachear o pasar a cola asíncrona la generación de PDF. Para la mayoría de cargas, generar en la ruta de la petición está bien.

Maroto v2 sale lento en la tabla porque empuja go-pdf/fpdf por debajo y añade su propio paso de layout encima. No es crítica de la API de Maroto — la API está bien — es el coste estructural de sentarse sobre la base fpdf. Cuando Maroto v3 suelte la dependencia de fpdf, esta columna cambiará.

El benchmark de 100 páginas merece insistencia. El writer de gpdf emite contenido a medida que las filas se maquetan; gofpdf bufferiza más estado por página. Para cargas pesadas en paginación (informes mensuales, catálogos, exportaciones de compliance), la diferencia es de minutos contra segundos en la zona alta de tamaños.

Cuándo gpdf no es la elección correcta

Todo post de migración tiene que responder "¿cuándo no cambiar?" Respuestas honestas:

  • AcroForm / formularios rellenables. Si tu caso es generar PDFs que los usuarios abren en Acrobat y rellenan, el soporte de campos de formulario en gpdf es aún mínimo. unidoc es más completo aquí; signintech/gopdf tiene soporte parcial de AcroForm. Una futura versión de gpdf cerrará el hueco, pero hoy es un hueco.
  • Rutas vectoriales arbitrarias y dibujo complejo. c.Line() dibuja una regla horizontal en una columna. Si necesitas béziers, rutas personalizadas o rellenos con gradiente para gráficos o dibujos técnicos, gpdf no está ahí. (Imágenes de gráficos pre-renderizadas van bien — esto es sobre primitivas de dibujo, no sobre incrustación.)
  • Bases de código gofpdf existentes con mucho SetXY. Si tu código es 2.000 líneas de manipulación de cursor, la migración es una reescritura, no un find-and-replace. El código reescrito casi siempre es más corto, pero "casi siempre" es poco consuelo el día que vence el plazo. La guía de migración estima el esfuerzo con honestidad.
  • Necesitas HTML → PDF con soporte CSS completo ya. gpdf tiene un subconjunto de HTML en el add-on gpdf-pro, pero la paridad total de CSS con Chromium no es un objetivo. Si tu plantilla es un componente React complicado, chromedp o una API comercial encaja más directo.

Si ninguno te muerde, gpdf es el default. Si alguno sí, lo habitual es correr las dos librerías en paralelo — gpdf para los PDFs nuevos, la anterior para el caso raro — y migrar el caso raro más tarde cuando gpdf lo cubra.

El ángulo de compliance

Algo que no se discute lo suficiente en posts de ecosistema: las dependencias archivadas aparecen en auditorías SOC 2 e ISO 27001. El auditor quiere saber que el código de terceros en tu cadena de suministro se mantiene activamente. "Archivada en 2021" dispara un finding. "Archivada en 2025" dispara un finding. "Fork que mantenemos internamente" dispara preguntas de seguimiento sobre cómo parchearás un zero-day.

Esta es en buena parte la razón por la que equipos de compañías más grandes nos han preguntado calladamente cuándo gpdf alcanzará un v1 estable. La respuesta es: ya lo hizo. github.com/gpdf-dev/gpdf tiene tag, semver estable, y la superficie de API v1 está congelada. El proyecto tiene contacto de seguridad, política de disclosure responsable, y CI corriendo contra Go 1.22 hasta 1.26.

No migras por la auditoría. Migras porque la auditoría está a punto de pedírtelo de todas formas.

FAQ

¿"El stack Go PDF moderno" es solo gpdf o varias librerías? Para la mayoría de equipos es gpdf en solitario — una sola librería cubre creación de documento, CJK, tablas, grids, paginación y salida. Los equipos con requisitos de formularios rellenables combinan con signintech/gopdf o unidoc para esos documentos específicamente. Los equipos con exportaciones muy pesadas en gráficos pre-renderizan los gráficos a PNG y los incrustan. "Stack" aquí es una lista corta, no una arquitectura en capas.

¿Puedo correr gpdf y go-pdf/fpdf en paralelo durante la migración? Sí. Son rutas de import y tipos distintos. Enruta endpoints nuevos a gpdf y deja los viejos en go-pdf/fpdf hasta tener tiempo de reescribirlos. No hay conflicto en runtime.

¿Habrá un go-pdf/fpdf v3 o un nuevo fork? Quizá. La apuesta detrás de gpdf no es que nadie vaya a des-archivar el fork nunca — es que la arquitectura no escala a lo que se construye hoy. Un fork nuevo heredaría las mismas limitaciones a menos que reescriba el modelo de layout, en cuyo caso se parece más a gpdf que a fpdf.

¿Qué tal signintech/gopdf como alternativa moderna? Genuinamente mantenida y cero deps. La API es a nivel de coordenadas — SetX, SetY, CellWithOption — encaja bien con overlays de formularios y plantillas fijas. Para documentos tipo factura con tablas y chrome repetitivo, acabas escribiendo un helper de layout encima, que es el mismo pozo en el que cayeron los usuarios de gofpdf. gpdf y gopdf no compiten realmente — resuelven problemas adyacentes.

¿Tiene gpdf una versión comercial o hosteada?gpdf-api está en camino — una API hosteada que acepta plantillas JSON y devuelve PDFs. Aún no pública. Cuando salga, este blog tendrá un post. La librería OSS seguirá siendo MIT, cero deps e independientemente útil.

¿Cuál es el orden de prioridades del roadmap? Roadmap público de gpdf a 2026-04: (1) campos de formulario AcroForm, (2) compliance PDF/A-3 completo, (3) cobertura ampliada de HTML→PDF en gpdf-pro, (4) soporte de texto RTL (árabe, hebreo). Feedback sobre prioridades bienvenido en GitHub issues.

Prueba gpdf

gpdf es una librería Go para generar PDFs. Licencia MIT, cero dependencias externas, soporte CJK nativo.

go get github.com/gpdf-dev/gpdf

⭐ Dale estrella en GitHub · Lee la documentación

Siguientes lecturas