Inventory → Substances
Purpose
Manage your clinic's controlled-substance formulary — the list of drugs you use, including DEA Schedule, form, concentration, and unit. Each entry here is a "substance" (the drug); physical bottles are tracked separately as containers.
Regulatory basis: DEA recordkeeping for every controlled substance handled, and the DEA schedule classifications.
When to use it
- First-time setup: add every controlled substance your clinic uses, before receiving any containers
- New drug added to formulary: add when your prescribing vet starts using a new substance
- Drug discontinued: mark a substance
inactiveordiscontinuedwhen you stop carrying it (existing transaction history is preserved) - Formulary cleanup: review periodically and deactivate substances no longer in use
Walkthrough
Step 1 — Open the substances list
Navigate to /home/<your-clinic>/inventory/substances.
The list shows every substance in your formulary with:
- Name + concentration
- Schedule
- Form (injectable / tablet / etc.)
- Unit
- Status (active / inactive / discontinued)
Use the search input to filter by name. Use the status filter to show only active, inactive, or discontinued substances. Use Bulk select when you need to deactivate several at once.

After clicking Add substance, the New Substance page (/inventory/substances/new) opens with an empty form:


After filling the required fields, Save becomes enabled. Clicking Save creates the substance and returns you to the list with the new row visible:

Step 2 — Click "Add substance"
The button is in the top-right corner. It takes you to the New Substance page (/inventory/substances/new) with the substance form.
Step 3 — Fill in the substance details
Required fields (cannot save without these):
- Substance name — the standard chemical or trade name (e.g., "Ketamine HCl", "Buprenorphine")
- DEA Schedule — pick from
II,III,IV,V - Form —
injectable,tablet,capsule,liquid,powder,transdermal, orother - Unit —
mL,tablets,capsules,patches,grams - Concentration — string in the format
<number><unit>or<number><unit>/<unit>. Examples:100mg,10mg/ml,50mg/tablet,0.3mg/ml. The form validates against this pattern; a typo prevents save.
Optional but recommended:
- Generic name — full chemical name (e.g., "Ketamine hydrochloride")
- Brand names — comma-separated list (e.g., "Ketaset, VetaKet")
- Species — comma-separated list (e.g., "dog, cat, horse"); helps prescribers find appropriate formulations
- NDC code — National Drug Code from the manufacturer (format:
12345-6789-01or 10-12 digits with hyphens) - DEA code — DEA chemical registration code if applicable
- Common uses — clinical indications (max 255 chars)
- Storage requirements — temperature, light, moisture conditions (max 255 chars)
- Special handling notes — warnings, precautions, disposal notes
- Reorder threshold — the on-hand amount (in this substance's unit) at or below which the substance is flagged as low stock. Leave blank to use the default "below 20% remaining" alert. See Reorder threshold & low-stock alerts below.
For container numbering (advanced):
- Substance code — 2-6 uppercase letters/digits used in container number templates (e.g.,
KETfor Ketamine, makes container numbers like2026-KET-001) - Container number template override — per-substance template; defaults to clinic-wide template if not set
- Starting sequence number — usually 0 or 1; useful when migrating from paper logs that used specific numbering
Click Save. The substance appears in the list immediately.
Step 4 — Edit a substance
Click the Actions menu on any row, then Edit. The Edit Substance page (/inventory/substances/<id>/edit) opens, pre-filled. You can change every field except the audit trail (created_by, created_at).
When you change concentration, form, or unit, double-check that no active containers reference outdated values — those containers were received under the old definition. If you have active inventory and change the unit (e.g., from mL to cc), all child container records still show the old unit historically; only new receipts use the new unit.
Step 5 — Deactivate a substance
Click Actions → Mark inactive (or Mark discontinued, depending on the reason). The substance is hidden from new-receipt forms but remains in transaction history.
The status field controls visibility:
- active — visible everywhere
- inactive — hidden from new-receipt and new-transaction forms; still queryable on reports
- discontinued — same as inactive but indicates the substance is permanently retired (e.g., manufacturer stopped making it)
You cannot delete a substance that has any associated containers or transactions — DEA records must be preserved. Use the status field instead.
Reorder threshold & low-stock alerts
Each substance can carry a reorder threshold — the on-hand quantity (in the substance's own unit) at which CS Logbook starts flagging it as low stock so you know to reorder. Because every clinic owns its own substance records, this is set per clinic, per substance — your reorder points are yours alone.
How the alert is decided
- If you set a reorder threshold, the substance is flagged low stock when its total on-hand across all active/unopened containers falls to or below that number (e.g., set
2for a substance measured in vials → alert once you're down to 2 vials or fewer). - If you leave it blank, CS Logbook uses the default rule: flag when the substance drops below 20% of its combined container capacity. Nothing changes for substances you never configure.
The threshold is an absolute quantity in the substance's unit, not a percentage. This is a soft alert only — it never blocks recording a transaction (per the CS Logbook compliance philosophy, ADR-006).
Two ways to set it
- On the substance form — the Reorder threshold field on the Add / Edit Substance page.
- Inline on the substance detail page — open a substance (
/home/<your-clinic>/inventory/substances/<id>), click the Reorder Threshold value in the Physical Properties card, type a number, and save. This is the quickest way to tune a single substance's reorder point.
To clear a threshold and return to the default 20% rule, blank the field (or clear the inline value) and save.
Where the alert shows up
- Inventory → Low Stock (
/home/<your-clinic>/inventory/low-stock) — every flagged substance, with a "Reorder at" column showing the configured threshold (or—when it's using the default 20% rule). - The Low Stock widget on the clinic dashboard.
Note: the Critical / Low urgency badge on the Low Stock page is still based on percent-remaining (Critical under 10%, Low under 20%), so a substance flagged purely by an absolute threshold may show a badge that doesn't line up with its threshold. The flagging itself honors your threshold; only the badge banding is percentage-based for now.
Field reference
Substance form
| Field | Label | Type | Required | Validation |
|---|---|---|---|---|
name |
Substance name | text | ✓ | 1-200 chars; duplicate check against name + concentration + form + unit |
schedule |
DEA Schedule | select | ✓ | enum: II, III, IV, V; required by DEA recordkeeping rules |
form |
Form | select | ✓ | enum: injectable, tablet, capsule, liquid, powder, transdermal, other |
unit |
Unit | select | ✓ | enum: mL, tablets, capsules, patches, grams |
concentration |
Concentration | text | ✓ | regex: <num><unit> or <num><unit>/<unit> |
genericName |
Generic name | text | optional | max 255 |
brandNames |
Brand names | text (CSV) | optional | parsed into array |
species |
Species | text (CSV) | optional | parsed into array |
ndc |
NDC code | text | optional | regex: ^\d{4,6}-\d{3,4}-\d{1,2}$ |
deaCode |
DEA code | text | optional | no format check |
commonUses |
Common uses | textarea | optional | max 255 |
storageRequirements |
Storage | textarea | optional | max 255 |
specialHandlingNotes |
Special handling | textarea | optional | max 255 |
substanceCode |
Substance code | text | optional | regex: ^[A-Z0-9]{2,6}$ |
containerNumberTemplateOverride |
Container template override | text | optional | must contain {SEQ} or {SEQ:n} |
startingSequenceNumber |
Starting sequence | number | optional | int 0-999999 |
reorderThreshold |
Reorder threshold | number | optional | ≥ 0; decimals allowed; blank ⇒ default <20% alert |
status |
Status | select | optional | enum: active, inactive, discontinued; default active |
Hidden fields (auto-populated): accountId, created_by, created_at, updated_at.
Edge cases
Duplicate detection
The form blocks save if it finds an existing substance with the same name + concentration + form + unit. This prevents two formulary entries for the same drug. If you intend to track two strengths of the same drug (e.g., 100mg/mL and 50mg/mL), the concentration differs so duplicate detection won't trigger.
Soft warning: NDC format mismatch
NDC codes have several legitimate formats. The strict regex matches the most common (5-4-2 / 5-3-2 / 4-4-2). If your manufacturer's NDC doesn't match, you can leave the field blank and document the NDC in Notes instead.
Container numbering template
If you set a substanceCode of KET and the clinic-wide template is {YEAR}-{CODE}-{SEQ:3}, container numbers auto-generate as 2026-KET-001, 2026-KET-002, etc.
If you override the template per substance (e.g., to use a clinic-internal scheme like KET/2026/0001), be consistent across substances — DEA inspectors prefer one numbering convention per clinic.
Changing a substance's schedule
You generally should not change the schedule on an existing substance — DEA reclassifications are rare. If you need to (e.g., a state moves a substance to Schedule II), do it during a quiet period and audit all related transactions afterward to verify nothing was misfiled.
Common mistakes
- Adding the same drug twice with slightly different names. "Ketamine" vs "Ketamine HCl" vs "Ketamine hydrochloride" all refer to the same substance. Pick one canonical name (we suggest
<chemical>) and stick with it. Use thegenericNameandbrandNamesfields for alternatives. - Leaving concentration blank. It's required. Even if your formulation is "tablet, 50mg", enter
50mg— without it, prescribers can't see the dosing on the formulary list. - Marking inactive when you mean discontinued.
inactive= temporarily out of stock;discontinued= permanently retired. The distinction matters for compliance reporting; pick the right one. - Setting
substanceCodeafter containers exist. Container numbers are computed at receive time. If you add a substance code mid-life, only new containers use it — historical containers keep their old numbering. This is fine, just be aware.
Related
- Containers — receive a physical bottle of a substance
- Transactions — log usage / waste / disposal
- Suppliers — track where your substances come from
- Glossary § Substance — domain-model definition
- Glossary § DEA Schedule classifications