Schema lint is a mode that checks the diagram against seventeen rules and shows what it finds on the diagram itself: a colored strip in the margin marks the exact entity, and the exact field row, concerned, with a count beside any entity carrying more than one. Findings are grouped by what they cost you - whether they fail at runtime, leave a constraint unenforced, cost you something forever, or are a convention worth confirming - rather than graded on a severity scale. It runs entirely on your machine, against the diagram you already have open, with no connection required.

What does schema lint check?

Seventeen rules ship today, each with a permanent id you will see in the Rules tab. They are listed here in the four groups the drawer uses.

Fails at runtime - the database will reject this, in production, on a day nobody chose:

RuleWhat it catches
fk-type-mismatchA foreign key whose type does not match the column it references - int to bigint, varchar(36) to uuid. Joins cannot use the index, and MySQL rejects the constraint outright.
default-contradicts-checkA default value the column’s own check constraint forbids - a default of 'pending' against a check allowing only 'open' and 'closed'. Every insert that relies on the default fails.
fk-cycle-all-not-nullA cycle of foreign keys in which every column is NOT NULL - a table referencing itself is the simplest case. Each row needs a row that does not exist yet, so the first one cannot be inserted. Making one column in the cycle nullable gives the root row somewhere to start.

Not enforced - the schema looks like it guarantees something and does not:

RuleWhat it catches
no-primary-keyNo primary key, so no row can be identified or referenced.
junction-no-uniquenessA link table with nothing enforcing uniqueness over its foreign key columns, so the same pairing inserts repeatedly.
junction-uniqueness-wrong-columnsUniqueness that exists but covers the wrong columns - unique on (member_id) alone, or a unique that drags in created_at and therefore constrains nothing.
unique-includes-nullableA multi-column unique containing a nullable column. NULLs compare as distinct, so rows repeating the same values are accepted whenever that column is NULL.
enum-like-no-constraintA status, type, state, role, or kind column with nothing restricting its values - typos and values retired years ago are all still accepted.
fk-array-columnAn array column holding foreign keys (PostgreSQL). Nothing checks the referenced rows exist, deletes leave dangling entries, and membership lookups scan the array.

Costs - it works, and it charges you for the rest of the schema’s life:

RuleWhat it catches
redundant-indexAn index whose columns are a leading prefix of a wider one. The wider index already serves the same queries, and every insert, update, and delete maintains both.
money-as-floatMoney or quantity in binary floating point. float and double cannot represent every decimal exactly, so totals drift as rows accumulate.
timestamp-not-timestamptztimestamp instead of timestamptz on PostgreSQL - the same value reads as a different instant depending on the session time zone.

Convention - not wrong, worth confirming you meant it:

RuleWhat it catches
id-not-primary-keyAn id column exists but the primary key is something else, so anything identifying a row by id is not using the key the database enforces.
composite-pk-with-inbound-fksA composite primary key on a table other tables reference, so every one of them repeats the whole key.
nullable-booleanA nullable boolean - three states where two were intended, and every read has to decide what NULL means.
created-at-nullable-or-no-defaultcreated_at nullable, or without a default, so rows can be written with no creation time.
nullable-string-no-uniquenessA nullable string column with no uniqueness, so absence has two spellings: NULL and empty string. A blank form field submits the empty string, not NULL.

Each rule reports at most 200 findings, and says so when it stops rather than truncating silently.

How do I open schema lint?

The Lint button sits in the control bar, next to Search. Clicking it opens the lint drawer on the right, titled with the total finding count, with two tabs:

  • Findings - every finding, in collapsible groups per category, each group headed with its own count.
  • Rules - one row per rule that applies to your database, with a switch.

The drawer does not dim or mask the canvas, so you read the diagram and the list side by side rather than one at a time.

What does the count badge on the Lint button mean?

The badge is live whether or not the drawer is open, so a new problem announces itself instead of waiting to be looked for. It shows the total number of findings (9+ past nine) and takes its color from the most serious category present - so a diagram carrying only naming conventions stays grey and quiet, while one that will fail at runtime turns red.

Where do findings appear on the diagram?

In the margin, never on top of the entity. Lint draws a thin vertical strip a few pixels to the left of each flagged entity:

  • A finding about the entity as a whole spans the entity’s full height.
  • A finding about one field marks that field’s row exactly, so orders.total being a float points at total, not at orders.
  • An entity carrying more than one finding gets a count in the margin beside the strip.

The strip is colored by category, and each category also has its own fill - solid, hollow, or dotted - so the marking survives greyscale printing and reads the same for a color-blind reviewer. Lint markings are included in SVG export, so a review copy of the diagram carries its findings with it.

To go the other way, from the list to the diagram, click Show on canvas on any finding. Schemity selects the entity and pans it into the part of the canvas the drawer is not covering.

What do the four groups mean, and why not severity levels?

Because a severity scale asks you to accept somebody else’s ranking of your schema. Grouping by consequence tells you what happens instead, and lets you decide:

  • Fails at runtime - the database rejects it.
  • Not enforced - the constraint you think you have is not one.
  • Costs - correct, but you pay for it on every write or every query, forever.
  • Convention - a choice worth confirming.

The same principle governs the wording: findings state facts, not verdicts. A nullable boolean is reported as three states where two were intended, not as a mistake - so a deliberate choice reads as a choice, and you can move on.

How does lint treat junction tables?

It knows the difference between a link table that is correct and one that is not. Schemity treats an entity as a junction table when it holds two or more foreign keys over distinct columns and carries nothing else of substance - an id, created_at, updated_at, or deleted_at does not count against it.

Both correct forms are accepted, and neither is flagged:

  • members_roles with a composite primary key over (member_id, role_id).
  • member_roles with a surrogate id plus a unique constraint over (member_id, role_id).

Uniqueness is read from wherever it actually comes from - a unique constraint, a unique index, a single-field unique flag, or the primary key itself - the same sources behind the U marker on the canvas. See Check Constraints & Composite Unique.

That leaves the two ways a link table goes wrong: nothing covers the foreign key pair at all (junction-no-uniqueness), or something covers the wrong columns (junction-uniqueness-wrong-columns). The third, unique-includes-nullable, is the one that looks correct in a schema dump and enforces nothing.

How do I ignore a finding or turn off a rule?

Both, per diagram:

  • Ignore on any finding drops that single finding.
  • The Rules tab switches a whole rule off for this diagram.

Both are saved with the diagram in its JSON file, so they travel with it - a schema carries the conventions its authors agreed on instead of every person on the team re-dismissing the same notes, and a change to them shows up in a pull request like any other diagram change.

Ignores are keyed by name, not by internal id. Renaming an entity or a field therefore brings its ignored findings back: the thing you decided about is not quite the thing you have now, so the decision is worth making again.

Which rules run on which database?

Rules that do not apply to your engine are hidden from the Rules tab rather than shown switched off - a PostgreSQL-only rule is not part of a MySQL diagram’s vocabulary at all. Today timestamp-not-timestamptz and fk-array-column are PostgreSQL-only; the other fifteen run everywhere Schemity connects.

Where is lint not available?

Schema lint is a licensed desktop feature, included in the 2-week trial and in a license. Beyond that, it is not offered in three places:

  • Context views - a context view is a read-only perspective on the main diagram, so the schema is linted where it is edited.
  • Read-only diagrams opened without their connection.
  • Database views and materialized views are not linted, since you do not control their shape from here. They are still read when checking foreign key types against them.

Does lint fix anything by itself?

No. Lint reports, you decide, and the change you make goes through the normal path: edit the ERD, review the generated migration SQL diff, apply it. Nothing is rewritten behind your back.

Next

Organize a growing schema with Context Views.