Skip to main content

Paprika-Recipes: Keep your paprika recipes in a directory of markdown files

Paprika is a lovely recipe app, but your recipes are then trapped inside it. This tool checks them out into a directory of plain markdown files that you can edit in whatever you already use -- your editor, your note vault, your usual git workflow -- and then sync your changes back.

# First: clone your recipes into a folder somewhere
$ paprika-recipes clone you@example.com ~/recipes
$ cd ~/recipes
# Second: make your changes to whatever recipe using whatever editor
$ vim Khachapuri.md
# Finally: push up your changes to Paprika
$ paprika-recipes push

Why

  • Your recipes become real files -- one markdown file per recipe: editable, greppable, diffable, and perfectly at home in a git repository.
  • Made to live in a note vault -- clone straight into Obsidian or anything like it: photos render inline, your own tags, aliases, and extra sections stay in the files without ever being uploaded, and a frontmatter prefix keeps Paprika's fields from colliding with your vault's.
  • Two-way, not just export -- edits you make locally go back to Paprika, changes you make in the app come down, and status shows you exactly what would be sent before anything is.
  • Photos sync in both directions -- each recipe's photo is downloaded and embedded beneath its title, and adding, swapping, or deleting that embed line uploads, replaces, or removes the photo in Paprika.
  • Write new recipes in your editor -- a markdown file with a # Title becomes a real Paprika recipe on the next push.
  • You already know the commands -- clone, pull, push, status, restore, with --dry-run everywhere and --json plus meaningful exit codes when you are scripting.

Paprika has no official public API; this tool speaks the same sync protocol the apps themselves use. That fact shapes its manners: nothing is uploaded without status being able to show it to you first, anything can be undone before it is pushed, and deleting a recipe only ever moves it to Paprika's own trash.

Contents

Installation

$ uv tool install paprika-recipes

If uv isn't your thing, pipx install paprika-recipes does the same job, and plain pip install paprika-recipes works in a virtualenv of your own.

Getting started

There is nothing to set up. Check out your recipes:

$ paprika-recipes clone you@example.com ~/recipes

You'll be asked for your Paprika password. It goes into your system keyring, and the account is recorded in the directory itself, so nothing asks again — and if your password ever changes, you're simply asked for the new one the next time it doesn't work.

Every recipe becomes a markdown file:

---
categories:
- Bread
rating: 4
source: simplyhomecooked.com
uid: 4C855813-25B8-41CD-96E7-5B38AA7AAAAF
---

# Khachapuri

![Photo of Khachapuri](attachments/D2246B0B-3E32-4C36-A9F9-6E5F53CD6EBD.jpg)

A Georgian cheese bread.

## Ingredients

- 3 1/2 cup all-purpose flour
- 1 tsp salt

## Directions

Combine the dry ingredients.

Bake for 20 minutes.

Edit them however you like, then:

$ paprika-recipes status   # what have I changed?
$ paprika-recipes push     # send it to Paprika
$ paprika-recipes pull     # bring down changes made elsewhere
$ paprika-recipes restore  # undo local changes

status tells you what push is going to do before you do it:

Changes not yet sent to Paprika:
  (use "paprika-recipes push" to send them)
  (use "paprika-recipes restore <recipe>..." to discard them)

        deleted:   Best-Ever Focaccia (will be moved to Paprika's trash)
        new file:  Brand New Thing (will be created in Paprika)
        modified:  One-Hour Pizza (rating)
        modified:  Vanilla Cupcake (formatting only; nothing to push)

Both pull and push accept --dry-run if you would rather see the whole plan first.

Writing a recipe yourself

Write a markdown file with a # Title, and push will create it in Paprika. The sections Paprika can hold are the ones clone writes — ## Ingredients, ## Directions, ## Notes, and ## Nutritional Information, spelled exactly that way — plus any prose directly beneath the title, which becomes the description. A section by any other name follows the usual rule: it stays in your file, but nothing in Paprika receives it. The recipe gets a uid: written into its frontmatter at the moment it's created, and is an ordinary tracked recipe from then on. If you embed a photo from attachments/ beneath the title, that goes up with it.

Commands

Command What it does
clone <email> [directory] Check an account's recipes out into a directory of markdown files, photos included. --frontmatter-prefix keeps Paprika's fields out of a vault's way.
pull Bring down changes made in Paprika, reconciling them with any local edits.
push Send local changes up: edits, new recipe files, photo changes, and deletions (into Paprika's trash).
status Show what push would do without doing it. --exit-code makes it exit 1 when there are changes, after git diff --exit-code.
restore <recipe>... Put recipes back the way they last arrived -- undoing an edit, or bringing back a deleted file. --all restores everything at once.

pull and push both take --dry-run, and every command takes --json (see Scripting). Two further commands work on exported archives rather than an account, and have a section of their own.

How syncing works

The directory keeps a record of each recipe as it last arrived from Paprika, in a .paprika directory beside your files. That is what lets it tell the difference between a recipe you changed, a recipe that changed on the server, and one that changed in both places.

A few things are worth knowing:

Changes on both sides are merged. If you edited a recipe locally and it also changed in Paprika, pull reconciles the two: edits to different parts of the recipe both survive, and only genuinely overlapping edits need you. Those get the same conflict markers git uses:

## Directions

<<<<<<< yours
Bake for 20 minutes.
=======
Bake for 25 minutes.
>>>>>>> paprika

The merged recipe is then just a local change like any other — status shows it, push sends it, restore throws it away. A recipe with markers still in it is refused by push until you have edited them out, so a half-resolved merge can never reach your account.

Two things can't be merged that way: the recipe's name, and any non-prose field like the rating or a time. There is nowhere in a rating to write "either 4 or 5, you decide", so if one of those changed on both sides, nothing is touched and you are told which field disagreed.

Deleting a file moves the recipe to Paprika's trash. Deletion syncs in both directions, but never destructively: a file you delete is pushed as a move into Paprika's own trash, where the app can still recover it, and a recipe you delete or trash in Paprika is removed from your directory on the next pull. Nothing is permanently destroyed by either.

Anything can be undone before you push it. restore puts a recipe back exactly the way it last arrived -- an edit, or the file itself if you deleted it:

$ paprika-recipes restore "Best-Ever Focaccia"   # by title
$ paprika-recipes restore ./Breads/Focaccia.md   # or by file
$ paprika-recipes restore --all                  # or everything

That includes a photo: restoring puts the embed back the way it arrived, and if you had swapped the image itself out, the substitute is discarded so that the next pull can put the original back — the one thing restore cannot do without the network is re-download it on the spot.

The one thing restore will not do is delete a recipe you created yourself and never pulled, since there is nothing to put such a file back to.

Reformatting a file is not an edit. Rewrapping a list or reordering the frontmatter changes the file without changing the recipe, and nothing gets uploaded for it.

Photos sync, in both directions. A recipe's photo is downloaded into an attachments/ folder beside your files, and the recipe embeds it with ordinary markdown image markup directly beneath its title — so it shows up in your editor, your vault, and anywhere else markdown renders. If you tidy the attachment away by hand, the next pull quietly puts it back.

The embed line is also how you change the photo. To give a recipe one, drop the image into attachments/ and write the embed yourself, spelled however you like:

# Khachapuri

![fresh out of the oven](attachments/my dinner.jpg)

push then uploads it exactly the way the app would — a square thumbnail onto the recipe, the full picture into its photo gallery — and renames your file to the name the server chose, rewriting the embed to match. Deleting the embed line removes the photo from Paprika on the next push, and swapping the image file out for different bytes replaces it, even though the file's text never changed; status reports every one of these as modified: (photo) so nothing leaves without your having seen it. Anything reasonable is accepted — a PNG becomes a JPEG on the way up, a sideways phone photo is stood upright, and anything larger than the 2048-pixel bound the app itself observes is scaled down to it.

One honest caveat: the copy in your directory keeps its full size, but what another clone downloads is the app's own copy of the recipe photo, which is the thumbnail. The full picture still lives in the recipe's gallery in the app.

Anything you add to a file is left alone. If these files live in a note vault, you will likely add tags: or aliases: to the frontmatter, and quite possibly a section of your own:

## Ingredients

- 1⅓ cups bread flour

## Substitutions

Bread flour works, but 00 flour is better.

## Directions

...

Paprika has nowhere to put that section, so it is never uploaded — but it is not discarded either. It stays where you left it, including when a pull rewrites the file around it, and it does not count as a change to the recipe.

If you want a line that genuinely begins with ## inside your directions, just write it: we escape it on the way out (\## Step one) and unescape it on the way back, which is standard Markdown and previews as you'd expect. An unescaped ## always means a section.

Renaming a file is fine. Recipes are tracked by the uid in their frontmatter, not by their filename or location, so you can rename files and sort them into folders freely.

Keeping out of your vault's way

If your vault already uses rating:, source:, categories: or created: for something of its own, clone with a prefix:

$ paprika-recipes clone you@example.com ~/vault/Recipes --frontmatter-prefix paprika_

Every field Paprika owns is then written as paprika_rating:, paprika_uid: and so on — and, just as importantly, an unprefixed field is yours. It stays in the file and is never uploaded, even if it happens to share a name with one of ours.

The prefix is chosen when you clone and cannot be changed afterwards without rewriting every file, so decide at the start. If the two ever do get out of step — a plugin that prunes frontmatter it does not recognise, say — push will notice that your files have stopped being identifiable and refuse to do anything, rather than treating them as new recipes and your existing ones as deleted.

Recipe files

The format is markdown with YAML frontmatter. The prose -- description, ingredients, directions, notes, nutritional information -- is passed through exactly as Paprika stores it, and everything else lives in the frontmatter.

Notably, ingredient amounts are not parsed. Paprika stores ingredients as a single blob of text, and real recipes contain lines like 185 g wet ingredients: 2 large eggs, 3 large egg yolks, and enough water to reach 185 g in total -- there is no amount to extract, and guessing at one would only lose information. What you write is what Paprika gets.

Working with exported archives

If you would rather not give this tool your account details at all, you can work with a .paprikarecipes export from the app instead. Export from Paprika, edit, import back:

$ paprika-recipes extract-archive export.paprikarecipes ./recipes/
$ paprika-recipes create-archive ./recipes/ new-export.paprikarecipes
Command What it does
extract-archive <archive> <directory> Unpack a .paprikarecipes export into the same markdown files clone writes.
create-archive <directory> <archive> Pack a directory of recipe files back into a .paprikarecipes archive.

You get the same markdown files clone writes, so everything above about the format applies. Recipe photos are written into the same attachments/ folder a cloned directory uses, embedded from their recipes, and folded back in when you repack — an archive stores them inline as base64, which is fine for a zip file and hopeless for a file you intend to read.

What this route does not have is any memory of where a recipe came from, so there is no status, no change detection and no conflict handling. It is a straight unpack and repack. If you want those, use clone.

create-archive searches subdirectories, and skips a .paprika directory if it finds one — so you can also point it at a directory you cloned, and get an archive out of your account. One caveat if you did that with --frontmatter-prefix: the archive commands have no directory to ask about a prefix and always read and write unprefixed files.

Scripting

Every command takes --json, which writes a versioned document to stdout and moves everything else — the report, the progress bar, any prompts — to stderr:

$ paprika-recipes status --json
{
  "version": 1,
  "unchanged": 81,
  "recipes": [
    {
      "uid": "4C855813-25B8-41CD-96E7-5B38AA7AAAAF",
      "name": "One-Hour Pizza",
      "path": "/home/you/recipes/One-Hour Pizza.md",
      "status": "modified",
      "conflicted": false,
      "changed_fields": ["rating"],
      "unsyncable": ["tags"]
    }
  ]
}

Exit codes say what happened without your having to read the output:

code meaning
0 everything asked for was done
1 it ran, but something needs you — a conflict, a recipe it would not push
2 the command line was malformed
3 could not log in
4 Paprika could not be reached, or refused what we sent
5 something about the directory or its files is wrong

status exits 0 whether or not you have local changes, since having them is the ordinary state of a working directory. Pass --exit-code — after git diff --exit-code — to have it answer that question instead.

Because --json implies nobody is watching, it will not stop to ask for a password; run any command once from a terminal to get your credentials into the keyring first.

Upgrading from 2.x

Version 3 is a rethink rather than an upgrade: 2.x moved YAML files up and down wholesale, while 3.x keeps markdown files under real change tracking. The old commands map like so:

2.x Where it went
download-recipes clone — which also remembers what it wrote, so that status, pull and push can know what changed since.
upload-recipes push — which sends only what you actually changed, and shows you first.
edit-recipe, create-recipe Retired. The whole point now is that recipes are ordinary files: edit them in your own editor, write a new one with a # Title, and push.
store-password Retired. clone asks for your password the first time it needs it — and it reads the same keyring entry store-password wrote, so a password you stored under 2.x is found without asking.
extract-archive, create-archive Still here, now reading and writing the same markdown files as everything else.

The default account is gone too, along with the global config file: a directory remembers which account it was cloned from, and that is the whole configuration.

A directory that download-recipes wrote is not something 3.x can adopt — the files were YAML, and nothing recorded what they looked like when they arrived. Start over with clone into a fresh directory; your password is already in the keyring, so it is exactly one command.

Other tools

Plenty of tools can get recipes out of Paprika as markdown. As far as I know, this is the only one that also gets your edits back in — the others are exporters, run once or on a schedule, with no memory of what you have changed since. If a one-time export is genuinely all you need, any of these will serve:

Reads from Writes Edits go back to Paprika Photos
paprika-recipes your account, or an archive markdown ✅ full two-way sync ✅ both directions
paprika-to-obsidian-markdown .paprikarecipes archive markdown (Obsidian/Dataview templates) download only
paprika-to-markdown .paprikarecipes archive markdown download only
paprika-exporter (archived) your account markdown (Jekyll-flavored)
paprika-exporter your account YAML download only

Two neighbours doing a different job entirely: kappari documents the Paprika API rather than wrapping it, and paprika-tools exposes Paprika to AI agents as an MCP server rather than as files on disk.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

paprika_recipes-3.0.0.tar.gz (100.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

paprika_recipes-3.0.0-py3-none-any.whl (69.5 kB view details)

Uploaded Python 3

File details

Details for the file paprika_recipes-3.0.0.tar.gz.

File metadata

  • Download URL: paprika_recipes-3.0.0.tar.gz
  • Upload date:
  • Size: 100.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.9.21

File hashes

Hashes for paprika_recipes-3.0.0.tar.gz
Algorithm Hash digest
SHA256 4ba0881f7b910d81eff5af9dfa607f4f3280ff9d55dce8d5c015dc501f9dc1b7
MD5 3e548b02693ceaea90d90d9b4ac3eac5
BLAKE2b-256 eb6ff997f2b204be0743c4e765f18eadb5e9527011e5012c7a216d5282a10e73

See more details on using hashes here.

File details

Details for the file paprika_recipes-3.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for paprika_recipes-3.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7e05cf984763b61ec56b5def2d7c431ea878eff25764f32f45e0d37da2cacfd8
MD5 be063e33d11b763a8e20e8a21fa9a537
BLAKE2b-256 1af4e7546b0e94f8eaee85d24965a8c3daeead9d8f9ab80241be6ca2ace210d6

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

3.0.0 This release

2 files

2.5.0

2 files

2.4.1

1 file

2.4.0

2 files

2.2.0

2 files

2.1.0

2 files

2.0.0

2 files

1.2.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page