Paprika stores its local data in an SQLite database located at:
- Windows:
C:\Users\[Username]\AppData\Local\Paprika Recipe Manager 3\Database\Paprika.sqlite - The database serves as a local cache that syncs with Paprika's cloud servers
Primary table for storing recipe data.
uid(TEXT PRIMARY KEY) - GUID in uppercase format (e.g., 'A2269D0E-9E1E-4A97-9DD0-2D5ED94A0F1E')status(TEXT) - Modification status: 'unmodified' or 'modified'sync_hash(TEXT) - Critical: SHA256 hash for sync tracking (64 hex characters)name(TEXT) - Recipe nameingredients(TEXT) - Ingredients listdirections(TEXT) - Cooking directionscreated(REAL) - Julian date timestampis_synced(INTEGER) - Boolean (0 or 1)
Full-text search (FTS4) table that must be kept in sync with the recipes table.
All sync entities follow similar patterns:
groceries- Grocery list itemsgrocery_aisles- Aisle categoriesgrocery_lists- Shopping listsmeals- Meal calendar entriesmenus- Menu collectionsmenu_items- Items within menuscategories- Recipe categoriesbookmarks- Web bookmarkspantry- Pantry inventory
The sync_hash field is critical for synchronization. It's not a content hash but a change tracking mechanism.
// Generate new GUID
string guid = Guid.NewGuid().ToString().ToUpper();
// Create SHA256 hash
byte[] hash = SHA256.Create().ComputeHash(Encoding.UTF8.GetBytes(guid));
// Format as hex string without dashes
string sync_hash = BitConverter.ToString(hash).Replace("-", "");import hashlib
import uuid
def generate_sync_hash():
# Generate new GUID and convert to uppercase
guid = str(uuid.uuid4()).upper()
# Create SHA256 hash
hash_bytes = hashlib.sha256(guid.encode('utf-8')).digest()
# Convert to hex string
return hash_bytes.hex().upper()- Object created locally: Gets new random hash
- Object modified locally: Gets NEW random hash
- Object synced from server: Uses server's hash value
Simple string values indicating modification state:
"unmodified"- Entity matches server state"modified"- Entity has local changes (value not confirmed in database)
To insert a recipe that will properly sync with Paprika servers:
-- Generate GUID for uid (must be uppercase)
-- Generate sync_hash using the algorithm aboveINSERT INTO recipes (
uid,
name,
ingredients,
directions,
status,
is_synced,
created,
sync_hash
) VALUES (
'YOUR-GUID-HERE',
'Recipe Name',
'Ingredient list',
'Directions',
'modified', -- Set to 'modified' for locally created
1, -- Mark as needing sync
julianday('now'), -- Current timestamp
'YOUR-SYNC-HASH-HERE'
);INSERT INTO recipes_search (
docid,
name,
ingredients,
directions,
description,
notes,
source
) SELECT
id,
name,
ingredients,
directions,
description,
notes,
source
FROM recipes
WHERE uid = 'YOUR-GUID-HERE';If adding categories, use the recipe_categories junction table:
INSERT INTO recipe_categories (recipe_id, category_id)
SELECT r.id, c.id
FROM recipes r, categories c
WHERE r.uid = 'YOUR-RECIPE-UID'
AND c.uid = 'YOUR-CATEGORY-UID';- The server validates the sync_hash format (must be 64 hex characters)
- Invalid hashes cause sync failures and retry loops
- The server uses sync_hash to detect changes, not content validation
During sync, Paprika compares local vs server sync hashes:
- If different: Downloads server version (server wins)
- If same: No action needed
- Empty/invalid hash: Sync fails
To avoid the sync loop issue:
- Always use properly formatted sync_hash (64 hex chars)
- Set status to 'modified' for locally created items
- Ensure is_synced = 1 for items needing sync
Paprika uses SQLite with Write-Ahead Logging (WAL):
Paprika.sqlite- Main database filePaprika.sqlite-shm- Shared memory filePaprika.sqlite-wal- Write-ahead log
All three files work together; modifying the database requires all to be in sync.
recipes↔categories(many-to-many viarecipe_categories)recipes↔photos(one-to-many)recipes→meals(via recipe_uid)recipes→menu_items(via recipe_uid)
grocery_lists→groceries(via list_uid)grocery_aisles→groceries(via aisle_uid)grocery_aisles→pantry(via aisle_uid)
- Missing FTS Updates: Recipes won't appear in search if recipes_search isn't updated
- Invalid Sync Hash: Must be exactly 64 hex characters (SHA256 output)
- Wrong Status: Using 'synced' for new items prevents upload
- Boolean Fields: SQLite uses 0/1, not true/false
- Date Format: Uses Julian dates (days since Nov 24, 4714 BC)
SELECT uid, name, sync_hash, length(sync_hash) as hash_length
FROM recipes
WHERE sync_hash IS NULL
OR length(sync_hash) != 64
OR sync_hash NOT GLOB '[0-9A-F]*';UPDATE recipes
SET status = 'modified',
sync_hash = 'NEW-HASH-HERE',
is_synced = 1
WHERE uid = 'RECIPE-UID';DELETE FROM recipe_categories
WHERE recipe_id NOT IN (SELECT id FROM recipes);