Cómo rellenar formularios PDF con Python
Los formularios PDF interactivos (AcroForm) son omnipresentes en contratos, solicitudes, facturas y documentos legales. Python ofrece varias bibliotecas maduras para rellenarlos de forma programática: pypdf (sucesor de PyPDF2), pdfrw y el wrapper de alto nivel fillpdf. Esta guía cubre los tres con ejemplos reales.
Conceptos clave: AcroForm
Los formularios PDF contienen campos AcroForm almacenados en el diccionario /AcroForm del PDF. Cada campo tiene:
/T— nombre del campo (clave para rellenar)/FT— tipo:/Tx(texto),/Btn(botón/checkbox),/Ch(lista/combo)/V— valor actual/AP— apariencia visual (puede necesitar actualización)
Instalación
pip install pypdf fillpdf pdfrw
Para formularios con cifrado o anotaciones complejas conviene tener también:
pip install reportlab
Inspeccionar campos de un formulario
Antes de rellenarlo, necesitas saber qué campos existen:
from pypdf import PdfReader
def listar_campos(ruta_pdf):
reader = PdfReader(ruta_pdf)
campos = reader.get_fields()
if not campos:
print("El PDF no tiene campos AcroForm")
return
for nombre, campo in campos.items():
tipo = campo.get('/FT', 'desconocido')
valor_actual = campo.get('/V', '(vacío)')
print(f"Campo: {nombre!r:40} Tipo: {tipo} Valor: {valor_actual}")
listar_campos("formulario.pdf")
Salida típica:
Campo: 'nombre_completo' Tipo: /Tx Valor: (vacío)
Campo: 'fecha_nacimiento' Tipo: /Tx Valor: (vacío)
Campo: 'acepto_terminos' Tipo: /Btn Valor: /Off
Campo: 'tipo_cuenta' Tipo: /Ch Valor: (vacío)
Rellenar con pypdf
pypdf permite leer y escribir formularios mediante la actualización del diccionario de anotaciones:
from pypdf import PdfReader, PdfWriter
from pypdf.generic import (
NameObject, BooleanObject, ArrayObject, NumberObject
)
def rellenar_pdf_pypdf(ruta_entrada, ruta_salida, datos):
reader = PdfReader(ruta_entrada)
writer = PdfWriter()
# Copiar todas las páginas
for page in reader.pages:
writer.add_page(page)
# Copiar el AcroForm
if "/AcroForm" in reader.trailer["/Root"]:
writer._root_object.update({
NameObject("/AcroForm"): reader.trailer["/Root"]["/AcroForm"]
})
# Rellenar campos
writer.update_page_form_field_values(
writer.pages[0],
datos,
auto_regenerate=False
)
with open(ruta_salida, "wb") as f:
writer.write(f)
print(f"PDF guardado: {ruta_salida}")
# Ejemplo de uso
datos_formulario = {
"nombre_completo": "Ana García López",
"fecha_nacimiento": "15/03/1990",
"ciudad": "Madrid",
"email": "ana.garcia@email.com",
}
rellenar_pdf_pypdf("formulario_vacío.pdf", "formulario_relleno.pdf", datos_formulario)
Rellenar múltiples páginas
from pypdf import PdfReader, PdfWriter
def rellenar_todas_las_paginas(ruta_entrada, ruta_salida, datos):
reader = PdfReader(ruta_entrada)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
# Actualizar campos en cada página
for i, page in enumerate(writer.pages):
writer.update_page_form_field_values(page, datos)
# Aplanar el formulario (hacer no editable)
writer.flatten_annotations(writer.pages[0]) # opcional
with open(ruta_salida, "wb") as f:
writer.write(f)
Rellenar checkboxes con pypdf
Los checkboxes usan valores /Yes o /Off:
from pypdf import PdfReader, PdfWriter
from pypdf.generic import NameObject
def marcar_checkbox(writer, nombre_campo, marcado=True):
valor = "/Yes" if marcado else "/Off"
for page in writer.pages:
if "/Annots" in page:
for annot in page["/Annots"]:
obj = annot.get_object()
if obj.get("/T") == nombre_campo:
obj.update({NameObject("/V"): NameObject(valor)})
obj.update({NameObject("/AS"): NameObject(valor)})
reader = PdfReader("formulario.pdf")
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
marcar_checkbox(writer, "acepto_terminos", marcado=True)
marcar_checkbox(writer, "recibir_newsletter", marcado=False)
with open("salida.pdf", "wb") as f:
writer.write(f)
Rellenar con fillpdf (el más sencillo)
fillpdf es el wrapper más fácil de usar para formularios simples:
from fillpdf import fillpdfs
# Listar campos
campos = fillpdfs.get_form_fields("formulario.pdf")
print(campos)
# Rellenar
datos = {
"nombre_completo": "Carlos Martínez",
"dni": "12345678A",
"telefono": "600 123 456",
"acepto_terminos": True, # checkbox
}
fillpdfs.write_fillable_pdf(
"formulario.pdf",
"formulario_relleno.pdf",
datos,
flatten=False # True = convierte a PDF estático no editable
)
Aplanar el PDF (flatten)
# Crear versión no editable (para archivar o enviar)
fillpdfs.flatten_pdf("formulario_relleno.pdf", "formulario_final.pdf")
Rellenar con pdfrw
pdfrw trabaja directamente con el árbol PDF y es muy rápido para lotes grandes:
import pdfrw
ANNOT_KEY = '/Annots'
ANNOT_FIELD_KEY = '/T'
ANNOT_VAL_KEY = '/V'
ANNOT_RECT_KEY = '/Rect'
SUBTYPE_KEY = '/Subtype'
WIDGET_SUBTYPE_KEY = '/Widget'
def rellenar_pdf_pdfrw(ruta_entrada, ruta_salida, datos):
template = pdfrw.PdfReader(ruta_entrada)
for page in template.pages:
annotations = page[ANNOT_KEY]
if not annotations:
continue
for annotation in annotations:
if annotation[SUBTYPE_KEY] == WIDGET_SUBTYPE_KEY:
if annotation[ANNOT_FIELD_KEY]:
nombre = annotation[ANNOT_FIELD_KEY][1:-1] # quitar paréntesis
if nombre in datos:
annotation.update(
pdfrw.PdfDict(V=datos[nombre], AS=datos[nombre])
)
template.Root.AcroForm.update(
pdfrw.PdfDict(NeedAppearances=pdfrw.PdfObject('true'))
)
pdfrw.PdfWriter().write(ruta_salida, template)
print(f"Guardado: {ruta_salida}")
datos = {
"nombre": "María Sánchez",
"cargo": "Directora de Operaciones",
"fecha": "24/04/2026",
}
rellenar_pdf_pdfrw("contrato.pdf", "contrato_firmado.pdf", datos)
Procesamiento por lotes
Rellenar 500 formularios desde un CSV:
import csv
from fillpdf import fillpdfs
from pathlib import Path
def batch_rellenar(plantilla_pdf, csv_datos, carpeta_salida):
carpeta_salida = Path(carpeta_salida)
carpeta_salida.mkdir(exist_ok=True)
with open(csv_datos, encoding='utf-8') as f:
lector = csv.DictReader(f)
for i, fila in enumerate(lector, 1):
nombre_archivo = fila.get('id', f'formulario_{i:04d}')
salida = carpeta_salida / f"{nombre_archivo}.pdf"
fillpdfs.write_fillable_pdf(
str(plantilla_pdf),
str(salida),
fila,
flatten=True
)
print(f"[{i}] Generado: {salida.name}")
# CSV con cabeceras: id, nombre_completo, dni, email, fecha
batch_rellenar(
"plantilla_solicitud.pdf",
"empleados.csv",
"formularios_generados/"
)
Añadir texto sobre PDF (sin AcroForm)
Si el PDF no tiene campos interactivos, puedes superponer texto con reportlab:
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from pypdf import PdfReader, PdfWriter
import io
def superponer_texto(pdf_entrada, pdf_salida, campos_posicion):
"""
campos_posicion: lista de (texto, x, y, tamaño_fuente)
Coordenadas en puntos (1 pt = 1/72 pulgada), origen abajo-izquierda
"""
# Crear capa de texto con reportlab
packet = io.BytesIO()
c = canvas.Canvas(packet, pagesize=A4)
c.setFont("Helvetica", 11)
for texto, x, y, size in campos_posicion:
c.setFontSize(size)
c.drawString(x, y, texto)
c.save()
packet.seek(0)
# Fusionar con el PDF original
overlay = PdfReader(packet)
reader = PdfReader(pdf_entrada)
writer = PdfWriter()
for i, page in enumerate(reader.pages):
if i < len(overlay.pages):
page.merge_page(overlay.pages[i])
writer.add_page(page)
with open(pdf_salida, "wb") as f:
writer.write(f)
superponer_texto(
"factura_base.pdf",
"factura_rellena.pdf",
[
("Empresa Ejemplo S.L.", 150, 680, 12),
("CIF: B12345678", 150, 665, 10),
("24/04/2026", 400, 720, 10),
("EUR 1.250,00", 420, 580, 12),
]
)
Seleccionar opciones en listas desplegables
from pypdf import PdfReader, PdfWriter
from pypdf.generic import NameObject, ArrayObject, TextStringObject
def seleccionar_opcion_lista(ruta_entrada, ruta_salida, nombre_campo, opcion):
reader = PdfReader(ruta_entrada)
writer = PdfWriter()
for page in reader.pages:
writer.add_page(page)
for page in writer.pages:
if "/Annots" not in page:
continue
for annot in page["/Annots"]:
obj = annot.get_object()
if obj.get("/T") == nombre_campo:
obj.update({
NameObject("/V"): TextStringObject(opcion),
NameObject("/DV"): TextStringObject(opcion),
})
with open(ruta_salida, "wb") as f:
writer.write(f)
seleccionar_opcion_lista(
"formulario.pdf",
"formulario_con_opcion.pdf",
"tipo_contrato",
"Indefinido"
)
Comparativa de bibliotecas
| Característica | pypdf | fillpdf | pdfrw |
|---|---|---|---|
| Facilidad de uso | Media | Alta | Media-baja |
| Checkboxes | Sí (manual) | Sí (automático) | Sí (manual) |
| Listas desplegables | Sí | Sí | Sí |
| Velocidad lotes | Media | Media | Alta |
| PDF cifrado | Sí | Limitado | No |
| Flatten (aplanar) | Sí | Sí | No |
| Dependencias | Mínimas | pdfrw + pypdf | Mínimas |
Solución de problemas frecuentes
Los campos se rellenan pero el texto no se ve:
# Forzar regeneración de apariencias
template.Root.AcroForm.update(
pdfrw.PdfDict(NeedAppearances=pdfrw.PdfObject('true'))
)
El PDF tiene protección de escritura:
from pypdf import PdfReader, PdfWriter
reader = PdfReader("protegido.pdf")
if reader.is_encrypted:
reader.decrypt("") # contraseña vacía
writer = PdfWriter()
writer.clone_reader_document_root(reader)
# ... rellenar campos
Nombres de campo con espacios o caracteres especiales:
# Los nombres pueden tener caracteres unicode — usar .encode()
nombre = annotation['/T'].decode('utf-8') if hasattr(annotation['/T'], 'decode') else str(annotation['/T'])
Recurso adicional
Para conversiones de PDF (PDF→Word, PDF→imagen, PDF→texto) sin necesidad de programar, utiliza KaijuConverter — rápido, gratuito y sin registro.
Conversiones relacionadas
Conversiones de documento que siguen este tema: