Documentation

The Complete Typstify Handbook

Find answers to common questions and browse our versioned documentation.

Introduction to TPIX

Tutorial / News

Introduction to TPIX

What is TPIX

TPIX (Typst package index & exchange) is a private Typst package registry, trying to address the package management problem in a way that is different from the official Typst compiler. It uses a workflow similar to npm for javascript/typescript, go mod for Golang, maven or gradle for java, but without a project manifest.

TPIX provides a CLI tool to search, download, bundle and publish packages from and to the TPIX server, which can easily be integrated into your own workflows. If you like you can also use it to manage the dependencies of your Typst project, using its pull command. Also it provides the remove command to help you manage your local cached packages.

So why do we need another package manager?

How Typst manages packages

The typst compiler added built-in package management in its early release. ^1 The idea is quite simple. Packages are submitted to the shared GitHub repository, then processed and distributed via their server. The users can then browse the packages from the Typst Universe. As a document writer, you only have to use the import directive to import the specific package into the Typst document(code). The syntax is like #import "@{namespace}/{name}:{version}" for a normal import. Nothing else should be done, the Typst compiler takes care of querying and downloading the imported packages, if not found in the local cache, or in the local package repository directory.

The local packages

As we have said, the Typst compiler looks for packages in both the local cache and the local package directory before deciding to download from the official package repository. So how are the local packages managed? For the package cache, Typst provides nothing but put new versions into it. And for local packages, it’s the user’s full responsibility to put their packages in it, update or remove the package, without proper tooling support, all these tasks need to be done by hand.

You may have noticed that the local package path is Typst’s current solution for packages not published to the public package repository, whether private or public. This leaves a unresolved gap between a user friendly package management system. If you want to host and share your private packages, the best choice for now is to use the Typst webapp, but that is closely tied to the web editor, and you can’t easily sync them and use it in an offline environment.

Implicit package management

As a Typst user, you never need to download public packages by hand from the public package repository, which is a pretty good experience so far. Even if package A depends on package B, and package B depends on package C, when you import package A, the compiler downloads all 3 package implicitly for you.

This has brought pretty good experiences to users, especially users from LaTex, and users with little CS background. You just import it and magic happens! But for package and template developers, things may not be the same.

If you are developing a complex package with lots of imports, or writing a document containing many external imports, you may find it annoying when trying to figure out what packages are used by your packages, or projects.

As Typst is becoming widely adopted, many teams use Typst to build their documents in their infrastructures during their CI/CD workflows. For teams require careful audit of the code they use, implicitly pulling packages from the network may be unexpected, especially when the envrionment has no public network access.

No project manifest

Typst packages have a manifest (typst.toml), but Typst projects — the documents you write — do not. There is no file that declares “this project depends on package X version Y”. Instead, the version is embedded in every import statement: #import "@preview/cetz:0.3.0". This works fine for small documents, but it has some consequences as projects grow.

When you want to upgrade a package to a newer version, you have to find and update every import across all your files. As a result, it is technically possible to end up with two different versions of the same package in one project, which is usually not what you want.

The community has a well-accepted workaround for this: ^2 create a deps.typ file that centralizes all your external imports:

// deps.typ — single source of truth for package versions
#import "@preview/cetz:0.3.0": *
#import "@preview/tablex:0.2.1": *

Then other files import from deps.typ instead of repeating the versioned package imports:

// main.typ
#import "deps.typ": cetz, tablex

This works because Typst re-exports a module’s imports to its scope, so downstream files get access to everything deps.typ imported. It is a nice pattern, but it is a convention, not something the tooling enforces. There is also no lockfile, so there is no built-in way to guarantee reproducible builds across different machines.

I have to admit that the no-manifest design keeps Typst simpler to adopt, and a complex package workflow may scare away beginners. These are trade-offs, not flaws, and the Typst team may well address some of them in the future.

Namespaces

Typst manages packages from different organizations using namespace by design. By now all packages live in one shared namespace named ‘preview’, public namespace allocation to different organization could be easy but it does not begin yet.

Assets and resources

Typst packages have a strict scoping rule: file paths in package code can only access files within the package itself. ^3 This is a reasonable security boundary, but it creates some challenges for template packages.

A notable limitation is that fonts cannot be distributed within packages at all. If your package or template requires a specific font, users have to obtain and install it separately. The Typst team has acknowledged this is not ideal and plans to improve it in future versions, but for now, package authors have to document the font requirements and hope users follow through.

License of packages

The Typst Universe takes licensing seriously. Every package must use an OSI-approved open-source license or a Creative Commons variant (CC-BY, CC-BY-SA, or CC0). ^4 Template packages have additional rules — if the license applies to the template output, authors must ensure users can freely modify and distribute their results. Multi-license packages are supported through SPDX expressions, as long as the README clearly explains which license applies to which part.

This is well thought out for a public open-source ecosystem. But it also means proprietary and unlicensed packages are simply not allowed. If your organization develops internal Typst libraries that are not open-source — maybe they contain trade secrets, or internal branding assets, or just code you don’t want to publish — there is no way to distribute them through the Typst Universe. The only option today is to copy files around manually using the local package path, which does not scale well across teams.

The TPIX approach

TPIX is developed as a self-hosted package registry with private namespace support. To make the transition smooth, it mirrors the official Typst Universe, so you can search and download both public and private packages in one place.

Typst packages and TPIX packages

Before we talk about how TPIX improves things, it is worth clarifying what a Typst package actually is. A Typst package is a .tar.gz archive containing a typst.toml manifest and at least one .typ source file. The manifest declares the package name, version, entrypoint, and optional metadata like description, license, authors, categories and so on. This is the format used by the Typst Universe, and it is also the format TPIX uses.

TPIX packages are fully compatible with Typst Universe packages. If you have a valid Typst Universe package, you can upload it to TPIX without any changes. The typst.toml format is identical, the archive structure is the same, and the Typst compiler doesn’t care where the package came from — as long as it ends up in the right directory under the local package path, it just works.

Where TPIX differs is in what it accepts and how it validates. The Typst Universe requires an OSI-approved or Creative Commons license for every package. TPIX also validates licenses, but it additionally accepts Proprietary and UNLICENSED as valid license values, because a private registry needs to host packages that are not open-source.

TPIX also runs a stricter validation pipeline when you upload a package. It checks archive size limits, scans for security issues like path traversal or hidden files, validates the manifest fields more thoroughly, and can even compile the package to verify it actually works. These are safeguards that make sense for a self-hosted registry where you want to keep the package quality high without a human review step.

The tpix bundle command in the CLI helps you create the .tar.gz archive from a source directory. It reads the exclude patterns from your typst.toml manifest (or from CLI flags), walks the directory, and produces a properly structured archive ready for upload. You could do this with tar by hand, but bundle takes care of the details like forward slashes in paths and excluding build artifacts.

Explicit dependency management

TPIX provides a CLI tool (tpix-cli) that gives you explicit control over your packages. The pull command downloads a package and all its transitive dependencies into your local cache, so you know exactly what you have before the compiler runs. No surprises, no implicit network calls during compilation.

For CI/CD environments, this is especially useful. You can run tpix pull in a setup step, and then compile your documents fully offline. If your build environment has no public network access, this is probably the only practical way to use external packages.

When you are ready to publish your own package, the bundle command creates a .tar.gz archive from your package source directory, ready to be uploaded with tpix push.

Private packages and namespaces

Unlike the official Typst Universe where all packages live under the shared preview namespace, TPIX supports multiple namespaces out of the box. You can create your own namespace, publish packages to it, and control who has access.

Private namespaces are fully supported. If your team has internal packages that should not be public, you can host them on your own TPIX instance and restrict access to team members only. The import syntax stays the same — #import "@myteam/internal-lib:0.1.0" — your document code doesn’t need to know whether a package is public or private.

Dependency analysis

TPIX analyzes the source code of every package it indexes to extract dependency information. On the package detail page, you can see what other packages a given package depends on, presented as clickable badges. The CLI can also resolve the full transitive dependency tree for you when pulling packages.

This makes it much easier to answer questions like “what packages does my project actually use?” or “if I upgrade this package, what else gets pulled in?” — questions that are surprisingly hard to answer with the built-in package management alone.

Package publishing

Publishing a package to TPIX is straightforward. You authenticate with the CLI, then use tpix push to upload your package archive. The server validates the package structure, extracts metadata from your typst.toml, and makes it available immediately.

You don’t need to open a pull request on a shared GitHub repository and wait for it to be reviewed. Your package is live as soon as you push it. For teams iterating quickly on internal libraries, this removes a lot of friction.

Project-level dependency management

As we discussed earlier, Typst projects have no manifest file to declare their dependencies. Versions are embedded in import statements, scattered across files. TPIX does not change this — there is no project manifest, no tpix install that reads a dependency list from a single file.

That said, Typst’s approach has one nice property: because every import specifies an exact version and package versions are immutable once published, the same source files will always resolve to the same packages. There is no version range resolution like ^0.3.0 or >=1.0. In that sense, reproducibility is built in, even without a lockfile. TPIX preserves this behavior by default — packages mirrored from the Typst Universe are immutable. However, TPIX also allows namespace owners to enable version overwriting for their own namespaces, which is useful during development when you want to iterate on a package without bumping the version every time. If overwriting is enabled, the same version string could point to different content over time, so keep that in mind if reproducibility matters to you.

And while TPIX does not introduce a project manifest, tpix pull offers a practical middle ground. It scans all .typ files in your project directory for import statements and downloads every package along with its transitive dependencies. It is not a manifest, but it gives you an explicit step to resolve and cache everything before compilation.

What TPIX doesn’t solve

TPIX improves the package distribution and discovery side of things, but there are limitations in Typst’s package ecosystem that TPIX cannot address, because they live in the compiler itself.

Package compatibility across compiler versions

Typst is still pre-1.0, and the language continues to evolve. When the compiler introduces breaking changes, packages that worked with an older version may stop compiling. As the author of Typst has discussed, ^5 this is a real tension — the ecosystem has grown large enough that breaking changes cause real pain, but the language still needs room to improve.

The proposed solution is a target field in the package manifest, inspired by Rust’s edition system, which would let packages declare compatibility with a specific compiler version and receive temporary backwards-compatible behavior during migration. This is a compiler-level concern that TPIX has no control over. If a package breaks with a new Typst release, TPIX will faithfully serve the broken package just the same.

Will TPIX replace Typst Universe?

No, and it is not trying to. The Typst Universe is the official public registry and it works well for what it does. TPIX is meant to complement it. If you are happy with the built-in package management and only use public packages, you probably don’t need TPIX at all. As a package developer, even if you publish public packages in TPIX, I’d suggest you send PRs to the Typst package repository to let more people get access to your packages.

But if you need private packages, explicit dependency control, offline builds, or a self-hosted registry for your team, TPIX fills that gap. It mirrors the Typst Universe so you don’t lose access to public packages, and it adds the tooling and workflows that the built-in system doesn’t provide.

Summary

That’s it. Thanks for reading, I’d be glad to hear your thoughts, for any questions about TPIX, you can post your issues in https://github.com/typstify/tpix-cli/issues, or write to me! If you find TPIX helpful, please go ahead and have a try: https://tpix.typstify.com/.