Open-source project
b0o/SchemaStore.nvim avatar
b0o/SchemaStore.nvim

SchemaStore.nvim: feeding jsonls and yamlls a local schema catalogue

🛍 JSON schemas for Neovim

1,046 stars22 forksLuaApache-2.0

At a glance

What is it?
SchemaStore.nvim hands the SchemaStore catalogue to Neovim's jsonls and yamlls so configuration files get validated against published schemas. It trades runtime schema downloads for a generated Lua table, and it is the wrong tool for projects that need only a couple of schemas.
Who is it for?
SchemaStore.nvim is worth adopting if you already run jsonls or yamlls in Neovim and want validation against the public catalogue without letting the language server fetch schemas while you work. Skip it if you need only two or three schemas, since keeping that short list in your own config is simpler, and skip it if you cannot turn off the built-in store in yamlls.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly Lua, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

jsonls has no catalogue of its own

Editors that speak LSP do not ship a registry of file formats. jsonls can validate a JSON file only if something tells it which schema that file follows, and yamlls needs the same kind of list, though it also ships a built-in schema store. Without that information both servers fall back to plain syntax checking, so a misspelled key in a workflow file or a settings file is not flagged until some other tool notices.

That is the problem SchemaStore.nvim addresses. It hands the SchemaStore catalogue to Neovim's language servers so that a file named package.json, tsconfig.json or any other name the catalogue knows is matched with its published schema. The audience is Neovim users who already run jsonls or yamlls through lspconfig and want validation and completion for structured configuration files without maintaining a list of schema URLs by hand.

The plugin is a thin Lua layer, not a language server of its own. It does not index your project, it does not start a process, and it does not replace lspconfig. It prepares a schema list and hands it to the server you already configured.

The catalogue is generated into a Lua file, not fetched while you edit

Everything the plugin serves is compiled into a Lua file in the repository. The Makefile at the root exposes two targets, and the generation one shows the pipeline plainly:

make
.PHONY: generate
generate:
	./scripts/generate ./lua/schemastore/catalog.lua

The script in scripts/ writes the catalogue into lua/schemastore/catalog.lua, and that file is what require('schemastore') ends up serving. The top level of the repository also contains a test/ directory and a stylua.toml, and the second Makefile target runs the test script:

make
.PHONY: test
test:
	./scripts/test

Two GitHub Actions workflows are referenced by the README badges, one named test.yaml and one named generate.yaml, so the catalogue and the test suite are both driven by continuous integration on the main branch.

The practical consequence is that schema matching is offline and synchronous. There is no request to schemastore.org when you open a file, and no schema download that can fail on a train. The cost is that the catalogue only knows what the last generation step captured, so a schema published upstream after that step will not appear until the plugin is updated.

Installing the plugin with Lazy.nvim or Packer

Two plugin managers are shown in the README. Under Lazy.nvim the plugin is added as a dependency string:

lua
  "b0o/schemastore.nvim",

Under Packer the same entry is written in its call form:

lua
use "b0o/schemastore.nvim"

Neither snippet carries a version pin, a lazy-loading option, or a dependency list, because the plugin has none of those in its own configuration. The README badges show the plugin in use by LazyVim and LunarVim, so if your distribution already ships it, adding it by hand again is usually wasted effort.

The README gives no manual installation path, so there is nothing to copy for a setup that avoids plugin managers. After installation, confirm that the module resolves by calling require('schemastore') from Lua. If that call errors, the plugin directory is not on the runtime path, which is a packaging problem rather than a catalogue problem.

Wiring the catalogue into jsonls and switching validation on

The jsonls setup is the shortest useful configuration. jsonls reads its schema list from settings.json.schemas, and the plugin produces that list:

lua
require('lspconfig').jsonls.setup {
  settings = {
    json = {
      schemas = require('schemastore').json.schemas(),
      validate = { enable = true },
    },
  },
}

Two details matter here. First, schemas() is called with no arguments, which means the whole catalogue is passed to the server. Second, validation is switched on explicitly, and the README points at issue #8 for the reasoning behind that recommendation, which tells you the choice is deliberate rather than incidental.

Validation is what turns a schema list into useful feedback, and it is also what can produce a wall of diagnostics if the catalogue is larger than the files you actually edit. The plugin does not filter the catalogue by default, so every schema the server is able to match is offered to it. Matching still happens by file name, so the cost is modest in most setups, but this is not a mechanism for keeping the server's work small.

yamlls only accepts the catalogue once its own store is disabled

yamlls is supported as well, but the settings differ in a way that surprises people. The YAML language server has its own built-in schema store, and the plugin needs it turned off:

lua
require('lspconfig').yamlls.setup {
  settings = {
    yaml = {
      schemaStore = {
        -- You must disable built-in schemaStore support if you want to use
        -- this plugin and its advanced options like `ignore`.
        enable = false,
        -- Avoid TypeError: Cannot read properties of undefined (reading 'length')
        url = "",
      },
      schemas = require('schemastore').yaml.schemas(),
    },
  },
}

The inline comments in that block are the documentation's own warnings. The built-in store and the plugin are two sources of the same thing, and the README says the built-in one has to be disabled before options such as ignore will work. The empty url is there to avoid a TypeError about reading 'length' of undefined.

This is also where the alternative approach is easiest to see. Leave the built-in store enabled and the language server handles catalogue retrieval itself, which is a smaller configuration but leaves you without control over which schemas are loaded. Disable it and the plugin's local catalogue becomes authoritative, which is the point of the plugin in the first place.

select and ignore are mutually exclusive, and extra is not folded in

A large catalogue is not always wanted, so the plugin exposes selection options. A subset is requested by name:

lua
require('lspconfig').jsonls.setup {
  settings = {
    json = {
      schemas = require('schemastore').json.schemas {
        select = {
          '.eslintrc',
          'package.json',
        },
      },
      validate = { enable = true },
    },
  },
}

The reverse operation excludes entries and uses the same list shape under the key ignore. The README states plainly that select and ignore are mutually exclusive and that using them together throws an error. That is a hard failure at configuration time rather than a warning, so a setup file merged from two sources will stop the server from starting instead of quietly choosing one behaviour.

A second trap sits next to it. When select is used together with extra, the extra schemas are not included automatically, and each one has to be named explicitly in the select array. Skip that step and you get a configuration that appears to add a custom schema and does nothing.

replace and extra for private or local schemas

Private and local schemas are handled by two more options. replace substitutes a schema of your own for a catalogue entry, and the replacement can be a full table or just a URL string:

lua
require('lspconfig').jsonls.setup {
  settings = {
    json = {
      schemas = require('schemastore').json.schemas {
        replace = {
          ['package.json'] = {
            description = 'package.json overridden',
            fileMatch = { 'package.json' },
            name = 'package.json',
            url = 'https://example.com/package.json',
          },
          -- Or you can override just the url
          ['package.json'] = 'https://example.com/package.json',
        },
      },
      validate = { enable = true },
    },
  },
}

extra adds schemas that the catalogue does not contain at all, and a local file works there as well, addressed with the file:// scheme or a plain path:

lua
require("schemastore").json.schemas({
  extra = {
    {
      description = "Local JSON schema",
      fileMatch = "local.json",
      name = "local.json",
      url = "file:///path/to/your/schema.json", -- or '/path/to/your/schema.json'
    },
  },
})

Note that the second snippet calls the module directly rather than passing the result into a server setup, so it is a standalone call in a config file or a script. A local path keeps the schema on disk and inside your own repository, which is the cleanest way to pin a schema version for a project that ships its own configuration format.

Maintenance, releases and the Apache 2.0 licence

The repository is not archived, and the last push was on 2026-10-15, so the catalogue generator and the plugin code are being changed. The README says nothing about a release schedule, and the repository has no GitHub releases, so there is no published version to pin to in the usual way. A plugin manager pulls the main branch unless you pin a commit yourself, and a pin means you own the update step.

The licence is Apache 2.0, and the README credits 2021-2026 Maddison Cohodas and Contributors. The top level of the repository carries a LICENSE file and a NOTICE file. Apache 2.0 is permissive and includes an explicit patent grant together with a notice condition, so if you redistribute the plugin in a dotfiles repository or bundle it, keep the licence text and the NOTICE file alongside it. That is a reading of the licence terms rather than legal advice.

One limit deserves emphasis, because it applies to every configuration in this article. The plugin cannot know about schemas the upstream catalogue does not contain. A company-internal format, a bespoke CI file, or any config file whose name is absent from the catalogue needs extra or replace, and a schema you maintain yourself has to be kept current by you.

Editorial conclusion

SchemaStore.nvim is worth adopting if you already run jsonls or yamlls in Neovim and want validation against the public catalogue without letting the language server fetch schemas while you work. Skip it if you need only two or three schemas, since keeping that short list in your own config is simpler, and skip it if you cannot turn off the built-in store in yamlls. Before switching, diff the plugin's json and yaml setup blocks against your existing lspconfig file, and confirm the catalogue generation step has run for the schema files you actually depend on.

Frequently asked questions

How do I install SchemaStore.nvim in Neovim?

Under Lazy.nvim add the dependency string "b0o/schemastore.nvim", and under Packer use the line use "b0o/schemastore.nvim". The README shows no manual installation path, and the plugin needs no dependencies of its own.

Why does yamlls report a TypeError about reading 'length' of undefined when SchemaStore.nvim is configured?

The README's yamlls block sets the built-in store to enable = false and its url to an empty string, with a comment saying the empty url avoids that TypeError. The built-in schemaStore support has to be disabled before the plugin's own options work.

Can I use the select and ignore options together in SchemaStore.nvim?

No. The README states that select and ignore are mutually exclusive and that attempting to use them together throws an error. Extra schemas also have to be listed explicitly in the select array, since they are not included automatically.

How do I point SchemaStore.nvim at a JSON schema file on my own disk?

Add the schema through the extra option and give its url with the file:// scheme, such as file:///path/to/your/schema.json, or use a plain path instead. The same extra option also accepts remote schema URLs.

Official sources

  1. b0o/SchemaStore.nvim on GitHub
  2. Issues
  3. License: Apache-2.0
  4. Project website
  5. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/b0o-schemastore-nvim.svg)](https://hysenlabs.com/projects/b0o-schemastore-nvim)

Was this useful?