{
  "schemaVersion": "1.0",
  "canonicalPage": "https://fileformat.co/ext/parquet",
  "inspectorUrl": "https://fileformat.co/inspect",
  "spec": {
    "slug": "parquet",
    "extension": ".parquet",
    "aliases": [
      ".pq"
    ],
    "name": "Apache Parquet Columnar Storage",
    "shortName": "Parquet",
    "category": "data-lake",
    "tier": 1,
    "summary": "De-facto standard hybrid columnar storage format for modern data lakes (Iceberg, Delta, DuckDB, Spark), placing Thrift Compact Protocol schema and statistics in the file trailer.",
    "technicalNotes": "Starts AND ends with 4-byte `PAR1` magic. The 4 bytes immediately preceding the trailing `PAR1` (`[size-8 .. size-4]`) store `uint32_le footer_length` for single-seek metadata reads without scanning row groups.",
    "mimeTypes": [
      "application/vnd.apache.parquet",
      "application/octet-stream"
    ],
    "identifiers": {
      "pronomPuid": "fmt/2023",
      "specUrl": "https://parquet.apache.org/docs/file-format/"
    },
    "binarySignature": {
      "endianness": "little",
      "magicBytes": [
        {
          "offset": 0,
          "hex": "50 41 52 31",
          "ascii": "PAR1",
          "description": "Leading 4-byte Apache Parquet magic (mirrored at EOF-4)"
        }
      ],
      "containerFamily": null,
      "headerLayout": [
        {
          "offset": "0x00 - 0x03",
          "byteOffset": 0,
          "byteLength": 4,
          "lengthLabel": "4 B",
          "dataType": "char[4]",
          "fieldName": "header_magic",
          "description": "ASCII 'PAR1' (or 'PARE' for encrypted footer)"
        },
        {
          "offset": "0x04 .. EOF-8-L",
          "byteOffset": 4,
          "byteLength": 16,
          "lengthLabel": "var",
          "dataType": "chunks",
          "fieldName": "row_groups",
          "description": "Compressed column chunks & data/dictionary pages"
        },
        {
          "offset": "EOF-8-L .. EOF-8",
          "byteOffset": 20,
          "byteLength": 16,
          "lengthLabel": "L B",
          "dataType": "thrift_compact",
          "fieldName": "file_metadata",
          "description": "Thrift Compact encoded FileMetaData (schema, stats, codecs)"
        },
        {
          "offset": "EOF-8 .. EOF-5",
          "byteOffset": 36,
          "byteLength": 4,
          "lengthLabel": "4 B",
          "dataType": "uint32_le",
          "fieldName": "footer_length",
          "description": "Byte length L of FileMetaData"
        },
        {
          "offset": "EOF-4 .. EOF-1",
          "byteOffset": 40,
          "byteLength": 4,
          "lengthLabel": "4 B",
          "dataType": "char[4]",
          "fieldName": "footer_magic",
          "description": "Trailing ASCII 'PAR1' confirming uncorrupted write"
        }
      ]
    },
    "securityProfile": {
      "riskLevel": "low",
      "allowsArbitraryCodeOrMacros": false,
      "xxeRisk": false,
      "polyglotPrependRisk": false,
      "pickleDeserializationRisk": false,
      "uploadValidationNotes": "Always check BOTH head[0:4] == 'PAR1' and tail[-4:] == 'PAR1' to reject interrupted multipart S3 uploads before enqueueing ETL jobs."
    },
    "codeSnippets": {
      "python": "import os, struct, sys\nwith open(sys.argv[1], \"rb\") as f:\n    head = f.read(4)\n    f.seek(-8, os.SEEK_END)\n    footer_len, tail = struct.unpack(\"<I4s\", f.read(8))\n    assert head == b\"PAR1\" and tail == b\"PAR1\"\n    print(f\"Valid Parquet: Thrift footer={footer_len} bytes\")",
      "typescript": "export async function checkParquetBounds(file: File) {\n  const head = new TextDecoder().decode(await file.slice(0, 4).arrayBuffer());\n  const tailBuf = await file.slice(file.size - 8, file.size).arrayBuffer();\n  const footerLen = new DataView(tailBuf).getUint32(0, true);\n  const tail = new TextDecoder().decode(tailBuf.slice(4));\n  return { valid: head === 'PAR1' && tail === 'PAR1', footerLen };\n}",
      "rust": "use std::{fs::File, io::{Read, Seek, SeekFrom}};\nfn main() -> std::io::Result<()> {\n    let mut f = File::open(std::env::args().nth(1).expect(\"usage: <prog> <file>\"))?;\n    f.seek(SeekFrom::End(-8))?;\n    let mut t = [0u8; 8]; f.read_exact(&mut t)?;\n    let flen = u32::from_le_bytes(t[0..4].try_into().expect(\"fixed-size byte window\"));\n    println!(\"Parquet footer={}B tail={:?}\", flen, &t[4..8]);\n    Ok(())\n}",
      "go": "package main\nimport (\"encoding/binary\"; \"fmt\"; \"os\")\nfunc main() {\n    f, err := os.Open(os.Args[1])\n    if err != nil {\n        fmt.Fprintln(os.Stderr, \"open:\", err)\n        os.Exit(1)\n    }\n    defer f.Close()\n    fi, err := f.Stat()\n    if err != nil {\n        fmt.Fprintln(os.Stderr, \"stat:\", err)\n        os.Exit(1)\n    }\n    b := make([]byte, 8)\n    if _, err := f.ReadAt(b, fi.Size()-8); err != nil {\n        fmt.Fprintln(os.Stderr, \"read footer:\", err)\n        os.Exit(1)\n    }\n    fmt.Printf(\"Parquet footer=%dB magic=%s\\n\", binary.LittleEndian.Uint32(b[:4]), string(b[4:]))\n}"
    },
    "relatedFormats": [
      "arrow",
      "feather",
      "orc",
      "duckdb"
    ],
    "verifiedAt": "2026-11-14T16:09:03Z"
  },
  "samples": [
    {
      "id": "parquet-minimal",
      "filename": "minimal-snappy.parquet",
      "variant": "minimal",
      "title": "Minimal Snappy Columnar Parquet (3 Rows)",
      "description": "Real Parquet file written by pyarrow with Snappy compression: 3 rows × user_id INT64, latency_ms DOUBLE, service UTF8.",
      "localPath": "/fixtures/v1/parquet/minimal-snappy.parquet",
      "cdnUrl": "https://fileformat.co/fixtures/v1/parquet/minimal-snappy.parquet",
      "sizeBytes": 1131,
      "sha256": "8b84a4eb04fdad847b748c47f6132f1a61f5b1de219178e86fea93aca5ac90f5",
      "expectValid": true
    },
    {
      "id": "parquet-realistic",
      "filename": "events-zstd-decimal.parquet",
      "variant": "realistic",
      "title": "Zstd Analytical Table with Decimal & Timestamp",
      "description": "pyarrow-written 250-row Parquet with ZSTD codec, DECIMAL(12,2) and TIMESTAMP_MICROS logical types.",
      "localPath": "/fixtures/v1/parquet/events-zstd-decimal.parquet",
      "cdnUrl": "https://fileformat.co/fixtures/v1/parquet/events-zstd-decimal.parquet",
      "sizeBytes": 3158,
      "sha256": "1b9a434953963317ee622358bccb62cce53d74422b0b8331f063f237ff8b123b",
      "expectValid": true
    },
    {
      "id": "parquet-edge-case",
      "filename": "truncated-footer.parquet",
      "variant": "edge-case",
      "title": "Interrupted Upload / Missing Trailing PAR1 Footer",
      "description": "Real Parquet bytes truncated before the footer length and trailing PAR1 magic — pyarrow must raise.",
      "localPath": "/fixtures/v1/parquet/truncated-footer.parquet",
      "cdnUrl": "https://fileformat.co/fixtures/v1/parquet/truncated-footer.parquet",
      "sizeBytes": 1121,
      "sha256": "042078532f4fd53caf059416a0dd17b81c8620cf769ad5c88dafc7a1d8f30491",
      "expectValid": false,
      "edgeCaseTag": "Truncated Footer EOF"
    }
  ]
}