A tiny JS/TS library and CLI for building AI model catalogs and listing/filtering models.
Model Catalog solves your data source problem for your js/ts AI apps. Fetch models.dev once. Add custom generators. Store the snapshot anywhere. Render the model list your app wants.
Note: the UI is not part of the lib. But you can reference it on examples/* if you want to build it for your own app.
demo.mp4
- Powered by
models.dev. - Supports custom providers/generators for models that
models.devdoes not cover. - Lists and filters models from catalog data with one core API:
catalog.listModels(). - Storage-agnostic: database, file, localStorage, API route, static JSON, generated TypeScript.
- Frameworkless core with copyable examples instead of framework adapters.
npm install model-catalogimport { createCatalog, refreshSnapshot } from "model-catalog";
const snapshot = await refreshSnapshot();
const catalog = createCatalog(snapshot);
const models = catalog.listModels({
includeProviders: ["anthropic"],
query: "claude tools",
groupBy: "providerId",
});
console.log(models.groups[0]?.models[0]?.providerLogoUrl);
refreshSnapshotis never a fast operation so make sure to persistsnapshotsomewhere (i.e. database, redis, a json file on a non-ephemeral deployment, indexedDB, localStorage) before using it withcreateCatalog.
The package does not model provider credentials or provider configuration. If your app only wants to show providers that the current user configured, derive those provider IDs in your app and pass them to includeProviders.
const enabledProviders = providerConfigs
.filter((provider) => provider.isEnabled && provider.isConfigured)
.map((provider) => provider.providerId);
const catalog = createCatalog(snapshot);
const models = catalog.listModels({
includeProviders: enabledProviders,
require: ["tool_call"],
minContext: 128_000,
excludeDeprecated: true,
});providerConfigs can be local UI state, but in production it is often a backend query. Store encrypted provider credentials server-side and return safe metadata to the client:
const providerConfigs = await api.providerConfigs.list();
// [{ providerId: "openai", isConfigured: true, isEnabled: true }, ...]
const enabledProviders = providerConfigs
.filter((provider) => provider.isEnabled && provider.isConfigured)
.map((provider) => provider.providerId);For example, the React example context accepts backend-owned configs directly:
function App() {
const { data: providerConfigs } = useQuery(api.providerConfigs.list);
return (
<CatalogProvider providerConfigs={providerConfigs ?? []}>
<ModelPicker />
</CatalogProvider>
);
}The built-in query option is intentionally a small substring filter, not a full-text search engine. The query is split on whitespace, lowercased, and every token must appear in the item search text.
catalog.listProviders({ query: "open" });
catalog.listModels({ query: "claude tools" });Provider queries search provider id and name. Model queries search model/provider identity fields plus common model metadata such as name, description, family, capabilities, and modalities.
For exact identity lookup, prefer the explicit APIs:
catalog.getProvider("openai");
catalog.getModel("openai", "gpt-4o");
catalog.listProviders({ includeProviders: ["openai"] });
catalog.listModels({ includeModels: ["openai/gpt-4o"] });For frontend-ranked, fuzzy, or indexed search, keep model-catalog as the data source and compose it with your app's search library. For instance, use something like examples/solid/src/lib/use-flex-search.ts to accomplish richer search.
Define the search index in an app-wide context so the index is created once and reused by your UI:
const [providerSearch, setProviderSearch] = createSignal("");
const providers = createMemo(() => catalog().listProviders());
const results = createFlexSearch(providers, providerSearch, {
indexerFn: (provider) => [provider.id, provider.name].join(" "),
});Generators run during refreshSnapshot() and receive a tiny context with ctx.addProvider(), ctx.addModel(), ctx.updateProvider(), and ctx.updateModel().
There are three useful patterns:
- Static JSON / static objects for small catalog extensions.
- API-backed generators that fetch from a remote endpoint and add plain provider/model records.
- Runtime generators that inspect the current machine, such as a local CLI. Runtime generators are Node/machine-only and should not be used in browser bundles.
If models.dev already has the provider and you only want to add or patch models, call ctx.addProvider() with the same provider id and only the models you care about. Existing provider data is preserved; model ids you define are added, and matching model ids are overwritten by your generator.
This is the same shape as the built-in catalog_extensions.json entry for xAI Composer 2.5.
import { defineGenerator, refreshSnapshot } from "model-catalog";
const xaiComposer = defineGenerator({
id: "xai-composer-extension",
kind: "static",
generate(ctx) {
ctx.addProvider({
id: "xai",
name: "xAI",
models: [
{
id: "grok-composer-2.5-fast",
name: "Composer 2.5",
family: "grok-build",
attachment: false,
reasoning: false,
reasoning_options: [],
tool_call: true,
structured_output: true,
temperature: true,
open_weights: false,
modalities: { input: ["text", "pdf"], output: ["text"] },
limit: { context: 256_000, output: 256_000 },
cost: { input: 0.5, output: 2.5, cache_read: 0.2 },
},
],
});
},
});
const snapshot = await refreshSnapshot({ generators: [xaiComposer] });For a provider that is not in models.dev, add the provider record and its models directly.
import { defineGenerator, refreshSnapshot } from "model-catalog";
const companyGateway = defineGenerator({
id: "company-gateway",
kind: "static",
generate(ctx) {
ctx.addProvider({
id: "company",
name: "Company Gateway",
logoUrl: "https://example.com/company.svg",
models: [
{
id: "fast",
name: "Fast",
attachment: false,
reasoning: true,
reasoning_options: [
{ type: "effort", values: ["low", "medium", "high"] },
],
tool_call: true,
structured_output: true,
temperature: true,
open_weights: false,
modalities: { input: ["text"], output: ["text"] },
limit: { context: 128_000 },
},
],
});
},
});
const snapshot = await refreshSnapshot({ generators: [companyGateway] });For providers with a remote model endpoint, put the fetch/parsing logic in your generator. model-catalog does not need to know the provider's API URL shape; your generator just converts the response into provider/model records.
import { defineGenerator } from "model-catalog";
const remoteModels = defineGenerator({
id: "remote-company-models",
kind: "api",
async generate(ctx) {
const response = await ctx.fetch("https://api.company.test/v1/models");
const payload = await response.json();
ctx.addProvider({
id: "company",
name: "Company",
api: "https://api.company.test/v1",
npm: "@ai-sdk/openai-compatible",
models: payload.data.map((model: { id: string; name?: string }) => ({
id: model.id,
name: model.name ?? model.id,
tool_call: true,
temperature: true,
modalities: { input: ["text"], output: ["text"] },
})),
});
},
});refreshSnapshot() already has the baseline catalog by default:
models.devcatalog_extensions.json, package-maintained patches for things models.dev does not cover yet
There are also a few opt-in built-in generators:
import {
commandCodeGenerator,
ollamaCliGenerator,
refreshSnapshot,
} from "model-catalog";
const snapshot = await refreshSnapshot({
generators: [
commandCodeGenerator(), // remote API-backed generator; opt-in latency
ollamaCliGenerator(), // local CLI generator; opt-in machine state
],
});- commandCodeGenerator - for commandcode.ai since they're pretty much ignoring this models.dev request and opencode request.
- ollamaCliGenerator() - runs
ollama lsto get a personalized model list based on the user's config. It only works in Node.js on machines where the Ollama CLI is installed. Never run it in the browser; browser code cannot execute local CLIs.
model-catalog refresh --out ./model-catalog.json
model-catalog generate --out ./src/model-catalog.generated.ts
model-catalog inspect ./model-catalog.json
model-catalog list ./model-catalog.json --provider anthropic --query claude --toolsThe core package does not export UI components. A self-contained, compile-ready Solid example lives at:
examples/solid/chat-model-selector.tsxIt shows how to achieve a great model-selector UX by consuming catalog.listModels()
- A chat/completions SDK.
- A replacement for Vercel AI SDK or provider SDKs.
- Provider credential flows or secret storage.
- A database/storage adapter layer.
- A required model-selector UI abstraction.
- A replacement for models.dev. It is powered by it.
MIT