From ajv-consistency
Write, review, refactor, or debug JSON Schema validation code with Ajv in Node.js (compile, validate, addSchema, ajv-formats, strict mode, draft 2020-12 vs draft-07) using one canonical, modern Ajv 8 idiom set. Use this skill whenever code validates JSON payloads against schemas with Ajv, or when the user hits "no schema with key or ref", "unknown keyword" strict-mode errors, email/date formats passing when they should fail, validate.errors being null or stale, schemas recompiling on every request, or "$schema": "2020-12" not behaving. Trigger it even when the user just says "validate this config against its JSON Schema in Node" — without saying "Ajv."
How this skill is triggered — by the user, by Claude, or both
Slash command
/ajv-consistency:ajv-consistencyThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Ajv 8 made three moves that training data hasn't absorbed: **dialects split across entry
Ajv 8 made three moves that training data hasn't absorbed: dialects split across entry
points (default = draft-07; 2020-12 lives at ajv/dist/2020), formats moved out to
ajv-formats, and strict mode by default. Generated code therefore validates against
the wrong draft, silently skips format checks, recompiles per request, and reads
overwritten validate.errors. This skill pins the canonical Ajv 8 setup.
| Always | Never | Why |
|---|---|---|
match the entry point to the schema's dialect: new Ajv() (draft-07) vs new Ajv2020() from "ajv/dist/2020" | one new Ajv() for every schema | A 2020-12 schema under plain Ajv hits meta-schema/keyword mismatches ("no schema with key", prefixItems ignored). |
addFormats(ajv) from ajv-formats | assuming format: "email" just works | Without ajv-formats, formats are unknown: strict mode errors — or with formats: false-era setups they pass everything. |
compile once, reuse: module-level const validate = ajv.compile(schema) | ajv.compile inside request handlers | Compilation builds a function (slow); per-request compile is the #1 Ajv perf bug, and addSchema re-registration throws. |
const ok = validate(data); if (!ok) handle([...validate.errors]) | reading validate.errors later/async | Errors live on the validate function and are overwritten by the next call — copy them out synchronously. |
| one shared Ajv instance per dialect per process | a fresh Ajv per module/call | The instance caches compiled schemas and $ref registrations. |
allErrors: true when reporting to humans | default first-error-only for form/API feedback | Users need the full list; (skip it for hot-path yes/no checks and untrusted schemas). |
register multi-schema setups via $id + ajv.addSchema(s); cross-ref with $ref | inlining/duplicating shared definitions | $id-based registration resolves refs once, consistently. |
treat strict-mode complaints as schema bugs (unknown keyword, type mismatches) | shotgun strict: false | Strict catches typos (requried, misplaced keywords) that otherwise pass silently forever; relax specific checks (strictTuples: false) only with cause. |
decide mutation explicitly: useDefaults, coerceTypes, removeAdditional | enabling them without knowing they mutate input | Validation that rewrites payloads surprises every caller downstream. |
House style:
// validator.js — one instance, compiled once, exported functions
const Ajv2020 = require("ajv/dist/2020");
const addFormats = require("ajv-formats");
const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv);
const orderSchema = require("./schemas/order.json"); // has "$id": ".../order.json"
ajv.addSchema(require("./schemas/address.json")); // $ref target
const validateOrder = ajv.compile(orderSchema);
function checkOrder(data) {
const valid = validateOrder(data);
return valid
? { valid: true }
: { valid: false, errors: validateOrder.errors.map(e => `${e.instancePath || "/"} ${e.message}`) };
}
module.exports = { checkOrder };
prefixItems/
unevaluatedProperties (unknown keywords are ignored outside strict mode) — invalid
data passes. The $schema in your schema file must match the Ajv entry point.format without ajv-formats historically passed everything; in strict v8 it
errors at compile — either way, wire addFormats and test one negative case.validate.errors is null after a passing call — code that
logs errors unconditionally logs the previous failure.$async: true return promises; normal compile
is sync. Random await validate(data) is a no-op that masks the boolean.removeAdditional: "all" deletes everything not in properties — including
fields handled by oneOf branches; combined with additionalProperties: false
in subtle compositions it can make invalid data valid. Prefer schema-level strip
control or post-validation picking.coerceTypes turns "25" into 25 in your input object and converts
scalars/arrays per its rules — great for query strings, wrong for JSON bodies that
should be type-strict.JSONSchemaType<T> ties the schema to the type at compile time —
use it; hand-cast as any schemas hide structural drift.allErrors is a ReDoS-adjacent surface; only compile schemas
you control.Target Ajv 8. Breaking line 6/7→8: strict mode default, ajv-formats split,
dialect entry points (ajv/dist/2020, ajv/dist/2019, JTD at ajv/dist/jtd), ESM/CJS
interop (new Ajv.default() shape under some ESM imports). Draft-04 schemas need
ajv-draft-04 or migration. ajv-errors (custom messages) and standalone compiled
validators (ajv compile CLI / standaloneCode) are the supported extension points.
$schema; instantiate the matching Ajv entry
point once per process, with addFormats and deliberate options.$id; compile every validator at startup
(fail-fast on bad schemas), export the compiled functions.instancePath/message/params into
your error shape.validate.errors, blanket strict: false, surprise mutation flags.For the dialect/entry-point matrix, option semantics, error-object anatomy, $ref
registration patterns, and standalone compilation, read
references/ajv-patterns.md.
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.
npx claudepluginhub guidogl/ajv-consistency --plugin ajv-consistency