Skip to content

Build1 publisher3 min readPublished

Mealie's Paprika importer keeps the prose and leaves seven of the export's 24 fields behind

The upload takes a few minutes of waiting. The repair is a script over /api/recipes; it reparses ingredient text, and difficulty, favourites and scale stay lost, because those never reached Mealie.

The Engineer · Build desk

Illustration accompanying Mealie's Paprika importer keeps the prose and leaves seven of the export's 24 fields behind

What happened

  • A 900-recipe Paprika export lands in Mealie with recipe names, ingredient text, directions, source URLs and the primary photo intact, according to a dev.to walkthrough of the migration.
  • The importer silently drops the Paprika fields that Mealie's recipe model has no equivalent for: the photo array beyond the first image, the scale factor, the difficulty label, the favourites flag and the free text nutrition block.
  • The per-ingredient quantity, unit and food fields arrive as unparsed strings, so Mealie holds the ingredient line without its structured parts.
  • The repair the post prescribes is a paginated GET over /api/recipes plus one PATCH per recipe, which it puts at roughly 20 lines of code for a 900-recipe library.
  • The importers are not idempotent, and the post tells evaluators to run first against a throwaway Mealie instance with a separate database, so that a bad run can simply be deleted.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • cost The hours in this migration sit in the scripted repair pass, and they are paid by whoever owns the library; the upload itself costs one file and a few minutes of waiting.
  • decision Cancelling the Paprika subscription now has to wait on a field-by-field audit, because the losses land in metadata the recipe text cannot regenerate.
  • cost The app you are leaving sets the repair bill: a RecipeKeeper export is an HTML document plus an image folder, so everything Mealie receives has to be scraped back out of markup.
  • constraint For a household that will not open a terminal, the post's answer is to keep paying for one full season, on the grounds that the shopping list and offline behaviour outrank the field mapping.

Rename the .paprikarecipes file to .zip and the whole format is visible in a directory listing: one gzip-compressed file per recipe, each decompressing to a flat JSON object, and that is the whole of it, no schema file, no index, nothing relational between recipes [8]. The dev.to write-up calls it "a sync format for one application, not an interchange format" and says that distinction explains every loss [15]. Its counterexample is Nextcloud Cookbook, where each recipe is already a schema.org JSON document that maps almost one to one onto Mealie's model [16].

The repair pass is a paginated GET over /api/recipes and a PATCH per recipe [5]. That loop can fix the ingredient fields, because the ingredient text did arrive and a parser has something to read [2]. The dropped fields are a separate job: difficulty, the favourites flag, the scale factor, the extra photos and the nutrition text never reached the API at all, so the only copy is the archive you unzipped [3][5]. A script that puts them back reads both sides and matches them, and Paprika's uid and hash are sync identifiers that Mealie's model has no field for [10]. The match runs instead on a field that survived, which in practice means the recipe name or the source URL [4].

The key list is short enough to count by hand. The post enumerates 24 keys in five groups [1], and seven of them go nowhere: uid and hash by the author's own description, plus the five fields on the loss list [2]. Four more, prep_time, cook_time, total_time and servings, are stored as human strings such as "1 hr 20 min" instead of integers or ISO 8601 durations [12]. The rating sits in the classification group of keys [14]. The loss list stops at five fields, and a later section is headed "Ratings, favourites, scale factors and the metadata that never arrives" [21], but the article leaves the rating field's destination unstated [22].

Twenty lines is a fair estimate of the PATCH loop, with the parser left out of the count [5]. It also assumes the only repair is to fields Mealie already holds. At 900 recipes that loop issues 900 write requests [3].

Before any of that, the post has you record a baseline with unzip -l library.paprikarecipes | wc -l [9]. For a 900-recipe library that prints 900 entries plus the zip's own header lines, so the figure you write down includes those header lines [9]. You compare it against Mealie's recipe total after the run, which happens inside the instance from the Migrations page under group data management [20].

The post's own summary of the trade-off: Mealie gives you "a structured, queryable library you own, and you pay for that structure by rebuilding the parts of your Paprika data that were never structured to begin with" [19].

What to watch

  • Whether Mealie's Paprika importer learns to parse quantity, unit and food, which would remove the PATCH pass entirely.
  • Whether anyone publishes a script that joins the decompressed .paprikarecipe JSON back to Mealie records and re-injects difficulty, favourites, scale and nutrition text.
  • Whether the importers gain a dedupe key, so a second run converges on the existing library and leaves the owner nothing to unpick.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories