JSON Configuration Files: Best Practices & Alternatives
TL;DR
- JSON is the default for configuration in the JavaScript ecosystem (package.json, tsconfig.json, eslintrc.json) but has real limitations: no comments, no variables, no environment support
- JSON5 and JSONC add comments and relaxed syntax. Use JSONC for VS Code settings and JSON5 for application configs
- Never commit secrets to JSON config files. Use environment variables and reference them in code
- Use schema-backed configs (tsconfig, package.json have JSON Schemas) for IDE autocompletion and validation
Table of Contents
- Where JSON Config Is Used
- JSON Config Limitations
- JSON5: JSON with Comments and More
- JSONC: JSON with Comments
- Environment-Specific Configuration
- Schema-Backed Configuration
- Configuration Anti-Patterns
- When to Use JSON vs YAML vs TOML
- Frequently Asked Questions
Where JSON Config Is Used
JSON is the dominant configuration format in the JavaScript/TypeScript ecosystem:
| File | Purpose | Ecosystem |
|---|---|---|
package.json | Project metadata, dependencies, scripts | npm/yarn/pnpm |
tsconfig.json | TypeScript compiler options | TypeScript |
.eslintrc.json | Linting rules | ESLint |
.prettierrc | Code formatting rules | Prettier |
vercel.json | Deployment configuration | Vercel |
firebase.json | Firebase project config | Firebase |
manifest.json | PWA manifest, Chrome extensions | Web Platform |
.vscode/settings.json | Editor settings | VS Code |
Outside JavaScript, JSON configs appear in AWS CloudFormation templates, Jupyter notebooks, Swagger/OpenAPI specs (originally JSON), and many CLI tools.
JSON Config Limitations
No comments. This is the biggest limitation. You cannot explain why a setting exists, document valid values, or leave notes for other developers:
{
"compilerOptions": {
"strict": true,
"target": "ES2022"
}
}
Why is target set to ES2022 instead of ESNext? You cannot say without external documentation.
No trailing commas. Adding or removing the last property requires editing the line above to add/remove a comma. This creates noisy diffs in version control:
{
"name": "my-app",
- "version": "1.0.0"
+ "version": "1.0.0",
+ "description": "My application"
}
No variables or references. You cannot define a value once and reference it in multiple places. Every repetition is a maintenance risk.
No multi-line strings. Long values like paths or descriptions must be on a single line, reducing readability.
No environment support. JSON has no concept of "use this value in production and that value in development." You need external tooling for environment management.
JSON5: JSON with Comments and More
JSON5 extends JSON with features borrowed from ECMAScript 5:
{
// Single-line comments
name: 'my-app', // Unquoted keys and single quotes
/* Multi-line
comments */
version: "2.0.0",
port: 3000, // Trailing commas allowed
description: "A multi-line \
string that spans \
multiple lines",
// Hex numbers, Infinity, NaN
maxConnections: 0xFF,
}
To use JSON5 in Node.js:
npm install json5
import JSON5 from 'json5';
import { readFileSync } from 'fs';
const config = JSON5.parse(readFileSync('config.json5', 'utf-8'));
JSON5 is ideal for application configuration files where developer readability matters more than strict compatibility.
JSONC: JSON with Comments
JSONC (JSON with Comments) is a more conservative extension. It only adds comments and trailing commas to standard JSON syntax.
VS Code uses JSONC for its settings files. When you open settings.json, VS Code parses it as JSONC, allowing comments.
VS Code settings example (JSONC):
{
// Font settings
"editor.fontSize": 14,
"editor.fontFamily": "'Fira Code', monospace",
// Disable minimap for cleaner workspace
"editor.minimap.enabled": false,
// Format on save - reduces code review noise
"editor.formatOnSave": true,
}
TypeScript also supports JSONC in tsconfig.json:
{
"compilerOptions": {
// Strict mode catches more bugs at compile time
"strict": true,
// Target ES2022 for top-level await support
"target": "ES2022",
// Use Node16 module resolution for ESM compatibility
"moduleResolution": "Node16",
}
}
Environment-Specific Configuration
Since JSON does not support variables, use a layered configuration approach:
Pattern 1: Separate files per environment
config/
default.json # Shared settings
development.json # Dev overrides
production.json # Prod overrides
test.json # Test overrides
Merge them at runtime:
import defaultConfig from './config/default.json';
import envConfig from `./config/${process.env.NODE_ENV}.json`;
const config = { ...defaultConfig, ...envConfig };
Pattern 2: Environment variables with JSON schema
import { z } from 'zod';
const ConfigSchema = z.object({
port: z.coerce.number().default(3000),
databaseUrl: z.string().url(),
redisUrl: z.string().url().optional(),
logLevel: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
corsOrigins: z.string().transform(s => s.split(',')),
});
const config = ConfigSchema.parse(process.env);
Never commit secrets to JSON config files. Database passwords, API keys, and tokens must come from environment variables or a secrets manager. Use our AES Encryption Tool to test encryption of sensitive configuration values.
Schema-Backed Configuration
Many JSON config files have official JSON Schemas that enable IDE autocompletion and error detection:
package.json uses SchemaStore:
{
"$schema": "https://json.schemastore.org/package.json",
"name": "my-app",
"version": "1.0.0"
}
Adding the $schema property tells VS Code to provide autocompletion for all valid fields, show documentation tooltips, and flag invalid values with red underlines.
Common schema store URLs:
- package.json:
https://json.schemastore.org/package.json - tsconfig.json:
https://json.schemastore.org/tsconfig - eslintrc:
https://json.schemastore.org/eslintrc - prettierrc:
https://json.schemastore.org/prettierrc
For more on JSON Schema syntax and creating your own schemas, see our JSON Schema guide.
Configuration Anti-Patterns
Anti-pattern 1: Massive monolithic config files
A single 500-line JSON config file is unreadable. Split by concern: database config, auth config, feature flags, logging config. Merge them at runtime.
Anti-pattern 2: Hardcoded secrets
{
"database": {
"password": "super-secret-password"
}
}
This gets committed to Git, shared in CI logs, and exposed in backups. Use environment variables.
Anti-pattern 3: Duplicate values
{
"api": {
"baseUrl": "https://api.example.com",
"authUrl": "https://api.example.com/auth",
"usersUrl": "https://api.example.com/users"
}
}
If the base URL changes, you must update three places. Use a single base URL and construct endpoint URLs in code.
Anti-pattern 4: No validation
Reading a config file without validating it means typos ("databse" instead of "database") silently become undefined values. Always validate config against a schema at startup.
Validate your JSON config files with our JSON Formatter to catch syntax errors before they cause runtime failures.
When to Use JSON vs YAML vs TOML
| Use Case | Recommended | Why |
|---|---|---|
| npm/Node.js config | JSON | Ecosystem standard |
| Docker/K8s | YAML | Industry convention |
| Rust/Go CLI tools | TOML | Designed for config |
| IDE settings | JSONC | VS Code standard |
| App config with comments | JSON5 or YAML | Both support comments |
| CI/CD pipelines | YAML | GitHub Actions, GitLab CI standard |
| API specs | JSON or YAML | OpenAPI supports both |
For the detailed comparison between formats, read: JSON vs XML vs YAML: When to Use What.
For the complete JSON developer reference, see our pillar guide: The Complete JSON Guide for Web Developers.
The Debuggers helps teams set up robust configuration management for production applications.
Frequently Asked Questions
Can I add comments to package.json?
Officially, no. npm's JSON parser is strict and will fail on comments. However, you can use a "//" key convention: "//": "This is a comment". npm ignores unknown root keys. Some developers also use a separate package.json5 file and transpile it to package.json. Neither approach is ideal, but the "//" convention is the most widely used workaround.
Should I use .env files or JSON for configuration?
Use .env files for secrets and environment-specific values (database URLs, API keys, feature flags). Use JSON for static, version-controlled configuration (compiler options, linting rules, project metadata). The two approaches complement each other: JSON for structure, .env for secrets.
How do I validate a JSON config file before deploying?
Add a validation step to your CI/CD pipeline that loads the config file and validates it against a Zod schema or JSON Schema. If validation fails, the deployment is blocked. This catches typos, missing required fields, and invalid value types before they reach production.
Is TOML better than JSON for configuration?
TOML was designed specifically for configuration files and has features JSON lacks: comments, date/time types, multi-line strings, and clear table syntax. For new projects outside the JavaScript ecosystem (Rust, Go, Python), TOML is often the better choice. For JavaScript projects, JSON is the established convention and has deeper tooling support.
Validate your JSON config files
Use our free JSON Formatter to catch syntax errors in configuration files. Convert config data to typed code with our JSON to TypeScript converter.
Need help with configuration management? The Debuggers provides software architecture consulting for modern web applications.
Found this helpful?
Join thousands of developers using our tools to write better code, faster.