Living Document Notice
Published 2026-09-15. The evolving architecture, revisions, and connected notes for this dispatch live in the Stax Digital Garden.

Unpacking the Paprika File Format

Unpacking the Paprika File Format: Warm golden amber P20 vector CRT macro showing isometric database package unfolding into stacked data planes above coordinate grid

Summary

Paprika is one of the most durable personal recipe management applications, with over a decade of widespread use across iOS, macOS, Windows, and Android platforms. Users looking to liberate their archives from proprietary database lock-in need access to the data stored inside .paprikarecipes export archives.

While the .paprikarecipes extension suggests a custom binary format, inspection reveals that it is a gzip-compressed container holding individual gzipped JSON documents or SQLite database snapshots depending on client version. FreeMyRecipes unpacks these archives and translates each record into portable plain-text Markdown.

Container Anatomy: Gzip vs Zip Encodings

The Paprika export package uses two distinct container variations based on application release versions:

Paprika Export File (.paprikarecipes)
  │
  ├── Legacy Format (v2 / v3 Export):
  │     Gzip stream wrapping concatenated individual gzipped JSON records
  │     [Header: 0x1F 0x8B] ──► Gzip Decompress ──► JSON Recipe Records
  │
  └── Database Backup Format:
        Gzip compressed SQLite 3 database file
        [Header: 0x1F 0x8B] ──► Gzip Decompress ──► SQLite3 (tables: recipes, categories)

In the standard .paprikarecipes user export, the root file is a gzip archive containing multiple nested .paprikarecipe members. Each individual member is itself a secondary gzip-compressed JSON payload containing recipe fields and base64-encoded photo blobs.

Paprika Record Schema vs Markdown Schema

The table below maps Paprika JSON keys to our standardized Markdown document frontmatter attributes.

Paprika JSON KeyData TypeMarkdown Target KeyTransformation Logic
nameStringtitlePreserved verbatim; sanitized for filename characters
ingredientsString (newline-separated)ingredientsSplit on \n; formatted as markdown unordered list
directionsString (newline-separated)instructionsSplit on \n; formatted as numbered steps
prep_timeStringprep_timePreserved; checked against standard ISO representations
cook_timeStringcook_timePreserved; checked against standard ISO representations
servingsStringyieldRenamed to yield for Schema.org alignment
source_urlStringsourceExtracted to frontmatter link
photo_dataBase64 StringLocal image fileDecoded to binary .jpg and written to local media subfolder

Unpacking Pipeline Implementation

The following Python script reads a .paprikarecipes file, extracts the gzipped JSON records, decodes embedded image blobs, and generates plain Markdown files.

import base64
import gzip
import json
import zipfile
from pathlib import Path
 
def unpack_paprika_export(archive_path: Path, output_dir: Path):
    output_dir.mkdir(parents=True, exist_ok=True)
    images_dir = output_dir / "attachments"
    images_dir.mkdir(exist_ok=True)
    
    # Check if archive is a standard zip or raw gzip
    with open(archive_path, 'rb') as f:
        magic_bytes = f.read(2)
        
    if magic_bytes == b'PK':
        # Zip container format
        with zipfile.ZipFile(archive_path, 'r') as zf:
            for item_name in zf.namelist():
                if item_name.endswith('.paprikarecipe'):
                    compressed_data = zf.read(item_name)
                    recipe_json = json.loads(gzip.decompress(compressed_data))
                    write_markdown_card(recipe_json, output_dir, images_dir)
    elif magic_bytes == b'\x1f\x8b':
        # Raw gzipped stream format
        with gzip.open(archive_path, 'rb') as gz:
            content = gz.read()
            # In multi-member archives, split by gzip magic header
            chunks = content.split(b'\x1f\x8b\x08')
            for i, chunk in enumerate(chunks):
                if not chunk:
                    continue
                try:
                    decompressed = gzip.decompress(b'\x1f\x8b\x08' + chunk)
                    recipe_json = json.loads(decompressed)
                    write_markdown_card(recipe_json, output_dir, images_dir)
                except Exception:
                    continue

Binary Blob Extraction and Image Handling

When photo_data exists in the Paprika payload, storing the large base64 string inside Markdown frontmatter degrades text editor performance. The extractor converts the payload directly into a local binary file.

def extract_photo(photo_b64: str, recipe_slug: str, images_dir: Path) -> str:
    image_bytes = base64.b64decode(photo_b64)
    image_path = images_dir / f"{recipe_slug}.jpg"
    with open(image_path, "wb") as img_file:
        img_file.write(image_bytes)
    return f"attachments/{recipe_slug}.jpg"

Running this converter via CLI extracts a user’s entire library of recipes in seconds:

freemyrecipes unpack --format=paprika input_library.paprikarecipes --output=./recipes/
# Unpacked 412 recipes and 389 image attachments into ./recipes/

  • Directus Target: freemyrecipes
  • Garden Source Reference: freemydata-index, galley-recipe-schema, MOC - Data Liberation Workbenches, MOC - Culinary & Domain Workspaces