Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool
Self-Documenting JSON Configuration Best Practices
Self-documenting JSON configuration should answer the questions a reader actually has while editing or reviewing a file: what a setting controls, which values are allowed, what units apply, what the default is, and whether the key is still safe to use.
In practice, that means combining readable JSON structure with the right supporting format. Use strict JSON when machines own the file, JSONC when humans edit it and your toolchain explicitly supports comments, and JSON Schema when you want validation, hover text, safer migrations, and fewer configuration mistakes.
Quick Rules
- Name keys for meaning, not shorthand.
- Encode units and formats in the key when it removes ambiguity.
- Group settings by domain so readers can predict where things live.
- Prefer JSON Schema over fake comment fields for durable documentation.
- Version the config format and fail fast on unknown keys in stable sections.
Start with Names and Structure
1. Name keys for meaning, unit, and intent
Short keys only help the author who already knows the system. Everyone else needs names that reveal the subject, the unit, and the kind of value being set. That is why requestTimeoutMs is better than rt, and newCheckoutEnabled is better than ff.
Ambiguous vs self-explanatory
{
"rt": 5,
"lg": "d",
"ff": "1"
}
{
"requestTimeoutMs": 5000,
"logLevel": "debug",
"newCheckoutEnabled": true
}Good names remove the need for side notes. A reader should not have to guess whether 5 means seconds, milliseconds, retries, or severity.
2. Group by domain, not by parser convenience
Put related settings together the way an operator thinks about the system: network settings together, logging together, feature flags together. Keep nesting intentional rather than deep for its own sake.
A predictable shape for a shared config file
{
"schemaVersion": 2,
"http": {
"baseUrl": "https://api.example.com",
"requestTimeoutMs": 5000,
"retry": {
"maxAttempts": 3,
"backoffMs": 250
}
},
"features": {
"newCheckout": {
"enabled": true
}
},
"logging": {
"level": "info",
"destination": "stdout"
}
}A top-level schemaVersion or configVersion field is small, but it gives migrations a clear anchor when the format evolves.
3. Let data types carry meaning
Use booleans for toggles, numbers for numeric thresholds, arrays for ordered lists, and constrained strings only when the domain really is an enum like debug, info, warn, and error. Avoid stringly typed settings like "true", "1", or "yes" unless a legacy interface forces them.
Decide Up Front: JSON or JSONC?
Strict JSON does not allow comments. If a configuration file is mostly machine-generated or consumed by multiple unknown tools, keep it strict JSON. If humans regularly edit it and your parser plus editor explicitly support JSONC, comments can make the file easier to maintain.
Human-edited JSONC example
{
// Human notes belong in JSONC, not strict JSON
"requestTimeoutMs": 5000,
"logLevel": "info",
/* Keep secrets out of the file itself */
"databasePasswordFromEnv": "DB_PASSWORD"
}- Choose JSONC only when your runtime loader, CI checks, and editor flow all agree on it.
- Avoid trailing commas if portability matters. They are easy to add by habit, but support is less predictable across tools than plain comments.
- Treat fake comment keys like
_commentor__descriptionas a last resort. They pollute the data model and can leak into runtime behavior if a consumer forgets to ignore them.
Use JSON Schema as Executable Documentation
For long-lived or shared configuration, JSON Schema is the strongest self-documenting layer because it explains the shape of the data and validates it at the same time. It is where you describe allowed values, defaults, examples, deprecated keys, required fields, and typo protection.
Modern schema example
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.com/schemas/app-config.schema.json",
"title": "App configuration",
"description": "Human-edited runtime settings for the web app.",
"$comment": "Use description for user-facing docs and $comment for maintainer notes.",
"type": "object",
"additionalProperties": false,
"properties": {
"schemaVersion": {
"type": "integer",
"const": 2,
"description": "Configuration schema version expected by the loader."
},
"requestTimeoutMs": {
"type": "integer",
"minimum": 100,
"default": 5000,
"examples": [1000, 5000, 10000],
"description": "Outbound HTTP timeout in milliseconds."
},
"logLevel": {
"type": "string",
"enum": ["debug", "info", "warn", "error"],
"default": "info",
"description": "Minimum log severity to emit."
},
"featureFlags": {
"$ref": "#/$defs/featureFlags"
},
"legacyApiBaseUrl": {
"type": "string",
"format": "uri",
"deprecated": true,
"description": "Old endpoint kept temporarily during migration."
}
},
"required": ["schemaVersion", "requestTimeoutMs", "logLevel"],
"$defs": {
"featureFlags": {
"type": "object",
"additionalProperties": false,
"properties": {
"newCheckout": {
"type": "boolean",
"default": false,
"description": "Enable the new checkout flow."
}
}
}
}
}descriptionandtitlehelp humans and editors understand a field quickly.defaultdocuments the intended fallback, but your loader still needs to apply it if you want missing values to be materialized at runtime.examplesmake edge cases clearer than prose alone.deprecatedis useful when you need a migration window instead of a hard break.additionalProperties: falseis often worth using on stable config objects so misspelled keys fail fast.
Modern schemas commonly use Draft 2020-12 and $defs. That is a good default for new work, but editor support is not identical everywhere. Before you standardize on newer keywords, confirm that your validator and your team's editor actually support the draft you choose.
If you want editor assistance in a JSON instance file, associate the file with a schema. Some tools let you do that with a $schema property in the document, while others use workspace settings or file-pattern mappings. If adding a $schema field would break consumers, prefer the editor mapping approach.
Design for Change, Not Just Readability
A configuration file is only truly self-documenting if it stays understandable during migrations and partial rollouts. That requires a few rules beyond naming.
- Version the format so loaders can migrate or reject unsupported files cleanly.
- Reject unknown keys in stable sections to catch typos before they become production bugs.
- Keep deprecated keys for a defined window, mark them in the schema, and log a clear warning.
- Validate sample configs in CI so your examples stay correct instead of becoming stale documentation.
Operational Guardrails
- Keep secrets out of checked-in JSON. Store environment variable names, secret-manager references, or file paths instead of raw API keys and passwords.
- Pick one casing convention for keys and keep it consistent across the whole repository.
- Make formats obvious in names:
cacheTtlSeconds,backupWindowCron,apiBaseUrl,releaseDateIso8601. - Format files with one formatter so key order, indentation, and trailing whitespace do not create review noise.
Review Checklist
- Can a new teammate understand each key without opening a separate doc first?
- Are units, allowed values, and defaults obvious from the key name or schema?
- Will a typo or unknown key fail validation instead of being silently ignored?
- Are deprecated settings clearly marked and migration-safe?
- Are comments handled intentionally through JSONC or schema, not by invalid JSON tricks?
Bottom Line
The best self-documenting JSON configuration is not just pretty JSON. It uses clear names, a predictable shape, strict typing, explicit versioning, and schema-backed validation so the file explains itself while your tooling enforces the rules. That combination is what keeps a config understandable after the original author moves on.
Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool