Dependencies and shared types
Choose what happens when a declaration refers to a type from another package.
Set group dispositions
groups is keyed by npm package name. Use typescript/lib for TypeScript's standard libraries.
ship
Emit the dependency’s declarations under groups/. Compile them before the binding that uses them.
reference
Use the group’s F# module name. Supply a separately generated binding with matching names.
map
Map named TypeScript types to existing F# types. Names missing from the map widen.
widen
Render the reference as obj and report a finding. This is the default for unlisted groups.
For typescript/lib, widen writes Promise and PromiseLike as JS.Promise, so they work with Fable.Promise and Async.AwaitPromise, and Error as exn. Every other named standard-library interface, class or alias is written as its Xantham.Fable.Core.TS name (Fable.Core.TS.Es.Map, Fable.Core.TS.Es.Uint8Array<'TArrayBuffer>, Fable.Core.TS.Dom.*). Anonymous library shapes and globalThis still widen to obj with a finding. Compile the binding against Xantham.Fable.Core.TS.
{
"namespace": "MyBindings",
"groups": {
"shared-models": "ship",
"another-package": "reference"
}
}
Configure the same namespace for related generation runs.
Ship mutually dependent groups
Set recursiveGroups when the entry package and shipped dependencies refer back to one another. Their modules share a namespace rec source at groups/<namespace>.fs, which compiles the complete cycle together.
{
"module": "MyBindings.Client",
"namespace": "MyBindings",
"recursiveGroups": true,
"groups": { "shared-models": "ship" }
}
The namespace must be explicit, and each emitted package module must be its immediate child. Compiler-library output keeps its configured layout. The default remains separate files. Declaration identities and qualified type names stay the same.
Map an existing type
A string destination takes no type arguments. Use the object form to state the destination's generic arity:
{
"groups": {
"typescript/lib": {
"map": {
"RegExp": "System.Text.RegularExpressions.Regex",
"WeakRef": { "name": "System.WeakReference", "arity": 1 }
}
}
}
}
Choose mappings whose Fable behavior matches the JavaScript type. A mismatched arity widens and reports finding TR053.
Share declaration identities
When several bindings must reuse the same types, emit a declaration catalog from the producer:
{
"module": "Example",
"declarationCatalog": true
}
The run writes declarations.json beside its F# output. Reference it from a later run:
{
"module": "Example.Adapter",
"entry": "adapter.d.ts",
"runtime": "example/adapter",
"declarationReferences": ["/bindings/root/declarations.json"]
}
Catalog paths are absolute or relative to the input package directory. Replace the example path with the producer's output path.
For Brotli output, configure the producer with "declarationCatalog": { "enabled": true, "compression": "brotli" } and reference declarations.json.br:
{
"declarationReferences": ["/bindings/root/declarations.json.br"]
}
A .br filename suffix selects Brotli, case-insensitively. Other filenames are read as JSON, including custom filenames. References may mix formats in their existing order. Invalid, incomplete or trailing compressed data fails generation before consumer files are written.
Both formats have an inclusive limit of 128 MiB (134,217,728 bytes) of decoded JSON. This measures the uncompressed catalogue's UTF-8 bytes, regardless of its compressed size. The reader streams these bytes into the JSON decoder; the JSON document and authenticated catalogue model still occupy memory. Switching producer formats preserves any old alternate-format file in the output directory; reference the file selected for the current run.
The consumer reuses the producer's F# identities while retaining its own imports. Compile producers first, following the catalog's ordered owners list.
Reuse the Core.TS package catalogue
Xantham.Fable.Core.TS ships its standard-library catalogue at xantham/declarations.json.br inside the NuGet package. Restore the package in your consumer project and reference the file from that restored version:
{
"module": "MyBindings",
"lib": ["esnext", "dom"],
"anyReturnAsUnit": "^on",
"declarationReferences": ["/path/to/packages/xantham.fable.core.ts/<version>/xantham/declarations.json.br"]
}
Replace the example path and version with your actual package-cache location. Compile the generated consumer against that same Xantham.Fable.Core.TS version. A PackageReference with GeneratePathProperty="true" exposes the package directory as $(PkgXantham_Fable_Core_TS) to MSBuild integrations. Generation still receives an explicit file path.
The accompanying xantham/catalogue.json descriptor identifies the package version and maps the authenticated owner FableCoreTsInput to Xantham.Fable.Core.TS. It records compatibility contracts and SHA-256 hashes of the compressed catalogue, assembly, binding sources and generation configuration. Pass the .br file to Xantham; the descriptor is metadata for integrations. Source and configuration hashes normalize CRLF to LF across checkouts.
This catalogue reuses qualified types such as Fable.Core.TS.Dom.HTMLInputElement, Fable.Core.TS.Dom.FileList and Fable.Core.TS.Es.Map<'K, 'V>. Install the exact TypeScript release/source revision recorded in the catalogue and keep your library profile compatible. Core.TS writes on* event-handler returns as unit, so the consumer sets anyReturnAsUnit to ^on as in the example; any other value fails the inference-profile check.
Choose a shared owner before generating related libraries
Identical anonymous literal unions share a structural catalog identity. Two independent producers can emit that identity as different F# enum types; a consumer referencing both catalogs rejects the conflicting owners. Catalog order does not choose a winner.
Choose the owner explicitly in the generation graph. Generate producer A first, then put A's declarations.json in producer B's declarationReferences and regenerate B. Consumers can then reference both catalogs. B's generated API uses A's enum, and B's catalog records its dependency on A. Compile and package that dependency as part of B's public API.
Make this ownership choice consistently across a binding family and regenerate its affected producers together. Selecting a preferred catalog only in the final consumer cannot change the distinct enum types already compiled into the producers. Named literal aliases retain their own declaration identities even when their values match.
Keep catalogs compatible
New catalogs use schema 2. Related bindings can reuse catalogs across Windows and Linux when the installed TypeScript packages have the exact same release and source revision, the AST protocol matches, and Xantham's identity, API, inference, customization, and policy contracts match. Rebuilding Xantham with the same contracts remains compatible. TypeScript standard-library catalogue sources use the logical package name typescript on every OS and CPU. Their identities retain the exact release, source revision, library path and source bytes. Platform distribution names and OS/CPU packaging fields do not change library identities. Other packages retain their manifest and source authentication. Computed-member identities also exclude the compiler's session-local symbol numbers. Regenerate Core.TS and dependent catalogues together when upgrading to identity contract 6, API contract 5 and inference contract 3. These retain constructor alias bounds, stabilize sanitized/default-export identities, and preserve source contracts at the frontier width cutoff. Older catalogues are rejected before reuse. Custom compiler executables without verifiable package metadata require identical compiler binary hashes. Matching TypeScript major/minor versions alone is insufficient.
Keep lib, types, group dispositions, and inference options compatible, including resolveNoInfer and anyReturnAsUnit: the inference profile records them, and a consumer whose profile differs from the producer's is rejected. Use separate catalogs for environments that need different globals.
Upgrade consumers before regenerating producers: older Xantham builds reject schema 2. Schema 1 catalogs retain exact compiler and generator binary checks. Regenerate their producers with the upgraded generator to obtain portable catalogs. Continue supplying explicit declarationReferences paths as shown above.
Both schemas check declaration identity, package/source hashes, owner dependencies, generic arity and constraints, F# APIs, and customization variants. Schema 2 retains compiler and generator fingerprints as producer provenance. JSON and Brotli carry the same schema and pass the same authentication checks. If a catalog is rejected, regenerate the related bindings together and inspect the diagnostic. Some entry-dependent or generic shapes still cannot share an emitted identity; those combinations require separate bindings or a mapping change.
A reference needs an implementation. Both reference groups and declaration catalogs require the producer’s generated F# or compiled assembly in the consumer project.