Skip to content

Latest commit

 

History

268 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

openplate

An open-source, self-hosted food tracker with BYOK (bring-your-own-key) AI plate identification. Snap a photo of your plate and your own AI provider (OpenRouter, Mistral, any OpenAI-compatible endpoint, or Anthropic) estimates the macros. Your key, your provider, your data.

There are no accounts. No sign-up, no login, no password: open the app and start logging. Your diary lives in your browser's own IndexedDB on the device you use, and the app server has no database at all: one stateless container, no secrets, nothing to provision. Optional end-to-end-encrypted sync between devices is a separate service you can ignore forever.

Try it without installing anything

https://openplate.lowcarbcheck.org runs this code. There is nothing to sign up for: open it and start logging, the same as a local install.

It is a demo instance, so treat it as one: no uptime promise, no support, and nothing there is backed up for you. Your diary lives in that browser's storage, and clearing the browser clears it. Self-hosting is the equally supported option, and it is the one below.

Quickstart

curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/compose.yml
docker compose -f compose.yml up -d

That is the whole setup: one container, no database, no .env step, no secret to generate. The app is reachable at http://localhost:3000.

Upgrading is docker compose -f compose.yml pull && docker compose -f compose.yml up -d. There is no server-side state to migrate or lose.

The app tells you when there is something to pull. Every six hours the server asks GitHub whether a newer openplate has been released and shows the answer under Settings > About, along with the exact version and commit you are running. The request goes from the server, not from anyone's browser, and carries nothing but your server's IP address. Set UPDATE_CHECK=off to stop it entirely; the About page then says checks are disabled and no request leaves the box. Details in docs/configuration.md.

Pick a language

openplate ships English and German. A visitor who has not chosen yet sees English; set DEFAULT_UI_LANGUAGE=de to start them in German instead.

DEFAULT_UI_LANGUAGE=de

It is a starting language, not a lock: whatever you set, anyone can switch in Settings and their choice sticks. An unsupported code fails the boot rather than falling back, so a typo is loud instead of serving the wrong language forever.

This is the interface only. It does not translate food names or AI replies.

Add sync

If you also want end-to-end-encrypted sync across devices, docker/topologies/compose.sync.yml brings up the app, the openplate-core service, and the Postgres that sync (and only sync) needs:

curl -O https://raw.githubusercontent.com/LowCarbCheck/openplate/main/docker/topologies/compose.sync.yml
echo "SERVER_SECRET=$(openssl rand -hex 32)" >> .env
# The URLs a BROWSER will use. Skip these two only for a localhost trial.
echo "PUBLIC_APP_URL=https://openplate.example.com" >> .env
echo "PUBLIC_SYNC_URL=https://sync.example.com" >> .env
docker compose -f compose.sync.yml up -d

There are two larger shapes as well: self-hosted AI, and everything at once. docker/topologies/README.md is the one-page map of all four, and compose.full.yml in particular needs four values edited inside the file before it will work.

Full walkthrough: docs/self-hosting.md and docs/sync.md.

Using it

  1. Open the app and go through the short onboarding: there is nothing to sign up for.
  2. Go to Settings → AI and connect an AI provider with your own API key. The fastest path is Connect with OpenRouter (one-click OAuth, no key to copy-paste). The key is stored only in that browser and is sent to nobody but the provider you chose.
  3. Use Scan Plate to upload a photo and get an AI-estimated macro breakdown, or Add to log entries manually.
  4. Profile → Your data downloads a full JSON backup and imports one. Your diary lives on this device, so that file is how you move it and how you keep it safe. Plate photos stay on the device that took them: they are never exported or synced.

The components

Run any subset. Only the first one is required.

Component What it is Needed?
openplate (this repo) The app. Accountless, local-first, stateless, boots with no secrets. Yes, it is the product.
openplate-core An account service whose first feature is end-to-end-encrypted sync. Stores an email address and ciphertext it holds no key for. It also backs the optional research console at /study (docs/sync.md), which stays dark unless the sync service sets SYNC_RESEARCH=true (off by default). No. Everything works without it.
openplate-inference A self-hosted, OpenAI-compatible plate-photo endpoint: open-weight models, your own hardware. No. BYOK cloud providers work without it.
openplate-gateway Archived 2026-09-04 (M192), merged into openplate-core: a managed instance's own account now carries the AI allowance, so the separate proxy is gone. n/a

Documentation

Guide What it covers
Architecture The four programs, what each one stores, and how they compose
Self-hosting Compose walkthroughs, first run, HTTPS, backups, upgrading
Configuration Every environment variable, the Content-Security-Policy, custom and instance-provided AI endpoints
Sync Enabling sync across devices, the encryption, and the operator's escrowed recovery key
Topologies What to run, from a browser-only install up to a self-hosted household
Family setup Sharing one AI bill across a household, with a spend limit and revocation per person
Legal review Status of the German legal text, machine-translated and awaiting a lawyer

Repository-level specifications live at the root: .adr/ (architecture decision records). Start with ADR-0006 for why this server has no accounts. Also AGENTS.md: coding guidelines for this repo; CLAUDE.md imports it. CONTRIBUTING.md: how to send a pull request. SECURITY.md: reporting a vulnerability.

Development

Requires Node.js ≥ 22 and pnpm, on Linux x64 or arm64. There is no database to install and no service to start alongside it.

The repo pins an exact pnpm in package.json's packageManager field, so run corepack enable first and let it fetch that version: a different global pnpm installs against a lockfile it does not match. pnpm-workspace.yaml also restricts optional native packages to Linux, so an install on macOS or Windows silently resolves none of them; build in a container there.

corepack enable
git clone https://github.com/LowCarbCheck/openplate.git
cd openplate
pnpm install
pnpm dev            # http://localhost:3000, nothing to provision first
pnpm typecheck      # react-router typegen && tsc
pnpm lint           # oxlint --max-warnings 0
pnpm test:unit      # node --test against tests/unit/**
pnpm test:integration # node --test against tests/integration/**
pnpm build          # react-router build (NODE_ENV=production)
pnpm start          # tsx ./server.ts (NODE_ENV=production)

Seeding a test device

A fresh browser holds no diary, so every review of a screen starts on an empty app, and there is no login on this server to sign in with. pnpm seed:test-account fixes both:

pnpm seed:test-account --diary-only          # just the diary file, no network at all

That writes seed-diary.json, a deterministic three-week diary with days over, at and under the carb ceiling, days with nothing logged, entries in every meal slot and in none, typed entries beside photographed ones, a weight series and goals. Open /settings/data on a device and upload it. A fresh browser has to be walked past onboarding first, or /settings/data is unreachable.

With an openplate-core instance running, the same command also creates an account you can sign in as, and pushes the diary to it, so a fresh device pulls a populated diary on its first sign-in:

ADMIN_TOKEN=... SEED_PASSPHRASE=... pnpm seed:test-account --url http://localhost:3000

ADMIN_TOKEN mints the invite and SEED_PASSPHRASE becomes the account's password. Neither has a flag: a credential in argv is a credential in your shell history. The default target is localhost and any other host needs --allow-remote, because the account this creates is a real account on whatever instance you point it at, and nothing removes it for you. Delete it when you are done, with pnpm sync-api accounts delete <id> --yes in openplate-core.

pnpm seed:test-account --help lists the rest: --weeks, --seed, --end-day, --timezone, --out, --email, --no-push.

Built with React Router v8, Express, and IndexedDB in the browser. Every environment variable is optional tuning: copy .env.example to .env if you want to change one. The image is built from Dockerfile.pnpm; its entrypoint just starts the server, with no migration or provisioning step.

License

openplate is open source under the MIT License (SPDX: MIT), as is openplate-core. Run it, read it, change it, fork it, redistribute it, host it for others, commercially or not, with no restrictions beyond keeping the copyright and license notice attached to any copy you distribute.

About

Open-source, self-hosted food tracker with bring-your-own-key AI plate identification — no accounts, local-first, MIT licensed.

Topics

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages