A lightweight web app to track electronics parts (category, subcategory, description, package, container, quantity, notes) with a simple HTML UI — including optional links to datasheets and pinouts.
Note
If you’re looking for a more professional-grade (and free) inventory system with broader features, you may want to check out InvenTree (it might be a better fit depending on your needs).
- Add, inline-edit, and delete parts
- Duplicate parts (pre-fills add form for quick variations)
- Search + filter by category and container (free-text search also matches container codes like
BX-50) - Click a container chip in the table to open the container page
- CSV export
- Optional links per part: datasheet + pinout (quick-open buttons in the table)
- Optional images per part: device photo + pinout image (hover preview in the table)
- Optional stock levels (low-stock warning colors) for quantity
- Container pages and printable container labels with QR codes
- Uses a local SQLite database file (no server required)
The Qty chip can be color-coded using optional stock levels:
- Enter levels as
hi:lo(example:10:5)-
Green: quantity
$\ge$ hi -
Yellow:
lo$\le$ quantity$<$ hi -
Red: quantity
$<$ lo
-
Green: quantity
- Enter a single number like
5to use the same value for both (5:5). - Leave empty to disable levels (chip stays neutral).
Fig. 1: Main inventory screen with search, filters, and inline editing.
Fig. 2 & 3: Printable container labels – combined QR + text.
![]() |
![]() |
|---|---|
| QR code + description | Box contents |
Fig. 4: Container page showing contents of box BX-50 (accessed by clicking the chip or scanning the QR code).
More screenshots:
- Main screen (board popup)
- Main screen (container pulldown)
- Label selection
- Printable Avery labels (with offset support for partially-used label sheets)
- Restore parts (trash)
- FastAPI (serves HTML)
- Jinja2 templates
- SQLite (stored in
inventory.db)
python3 -m venv .venv
source .venv/bin/activatepip install -r requirements.txtQR code support is included via the qrcode dependency in requirements.txt.
This app uses a login page + server-side sessions. You must configure credentials via environment variables.
Local-only (no authentication):
INVENTORY_DISABLE_AUTH=1 \
INVENTORY_BASE_URL="http://127.0.0.1:8001" \
uvicorn app:app --reload --host 127.0.0.1 --port 8001Do not use INVENTORY_DISABLE_AUTH on an internet-exposed instance.
If you run behind a reverse proxy (recommended for HTTPS), also set INVENTORY_BASE_URL so label QR codes point to the correct external hostname.
Example (behind reverse proxy + HTTPS):
export INVENTORY_BASE_URL="https://inventory.reverseproxy.com"Dev (auto-reload on code changes):
INVENTORY_USER="andreas" \
INVENTORY_PASS_HASH='$pbkdf2-sha256$...$...$...' \
INVENTORY_BASE_URL="http://127.0.0.1:8001" \
uvicorn app:app --reload --host 0.0.0.0 --port 8001 --proxy-headersProd (no reload):
INVENTORY_USER="andreas" \
INVENTORY_PASS_HASH='$pbkdf2-sha256$...$...$...' \
INVENTORY_BASE_URL="http://127.0.0.1:8001" \
uvicorn app:app --host 0.0.0.0 --port 8001 --proxy-headersOpen:
On startup, the app creates/uses a SQLite database at inventory.db (in the repo directory).
- To reset all data: stop the server and delete
inventory.db.
/– main inventory table/help– help page/export.csv– download CSV export (respects current filters via query params)/containers/{code}– show parts in a specific container/containers/labels– printable labels with QR codes
Each part can store optional image URLs:
image_url– photo of the IC/module/device
Pinouts are stored in pinout_url. If pinout_url points directly to an image, the table shows a hover preview.
Store images under the repo's static/ folder (for example static/images/ and static/pinouts/).
Then reference them with a URL starting with /static/...:
image_url:/static/images/MP2307_HW133ABC_board.jpgpinout_url:/static/pinouts/MP2307_pinout.jpg
Shortcut: when editing image_url / pinout_url, you can paste just the filename (for example MP2307_HW133ABC_board.jpg or MP2307_pinout.jpg).
The app will automatically resolve it under /static/images/ or /static/pinouts/.
Note: static/images/ and static/pinouts/ are present in the repo (via .gitkeep), but the actual image files are intentionally ignored by git.
If the URL ends with an image extension (.png, .jpg, .jpeg, .gif, .webp, .svg), the table shows a hover preview.
Clicking the preview opens the image in a new tab.
On touch devices (mobile/tablet), previews are shown by tapping the icon (instead of hovering). Use the × button to close the preview.
Container label QR codes are generated using BASE_URL in app.py.
- Configure the external base URL via
INVENTORY_BASE_URLso QR codes point to a reachable URL (LAN IP or public HTTPS hostname).
The app supports two types of labels:
- Asset (QR only) – container code + QR code
- Asset + text (combined) – container code + QR code + optional text on one label
- Content (text only) – container code + descriptive text
- Separate labels (QR + text) – creates two labels per container
Part labels are designed for small compartments inside containers, e.g, Gridfinity bins or other. Each label shows:
- Container – which container the part is in (top, small)
- Description – part name/description (large, prominent)
- Notes – additional details (smaller)
- Subcategory – part classification (bottom, italic)
To print part labels:
- Go to Labels in the navigation
- Select Part Labels (mutually exclusive with container labels)
- Filter parts by search text, category, or container
- Check the parts you want to label
- Select a preset and print
Note: Part label text sizes automatically scale down for smaller presets (like Avery 3666) via CSS custom properties.
If your Avery sheet has some labels already used, set "Start at position" to skip them. Positions are counted row-wise (left to right, top to bottom). The number of columns depends on your label preset.
Example for a 2-column layout (like Avery 3425):
┌─────┬─────┐
│ 1 │ 2 │
├─────┼─────┤
│ 3 │ 4 │
├─────┼─────┤
│ 5 │ 6 │
└─────┴─────┘
For example, if positions 1–2 are used, set start position to 3 to begin on the second row.
Label printing is implemented as:
- Shared label UI/print styles:
static/labels.css - One layout file per Avery preset:
static/avery_<preset>.css(e.g.static/avery_3425.css)
Trademark notice: Avery® is a registered trademark of its respective owner (commonly Avery Products Corporation and/or affiliated entities). This project is not affiliated with, sponsored, or endorsed by Avery. The Avery name and label numbers are used only to indicate intended compatibility with commonly available label sheets.
To add a new layout:
- Create
static/avery_<new-id>.css - Add a metadata comment near the top of the file:
This enables the UI to show grid info (e.g., "2×8 = 16 labels") and position hints.
/* Meta: columns=2, rows=8, label_size=105x57mm */ - Reload the Labels page — the preset list is discovered automatically from
static/avery_*.css
Printers often introduce small feed/margin offsets even with “Margins: none”. Each preset CSS supports knobs:
--label_offset_x/--label_offset_y(shift the whole sheet; negative moves left/up)--label_gap_x/--label_gap_y(space between labels)
Example (move 5mm left and 1mm up):
--label_offset_x: -5mm;
--label_offset_y: -1mm;By default, label borders are not printed (best for real Avery sheets where tiny alignment offsets can make printed outlines look off).
For calibration or printing on plain paper (where you want cut guides), enable Print cut outlines on the Labels page before printing (always shown on screen; printed only when enabled).
This app uses a login page + server-side sessions (stored in SQLite) for all routes.
Configure it with:
INVENTORY_USER– usernameINVENTORY_PASS_HASH– password hash in Passlibpbkdf2_sha256formatINVENTORY_BASE_URL– optional; external base URL used for container label QR codes
Optional (local-only):
INVENTORY_DISABLE_AUTH– when set to1/true, disables authentication entirely. Use only for fully local deployments (e.g. bound to127.0.0.1). Do not enable this on an internet-exposed instance.
Note: the session cookie is configured as secure, so you should access the app via HTTPS (directly or via reverse proxy).
Generate a hash (example):
python3 -c "from passlib.hash import pbkdf2_sha256; print(pbkdf2_sha256.hash('change-me'))"Run with credentials (example):
export INVENTORY_USER="andreas"
export INVENTORY_PASS_HASH='<paste-hash-here>'
uvicorn app:app --host 0.0.0.0 --port 8001 --proxy-headersIf INVENTORY_USER / INVENTORY_PASS_HASH are not set, the login page will show an error because auth is not configured.
Note: the Passlib hash contains $ characters. Use single quotes around INVENTORY_PASS_HASH (or escape $) to avoid shell expansion.
- Security: even with Basic Auth, treat this as a trusted-LAN tool. Do not expose it directly to the public internet.
- If you need remote access, prefer a VPN and restrict access.
MIT — see LICENSE.



