How JSON Schema Gives Your YAML Editor Autocomplete and Validation
Your editor catches typos in settings.json and GitHub Actions YAML because of JSON Schema. Here is how it works and how to add it to your own files.
Open settings.json in VS Code and misspell a key:
{
"editor.fontSize": 14,
"editor.tabSiz": 4
}
editor.tabSiz lights up red instantly. Do the same in a GitHub Actions workflow:
on:
push:
branchs:
- main
Your editor underlines branchs and suggests branches. Two completely different files, two completely different tools, and yet you get autocomplete, inline docs, and typo detection in both.
Here is the thing worth internalizing: your editor does not "know" what belongs in settings.json. GitHub Actions workflows do not magically come with field suggestions. Someone had to formally tell the editor what every valid field is, what type it holds, and what it means. That "someone" is JSON Schema, and once you understand the mechanism you can give the exact same experience to any YAML or JSON file you own.
The problem: editors are not psychic
A YAML file is just text. The YAML language server can parse the syntax (indentation, key/value pairs, lists), but syntax is not meaning. It has no idea whether tabSiz is a real key or a typo, whether fontSize expects a number or a string, or whether you forgot a required field. To do any of that, it needs a description of the document's structure.
You could write that description in prose: "The name field is required and must be a string. The age field is optional and must be a non-negative integer." That is documentation for humans. An editor cannot act on it. What you need is the same information in a machine-readable format. That format is JSON Schema.
What JSON Schema actually is
JSON Schema is a standard, vocabulary-based way to describe the shape of a JSON or YAML document. (YAML is a superset of JSON for this purpose, so a single schema validates both.) A schema is itself a JSON document. Here is a small one:
{
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Your full name"
},
"age": {
"type": "integer",
"minimum": 0,
"description": "Your age in years"
},
"email": {
"type": "string",
"format": "email"
}
},
"required": ["name"]
}
Read it top to bottom and it says: a valid document is an object; it must have a name (a string); it may have an age (a non-negative integer); it may have an email (a string in email format). The description fields are not decoration. Editors surface them as hover tooltips, so the schema doubles as inline documentation.
That is the whole idea. Once an editor has this schema associated with a file, it can:
- Autocomplete field names as you type, because it knows the full set of valid keys.
- Validate types and constraints, flagging a string where an integer belongs or a negative
age. - Catch typos, because an unknown key is, by definition, not in
properties. - Show inline docs from the
descriptionfields.
Why JSON Schema is everywhere
JSON and YAML are the lingua franca of configuration: CI/CD pipelines, application settings, infrastructure definitions, API payloads, data files. Every one of those benefits from validation, so nearly every serious tool ships a schema. A non-exhaustive tour:
- VS Code settings. Microsoft publishes a schema for
settings.json; that is why your editor autocompletes its own settings. - GitHub Actions. GitHub publishes a workflow schema, which is how
runs-onandstepsget suggested andbranchsgets flagged. - Kubernetes manifests, Docker Compose, Terraform, ESLint config.
package.jsonandtsconfig.json, two files almost every JavaScript developer touches, are both schema-backed.
The pattern scales because the schema is decoupled from the editor. The tool author writes one schema; every schema-aware editor everywhere gets validation for free.
How an editor discovers which schema to use
Writing a schema is only half the job. The editor still has to associate your specific file with the right schema. There are two mechanisms, and they cover different situations.
1. The inline $schema comment
The explicit approach is a magic comment on the first line, read by the YAML language server (bundled in the VS Code YAML extension):
# yaml-language-server: $schema=https://raw.githubusercontent.com/rendercv/rendercv/refs/tags/v2.4/schema.json
cv:
name: John Doe
This says, unambiguously: validate this file against the schema at that URL. The URL points at a version tag (v2.4), so you pin the schema to the exact tool version you are using and avoid validating against fields that do not exist yet in your release. The downside is that you have to add the comment to every file by hand.
2. SchemaStore auto-activation by filename
The automatic approach relies on SchemaStore, a central, community-maintained registry of schemas that most IDEs consult out of the box. Each entry maps a filename glob to a schema URL. SchemaStore is exactly how tsconfig.json and *.github/workflows/*.yaml get picked up without you doing anything.
RenderCV is registered there too, configured to activate for any file ending in _CV.yaml. So if you name your file John_Doe_CV.yaml and your editor uses SchemaStore (VS Code with the YAML extension does), you get full autocomplete with zero configuration and no comment. Getting your own project listed is a pull request against the SchemaStore catalog.
In short: the $schema comment is precise and version-pinned; SchemaStore is frictionless. They are complementary, and you can offer both.
How RenderCV generates its schema (without writing it by hand)
A schema like RenderCV's describes a deeply nested CV structure with dozens of sections and fields. Hand-writing that JSON would be tedious and, worse, it would immediately drift out of sync with the actual code that parses the YAML. So RenderCV does not write it by hand. It generates schema.json directly from the same models that validate the data at runtime.
RenderCV's entire data structure is defined as Pydantic models. Pydantic is already doing the runtime validation (types, constraints, dates), so it has a complete description of the valid structure. It exposes that description as a schema through a single method, model_json_schema():
from pydantic import BaseModel, Field
class CV(BaseModel):
name: str = Field(description="Your full name")
email: str | None = Field(default=None, description="Your email address")
schema = CV.model_json_schema()
# {
# "type": "object",
# "title": "CV",
# "properties": {
# "name": {"type": "string", "description": "Your full name", "title": "Name"},
# "email": {
# "anyOf": [{"type": "string"}, {"type": "null"}],
# "default": null,
# "description": "Your email address"
# }
# },
# "required": ["name"]
# }
Notice that the description you wrote on each Field flows straight into the schema, which means it becomes a hover tooltip in the editor. The types, the optionality, the constraints, all derived from the model. There is one source of truth: the Python models. Validation logic and editor schema can never disagree, because they are the same definition.
To regenerate the file after a model change, RenderCV runs a small script (scripts/update_schema.py) wired to a just update-schema command. That is the entire maintenance burden: change a model, run one command, commit the regenerated schema.json. This is the same data-first philosophy behind treating your resume as code, and it is a core part of how RenderCV works under the hood.
Applying this to your own project
The recipe generalizes cleanly, whether your config is in Python, TypeScript, Go, or anything else:
- Define your config's structure as code. If you already validate config at runtime, you almost certainly have this. Pydantic in Python, Zod in TypeScript, and many others can emit JSON Schema. See Pydantic's JSON schema docs for the details.
- Generate the schema, do not write it. Wire schema generation into a script so it regenerates whenever your models change. A hand-maintained schema rots.
- Publish
schema.jsonsomewhere fetchable, such as a raw file URL on a tagged release, so you can pin versions. - Tell editors how to find it. Document the
# yaml-language-server: $schema=...comment for explicit opt-in, and submit a SchemaStore entry mapping a filename pattern to your schema for zero-config activation.
That is the full loop. The payoff is real: anyone editing your config gets autocomplete, type checking, and inline documentation, and you maintain none of it separately from the code you were already writing.
RenderCV ships all of this so that writing a CV in YAML feels like writing typed code, which is exactly the point of an open-source resume builder built for engineers. Name your file Your_Name_CV.yaml, open it in VS Code, and start typing. Try it at rendercv.com.