Kit

Resolvers

createResolver builds a Resolver that controls file naming and path resolution for a plugin, with auto-injected defaults under resolver.default and Resolver.merge for layering overrides.

createResolver

createResolver builds a plugin resolver for names, paths, and imports.

Provide pluginName. Methods use this to reach the merged resolver, so namespaced methods call this.name(...) for active casing.

resolver.ts
import { createResolver } from 'kubb/kit'
import type { PluginFactoryOptions, Resolver } from 'kubb/kit'

// Extend the base Resolver with plugin-specific naming namespaces.
type MyResolver = Resolver & {
  schema: {
    name(node: { name: string }): string
  }
}

type MyPlugin = PluginFactoryOptions<'plugin-example', object, object, MyResolver>

export const resolver = createResolver<MyPlugin>({
  pluginName: 'plugin-example',
  name(name) {
    return `${name.charAt(0).toUpperCase()}${name.slice(1)}`
  },
  schema: {
    name(node) {
      return this.name(node.name)
    },
  },
})

Auto-injected resolver defaults

MethodDefault behavior
nameActive top-level identifier casing. Delegates to default.name when omitted
fileTop-level FileNode builder, delegates to default.file
default.nameThe core camelCase generated-identifier casing
default.optionsApplies exclude, include, and override filters
default.pathResolves to output.path, with optional tag/path-based subdirectories
default.fileConstructs a full FileNode using the resolver's file.baseName casing (default toFilePath)
default.bannerReturns output.banner or the standard "Generated by Kubb" header
default.footerReturns output.footer when set

resolver.imports

resolver.imports builds one import entry per $ref in a schema tree, so a generator emits the imports for every schema the current node references. Each ref resolves through ast.resolveRefName, which prefers the node's targetName and falls back to the pointer's last segment.

The resulting names and paths go through the resolver's own name and file conventions. extname defaults to .ts.

imports.ts
const imports = resolver.imports({ node, root, output })
// → [{ kind: 'Import', name: ['pet'], path: '/src/types/pet.ts' }]

Pass name to override how a referenced schema name becomes the imported identifier, for example to point enum refs at a suffixed type name:

importsName.ts
const imports = resolver.imports({
  node,
  root,
  output,
  name: (schemaName) => `${resolver.name(schemaName)}Type`,
})

Resolver.merge

Resolver.merge(base, patch) returns a new resolver with patch's fields layered over base's and every helper re-bound. A top-level name replaces, while file and each namespace merge per member, so overriding query.name keeps the base query.keyName.

Use it to compose resolvers or apply partial overrides.

Type a patch with ResolverPatch<T> to keep this and namespace shapes checked against the target resolver.
merge.ts
const patched = Resolver.merge(resolver, {
  name(name) {
    return `Custom${this.default.name(name)}`
  },
})

Resolver overrides

name changes identifier casing. file.baseName and file.path control files. Plugin namespaces control specific symbols.

Type definition
type ResolverPatch = {
  name?: (name: string) => string
  file?: {
    baseName?: (params: { name: string; extname: string }) => string
    path?: (params: { baseName: string; output: Output }) => string
  }
  // plugin-specific namespaces, such as query.keyName or schema.typeName
}

Methods run with a this context bound to the full, merged resolver, so write them as regular functions rather than arrow functions. this.default.name(name) always applies Kubb's core camelCase default. The plugin preset's name method remains separate.

From a namespaced method, this.name(name) calls the active top-level name method and follows any user override. Calling this.name from the top-level name method itself recurses, so call an exported preset resolver when you want to wrap its casing.

See also