[{"data":1,"prerenderedAt":2375},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python":3,"surround:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python":2340,"ancestors:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python":2349,"children:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python":2354,"related:\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python":2355},{"id":4,"title":5,"body":6,"dateModified":2307,"datePublished":2307,"description":2308,"extension":2309,"faq":2310,"keyword":2319,"kind":2320,"meta":2321,"navigation":293,"path":2329,"seo":2330,"slug":2333,"stem":2334,"topics":2335,"type":2338,"__hash__":2339},"docs\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python\u002Findex.md","Fill PDF Forms from Excel Rows with Python",{"type":7,"value":8,"toc":2294},"minimark",[9,24,29,61,71,258,262,265,415,449,453,456,833,1082,1091,1095,1098,1271,1285,1289,1296,1496,1509,1620,1624,1627,1823,1826,1830,1840,2131,2144,2148,2199,2203,2218,2222,2235,2245,2251,2257,2261,2290],[10,11,12,13,17,18,23],"p",{},"Some documents have to be on someone else's form: a supplier's order form, an insurer's claim template, a government return, an internal expense claim that finance will only accept in its own layout. When the data is a spreadsheet with one row per form, filling them by hand means copying the same fields dozens of times. A fillable PDF — an ",[14,15,16],"em",{},"AcroForm"," — has named fields, and pypdf can write values into them, so a script fills one copy per row, ticks the right boxes and, if needed, flattens the result so the values are part of the page. This page is part of ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002F","Generating Excel Reports from Templates",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pypdf pandas openpyxl\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55,58],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," pypdf",[40,56,57],{"class":50}," pandas",[40,59,60],{"class":50}," openpyxl\n",[10,62,63,66,67,70],{},[37,64,65],{},"claim_form.pdf"," is a one-page expense claim form with five text fields and a \"receipts attached\" checkbox. ",[37,68,69],{},"claims.xlsx"," has four claims, one per row, with columns that correspond to the fields but are not named the same way — the usual situation.",[72,73,81,82,81,86,81,90,81,98,81,108,81,119,81,126,81,131,81,137,81,142,81,146,81,153,81,159,81,163,81,167,81,171,81,174,81,177,81,183,81,188,81,191,81,194,81,198,81,202,81,205,81,210,81,217,81,222,81,225,81,229,81,232,81,237,81,244,81,249,81,252,81,255],"svg",{"viewBox":74,"role":75,"ariaLabelledBy":76,"xmlns":79,"style":80},"4 0 752 160","img",[77,78],"pf-flow-t","pf-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[83,84,85],"title",{"id":77},"From a row to a filled, flattened form",[87,88,89],"desc",{"id":78},"The form's field names are listed once, each sheet column is mapped to a field, values are formatted as text, each row fills a copy of the form, the copy is flattened, and the result is checked.",[91,92],"rect",{"x":93,"y":94,"width":95,"height":96,"fill":97},"4","0","752","160","#ffffff",[91,99],{"x":100,"y":101,"width":102,"height":103,"rx":104,"fill":105,"stroke":106,"style":107},"20.0","16.0","115.2","128.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[109,110,112],"g",{"transform":111},"translate(65.6,46.5) scale(1.000)",[113,114],"path",{"d":115,"fill":116,"stroke":117,"style":118},"M11 4a7 7 0 1 0 .01 0z M16 16l5 5","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[120,121,125],"text",{"x":122,"y":123,"style":124},"77.6","93.0","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","List fields",[120,127,130],{"x":122,"y":128,"style":129},"112.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","get_fields()",[42,132],{"x1":133,"y1":134,"x2":135,"y2":134,"stroke":136,"style":107},"140.2","80.0","162.2","var(--brand,#5b5cf0)",[138,139],"polygon",{"points":140,"fill":141},"166.2,80.0 157.2,85.0 157.2,75.0","#5b5cf0",[91,143],{"x":144,"y":101,"width":102,"height":103,"rx":104,"fill":145,"stroke":136,"style":107},"171.2","#f0f4ff",[109,147,149],{"transform":148},"translate(216.8,37.8) scale(1.000)",[113,150],{"d":151,"fill":116,"stroke":152,"style":118},"M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1 M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1","var(--brand-strong,#4338ca)",[120,154,158],{"x":155,"y":156,"style":157},"228.8","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Map columns",[120,160,162],{"x":155,"y":161,"style":129},"103.8","sheet → field",[120,164,166],{"x":155,"y":165,"style":129},"121.2","names",[42,168],{"x1":169,"y1":134,"x2":170,"y2":134,"stroke":136,"style":107},"291.4","313.4",[138,172],{"points":173,"fill":141},"317.4,80.0 308.4,85.0 308.4,75.0",[91,175],{"x":176,"y":101,"width":102,"height":103,"rx":104,"fill":145,"stroke":136,"style":107},"322.4",[109,178,180],{"transform":179},"translate(368.0,29.0) scale(1.000)",[113,181],{"d":182,"fill":116,"stroke":152,"style":118},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1",[120,184,187],{"x":185,"y":186,"style":157},"380.0","75.5","Format",[120,189,190],{"x":185,"y":123,"style":157},"values",[120,192,193],{"x":185,"y":128,"style":129},"numbers, dates,",[120,195,197],{"x":185,"y":196,"style":129},"130.0","ticks",[42,199],{"x1":200,"y1":134,"x2":201,"y2":134,"stroke":136,"style":107},"442.6","464.6",[138,203],{"points":204,"fill":141},"468.6,80.0 459.6,85.0 459.6,75.0",[91,206],{"x":207,"y":101,"width":102,"height":103,"rx":104,"fill":208,"stroke":209,"style":107},"473.6","#d9f4f1","var(--teal,#0f9488)",[109,211,213],{"transform":212},"translate(519.2,46.5) scale(1.000)",[113,214],{"d":215,"fill":116,"stroke":216,"style":118},"M6 3h9l4 4v14H6z M15 3v4h4 M9 12h6 M9 15.5h6 M9 19h4","var(--teal-ink,#0b6157)",[120,218,221],{"x":219,"y":123,"style":220},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Fill a copy",[120,223,224],{"x":219,"y":128,"style":129},"per row",[42,226],{"x1":227,"y1":134,"x2":228,"y2":134,"stroke":136,"style":107},"593.8","615.8",[138,230],{"points":231,"fill":141},"619.8,80.0 610.8,85.0 610.8,75.0",[91,233],{"x":234,"y":101,"width":102,"height":103,"rx":104,"fill":235,"stroke":236,"style":107},"624.8","#fdefd8","var(--gold,#b4740a)",[109,238,240],{"transform":239},"translate(670.4,29.0) scale(1.000)",[113,241],{"d":242,"fill":116,"stroke":243,"style":118},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[120,245,248],{"x":246,"y":186,"style":247},"682.4","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Flatten +",[120,250,251],{"x":246,"y":123,"style":247},"check",[120,253,254],{"x":246,"y":128,"style":129},"values on the",[120,256,257],{"x":246,"y":196,"style":129},"page",[25,259,261],{"id":260},"step-1-list-the-forms-fields","Step 1 — List the form's fields",[10,263,264],{},"Every fillable field has an internal name, which is what the script writes to — not the label printed next to it. List them with their types and current values:",[30,266,270],{"className":267,"code":268,"language":269,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","from pypdf import PdfReader\n\nfields = PdfReader(\"claim_form.pdf\").get_fields()\nfor name, field in fields.items():\n    print(f\"{name:12} type={field.get('\u002FFT')} value={field.get('\u002FV')!r} states={field.get('\u002F_States_', '')}\")\n","python",[37,271,272,288,295,313,328],{"__ignoreMap":35},[40,273,274,278,282,285],{"class":42,"line":43},[40,275,277],{"class":276},"s-kum","from",[40,279,281],{"class":280},"skGVy"," pypdf ",[40,283,284],{"class":276},"import",[40,286,287],{"class":280}," PdfReader\n",[40,289,291],{"class":42,"line":290},2,[40,292,294],{"emptyLinePlaceholder":293},true,"\n",[40,296,298,301,304,307,310],{"class":42,"line":297},3,[40,299,300],{"class":280},"fields ",[40,302,303],{"class":276},"=",[40,305,306],{"class":280}," PdfReader(",[40,308,309],{"class":50},"\"claim_form.pdf\"",[40,311,312],{"class":280},").get_fields()\n",[40,314,316,319,322,325],{"class":42,"line":315},4,[40,317,318],{"class":276},"for",[40,320,321],{"class":280}," name, field ",[40,323,324],{"class":276},"in",[40,326,327],{"class":280}," fields.items():\n",[40,329,331,335,338,341,344,348,351,354,357,360,362,365,368,371,373,376,378,380,383,385,388,390,393,395,397,400,403,406,408,410,412],{"class":42,"line":330},5,[40,332,334],{"class":333},"sP0c6","    print",[40,336,337],{"class":280},"(",[40,339,340],{"class":276},"f",[40,342,343],{"class":50},"\"",[40,345,347],{"class":346},"sSjpA","{",[40,349,350],{"class":280},"name",[40,352,353],{"class":276},":12",[40,355,356],{"class":346},"}",[40,358,359],{"class":50}," type=",[40,361,347],{"class":346},[40,363,364],{"class":280},"field.get(",[40,366,367],{"class":50},"'\u002FFT'",[40,369,370],{"class":280},")",[40,372,356],{"class":346},[40,374,375],{"class":50}," value=",[40,377,347],{"class":346},[40,379,364],{"class":280},[40,381,382],{"class":50},"'\u002FV'",[40,384,370],{"class":280},[40,386,387],{"class":276},"!r",[40,389,356],{"class":346},[40,391,392],{"class":50}," states=",[40,394,347],{"class":346},[40,396,364],{"class":280},[40,398,399],{"class":50},"'\u002F_States_'",[40,401,402],{"class":280},", ",[40,404,405],{"class":50},"''",[40,407,370],{"class":280},[40,409,356],{"class":346},[40,411,343],{"class":50},[40,413,414],{"class":280},")\n",[10,416,417,420,421,424,425,428,429,432,433,436,437,440,441,444,445,448],{},[37,418,419],{},"\u002FTx"," is a text field and ",[37,422,423],{},"\u002FBtn"," a button — here a checkbox. For checkboxes, ",[37,426,427],{},"\u002F_States_"," lists the values the box accepts: ",[37,430,431],{},"\u002FOff"," and the \"on\" state, which is usually ",[37,434,435],{},"\u002FYes"," but can be anything the form's author chose, such as ",[37,438,439],{},"\u002FOn"," or ",[37,442,443],{},"\u002F1",". Writing any other value leaves the box looking unticked, so always read the state name from the form rather than assuming. Real-world forms often have unhelpful internal names like ",[37,446,447],{},"Text12","; list them once, and if the names do not say which box is which, fill each with its own name as a test and look at the result.",[25,450,452],{"id":451},"step-2-map-columns-to-fields-and-format-the-values","Step 2 — Map columns to fields and format the values",[10,454,455],{},"Keep the mapping between the sheet and the form in one dictionary. Everything a PDF field holds is text, so format numbers and dates exactly as they should appear:",[30,457,459],{"className":267,"code":458,"language":269,"meta":35,"style":35},"import pandas as pd\n\nclaims = pd.read_excel(\"claims.xlsx\", parse_dates=[\"ClaimDate\"])\nCHECKED = [s for s in fields[\"receipts\"][\"\u002F_States_\"] if s != \"\u002FOff\"][0]\n\ndef field_values(row):\n    return {\n        \"employee\": row.Name,\n        \"employee_id\": row.EmployeeID,\n        \"cost_centre\": row.CostCentre,\n        \"amount\": f\"{row.Amount:,.2f}\",\n        \"claim_date\": f\"{row.ClaimDate:%d\u002F%m\u002F%Y}\",\n        \"receipts\": CHECKED if str(row.Receipts).strip().lower() in {\"yes\", \"y\", \"true\", \"1\"} else \"\u002FOff\",\n    }\n\nvalues = [field_values(r) for r in claims.itertuples()]\nmissing = set().union(*values) - set(fields)\nassert not missing, f\"no such fields in the form: {missing}\"\nprint(values[1])\n",[37,460,461,474,478,508,559,563,576,585,594,603,612,640,680,732,738,743,764,792,819],{"__ignoreMap":35},[40,462,463,465,468,471],{"class":42,"line":43},[40,464,284],{"class":276},[40,466,467],{"class":280}," pandas ",[40,469,470],{"class":276},"as",[40,472,473],{"class":280}," pd\n",[40,475,476],{"class":42,"line":290},[40,477,294],{"emptyLinePlaceholder":293},[40,479,480,483,485,488,491,493,497,499,502,505],{"class":42,"line":297},[40,481,482],{"class":280},"claims ",[40,484,303],{"class":276},[40,486,487],{"class":280}," pd.read_excel(",[40,489,490],{"class":50},"\"claims.xlsx\"",[40,492,402],{"class":280},[40,494,496],{"class":495},"sa561","parse_dates",[40,498,303],{"class":276},[40,500,501],{"class":280},"[",[40,503,504],{"class":50},"\"ClaimDate\"",[40,506,507],{"class":280},"])\n",[40,509,510,513,516,519,521,524,526,529,532,535,538,541,544,546,549,552,554,556],{"class":42,"line":315},[40,511,512],{"class":333},"CHECKED",[40,514,515],{"class":276}," =",[40,517,518],{"class":280}," [s ",[40,520,318],{"class":276},[40,522,523],{"class":280}," s ",[40,525,324],{"class":276},[40,527,528],{"class":280}," fields[",[40,530,531],{"class":50},"\"receipts\"",[40,533,534],{"class":280},"][",[40,536,537],{"class":50},"\"\u002F_States_\"",[40,539,540],{"class":280},"] ",[40,542,543],{"class":276},"if",[40,545,523],{"class":280},[40,547,548],{"class":276},"!=",[40,550,551],{"class":50}," \"\u002FOff\"",[40,553,534],{"class":280},[40,555,94],{"class":333},[40,557,558],{"class":280},"]\n",[40,560,561],{"class":42,"line":330},[40,562,294],{"emptyLinePlaceholder":293},[40,564,566,569,573],{"class":42,"line":565},6,[40,567,568],{"class":276},"def",[40,570,572],{"class":571},"s_Opv"," field_values",[40,574,575],{"class":280},"(row):\n",[40,577,579,582],{"class":42,"line":578},7,[40,580,581],{"class":276},"    return",[40,583,584],{"class":280}," {\n",[40,586,588,591],{"class":42,"line":587},8,[40,589,590],{"class":50},"        \"employee\"",[40,592,593],{"class":280},": row.Name,\n",[40,595,597,600],{"class":42,"line":596},9,[40,598,599],{"class":50},"        \"employee_id\"",[40,601,602],{"class":280},": row.EmployeeID,\n",[40,604,606,609],{"class":42,"line":605},10,[40,607,608],{"class":50},"        \"cost_centre\"",[40,610,611],{"class":280},": row.CostCentre,\n",[40,613,615,618,621,623,625,627,630,633,635,637],{"class":42,"line":614},11,[40,616,617],{"class":50},"        \"amount\"",[40,619,620],{"class":280},": ",[40,622,340],{"class":276},[40,624,343],{"class":50},[40,626,347],{"class":346},[40,628,629],{"class":280},"row.Amount",[40,631,632],{"class":276},":,.2f",[40,634,356],{"class":346},[40,636,343],{"class":50},[40,638,639],{"class":280},",\n",[40,641,643,646,648,650,652,654,657,660,663,666,669,671,674,676,678],{"class":42,"line":642},12,[40,644,645],{"class":50},"        \"claim_date\"",[40,647,620],{"class":280},[40,649,340],{"class":276},[40,651,343],{"class":50},[40,653,347],{"class":346},[40,655,656],{"class":280},"row.ClaimDate:",[40,658,659],{"class":276},"%",[40,661,662],{"class":280},"d",[40,664,665],{"class":276},"\u002F%",[40,667,668],{"class":280},"m",[40,670,665],{"class":276},[40,672,673],{"class":280},"Y",[40,675,356],{"class":346},[40,677,343],{"class":50},[40,679,639],{"class":280},[40,681,683,686,688,690,693,696,699,701,704,707,709,712,714,717,719,722,725,728,730],{"class":42,"line":682},13,[40,684,685],{"class":50},"        \"receipts\"",[40,687,620],{"class":280},[40,689,512],{"class":333},[40,691,692],{"class":276}," if",[40,694,695],{"class":333}," str",[40,697,698],{"class":280},"(row.Receipts).strip().lower() ",[40,700,324],{"class":276},[40,702,703],{"class":280}," {",[40,705,706],{"class":50},"\"yes\"",[40,708,402],{"class":280},[40,710,711],{"class":50},"\"y\"",[40,713,402],{"class":280},[40,715,716],{"class":50},"\"true\"",[40,718,402],{"class":280},[40,720,721],{"class":50},"\"1\"",[40,723,724],{"class":280},"} ",[40,726,727],{"class":276},"else",[40,729,551],{"class":50},[40,731,639],{"class":280},[40,733,735],{"class":42,"line":734},14,[40,736,737],{"class":280},"    }\n",[40,739,741],{"class":42,"line":740},15,[40,742,294],{"emptyLinePlaceholder":293},[40,744,746,749,751,754,756,759,761],{"class":42,"line":745},16,[40,747,748],{"class":280},"values ",[40,750,303],{"class":276},[40,752,753],{"class":280}," [field_values(r) ",[40,755,318],{"class":276},[40,757,758],{"class":280}," r ",[40,760,324],{"class":276},[40,762,763],{"class":280}," claims.itertuples()]\n",[40,765,767,770,772,775,778,781,784,787,789],{"class":42,"line":766},17,[40,768,769],{"class":280},"missing ",[40,771,303],{"class":276},[40,773,774],{"class":333}," set",[40,776,777],{"class":280},"().union(",[40,779,780],{"class":276},"*",[40,782,783],{"class":280},"values) ",[40,785,786],{"class":276},"-",[40,788,774],{"class":333},[40,790,791],{"class":280},"(fields)\n",[40,793,795,798,801,804,806,809,811,814,816],{"class":42,"line":794},18,[40,796,797],{"class":276},"assert",[40,799,800],{"class":276}," not",[40,802,803],{"class":280}," missing, ",[40,805,340],{"class":276},[40,807,808],{"class":50},"\"no such fields in the form: ",[40,810,347],{"class":346},[40,812,813],{"class":280},"missing",[40,815,356],{"class":346},[40,817,818],{"class":50},"\"\n",[40,820,822,825,828,831],{"class":42,"line":821},19,[40,823,824],{"class":333},"print",[40,826,827],{"class":280},"(values[",[40,829,830],{"class":333},"1",[40,832,507],{"class":280},[72,834,81,840,81,843,81,846,81,851,81,858,81,862,81,868,81,872,81,876,81,880,81,884,81,886,81,890,81,892,81,895,81,898,81,902,81,904,81,908,81,910,81,913,81,916,81,920,81,922,81,925,81,927,81,930,81,933,81,936,81,938,81,941,81,943,81,946,81,949,81,953,81,955,81,958,81,960,81,963,81,966,81,970,81,973,81,976,81,979,81,981,81,984,81,986,81,989,81,991,81,994,81,996,81,998,81,1000,81,1003,81,1005,81,1007,81,1009,81,1011,81,1013,81,1016,81,1019,81,1023,81,1025,81,1027,81,1029,81,1032,81,1034,81,1037,81,1039,81,1041,81,1043,81,1046,81,1048,81,1050,81,1056,81,1060,81,1065,81,1068,81,1076],{"viewBox":835,"role":75,"ariaLabelledBy":836,"xmlns":79,"style":839},"38 0 683 220",[837,838],"pf-map-t","pf-map-d","width:100%;max-width:683px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[83,841,842],{"id":837},"Sheet columns mapped to form fields",[87,844,845],{"id":838},"On the left a row of the claims sheet with its column names. On the right the same values as form field names and the text written into each field, with the amount and date formatted and Yes turned into the checkbox's on state.",[91,847],{"x":848,"y":94,"width":849,"height":850,"fill":97},"38","683","220",[91,852],{"x":853,"y":854,"width":855,"height":856,"rx":94,"fill":105,"stroke":106,"style":857},"54.7","40.0","32.0","24.0","stroke-width:1px",[91,859],{"x":860,"y":854,"width":861,"height":856,"rx":94,"fill":105,"stroke":106,"style":857},"86.7","82.2",[120,863,867],{"x":864,"y":865,"style":866},"127.8","57.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[91,869],{"x":870,"y":854,"width":871,"height":856,"rx":94,"fill":105,"stroke":106,"style":857},"168.9","113.1",[120,873,875],{"x":874,"y":865,"style":866},"225.5","B",[91,877],{"x":853,"y":878,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},"64.0","28.0",[120,881,830],{"x":882,"y":883,"style":866},"70.7","83.0",[91,885],{"x":860,"y":878,"width":861,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,887,889],{"x":864,"y":883,"style":888},"font-size:11px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Column",[91,891],{"x":870,"y":878,"width":871,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,893,894],{"x":874,"y":883,"style":888},"Value",[91,896],{"x":853,"y":897,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},"92.0",[120,899,901],{"x":882,"y":900,"style":866},"111.0","2",[91,903],{"x":860,"y":897,"width":861,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,905,907],{"x":864,"y":900,"style":906},"font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","Name",[91,909],{"x":870,"y":897,"width":871,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,911,912],{"x":874,"y":900,"style":906},"Jonas Lindqvist",[91,914],{"x":853,"y":915,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},"120.0",[120,917,919],{"x":882,"y":918,"style":866},"139.0","3",[91,921],{"x":860,"y":915,"width":861,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,923,924],{"x":864,"y":918,"style":906},"Amount",[91,926],{"x":870,"y":915,"width":871,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,928,929],{"x":874,"y":918,"style":906},"1275",[91,931],{"x":853,"y":932,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},"148.0",[120,934,93],{"x":882,"y":935,"style":866},"167.0",[91,937],{"x":860,"y":932,"width":861,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,939,940],{"x":864,"y":935,"style":906},"ClaimDate",[91,942],{"x":870,"y":932,"width":871,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,944,945],{"x":874,"y":935,"style":906},"2026-09-26",[91,947],{"x":853,"y":948,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},"176.0",[120,950,952],{"x":882,"y":951,"style":866},"195.0","5",[91,954],{"x":860,"y":948,"width":861,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,956,957],{"x":864,"y":951,"style":906},"Receipts",[91,959],{"x":870,"y":948,"width":871,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,961,962],{"x":874,"y":951,"style":906},"yes",[91,964],{"x":965,"y":854,"width":855,"height":856,"rx":94,"fill":105,"stroke":106,"style":857},"475.3",[91,967],{"x":968,"y":854,"width":969,"height":856,"rx":94,"fill":105,"stroke":106,"style":857},"507.3","84.9",[120,971,867],{"x":972,"y":865,"style":866},"549.7",[91,974],{"x":975,"y":854,"width":871,"height":856,"rx":94,"fill":105,"stroke":106,"style":857},"592.2",[120,977,875],{"x":978,"y":865,"style":866},"648.7",[91,980],{"x":965,"y":878,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},[120,982,830],{"x":983,"y":883,"style":866},"491.3",[91,985],{"x":968,"y":878,"width":969,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,987,988],{"x":972,"y":883,"style":888},"Field",[91,990],{"x":975,"y":878,"width":871,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,992,993],{"x":978,"y":883,"style":888},"Written",[91,995],{"x":965,"y":897,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},[120,997,901],{"x":983,"y":900,"style":866},[91,999],{"x":968,"y":897,"width":969,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,1001,1002],{"x":972,"y":900,"style":906},"employee",[91,1004],{"x":975,"y":897,"width":871,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,1006,912],{"x":978,"y":900,"style":906},[91,1008],{"x":965,"y":915,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},[120,1010,919],{"x":983,"y":918,"style":866},[91,1012],{"x":968,"y":915,"width":969,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,1014,1015],{"x":972,"y":918,"style":906},"amount",[91,1017],{"x":975,"y":915,"width":871,"height":879,"rx":94,"fill":208,"stroke":209,"style":1018},"stroke-width:1.5px",[120,1020,1022],{"x":978,"y":918,"style":1021},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","1,275.00",[91,1024],{"x":965,"y":932,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},[120,1026,93],{"x":983,"y":935,"style":866},[91,1028],{"x":968,"y":932,"width":969,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,1030,1031],{"x":972,"y":935,"style":906},"claim_date",[91,1033],{"x":975,"y":932,"width":871,"height":879,"rx":94,"fill":208,"stroke":209,"style":1018},[120,1035,1036],{"x":978,"y":935,"style":1021},"26\u002F09\u002F2026",[91,1038],{"x":965,"y":948,"width":855,"height":879,"rx":94,"fill":105,"stroke":106,"style":857},[120,1040,952],{"x":983,"y":951,"style":866},[91,1042],{"x":968,"y":948,"width":969,"height":879,"rx":94,"fill":97,"stroke":106,"style":857},[120,1044,1045],{"x":972,"y":951,"style":906},"receipts",[91,1047],{"x":975,"y":948,"width":871,"height":879,"rx":94,"fill":208,"stroke":209,"style":1018},[120,1049,435],{"x":978,"y":951,"style":1021},[120,1051,1055],{"x":1052,"y":1053,"style":1054},"168.4","29.0","font-size:12.5px;font-weight:700;fill:var(--muted,#5b6780);text-anchor:middle","before",[120,1057,1059],{"x":1058,"y":1053,"style":1054},"590.3","after",[42,1061],{"x1":1062,"y1":1063,"x2":1064,"y2":1063,"stroke":136,"style":107},"292.0","122.0","461.3",[138,1066],{"points":1067,"fill":141},"465.3,122.0 456.3,127.0 456.3,117.0",[91,1069],{"x":1070,"y":1071,"width":1072,"height":1073,"rx":1074,"fill":97,"stroke":106,"style":1075},"317.4","89.0","122.6","22.0","11","stroke-width:1.2px",[120,1077,1081],{"x":1078,"y":1079,"style":1080},"378.7","104.0","font-size:11.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","field_values(row)",[10,1083,1084,1085,1087,1088,1090],{},"The assertion catches a mapping that names a field the form does not have — a typo, or a new version of the form with renamed fields — before any file is written. Formatting in Python makes the output independent of whatever formatting the form itself might apply: ",[37,1086,1022],{}," and ",[37,1089,1036],{}," appear exactly as written.",[25,1092,1094],{"id":1093},"step-3-fill-one-copy-per-row","Step 3 — Fill one copy per row",[10,1096,1097],{},"Clone the blank form for each row, write the values, and save under a name built from the row:",[30,1099,1101],{"className":267,"code":1100,"language":269,"meta":35,"style":35},"from pathlib import Path\nfrom pypdf import PdfWriter\n\nout = Path(\"filled\")\nout.mkdir(exist_ok=True)\nfor row, vals in zip(claims.itertuples(), values):\n    writer = PdfWriter(clone_from=\"claim_form.pdf\")\n    writer.update_page_form_field_values(writer.pages[0], vals, auto_regenerate=False)\n    writer.set_need_appearances_writer(True)\n    writer.write(out \u002F f\"claim_{row.EmployeeID}.pdf\")\nprint(sorted(p.name for p in out.iterdir()))\n",[37,1102,1103,1115,1126,1130,1145,1160,1175,1194,1214,1223,1249],{"__ignoreMap":35},[40,1104,1105,1107,1110,1112],{"class":42,"line":43},[40,1106,277],{"class":276},[40,1108,1109],{"class":280}," pathlib ",[40,1111,284],{"class":276},[40,1113,1114],{"class":280}," Path\n",[40,1116,1117,1119,1121,1123],{"class":42,"line":290},[40,1118,277],{"class":276},[40,1120,281],{"class":280},[40,1122,284],{"class":276},[40,1124,1125],{"class":280}," PdfWriter\n",[40,1127,1128],{"class":42,"line":297},[40,1129,294],{"emptyLinePlaceholder":293},[40,1131,1132,1135,1137,1140,1143],{"class":42,"line":315},[40,1133,1134],{"class":280},"out ",[40,1136,303],{"class":276},[40,1138,1139],{"class":280}," Path(",[40,1141,1142],{"class":50},"\"filled\"",[40,1144,414],{"class":280},[40,1146,1147,1150,1153,1155,1158],{"class":42,"line":330},[40,1148,1149],{"class":280},"out.mkdir(",[40,1151,1152],{"class":495},"exist_ok",[40,1154,303],{"class":276},[40,1156,1157],{"class":333},"True",[40,1159,414],{"class":280},[40,1161,1162,1164,1167,1169,1172],{"class":42,"line":565},[40,1163,318],{"class":276},[40,1165,1166],{"class":280}," row, vals ",[40,1168,324],{"class":276},[40,1170,1171],{"class":333}," zip",[40,1173,1174],{"class":280},"(claims.itertuples(), values):\n",[40,1176,1177,1180,1182,1185,1188,1190,1192],{"class":42,"line":578},[40,1178,1179],{"class":280},"    writer ",[40,1181,303],{"class":276},[40,1183,1184],{"class":280}," PdfWriter(",[40,1186,1187],{"class":495},"clone_from",[40,1189,303],{"class":276},[40,1191,309],{"class":50},[40,1193,414],{"class":280},[40,1195,1196,1199,1201,1204,1207,1209,1212],{"class":42,"line":587},[40,1197,1198],{"class":280},"    writer.update_page_form_field_values(writer.pages[",[40,1200,94],{"class":333},[40,1202,1203],{"class":280},"], vals, ",[40,1205,1206],{"class":495},"auto_regenerate",[40,1208,303],{"class":276},[40,1210,1211],{"class":333},"False",[40,1213,414],{"class":280},[40,1215,1216,1219,1221],{"class":42,"line":596},[40,1217,1218],{"class":280},"    writer.set_need_appearances_writer(",[40,1220,1157],{"class":333},[40,1222,414],{"class":280},[40,1224,1225,1228,1231,1234,1237,1239,1242,1244,1247],{"class":42,"line":605},[40,1226,1227],{"class":280},"    writer.write(out ",[40,1229,1230],{"class":276},"\u002F",[40,1232,1233],{"class":276}," f",[40,1235,1236],{"class":50},"\"claim_",[40,1238,347],{"class":346},[40,1240,1241],{"class":280},"row.EmployeeID",[40,1243,356],{"class":346},[40,1245,1246],{"class":50},".pdf\"",[40,1248,414],{"class":280},[40,1250,1251,1253,1255,1258,1261,1263,1266,1268],{"class":42,"line":614},[40,1252,824],{"class":333},[40,1254,337],{"class":280},[40,1256,1257],{"class":333},"sorted",[40,1259,1260],{"class":280},"(p.name ",[40,1262,318],{"class":276},[40,1264,1265],{"class":280}," p ",[40,1267,324],{"class":276},[40,1269,1270],{"class":280}," out.iterdir()))\n",[10,1272,1273,1276,1277,1280,1281,1284],{},[37,1274,1275],{},"PdfWriter(clone_from=...)"," copies the whole form, fields and all, so each output starts blank. ",[37,1278,1279],{},"update_page_form_field_values"," writes the values and generates appearance streams — the drawing of the text inside each box that viewers display. ",[37,1282,1283],{},"set_need_appearances_writer(True)"," additionally asks viewers to redraw the fields from their values, which helps viewers that ignore generated appearances. The filled files are still forms: the recipient can change the values.",[25,1286,1288],{"id":1287},"step-4-flatten-so-the-values-cannot-be-changed","Step 4 — Flatten so the values cannot be changed",[10,1290,1291,1292,1295],{},"For a claim that has been signed off, the values should be part of the page, not editable fields. ",[37,1293,1294],{},"flatten=True"," draws the values into the page content; removing the field widgets and the form dictionary then leaves a plain PDF:",[30,1297,1299],{"className":267,"code":1298,"language":269,"meta":35,"style":35},"flat = Path(\"flattened\")\nflat.mkdir(exist_ok=True)\nfor row, vals in zip(claims.itertuples(), values):\n    writer = PdfWriter(clone_from=\"claim_form.pdf\")\n    writer.update_page_form_field_values(writer.pages[0], vals, auto_regenerate=False, flatten=True)\n    writer.remove_annotations(subtypes=\"\u002FWidget\")\n    if \"\u002FAcroForm\" in writer._root_object:\n        del writer._root_object[\"\u002FAcroForm\"]\n    writer.write(flat \u002F f\"claim_{row.EmployeeID}.pdf\")\n\ncheck = PdfReader(flat \u002F \"claim_E02.pdf\")\nprint(\"fields left:\", check.get_fields())\nprint(check.pages[0].extract_text().splitlines()[-5:])\n",[37,1300,1301,1315,1328,1340,1356,1381,1396,1410,1423,1444,1448,1465,1477],{"__ignoreMap":35},[40,1302,1303,1306,1308,1310,1313],{"class":42,"line":43},[40,1304,1305],{"class":280},"flat ",[40,1307,303],{"class":276},[40,1309,1139],{"class":280},[40,1311,1312],{"class":50},"\"flattened\"",[40,1314,414],{"class":280},[40,1316,1317,1320,1322,1324,1326],{"class":42,"line":290},[40,1318,1319],{"class":280},"flat.mkdir(",[40,1321,1152],{"class":495},[40,1323,303],{"class":276},[40,1325,1157],{"class":333},[40,1327,414],{"class":280},[40,1329,1330,1332,1334,1336,1338],{"class":42,"line":297},[40,1331,318],{"class":276},[40,1333,1166],{"class":280},[40,1335,324],{"class":276},[40,1337,1171],{"class":333},[40,1339,1174],{"class":280},[40,1341,1342,1344,1346,1348,1350,1352,1354],{"class":42,"line":315},[40,1343,1179],{"class":280},[40,1345,303],{"class":276},[40,1347,1184],{"class":280},[40,1349,1187],{"class":495},[40,1351,303],{"class":276},[40,1353,309],{"class":50},[40,1355,414],{"class":280},[40,1357,1358,1360,1362,1364,1366,1368,1370,1372,1375,1377,1379],{"class":42,"line":330},[40,1359,1198],{"class":280},[40,1361,94],{"class":333},[40,1363,1203],{"class":280},[40,1365,1206],{"class":495},[40,1367,303],{"class":276},[40,1369,1211],{"class":333},[40,1371,402],{"class":280},[40,1373,1374],{"class":495},"flatten",[40,1376,303],{"class":276},[40,1378,1157],{"class":333},[40,1380,414],{"class":280},[40,1382,1383,1386,1389,1391,1394],{"class":42,"line":565},[40,1384,1385],{"class":280},"    writer.remove_annotations(",[40,1387,1388],{"class":495},"subtypes",[40,1390,303],{"class":276},[40,1392,1393],{"class":50},"\"\u002FWidget\"",[40,1395,414],{"class":280},[40,1397,1398,1401,1404,1407],{"class":42,"line":578},[40,1399,1400],{"class":276},"    if",[40,1402,1403],{"class":50}," \"\u002FAcroForm\"",[40,1405,1406],{"class":276}," in",[40,1408,1409],{"class":280}," writer._root_object:\n",[40,1411,1412,1415,1418,1421],{"class":42,"line":587},[40,1413,1414],{"class":276},"        del",[40,1416,1417],{"class":280}," writer._root_object[",[40,1419,1420],{"class":50},"\"\u002FAcroForm\"",[40,1422,558],{"class":280},[40,1424,1425,1428,1430,1432,1434,1436,1438,1440,1442],{"class":42,"line":596},[40,1426,1427],{"class":280},"    writer.write(flat ",[40,1429,1230],{"class":276},[40,1431,1233],{"class":276},[40,1433,1236],{"class":50},[40,1435,347],{"class":346},[40,1437,1241],{"class":280},[40,1439,356],{"class":346},[40,1441,1246],{"class":50},[40,1443,414],{"class":280},[40,1445,1446],{"class":42,"line":605},[40,1447,294],{"emptyLinePlaceholder":293},[40,1449,1450,1453,1455,1458,1460,1463],{"class":42,"line":614},[40,1451,1452],{"class":280},"check ",[40,1454,303],{"class":276},[40,1456,1457],{"class":280}," PdfReader(flat ",[40,1459,1230],{"class":276},[40,1461,1462],{"class":50}," \"claim_E02.pdf\"",[40,1464,414],{"class":280},[40,1466,1467,1469,1471,1474],{"class":42,"line":642},[40,1468,824],{"class":333},[40,1470,337],{"class":280},[40,1472,1473],{"class":50},"\"fields left:\"",[40,1475,1476],{"class":280},", check.get_fields())\n",[40,1478,1479,1481,1484,1486,1489,1491,1493],{"class":42,"line":682},[40,1480,824],{"class":333},[40,1482,1483],{"class":280},"(check.pages[",[40,1485,94],{"class":333},[40,1487,1488],{"class":280},"].extract_text().splitlines()[",[40,1490,786],{"class":276},[40,1492,952],{"class":333},[40,1494,1495],{"class":280},":])\n",[10,1497,1498,1500,1501,1504,1505,1508],{},[37,1499,1294],{}," on its own writes the values into the page but leaves the field objects in place; removing the ",[37,1502,1503],{},"\u002FWidget"," annotations and the ",[37,1506,1507],{},"\u002FAcroForm"," entry turns it into an ordinary PDF with no fields. The extracted text now contains the values as page text, which is also what makes flattened forms searchable and printable everywhere. Keep the editable version too if the forms may need correcting later, since changing a flattened form means filling it again.",[72,1510,81,1515,81,1518,81,1521,81,1524,81,1529,81,1535,81,1541,81,1546,81,1551,81,1557,81,1561,81,1566,81,1570,81,1577,81,1580,81,1586,81,1591,81,1595,81,1599,81,1604,81,1607,81,1612,81,1615],{"viewBox":1511,"role":75,"ariaLabelledBy":1512,"xmlns":79,"style":80},"4 0 752 207",[1513,1514],"pf-flat-t","pf-flat-d",[83,1516,1517],{"id":1513},"Filled form or flattened copy",[87,1519,1520],{"id":1514},"Two panels. A filled form keeps its fields, so values can still be edited and read back as fields. A flattened copy has the values drawn on the page and no fields, so it cannot be edited but prints and searches everywhere.",[91,1522],{"x":93,"y":94,"width":95,"height":1523,"fill":97},"207",[91,1525],{"x":100,"y":101,"width":1526,"height":1527,"rx":1528,"fill":145,"stroke":136,"style":107},"351.0","175.0","14",[109,1530,1532],{"transform":1531},"translate(182.5,29.0) scale(1.083)",[113,1533],{"d":215,"fill":116,"stroke":152,"style":1534},"stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[120,1536,1540],{"x":1537,"y":1538,"style":1539},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Filled form",[42,1542],{"x1":1543,"y1":1544,"x2":1545,"y2":1544,"stroke":136,"style":857},"36.0","91.0","355.0",[120,1547,1550],{"x":1548,"y":900,"style":1549},"60.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","values in named fields",[109,1552,1554],{"transform":1553},"translate(38.5,99.5) scale(0.625)",[113,1555],{"d":242,"fill":116,"stroke":209,"style":1556},"stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[120,1558,1560],{"x":1548,"y":1559,"style":1549},"137.0","easy to correct later",[109,1562,1564],{"transform":1563},"translate(38.5,125.5) scale(0.625)",[113,1565],{"d":242,"fill":116,"stroke":209,"style":1556},[120,1567,1569],{"x":1548,"y":1568,"style":1549},"163.0","recipient can change values",[109,1571,1573],{"transform":1572},"translate(38.5,151.5) scale(0.625)",[113,1574],{"d":1575,"fill":116,"stroke":1576,"style":1556},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[91,1578],{"x":1579,"y":101,"width":1526,"height":1527,"rx":1528,"fill":208,"stroke":209,"style":107},"389.0",[109,1581,1583],{"transform":1582},"translate(551.5,29.0) scale(1.083)",[113,1584],{"d":1585,"fill":116,"stroke":216,"style":1534},"M6 11h12v9H6z M8.5 11V8a3.5 3.5 0 0 1 7 0v3",[120,1587,1590],{"x":1588,"y":1538,"style":1589},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Flattened copy",[42,1592],{"x1":1593,"y1":1544,"x2":1594,"y2":1544,"stroke":209,"style":857},"405.0","724.0",[120,1596,1598],{"x":1597,"y":900,"style":1549},"429.0","values are page content",[109,1600,1602],{"transform":1601},"translate(407.5,99.5) scale(0.625)",[113,1603],{"d":242,"fill":116,"stroke":209,"style":1556},[120,1605,1606],{"x":1597,"y":1559,"style":1549},"prints and searches everywhere",[109,1608,1610],{"transform":1609},"translate(407.5,125.5) scale(0.625)",[113,1611],{"d":242,"fill":116,"stroke":209,"style":1556},[120,1613,1614],{"x":1597,"y":1568,"style":1549},"no fields left to edit",[109,1616,1618],{"transform":1617},"translate(407.5,151.5) scale(0.625)",[113,1619],{"d":1575,"fill":116,"stroke":1576,"style":1556},[25,1621,1623],{"id":1622},"step-5-verify-every-file","Step 5 — Verify every file",[10,1625,1626],{},"Check each output against its row: read the fields back from the editable files, and the page text from the flattened ones:",[30,1628,1630],{"className":267,"code":1629,"language":269,"meta":35,"style":35},"problems = []\nfor row, vals in zip(claims.itertuples(), values):\n    got = {k: v.get(\"\u002FV\") for k, v in PdfReader(out \u002F f\"claim_{row.EmployeeID}.pdf\").get_fields().items()}\n    if got != vals:\n        problems.append((row.EmployeeID, \"fields\"))\n    text = PdfReader(flat \u002F f\"claim_{row.EmployeeID}.pdf\").pages[0].extract_text()\n    if vals[\"amount\"] not in text or row.Name not in text:\n        problems.append((row.EmployeeID, \"flattened text\"))\nprint(problems or \"all forms correct\")\nprint({r.EmployeeID: r.Receipts for r in claims.itertuples()})\n",[37,1631,1632,1642,1654,1697,1709,1720,1751,1784,1793,1807],{"__ignoreMap":35},[40,1633,1634,1637,1639],{"class":42,"line":43},[40,1635,1636],{"class":280},"problems ",[40,1638,303],{"class":276},[40,1640,1641],{"class":280}," []\n",[40,1643,1644,1646,1648,1650,1652],{"class":42,"line":290},[40,1645,318],{"class":276},[40,1647,1166],{"class":280},[40,1649,324],{"class":276},[40,1651,1171],{"class":333},[40,1653,1174],{"class":280},[40,1655,1656,1659,1661,1664,1667,1670,1672,1675,1677,1680,1682,1684,1686,1688,1690,1692,1694],{"class":42,"line":297},[40,1657,1658],{"class":280},"    got ",[40,1660,303],{"class":276},[40,1662,1663],{"class":280}," {k: v.get(",[40,1665,1666],{"class":50},"\"\u002FV\"",[40,1668,1669],{"class":280},") ",[40,1671,318],{"class":276},[40,1673,1674],{"class":280}," k, v ",[40,1676,324],{"class":276},[40,1678,1679],{"class":280}," PdfReader(out ",[40,1681,1230],{"class":276},[40,1683,1233],{"class":276},[40,1685,1236],{"class":50},[40,1687,347],{"class":346},[40,1689,1241],{"class":280},[40,1691,356],{"class":346},[40,1693,1246],{"class":50},[40,1695,1696],{"class":280},").get_fields().items()}\n",[40,1698,1699,1701,1704,1706],{"class":42,"line":315},[40,1700,1400],{"class":276},[40,1702,1703],{"class":280}," got ",[40,1705,548],{"class":276},[40,1707,1708],{"class":280}," vals:\n",[40,1710,1711,1714,1717],{"class":42,"line":330},[40,1712,1713],{"class":280},"        problems.append((row.EmployeeID, ",[40,1715,1716],{"class":50},"\"fields\"",[40,1718,1719],{"class":280},"))\n",[40,1721,1722,1725,1727,1729,1731,1733,1735,1737,1739,1741,1743,1746,1748],{"class":42,"line":565},[40,1723,1724],{"class":280},"    text ",[40,1726,303],{"class":276},[40,1728,1457],{"class":280},[40,1730,1230],{"class":276},[40,1732,1233],{"class":276},[40,1734,1236],{"class":50},[40,1736,347],{"class":346},[40,1738,1241],{"class":280},[40,1740,356],{"class":346},[40,1742,1246],{"class":50},[40,1744,1745],{"class":280},").pages[",[40,1747,94],{"class":333},[40,1749,1750],{"class":280},"].extract_text()\n",[40,1752,1753,1755,1758,1761,1763,1766,1768,1771,1774,1777,1779,1781],{"class":42,"line":578},[40,1754,1400],{"class":276},[40,1756,1757],{"class":280}," vals[",[40,1759,1760],{"class":50},"\"amount\"",[40,1762,540],{"class":280},[40,1764,1765],{"class":276},"not",[40,1767,1406],{"class":276},[40,1769,1770],{"class":280}," text ",[40,1772,1773],{"class":276},"or",[40,1775,1776],{"class":280}," row.Name ",[40,1778,1765],{"class":276},[40,1780,1406],{"class":276},[40,1782,1783],{"class":280}," text:\n",[40,1785,1786,1788,1791],{"class":42,"line":587},[40,1787,1713],{"class":280},[40,1789,1790],{"class":50},"\"flattened text\"",[40,1792,1719],{"class":280},[40,1794,1795,1797,1800,1802,1805],{"class":42,"line":596},[40,1796,824],{"class":333},[40,1798,1799],{"class":280},"(problems ",[40,1801,1773],{"class":276},[40,1803,1804],{"class":50}," \"all forms correct\"",[40,1806,414],{"class":280},[40,1808,1809,1811,1814,1816,1818,1820],{"class":42,"line":605},[40,1810,824],{"class":333},[40,1812,1813],{"class":280},"({r.EmployeeID: r.Receipts ",[40,1815,318],{"class":276},[40,1817,758],{"class":280},[40,1819,324],{"class":276},[40,1821,1822],{"class":280}," claims.itertuples()})\n",[10,1824,1825],{},"Every form holds exactly the values mapped from its row, including the checkbox states — ticked for the three claims with receipts and unticked for Claire Martin's. Running this check before the forms are sent is what makes a batch of filled forms trustworthy.",[25,1827,1829],{"id":1828},"forms-that-are-not-fillable","Forms that are not fillable",[10,1831,1832,1833,1835,1836,1839],{},"Many \"forms\" are just printed layouts saved as PDF, with no fields at all — ",[37,1834,130],{}," returns ",[37,1837,1838],{},"None",". They can still be filled: draw the values at the right coordinates on a transparent overlay page and merge it onto the form, the same technique used for watermarks:",[30,1841,1843],{"className":267,"code":1842,"language":269,"meta":35,"style":35},"from reportlab.pdfgen import canvas as rl_canvas\n\nPOSITIONS = {\"employee\": (185, 726), \"amount\": (185, 606), \"claim_date\": (185, 566)}\n\ndef overlay_fill(blank, vals, dest):\n    page = PdfReader(blank).pages[0]\n    w, h = float(page.mediabox.width), float(page.mediabox.height)\n    c = rl_canvas.Canvas(\"_overlay.pdf\", pagesize=(w, h))\n    c.setFont(\"Helvetica\", 11)\n    for name, (x, y) in POSITIONS.items():\n        c.drawString(x, y, vals[name])\n    c.save()\n    writer = PdfWriter(clone_from=blank)\n    writer.pages[0].merge_page(PdfReader(\"_overlay.pdf\").pages[0])\n    writer.write(dest)\n\nprint(\"fields in printed form:\", PdfReader(\"printed_form.pdf\").get_fields())\noverlay_fill(\"printed_form.pdf\", values[0], \"overlay_E01.pdf\")\nprint(PdfReader(\"overlay_E01.pdf\").pages[0].extract_text().splitlines()[-3:])\n",[37,1844,1845,1862,1866,1920,1924,1934,1948,1967,1990,2004,2020,2025,2030,2045,2063,2068,2072,2090,2110],{"__ignoreMap":35},[40,1846,1847,1849,1852,1854,1857,1859],{"class":42,"line":43},[40,1848,277],{"class":276},[40,1850,1851],{"class":280}," reportlab.pdfgen ",[40,1853,284],{"class":276},[40,1855,1856],{"class":280}," canvas ",[40,1858,470],{"class":276},[40,1860,1861],{"class":280}," rl_canvas\n",[40,1863,1864],{"class":42,"line":290},[40,1865,294],{"emptyLinePlaceholder":293},[40,1867,1868,1871,1873,1875,1878,1881,1884,1886,1889,1892,1894,1896,1898,1900,1903,1905,1908,1910,1912,1914,1917],{"class":42,"line":297},[40,1869,1870],{"class":333},"POSITIONS",[40,1872,515],{"class":276},[40,1874,703],{"class":280},[40,1876,1877],{"class":50},"\"employee\"",[40,1879,1880],{"class":280},": (",[40,1882,1883],{"class":333},"185",[40,1885,402],{"class":280},[40,1887,1888],{"class":333},"726",[40,1890,1891],{"class":280},"), ",[40,1893,1760],{"class":50},[40,1895,1880],{"class":280},[40,1897,1883],{"class":333},[40,1899,402],{"class":280},[40,1901,1902],{"class":333},"606",[40,1904,1891],{"class":280},[40,1906,1907],{"class":50},"\"claim_date\"",[40,1909,1880],{"class":280},[40,1911,1883],{"class":333},[40,1913,402],{"class":280},[40,1915,1916],{"class":333},"566",[40,1918,1919],{"class":280},")}\n",[40,1921,1922],{"class":42,"line":315},[40,1923,294],{"emptyLinePlaceholder":293},[40,1925,1926,1928,1931],{"class":42,"line":330},[40,1927,568],{"class":276},[40,1929,1930],{"class":571}," overlay_fill",[40,1932,1933],{"class":280},"(blank, vals, dest):\n",[40,1935,1936,1939,1941,1944,1946],{"class":42,"line":565},[40,1937,1938],{"class":280},"    page ",[40,1940,303],{"class":276},[40,1942,1943],{"class":280}," PdfReader(blank).pages[",[40,1945,94],{"class":333},[40,1947,558],{"class":280},[40,1949,1950,1953,1955,1958,1961,1964],{"class":42,"line":578},[40,1951,1952],{"class":280},"    w, h ",[40,1954,303],{"class":276},[40,1956,1957],{"class":333}," float",[40,1959,1960],{"class":280},"(page.mediabox.width), ",[40,1962,1963],{"class":333},"float",[40,1965,1966],{"class":280},"(page.mediabox.height)\n",[40,1968,1969,1972,1974,1977,1980,1982,1985,1987],{"class":42,"line":587},[40,1970,1971],{"class":280},"    c ",[40,1973,303],{"class":276},[40,1975,1976],{"class":280}," rl_canvas.Canvas(",[40,1978,1979],{"class":50},"\"_overlay.pdf\"",[40,1981,402],{"class":280},[40,1983,1984],{"class":495},"pagesize",[40,1986,303],{"class":276},[40,1988,1989],{"class":280},"(w, h))\n",[40,1991,1992,1995,1998,2000,2002],{"class":42,"line":596},[40,1993,1994],{"class":280},"    c.setFont(",[40,1996,1997],{"class":50},"\"Helvetica\"",[40,1999,402],{"class":280},[40,2001,1074],{"class":333},[40,2003,414],{"class":280},[40,2005,2006,2009,2012,2014,2017],{"class":42,"line":605},[40,2007,2008],{"class":276},"    for",[40,2010,2011],{"class":280}," name, (x, y) ",[40,2013,324],{"class":276},[40,2015,2016],{"class":333}," POSITIONS",[40,2018,2019],{"class":280},".items():\n",[40,2021,2022],{"class":42,"line":614},[40,2023,2024],{"class":280},"        c.drawString(x, y, vals[name])\n",[40,2026,2027],{"class":42,"line":642},[40,2028,2029],{"class":280},"    c.save()\n",[40,2031,2032,2034,2036,2038,2040,2042],{"class":42,"line":682},[40,2033,1179],{"class":280},[40,2035,303],{"class":276},[40,2037,1184],{"class":280},[40,2039,1187],{"class":495},[40,2041,303],{"class":276},[40,2043,2044],{"class":280},"blank)\n",[40,2046,2047,2050,2052,2055,2057,2059,2061],{"class":42,"line":734},[40,2048,2049],{"class":280},"    writer.pages[",[40,2051,94],{"class":333},[40,2053,2054],{"class":280},"].merge_page(PdfReader(",[40,2056,1979],{"class":50},[40,2058,1745],{"class":280},[40,2060,94],{"class":333},[40,2062,507],{"class":280},[40,2064,2065],{"class":42,"line":740},[40,2066,2067],{"class":280},"    writer.write(dest)\n",[40,2069,2070],{"class":42,"line":745},[40,2071,294],{"emptyLinePlaceholder":293},[40,2073,2074,2076,2078,2081,2084,2087],{"class":42,"line":766},[40,2075,824],{"class":333},[40,2077,337],{"class":280},[40,2079,2080],{"class":50},"\"fields in printed form:\"",[40,2082,2083],{"class":280},", PdfReader(",[40,2085,2086],{"class":50},"\"printed_form.pdf\"",[40,2088,2089],{"class":280},").get_fields())\n",[40,2091,2092,2095,2097,2100,2102,2105,2108],{"class":42,"line":794},[40,2093,2094],{"class":280},"overlay_fill(",[40,2096,2086],{"class":50},[40,2098,2099],{"class":280},", values[",[40,2101,94],{"class":333},[40,2103,2104],{"class":280},"], ",[40,2106,2107],{"class":50},"\"overlay_E01.pdf\"",[40,2109,414],{"class":280},[40,2111,2112,2114,2117,2119,2121,2123,2125,2127,2129],{"class":42,"line":821},[40,2113,824],{"class":333},[40,2115,2116],{"class":280},"(PdfReader(",[40,2118,2107],{"class":50},[40,2120,1745],{"class":280},[40,2122,94],{"class":333},[40,2124,1488],{"class":280},[40,2126,786],{"class":276},[40,2128,919],{"class":333},[40,2130,1495],{"class":280},[10,2132,2133,2136,2137,2139,2140,2143],{},[37,2134,2135],{},"printed_form.pdf"," has the same boxes as the fillable form but no fields, as ",[37,2138,130],{}," confirms. Coordinates are in points from the ",[14,2141,2142],{},"bottom-left"," corner of the page; each value is placed a few points inside its printed box. The example fills three of the boxes to keep the positions table short; a real script lists every box, and the same verification — extracting the page text and looking for each row's values — works on overlay-filled forms exactly as on flattened ones. Finding them is trial and error the first time — render the result, adjust, repeat — and they have to be updated whenever the form's layout changes, which is exactly why fillable fields are better when the form's owner offers them.",[25,2145,2147],{"id":2146},"common-pitfalls","Common pitfalls",[2149,2150,2151,2162,2172,2178,2190],"ul",{},[2152,2153,2154,2158,2159,2161],"li",{},[2155,2156,2157],"strong",{},"Writing to label text instead of field names."," List the fields with ",[37,2160,130],{}," and use their internal names.",[2152,2163,2164,2169,2170,23],{},[2155,2165,2166,2167,23],{},"Assuming the checkbox value is ",[37,2168,435],{}," Read the on-state from ",[37,2171,427],{},[2152,2173,2174,2177],{},[2155,2175,2176],{},"Numbers and dates written raw."," Fields hold text; format values exactly as they should appear.",[2152,2179,2180,2186,2187,2189],{},[2155,2181,2182,2183,2185],{},"Expecting ",[37,2184,1294],{}," to remove the form."," Remove the widget annotations and ",[37,2188,1507],{}," as well for a plain PDF.",[2152,2191,2192,2195,2196,2198],{},[2155,2193,2194],{},"XFA forms."," Some government and insurance forms use Adobe's XFA format instead of AcroForm fields; pypdf cannot fill those, and ",[37,2197,130],{}," returns little or nothing.",[25,2200,2202],{"id":2201},"conclusion","Conclusion",[10,2204,2205,2206,2208,2209,2211,2212,2214,2215,2217],{},"List the form's field names and checkbox states with ",[37,2207,130],{},", map sheet columns to fields in one dictionary, format every value as the text that should appear, and fill a fresh clone of the form per row with ",[37,2210,1279],{},". Flatten with ",[37,2213,1294],{}," plus removal of the widgets and ",[37,2216,1507],{}," when the values must not change, and read every output back — fields or page text — to confirm each file carries its own row's values.",[25,2219,2221],{"id":2220},"frequently-asked-questions","Frequently asked questions",[10,2223,2224,2227,2228,2231,2232,2234],{},[2155,2225,2226],{},"How do I fill a PDF form with Python?","\nOpen the form with pypdf's ",[37,2229,2230],{},"PdfWriter(clone_from=\"form.pdf\")",", pass a dictionary of field names and values to ",[37,2233,1279],{}," for the page, and write the result. Use PdfReader(...).get_fields() to find the field names.",[10,2236,2237,2240,2241,2244],{},[2155,2238,2239],{},"How do I tick a checkbox in a PDF form with pypdf?","\nSet the field to its on-state name, which you find in the field's \u002F",[14,2242,2243],{},"States"," list — usually \u002FYes, but it can differ. Set it to \u002FOff to untick it.",[10,2246,2247,2250],{},[2155,2248,2249],{},"How do I flatten a filled PDF form?","\nPass flatten=True to update_page_form_field_values, then remove the \u002FWidget annotations and the \u002FAcroForm entry from the document. The values remain drawn on the page and no editable fields are left.",[10,2252,2253,2256],{},[2155,2254,2255],{},"Why can't pypdf find any fields in my form?","\nThe PDF may use XFA, a different form technology used by some government and insurance forms, or it may not be fillable at all — just a printed layout. Check with get_fields(); for non-fillable PDFs, draw the values onto the page instead.",[25,2258,2260],{"id":2259},"related","Related",[2149,2262,2263,2269,2276,2283],{},[2152,2264,2265,2266,2268],{},"Up one level: ",[19,2267,22],{"href":21}," — template-driven report generation.",[2152,2270,2271,2275],{},[19,2272,2274],{"href":2273},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-one-pdf-per-excel-row\u002F","Generate One PDF per Excel Row"," — when you control the layout yourself.",[2152,2277,2278,2282],{},[19,2279,2281],{"href":2280},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-word-documents-from-excel-rows\u002F","Generate Word Documents from Excel Rows"," — the same loop for Word templates.",[2152,2284,2285,2289],{},[19,2286,2288],{"href":2287},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fmerge-excel-pdf-exports-into-one-file\u002F","Merge Excel PDF Exports into One File"," — combining the filled forms.",[2291,2292,2293],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":35,"searchDepth":290,"depth":290,"links":2295},[2296,2297,2298,2299,2300,2301,2302,2303,2304,2305,2306],{"id":27,"depth":290,"text":28},{"id":260,"depth":290,"text":261},{"id":451,"depth":290,"text":452},{"id":1093,"depth":290,"text":1094},{"id":1287,"depth":290,"text":1288},{"id":1622,"depth":290,"text":1623},{"id":1828,"depth":290,"text":1829},{"id":2146,"depth":290,"text":2147},{"id":2201,"depth":290,"text":2202},{"id":2220,"depth":290,"text":2221},{"id":2259,"depth":290,"text":2260},"2026-09-29","Fill a fillable PDF form — an expense claim, an order form, a government template — once per spreadsheet row with pypdf: list the form's field names, map sheet columns to fields, format numbers and dates as text, tick checkboxes with the right state name, flatten the result so it cannot be edited, and verify every filled file.","md",[2311,2313,2315,2317],{"q":2226,"a":2312},"Open the form with pypdf's PdfWriter(clone_from=\"form.pdf\"), pass a dictionary of field names and values to update_page_form_field_values for the page, and write the result. Use PdfReader(...).get_fields() to find the field names.",{"q":2239,"a":2314},"Set the field to its on-state name, which you find in the field's \u002F_States_ list — usually \u002FYes, but it can differ. Set it to \u002FOff to untick it.",{"q":2249,"a":2316},"Pass flatten=True to update_page_form_field_values, then remove the \u002FWidget annotations and the \u002FAcroForm entry from the document. The values remain drawn on the page and no editable fields are left.",{"q":2255,"a":2318},"The PDF may use XFA, a different form technology used by some government and insurance forms, or it may not be fillable at all — just a printed layout. Check with get_fields(); for non-fillable PDFs, draw the values onto the page instead.","fill pdf forms from excel rows with python",null,{"breadcrumb":2322},[2323,2325,2328],{"name":2324,"item":1230},"Home",{"name":2326,"item":2327},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":22,"item":21},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python",{"title":2331,"description":2332},"Fill a PDF Form from Excel Data with Python pypdf","Fill AcroForm PDF fields from each Excel row with pypdf: get_fields to discover names, update_page_form_field_values, checkbox \u002FYes states, text formatting for numbers and dates, flatten=True plus widget removal, and a check that each file holds the right values.","fill-pdf-forms-from-excel-rows-with-python","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-pdf-forms-from-excel-rows-with-python\u002Findex",[2336,2337],"pdf-export","report-layouts","how-to","M5Uff-zA_SHDzp2yLQPrds61kJeN7jynKS8ltXEeIWk",[2341,2345],{"title":2342,"path":2343,"stem":2344,"children":-1},"Fill an Excel Template with Python and openpyxl","\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Ffill-excel-template-with-python-openpyxl\u002Findex",{"title":2346,"path":2347,"stem":2348,"children":-1},"Generate an HTML Report from Excel Data with Jinja2","\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-an-html-report-from-excel-data-with-jinja2","automating-reporting-workflows\u002Fgenerating-excel-reports-from-templates\u002Fgenerate-an-html-report-from-excel-data-with-jinja2\u002Findex",[2350,2352],{"path":2351,"title":22},"\u002Fautomating-reporting-workflows\u002Fgenerating-excel-reports-from-templates",{"path":2353,"title":2326},"\u002Fautomating-reporting-workflows",[],[2356,2360,2363,2366,2369,2372],{"path":2357,"title":2358,"kind":2359},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf","Exporting Excel Reports to PDF","guide",{"path":2361,"title":2362,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-bookmarks-and-a-cover-page-to-an-excel-pdf-export","Add Bookmarks and a Cover Page to an Excel PDF Export",{"path":2364,"title":2365,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fadd-headers-footers-and-page-numbers-to-an-excel-pdf","Add Headers, Footers and Page Numbers to an Excel PDF",{"path":2367,"title":2368,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fcheck-a-pdf-export-for-cut-off-columns","Check a PDF Export for Cut-Off Columns",{"path":2370,"title":2371,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-file-to-pdf-with-python","Convert an Excel File to PDF with Python",{"path":2373,"title":2374,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Fconvert-excel-to-pdf-on-linux-with-libreoffice","Convert Excel to PDF on Linux with LibreOffice",1790972137972]