Dev.to WebDev 🛠 Dev 👁 0 📖 6 min read

Why USCIS & California Court PDFs (I-765, I-485, I-130, FL-150) Open Blank After Saving in macOS Preview — And How Hybrid XFA Works

Every week in immigration law firms and California family or probate practices, the same bug plays out: a client opens an official USCIS petition (Form I-765, Form I-485, or Form I-130) or a California Judicial Council c

Every week in immigration law firms and California family or probate practices, the same bug plays out: a client opens an official USCIS petition (Form I-765, Form I-485, or Form I-130) or a California Judicial Council court packet (FL-150, DE-111, or CM-010) on a MacBook, fills out every page in macOS Preview, saves the file, and emails it back to the paralegal.

On the client's Mac, the PDF still looks filled out. Yet the moment the paralegal opens that attachment in Adobe Acrobat Reader on Windows—or uploads it to a court Electronic Filing Service Provider (EFSP) portal—every single text box and checkbox renders 100% blank, or the portal rejects the upload with an XFA/encryption error.

This isn't user error. It is a deterministic byte-level collision between Adobe LiveCycle / AEM Forms Hybrid XFA packets, ISO 32000-1 /AcroForm widget trees, and macOS Preview's PDFKit serializer.

In our 137 Government Forms Fillability Study, we programmatically inspected 134 public government PDFs with pypdf 6.6:

  • 85.1% (114 of 134) of government forms are Hybrid XFA (xfa_hybrid) documents containing both an /AcroForm widget hierarchy and an /XFA XML packet array.
  • 92.3% (24 of 26) of analyzed USCIS forms (I-765, I-485, I-130, I-864, I-131, N-400) are Hybrid XFA and carry owner encryption locks (/Encrypt + /Perms), averaging a median of 227 fields across 8 pages.
  • 76.9% (10 of 13) of California Judicial Council forms (FL-150, CM-010, DE-111, FL-100) are Hybrid XFA, and 100% (13 of 13) carry owner permission restrictions that block document assembly and flattening.

1. Inside /Root -> /AcroForm: How Hybrid XFA PDFs Store Data Twice

When an agency authors a form in Adobe LiveCycle Designer or AEM Forms and saves it as an "Acrobat 8 (Static) XML Form PDF," the producer writes two parallel form representations inside /Catalog -> /AcroForm:

  1. /Fields (ISO 32000-1 §12.7 AcroForm Widget Tree): PDF dictionary objects (/Subtype /Widget, /FT /Tx or /FT /Btn) nested under form1[0].#subform[0]..., storing values in /V and glyph streams in /AP << /N ... >>.
  2. /XFA (XML Forms Architecture Packet Array): An array of key-stream pairs (preamble, config, template, datasets, form, postamble) holding compressed XML streams.

Inspecting /Catalog -> /AcroForm inside USCIS Form I-765 (7 pages, 161 fields) or California FL-150 (4 pages, 262 fields) reveals both structures side by side:

1 0 obj
<<
  /Type /Catalog
  /Pages 3 0 R
  /Perms << /UR3 4 0 R >>
  /AcroForm <<
    /Fields [ 12 0 R ]
    /DA (/CourierStd 10 Tf 0 g)
    /XFA [
      (preamble)  15 0 R  (config)    16 0 R
      (template)  17 0 R  (datasets)  18 0 R
      (form)      19 0 R  (postamble) 20 0 R
    ]
  >>
>>
endobj

Stream 18 0 R (datasets) stores the <xfa:datasets> XML tree read by XFA-aware viewers:

<xfa:datasets xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
  <xfa:data>
    <form1><Page1><Pt1Line1a_FamilyName/><Pt1Line1b_GivenName/></Page1></form1>
  </xfa:data>
</xfa:datasets>

Meanwhile, the corresponding AcroForm widget dictionary inside /Fields represents the same input box for non-XFA viewers:

245 0 obj
<< /Type /Annot /Subtype /Widget /FT /Tx /T (Pt1Line1a_FamilyName[0])
   /TU (Part 1. Line 1a. Family Name (Last Name))
   /Rect [ 54.00 582.10 294.00 596.50 ] /V () /AP << /N 246 0 R >> >>
endobj

2. Why macOS Preview Saves Open Blank in Windows Adobe Acrobat

Apple's macOS Preview (PDFKit) does not implement an XFA XML parser. When a user fills and saves a Hybrid XFA PDF in Preview:

  1. Preview ignores /XFA: It renders and edits only the /AcroForm -> /Fields widget tree.
  2. Preview writes user input only into /AcroForm /Fields /V: Typing into Pt1Line1a_FamilyName[0] writes /V (Smith) in the PDF incremental update trailer while leaving stream 18 0 R (<xfa:datasets>) untouched and empty (<Pt1Line1a_FamilyName/>).
  3. Preview often omits or corrupts portable /AP /N appearance streams: Preview writes Apple font references or alters /NeedAppearances while leaving the /XFA key intact inside /AcroForm.

When Adobe Acrobat Reader on Windows opens a hybrid PDF that still contains /XFA, Acrobat's XFA runtime takes precedence over /AcroForm /Fields /V:

Does /Root/AcroForm contain an /XFA key?
 ├── YES -> Adobe Acrobat loads stream 18 0 R (<xfa:datasets>)
 │          Ignores /AcroForm /Fields /V ("Smith")
 │          Renders empty <xfa:datasets> XML -> 100% BLANK FIELDS
 └── NO  -> Standard ISO 32000-1 renderer reads /AcroForm /Fields /V
            All typed values render normally

Worse: if a paralegal clicks Save in Acrobat while that blank XFA view is open, Acrobat overwrites /AcroForm /Fields /V with the empty <xfa:datasets> state—permanently erasing the client's answers.

3. Recovering "Blank" Preview PDFs & Stripping /XFA + /Perms in Python

Before re-saving a client's PDF in Acrobat, you can extract every /V value saved by macOS Preview, strip the stale /XFA packet and /Perms usage-rights lock, and regenerate appearance streams using pikepdf and pypdf:

#!/usr/bin/env python3
"""Recover macOS Preview edits from Hybrid XFA PDFs and strip /XFA + /Perms locks."""
import json, sys
from pathlib import Path
import pikepdf
from pypdf import PdfReader, PdfWriter
from pypdf.generic import BooleanObject, NameObject

def walk_values(node_ref, prefix="", out=None):
    out = {} if out is None else out
    node = node_ref.get_object()
    name = str(node.get("/T", ""))
    full = f"{prefix}.{name}" if prefix and name else (name or prefix)
    if "/Kids" in node:
        for kid in node["/Kids"]:
            walk_values(kid, full, out)
    elif node.get("/V") not in (None, "", "/Off"):
        out[full] = str(node.get("/V"))
    return out

def normalize_hybrid_pdf(input_pdf: str, output_pdf: str) -> dict:
    tmp = Path(output_pdf).with_suffix(".tmp.pdf")
    with pikepdf.open(input_pdf, password="") as pdf:
        if "/Perms" in pdf.Root:
            del pdf.Root["/Perms"]
        if "/AcroForm" in pdf.Root and "/XFA" in pdf.Root.AcroForm:
            del pdf.Root.AcroForm["/XFA"]
        pdf.save(tmp)

    reader = PdfReader(str(tmp))
    writer = PdfWriter()
    writer.append(reader)
    acroform = writer._root_object[NameObject("/AcroForm")].get_object()
    acroform[NameObject("/NeedAppearances")] = BooleanObject(True)

    recovered = {}
    for ref in acroform.get("/Fields", []):
        recovered.update(walk_values(ref))

    for page in writer.pages:
        for annot_ref in page.get("/Annots", []):
            annot = annot_ref.get_object()
            if annot.get("/Subtype") == "/Widget" and annot.get("/FT") == "/Tx":
                if annot.get("/V") and NameObject("/AP") in annot:
                    del annot[NameObject("/AP")]

    with open(output_pdf, "wb") as f:
        writer.write(f)
    tmp.unlink(missing_ok=True)
    return recovered

if __name__ == "__main__":
    recovered = normalize_hybrid_pdf(sys.argv[1], sys.argv[2])
    print(f"Recovered {len(recovered)} fields:", json.dumps(recovered, indent=2))

Note why deleting /Root/Perms alongside /AcroForm/XFA is mandatory: USCIS and California court PDFs include an Adobe /UR3 usage-rights signature inside /Perms. If you strip /XFA without removing /Perms, Acrobat detects a modified byte digest and blocks saving.

4. Audited USCIS & California Court Forms & Browser Workflows

Form Code Agency Pages Audited Fields Hybrid /XFA Owner /Encrypt
Form I-765 USCIS 7 161 (119 Tx, 38 Btn, 4 Ch) Yes Yes
Form I-130 USCIS 12 450 (302 Tx, 128 Btn, 20 Ch) Yes Yes
Form I-485 USCIS 24 760 (334 Tx, 416 Btn, 10 Ch) Yes Yes
Form FL-150 CA Judicial Council 4 262 (212 Tx, 50 Btn) Yes Yes
Form CM-010 CA Judicial Council 3 84 (23 Tx, 61 Btn) Yes Yes
Form DE-111 CA Judicial Council 4 Probate Petition (Tx + Btn) Yes Yes

If you want clients or legal staff to complete these forms in Chrome, Safari, Edge, or Firefox on macOS or Windows without triggering the macOS Preview /XFA blank-fields bug—or convert locked XFA files into clean AcroForm or flattened PDFs—use these tools and audited guides:

Disclosure: I am the solo developer of JustFill.

AI disclosure: AI coding agents drafted and revised this article and verified the code examples above.

📰 Read the original article on Dev.to WebDev

Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.