Skip to content
All work

Source available · Reference build · 2026

Enamel

A dental practice backend built to answer the question a blog demo never has to: who can see which fields, and how do you know? Thirty collections, an append-only clinical record, and 108 checks that try to break it.

The Enamel login screen, branded through the Directus settings API
Role
Data model, access policies, custom extension, provisioning, branding — all of it
Year
2026
Languages
EnglishGermanDutchFrench
Stack
DirectusTypeScriptVue 3PostgreSQLRedisDocker

Most Directus demos model a blog. A blog has one kind of user and nothing worth protecting, so it never has to answer the question that decides whether a real project succeeds. A dental practice answers it on day one: the receptionist books the appointment and takes the payment, so she needs the patient list — and has no business reading the medical alerts.

  1. Field-level access, not hidden inputs

    Front desk reads patient contact details and is denied medical alerts, allergies and clinical notes at the field level — and denied treatment records and the tooth chart outright. Request the field explicitly with a token and the API returns 403. No frontend is involved, so no frontend can leak it.

  2. Multi-tenancy as a row filter

    Every collection carries a clinic, and every policy compares it to the signed-in user's. Creates are stamped with the same value, so nobody can plant a row in another practice. The demo seeds two practices on purpose — with one, a broken tenant filter is invisible because everything you can see happens to be yours.

  3. The permission model is tested

    A permission model nobody has tried to break is a hope, not a policy. A suite logs in as each of the five roles and asserts both directions — what they must be able to do and what they must not. A hundred and eight checks, including a cross-tenant fetch by ID, a radiograph requested by its asset URL, an invoice header weighed against the lines it claims to total, and a deliberately sabotaged instance to prove the checks can fail at all.

  4. A tooth chart as a custom interface

    FDI notation, arches laid out as a clinician faces the patient, molars sized wider than incisors. It reads an append-only findings log rather than mutable rows, so a chart is a view over history. Missing teeth are drawn as an absence, not a pale tooth.

  5. Fewer teeth, more teeth, and neither

    A chart with 32 boxes for an adult and 20 for a child, picked from the date of birth, is wrong in three directions. Agenesis is ordinary — the lower second premolars are absent in 29.9% of affected people. Supernumerary teeth do not fit FDI at all, which is why ISO 10394 exists: a mesiodens is AB, not 11, and the same code may legitimately describe two teeth. And age does not decide dentition, because a retained milk tooth in a forty-year-old keeps its primary designation. So dentition is recorded per tooth, never derived.

  6. Records that cannot be rewritten

    Clinical notes are entries, never edits: a correction is a new entry pointing at the one it corrects, with a reason, leaving the original legible. No policy grants update or delete on notes, histories or consents — including the practice owner's — and a check asserts no such rule exists. Consent stores the answers and a fingerprint of what was signed, not a PDF that can go stale in silence.

  7. The modules a dentist looks for

    Recall with NICE's three-month interval steps and the field most schemas omit — whether the patient agreed with the interval. Treatment planning where the presented total is frozen at presentation, so case acceptance by value cannot be rewritten by a price rise. Periodontal charting with six sites per tooth and the gingival margin named recession_mm, because it is the one field where an inverted sign produces clinically wrong output in silence. Payment plans that amortise to exactly zero, a cancellation list that does not invent a priority scheme the products do not have, and lab work as four timestamps rather than a status — because a status cannot tell you whether the fit was checked before the patient sat down.

  8. Provisioned as reviewable code

    Schema, policies, roles, bookmarks, flows and branding are declarative TypeScript rather than a schema-snapshot blob nobody can read. Fully idempotent, so a permission change arrives as a diff in a pull request instead of a click nobody witnessed.

The same patient record as seen by the front desk and by a dentist
The custom FDI tooth chart interface inside the Directus admin
The admin running in Dutch — collections, fields, notes and bookmarks all translated
A patient record with the global bookmarks in the sidebar
Periodontal site readings — probing depth, recession, bleeding, plaque and suppuration, deepest pockets first
A treatment plan with the presented and accepted totals kept side by side