Onykia Docs

Packages

Typst documents pull in libraries with #import. To resolve them, you need to fetch the package tarballs and feed it to the engine.

  • package ask handler - given a (namespace, name, version), return the package's .tar.gz bytes. Called lazily.
  • setRemotePackages - feed the engine a package index so autocomplete and version resolution know what exists, before any import is written.

Resolving @preview from the public registry

The @preview namespace maps to the official Typst package registry. Wire the ask handler to fetch tarballs from it:

const core = new Core({
  wasm,
  package: async (namespace, name, version) => {
    const url = `https://packages.typst.org/${namespace}/${name}-${version}.tar.gz`;
    const res = await fetch(url);
    if (!res.ok) throw new Error(`package fetch failed: ${url}`);
    return new Uint8Array(await res.arrayBuffer());
  },
});

This allows for imports to resolve. To also populate the IDE catalog (so completions list available packages and bare versions resolve), forward the preview index:

const res = await fetch('https://packages.typst.org/preview/index.json');
const bytes = new Uint8Array(await res.arrayBuffer());
await core.setRemotePackages(bytes, []);

Index loading is best-effort: if it fails, imports still resolve through the per-tarball ask path - only the autocomplete catalog goes dark. The index decoder currently accepts JSON only.

Private namespaces

setRemotePackages(index, privateNamespaces) takes a second argument: a list of your own namespaces, each with its own index. Use this to expose internal packages (e.g. company templates) under a custom namespace such as @acme:

await core.setRemotePackages(previewIndexBytes, [
  { namespace: 'acme', data: acmeIndexBytes },
]);

Imports like #import "@acme/letterhead:1.0.0" then resolve through the same package ask handler - branch on namespace there to serve private tarballs from your own backend.

Cross-origin note

Fetching tarballs and the index from a third-party host requires those requests to pass cross-origin isolation. If the registry sends CORS but not a Cross-Origin-Resource-Policy header, serve your app with Cross-Origin-Embedder-Policy: credentialless (see Quickstart).

On this page