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
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 Key | Data Type | Markdown Target Key | Transformation Logic |
|---|---|---|---|
name | String | title | Preserved verbatim; sanitized for filename characters |
ingredients | String (newline-separated) | ingredients | Split on \n; formatted as markdown unordered list |
directions | String (newline-separated) | instructions | Split on \n; formatted as numbered steps |
prep_time | String | prep_time | Preserved; checked against standard ISO representations |
cook_time | String | cook_time | Preserved; checked against standard ISO representations |
servings | String | yield | Renamed to yield for Schema.org alignment |
source_url | String | source | Extracted to frontmatter link |
photo_data | Base64 String | Local image file | Decoded 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:
continueBinary 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