Velodok
A digital ID for your bicycles.
Velodok is an unofficial replacement for a cancelled government app that was meant to help you register your bike and prove ownership in case of theft. It keeps the full record of every bike you own - frame number, photos, receipts, components - on your phone, so that when you need it, you have it. No account, no cloud, no waiting for a public service that never shipped.
What it is
Velodok is a bike registry that lives on your phone. You add each bike once, in a five-step form, and it stores the things a police report or an insurance claim will actually ask for:
- Identity - name, type, brand, model, year, size, colour, and the two numbers that matter: the frame number and the police registration number.
- Purchase - date, price, currency, dealer, insured value, weight.
- Components - a per-part list covering 19 component types, from frame and fork down to handlebar tape and bottom bracket, each with its own brand, model, price, weight and purchase date.
- Photos and documents - an overview shot, detail photos, a dedicated frame-number photo, and the purchase receipt. Taken with the camera or picked from the gallery, viewable full-screen.
- Notes - anything that doesn’t fit a field.
It’s German-language, Material Design, and works with the phone in flight mode. There is no sign-up step, because there is nothing to sign up to.
Why it exists
Bike theft is a paperwork problem as much as a crime problem. The police want a frame number. The insurer wants a receipt and proof the bike was yours. Almost nobody has either to hand, because that information lives on a sticker in a drawer, in an email from three years ago, and in a photo you didn’t take.
A government app was supposed to solve exactly this - a national bike registry you could point at after a theft. It was cancelled. The need didn’t go away with it.
Velodok is the version that exists:
- Prepared, not reactive. The five minutes you spend adding a bike happen while you still own it. That’s the whole point.
- Yours, on your device. Everything is in a local SQLite database and the phone’s own filesystem. No account, no server, nothing to leak, nothing to shut down. A registry that can be cancelled a second time isn’t much of a registry.
- Detailed enough to be evidence. A frame number and a photo of it. A receipt. A component list that distinguishes your bike from every other black gravel bike on the rack.
Core features
| Feature | What it does |
|---|---|
| 🚲 Multiple bikes | Every bike is a card on the home screen with its cover photo. 11 types covered - road, MTB, gravel, e-bike, city, trekking, fixie, full-suspension, folding, cargo, other. |
| 🔢 The numbers that matter | Dedicated fields for frame number and police registration number, plus a dedicated photo category for the frame number itself. |
| 🧩 Component list | 19 component types per bike, each with brand, model, price, weight, purchase date and notes. Added through a bottom sheet, so it doesn’t interrupt the form. |
| 📸 Photos & receipts | Four categories - cover, detail, frame number, receipt. Camera or gallery, stored on-device, viewable full-screen. |
| 🧾 Purchase record | Date, price, currency, dealer, insured value and weight - the fields an insurance claim asks for. |
| 🪜 Five-step entry | Grunddaten → Kauf → Komponenten → Fotos → Notizen, with a progress bar. A bike has too many fields for one scroll. |
| 🌗 Dark mode | Follows the system theme, applied through Material Design 3 across the app and the Android navigation bar. |
| ✈️ Fully offline | No network code at all. The app has never made a request and doesn’t need permission to. |
Vision
The local app is the product, and it stays usable without an account forever. But a registry you can only read on the phone in your pocket has an obvious limit - it’s the one device a thief might also have taken. The direction:
- Optional accounts and cloud sync. Opt-in, never required. Bikes and media sync so a lost or stolen phone doesn’t take the evidence with it.
- Read-only share links with a QR code. A single link you can hand to the police or an insurer - printable, no app needed on their end - with a configurable expiry.
- PDF export for insurance paperwork, generated from the record you already have.
- French and Italian alongside German, to actually cover the countries this is aimed at.
Beyond that, Velodok stays deliberately narrow. It’s a digital ID - it proves the bike is yours. Service history and maintenance tracking are a different job, which is what Velobase is for.
Technical details
Stack
| Layer | Choice |
|---|---|
| Runtime | Expo SDK 55 (managed), React Native 0.83, React 19.2 |
| Language | TypeScript |
| Navigation | Expo Router - file-based, typed routes |
| UI | React Native Paper (Material Design 3) + @expo/ui |
| Styling | NativeWind 5 + Tailwind v4 |
| Database | expo-sqlite (on-device only, WAL mode) |
| ORM | Drizzle ORM + drizzle-kit, committed migrations |
| Media | expo-image-picker, expo-image-manipulator, expo-file-system, expo-image |
| Sheets | @gorhom/bottom-sheet |
| Animation | react-native-reanimated 4 + worklets |
| Local KV | react-native-mmkv |
| Releases | release-please + GitHub Actions → EAS local build → APK on the release |
Engineering decisions
- No network layer, on purpose. There is no API client, no auth code and no sync engine in the app - not stubbed, not disabled, absent. Everything reads and writes local SQLite. That’s what makes “works offline” a property of the architecture rather than a feature that can regress.
- The schema dropped its sync bookkeeping. An earlier design carried
local_id/server_id/is_dirty/is_deletedon every table for a sync layer that hadn’t been built. The shipped schema uses a plain UUID primary key instead. Carrying the cost of a distributed-systems design for a single-device app is a tax with no payer; it can be reintroduced alongside the sync layer that justifies it. - Media is filesystem, never blob. The database stores a
local_pathper image; the bytes live in the app’s own directory viaexpo-file-system. The SQLite file stays small enough to stay fast, and photos are handed straight toexpo-imagewithout a round trip through the DB. - Deletion is enforced by the database.
PRAGMA foreign_keys = ONat connection time, withonDelete: "cascade"from bikes to both components and media. Removing a bike can’t leave orphaned rows behind, regardless of what the calling code forgets. - WAL from the start.
PRAGMA journal_mode = WAL- the reads (list screen, detail screen) and the writes (form submit, photo capture) stop blocking each other, which is what keeps navigation snappy while a photo is being saved. - Three variants, three bundle IDs.
net.ntrapp.velodok,.devand.test, each with its own name and accent colour. Development, preview and production builds coexist on one phone, so testing a release candidate doesn’t mean uninstalling the copy holding your actual bikes. - A stepper instead of a wall of inputs. Sixteen fields, a component list and a photo picker in one scroll is an abandoned form. Splitting it into five labelled steps with a progress bar means every screen has an obvious end.
- Releases are fully automated. Conventional commits drive release-please, which opens the release PR and tags the version; publishing the release triggers a local EAS production build in CI that attaches the signed APK. The app version is read from
package.json, so the binary can’t disagree with the tag.
Get & use Velodok
Install (Android APK)
Velodok is not on the Play Store, so you install the APK directly:
- Download the latest build: Velodok.apk.
- Allow installs from your browser or file manager (Settings → Apps → Special access → Install unknown apps).
- Open the APK to install. Android will warn you the app isn’t verified - that’s expected for a sideloaded build.
First run
- Tap Velo hinzufügen on the empty home screen.
- Work through the five steps. The two fields worth not skipping are the frame number and a photo of the frame number - they’re what a police report is built on.
- Add the purchase receipt under Fotos while you can still find it.
Build from source
Velodok is a managed Expo app targeting Android. It depends on native modules (SQLite, MMKV, Reanimated), so run it as a development build rather than in Expo Go.
git clone https://github.com/elRatto21/Velodok
cd Velodok
pnpm install
# Start the dev-client bundler
pnpm dev
Schema changes go through Drizzle:
pnpm generate # drizzle-kit generate
Uses pnpm for all package management.
Velodok - your bike, on the record.