Typix Logo
Typix

Typix aims to make it easier to use Nix in Typst projects.

  • Dependency management: supports arbitrary dependencies including fonts, images, and data
  • Reproducible: via a hermetically sealed build environment

Features

  • Automatically fetch dependencies and compile in a single command (nix run .#build)
  • Watch input files and recompile on changes (nix run .#watch)
  • Set up a shell environment with all dependencies made available via environment variables and symlinks
  • Extensible via mkTypstDerivation
  • Support for dependencies such as:

Typst packages are currently unsupported, however there is a workaround.

Getting Started

After installing Nix and enabling flakes, you can initialize a flake from the default template:

nix flake init -t github:loqusion/typix

Here are some commands you can run from the default template:

  • nix run .#watch — watch the input files and recompile on changes
  • nix run .#build — compile and copy the output to the current directory

For more information, check out the docs.

Getting Started

First, install Nix:

bash <(curl -L https://nixos.org/nix/install)

Then enable flakes and the nix command (see here for other ways):

~/.config/nix/nix.conf

experimental-features = nix-command flakes

Finally, you can initialize a flake from the default template:

nix flake init -t github:loqusion/typix

Here are some commands you can run from the default template:

  • nix run .#watch — watch the input files and recompile on changes
  • nix run .#build — compile and copy the output to the current directory

For more info, see nix run --help.

Adding dependencies

You can add dependencies to your flake inputs1 so that Typst compilation does not depend on the transient state of the local system: instead, any dependencies are automatically fetched and made available in a sandboxed environment.

Examples of dependencies you might want to add:

For a more complete description of how to specify flake inputs, see the Nix Reference Manual section on flakerefs.

nixpkgs

Many popular fonts are available as packages to nixpkgs, so if you're wanting to add a font it's good to try that before anything else.

To determine the path(s) to the files you wish to include, first run the following command (which creates a symbolic link named result in the current directory):

nix-build '<nixpkgs>' --out-link result --attr PACKAGE_NAME

Then explore the result with whichever commands you like — for instance, using unix-tree:

tree ./result
result
└── share
    └── fonts
        └── truetype
            ├── Roboto-BlackItalic.ttf
            ├── Roboto-Black.ttf
            ├── Roboto-BoldItalic.ttf
            ├── Roboto-Bold.ttf
            ├── RobotoCondensed-BoldItalic.ttf
            ├── RobotoCondensed-Bold.ttf
            ├── RobotoCondensed-Italic.ttf
            ├── RobotoCondensed-LightItalic.ttf
            ├── RobotoCondensed-Light.ttf
            ├── RobotoCondensed-MediumItalic.ttf
            ├── RobotoCondensed-Medium.ttf
            ├── RobotoCondensed-Regular.ttf
            ├── Roboto-Italic.ttf
            ├── Roboto-LightItalic.ttf
            ├── Roboto-Light.ttf
            ├── Roboto-MediumItalic.ttf
            ├── Roboto-Medium.ttf
            ├── Roboto-Regular.ttf
            ├── Roboto-ThinItalic.ttf
            └── Roboto-Thin.ttf

Here, we can see that the relative path should be share/fonts/truetype, so in flake.nix we use that information like so:

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    packages.${system}.default = typix.lib.${system}.mkTypstDerivation {
      fontPaths = [
        "${pkgs.roboto}/share/fonts/truetype"
      ];
    };
  };
}

GitHub

GitHub hosts a great deal of fonts and icon libraries, and Nix makes it easy to add GitHub repositories as flake inputs with URL-like syntax.

Here's an example of specifying a GitHub URL as a flake input and adding it to virtualPaths, specifying that we want the svgs/regular directory to be accessible from icons:

{
  inputs = {
    font-awesome = {
      url = "github:FortAwesome/Font-Awesome";
      flake = false;
    };
  };

  outputs = { typix, font-awesome }: let
    system = "x86_64-linux";
  in {
    packages.${system}.default = typix.lib.${system}.mkTypstDerivation {
      virtualPaths = [
        {
          dest = "icons";
          src = "${font-awesome}/svgs/regular";
        }
      ];
    };
  };
}

With this, we can use it in Typst as if it were any other local path:

#image("icons/heart.svg")

Using local files

If all else fails, you can always manually download what you need and move it to your Typst project directory.

Here's what you need to know:

See "Specifying sources" for information on how to expand a source tree to include the files you need.

Specifying sources

A number of derivations in Typix accept source trees as parameters, such as src, fontPaths, and virtualPaths. Specifying these is usually as simple as cleanTypstSource in the case of src and string interpolation (via ${...}) in the case of fontPaths and virtualPaths, but there are situations where more is required or desirable:

  • cleanTypstSource omits local files which are required by your Typst project
  • An input you're sourcing contains a large number of files which would be expensive to copy to the Nix store, resulting in longer build times and higher disk usage

Expanding a source tree

TL;DR: you can use lib.sources.cleanSource, but the problem with this approach is that every change to a file tracked by git will invalidate the cache and trigger a rebuild.

To include more local files1 in a source tree, you can use a combination of different functions in lib.fileset such as lib.fileset.unions, lib.fileset.fromSource, and lib.fileset.toSource, like so:

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
    inherit (pkgs) lib;
    typixLib = typix.lib.${system};
    myTypstSource = typixLib.cleanTypstSource ./.;
  in {
    packages.${system}.default = typixLib.mkTypstDerivation {
      src = lib.fileset.toSource {
        root = ./.;
        fileset = lib.fileset.unions [
          (lib.fileset.fromSource myTypstSource)
          ./path.svg
          ./other/path.svg
          ./another
        ];
      };
    };
  };
}

This will create a source tree that looks something like:

/nix/store/...
├── another
│  ├── path1.svg
│  ├── path2.svg
│  └── path3.svg
├── path.svg
├── other
│  └── path.svg
└── ...
1

lib.fileset functions can only be used with local files, not e.g. flake inputs, which is what virtualPaths is for.

Source filtering

You can do source filtering primarily using builtins.filterSource and functions in lib.sources such as lib.sources.cleanSourceWith.

A more detailed explanation can be found in the Nix discussion: "Filtering Source Trees with Nix and Nixpkgs".

Here's an example which picks specific files by name:

{
  outputs = { nixpkgs, typix, font-awesome }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};

    fontAwesomeSubset = let
      icons = [
        "gem.svg"
        "heart.svg"
        "lightbulb.svg"
      ];
    in lib.sources.cleanSourceWith {
      src = "${font-awesome}/svgs/regular";
      filter = path: type:
        builtins.any (icon: builtins.baseNameOf path == icon) icons;
    };
  in {
    packages.${system}.default = typix.lib.${system}.mkTypstDerivation {
      virtualPaths = [
        fontAwesomeSubset
      ];
    };
  };
}

Declaring a shell environment

You can automatically pull your project's dependencies into your shell by declaring a shell environment and then activating it with nix develop or direnv.

Here's an example in a flake using Typix's devShell:

{
  outputs = { typix }: let
    system = "x86_64-linux";
    typixLib = typix.lib.${system};

    watch-script = typixLib.watchTypstProject {/* ... */};
  in {
    # packages, apps, etc. omitted

    devShells.${system}.default = typixLib.devShell {
      fontPaths = [/* ... */];
      virtualPaths = [/* ... */];
      packages = [
        watch-script
      ];
    };
  };
}

What this example does:

  • Fonts added to fontPaths will be made available to typst commands via the TYPST_FONT_PATHS environment variable.
  • Files in virtualPaths will be recursively symlinked to the current directory (only overwriting existing files when forceVirtualPaths is true).
  • For convenience, the typst-watch script is added, which will run watchTypstProject.

Using Typst packages

If none of the advice on this page works for you, and there are no open issues related to your problem, feel free to open an issue.

Typst packages are still experimental, so Typix doesn't provide direct support for them yet. However, there are ways you can get them to work.

Typst packages should work out of the box for:

For the other derivation constructors, see below.

Providing the package cache

Typst caches downloaded packages for a given namespace in {cache-dir}/typst/packages/{namespace}.

With flake inputs defined like:

{
  inputs.typst-packages = {
    url = "github:typst/packages";
    flake = false;
  };

  # Contrived example of an additional package repository
  inputs.my-typst-packages = {
    url = "github:loqusion/my-typst-packages";
    flake = false;
  };
}

...we can provide them where Typst expects them:

let
  typstPackagesSrc = pkgs.symlinkJoin {
    name = "typst-packages-src";
    paths = [
      "${inputs.typst-packages}/packages"
      "${inputs.my-typst-packages}/..."
    ];
  };
  # You can use this if you only need to use official packages
  # typstPackagesSrc = "${inputs.typst-packages}/packages";

  typstPackagesCache = pkgs.stdenv.mkDerivation {
    name = "typst-packages-cache";
    src = typstPackagesSrc;
    dontBuild = true;
    installPhase = ''
      mkdir -p "$out/typst/packages"
      cp -LR --reflink=auto --no-preserve=mode -t "$out/typst/packages" "$src"/*
    '';
  };
in {
  build-drv = typixLib.buildTypstProject {
    XDG_CACHE_HOME = typstPackagesCache;
    # ...
  };

  build-script = typixLib.buildTypstProjectLocal {
    XDG_CACHE_HOME = typstPackagesCache;
    # ...
  };
}

Then, we can use them in a Typst file like so:

#import "@preview/example:0.1.0"
#import "@loqusion/my-package:0.2.1"

Derivations

As paraphrased from the Nix Reference Manual:

A derivation is a specification for running an executable on precisely defined input files to repeatably produce output files at uniquely determined file system paths.

The derivation constructors defined in Typix extend this behavior by running typst compile/typst watch in a context where all the dependencies of your Typst project (fonts, images, etc.) will be made available to the Typst compiler, regardless of if they're already present on the system it runs on.

buildTypstProject

Returns a derivation for compiling a Typst project.

If you want to build to the project directory, use buildTypstProjectLocal instead.

Parameters

All parameters accepted by mkTypstDerivation are also accepted by buildTypstProject. They are repeated below for convenience.

src

Source containing all local files needed in your Typst project.

fontPaths (optional)

List of sources specifying paths to font files that will be made available to your Typst project. With this, you can compile Typst projects even when the fonts it uses are not available on your system.

Used for setting TYPST_FONT_PATHS (see text#font).

Example

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    packages.${system}.default = typix.lib.${system}.buildTypstProject {
      fontPaths = [
        "${pkgs.roboto}/share/fonts/truetype"
      ];
    };
  };
}

installPhaseCommand (optional)

Command (or commands) to run during installPhase.

typstCompileCommand (optional)

Base Typst command to run to compile the project. Other arguments will be appended based on the parameters you supply.

Default is typst compile.

typstOpts (optional)

Attrset specifying command-line options to pass to the typst command.

These are in addition to any options you manually pass in typstCompileCommand.

Default:

{
  format = "pdf";
}

Example

{
  format = "png";
  ppi = 300;
}

...will result in a command like:

typst compile --format png --ppi 300 <source> <output>

typstSource (optional)

Typst input file to compile.

Default is main.typ.

virtualPaths (optional)

List of sources that will be made virtually available to your Typst project. Useful for projects which rely on remote resources, such as images or data.

Each element of the list is an attribute set with the following keys:

  • src: path to source file or directory
  • dest (optional): path where file(s) will be made available (defaults to .)
    • If src is a directory, dest will be a directory containing the files in that directory.
      • Specifying the same dest for multiple src directories will merge them.
    • If src is a file, dest will be a copy of that file.

Instead of an attrset, you may use a path which will be interpreted the same as if you had specified an attrset with just src.

Example

You can specify dependencies in your flake input, and then use them in your project with something like:

{
  inputs = {
    font-awesome = {
      url = "github:FortAwesome/Font-Awesome";
      flake = false;
    };
  };

  outputs = { typix, font-awesome }: let
    system = "x86_64-linux";
  in {
    packages.${system}.default = typix.lib.${system}.buildTypstProject {
      virtualPaths = [
        {
          dest = "icons";
          src = "${font-awesome}/svgs/regular";
        }
      ];
    };
  };
}

Then, reference the files in Typst:

#image("icons/heart.svg")

Source

buildTypstProjectLocal

Returns a derivation for compiling a Typst project and copying the output to the current directory.

This is essentially a script which wraps buildTypstProject.

Using this derivation requires allow-import-from-derivation to be true (which is the default at the time of writing).

More info: https://nixos.org/manual/nix/stable/language/import-from-derivation

Invoking the script produced by this derivation directly is currently unsupported. Instead, use nix run.

See this issue for more information.

Parameters

All parameters accepted by buildTypstProject are also accepted by buildTypstProjectLocal. They are repeated below for convenience.

src

Source containing all local files needed in your Typst project.

fontPaths (optional)

List of sources specifying paths to font files that will be made available to your Typst project. With this, you can compile Typst projects even when the fonts it uses are not available on your system.

Used for setting TYPST_FONT_PATHS (see text#font).

Example

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    apps.${system}.default = typix.lib.${system}.buildTypstProjectLocal {
      fontPaths = [
        "${pkgs.roboto}/share/fonts/truetype"
      ];
    };
  };
}

installPhaseCommand (optional)

Command (or commands) to run during installPhase.

scriptName (optional)

Name of the script that will be added to the Nix store. You can use this name after entering a development shell to invoke the script.

Default is typst-build.

typstCompileCommand (optional)

Base Typst command to run to compile the project. Other arguments will be appended based on the parameters you supply.

Default is typst compile.

typstOpts (optional)

Attrset specifying command-line options to pass to the typst command.

These are in addition to any options you manually pass in typstCompileCommand.

Default:

{
  format = "pdf";
}

Example

{
  format = "png";
  ppi = 300;
}

...will result in a command like:

typst compile --format png --ppi 300 <source> <output>

typstOutput (optional)

Destination path for Typst output.

If omitted, it will be inferred from typstSource — for example, page.typ will become page.pdf for PDF output.

typstSource (optional)

Typst input file to compile.

Default is main.typ.

virtualPaths (optional)

List of sources that will be made virtually available to your Typst project. Useful for projects which rely on remote resources, such as images or data.

Each element of the list is an attribute set with the following keys:

  • src: path to source file or directory
  • dest (optional): path where file(s) will be made available (defaults to .)
    • If src is a directory, dest will be a directory containing the files in that directory.
      • Specifying the same dest for multiple src directories will merge them.
    • If src is a file, dest will be a copy of that file.

Instead of an attrset, you may use a path which will be interpreted the same as if you had specified an attrset with just src.

Example

You can specify dependencies in your flake input, and then use them in your project with something like:

{
  inputs = {
    font-awesome = {
      url = "github:FortAwesome/Font-Awesome";
      flake = false;
    };
  };

  outputs = { typix, font-awesome }: let
    system = "x86_64-linux";
  in {
    apps.${system}.default = typix.lib.${system}.buildTypstProjectLocal {
      virtualPaths = [
        {
          dest = "icons";
          src = "${font-awesome}/svgs/regular";
        }
      ];
    };
  };
}

Then, reference the files in Typst:

#image("icons/heart.svg")

Source

devShell

Sets up a shell environment that activates with nix develop or direnv.

Parameters

Note: All parameters for mkShell are also supported.

extraShellHook (optional)

Bash statements added to the shellHook attribute.

fontPaths (optional)

List of sources specifying paths to font files that will be made available to your Typst project. With this, you can compile Typst projects even when the fonts it uses are not available on your system.

Used for setting TYPST_FONT_PATHS (see text#font).

Example

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    devShells.${system}.default = typix.lib.${system}.devShell {
      fontPaths = [
        "${pkgs.roboto}/share/fonts/truetype"
      ];
    };
  };
}

forceVirtualPaths (optional)

If there are any conflicts between virtualPaths and files in your project directory, they will not be overwritten unless forceVirtualPaths is true.

Default is false.

virtualPaths (optional)

List of sources that will be made virtually available to your Typst project. Useful for projects which rely on remote resources, such as images or data.

Each element of the list is an attribute set with the following keys:

  • src: path to source file or directory
  • dest (optional): path where file(s) will be made available (defaults to .)
    • If src is a directory, dest will be a directory containing the files in that directory.
      • Specifying the same dest for multiple src directories will merge them.
    • If src is a file, dest will be a copy of that file.

Instead of an attrset, you may use a path which will be interpreted the same as if you had specified an attrset with just src.

NOTE: Any paths specified here will not overwrite files in your project directory, unless you set forceVirtualPaths to true.

Example

You can specify dependencies in your flake input, and then use them in your project with something like:

{
  inputs = {
    font-awesome = {
      url = "github:FortAwesome/Font-Awesome";
      flake = false;
    };
  };

  outputs = { typix, font-awesome }: let
    system = "x86_64-linux";
  in {
    devShells.${system}.default = typix.lib.${system}.devShell {
      virtualPaths = [
        {
          dest = "icons";
          src = "${font-awesome}/svgs/regular";
        }
      ];
    };
  };
}

Then, reference the files in Typst:

#image("icons/heart.svg")

Source

mkTypstDerivation

A generic derivation constructor for running Typst commands.

Parameters

Note: All parameters for stdenv.mkDerivation1 are also available.

buildPhaseTypstCommand

Command (or commands) to run during buildPhase. Any output should typically be written to $out, e.g. typsts compile <source> "$out".

See also: Typst CLI Usage

src

Source containing all local files needed in your Typst project.

fontPaths (optional)

List of sources specifying paths to font files that will be made available to your Typst project. With this, you can compile Typst projects even when the fonts it uses are not available on your system.

Used for setting TYPST_FONT_PATHS (see text#font).

Example

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    packages.${system}.default = typix.lib.${system}.mkTypstDerivation {
      fontPaths = [
        "${pkgs.roboto}/share/fonts/truetype"
      ];
    };
  };
}

installPhaseCommand (optional)

Command (or commands) to run during installPhase.

virtualPaths (optional)

List of sources that will be made virtually available to your Typst project. Useful for projects which rely on remote resources, such as images or data.

Each element of the list is an attribute set with the following keys:

  • src: path to source file or directory
  • dest (optional): path where file(s) will be made available (defaults to .)
    • If src is a directory, dest will be a directory containing the files in that directory.
      • Specifying the same dest for multiple src directories will merge them.
    • If src is a file, dest will be a copy of that file.

Instead of an attrset, you may use a path which will be interpreted the same as if you had specified an attrset with just src.

Example

You can specify dependencies in your flake input, and then use them in your project with something like:

{
  inputs = {
    font-awesome = {
      url = "github:FortAwesome/Font-Awesome";
      flake = false;
    };
  };

  outputs = { typix, font-awesome }: let
    system = "x86_64-linux";
  in {
    packages.${system}.default = typix.lib.${system}.mkTypstDerivation {
      virtualPaths = [
        {
          dest = "icons";
          src = "${font-awesome}/svgs/regular";
        }
      ];
    };
  };
}

Then, reference the files in Typst:

#image("icons/heart.svg")

Source

Footnotes

1

stdenv (not for the faint of heart)

watchTypstProject

Returns a derivation for a script that watches an input file and recompiles on changes.

Parameters

Note: All parameters for writeShellApplication are also supported (besides text).

fontPaths (optional)

List of sources specifying paths to font files that will be made available to your Typst project. With this, you can compile Typst projects even when the fonts it uses are not available on your system.

Used for setting TYPST_FONT_PATHS (see text#font).

Example

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    apps.${system}.default = typix.lib.${system}.watchTypstProject {
      fontPaths = [
        "${pkgs.roboto}/share/fonts/truetype"
      ];
    };
  };
}

forceVirtualPaths (optional)

If there are any conflicts between virtualPaths and files in your project directory, they will not be overwritten unless forceVirtualPaths is true.

Default is false.

scriptName (optional)

Name of the script that will be added to the Nix store. You can use this name after entering a development shell to invoke the script.

Default is typst-watch.

typstOpts (optional)

Attrset specifying command-line options to pass to the typst command.

These are in addition to any options you manually pass in typstWatchCommand.

Default:

{
  format = "pdf";
}

Example

{
  format = "png";
  ppi = 300;
}

...will result in a command like:

typst watch --format png --ppi 300 <source> <output>

typstOutput (optional)

Destination path for Typst output.

If omitted, it will be inferred from typstSource — for example, page.typ will become page.pdf for PDF output.

typstSource (optional)

Typst input file to compile.

Default is main.typ.

typstWatchCommand (optional)

Base Typst command to run to watch the project. Other arguments will be appended based on the other parameters you supply.

Default is typst watch.

virtualPaths (optional)

List of sources that will be made virtually available to your Typst project. Useful for projects which rely on remote resources, such as images or data.

Each element of the list is an attribute set with the following keys:

  • src: path to source file or directory
  • dest (optional): path where file(s) will be made available (defaults to .)
    • If src is a directory, dest will be a directory containing the files in that directory.
      • Specifying the same dest for multiple src directories will merge them.
    • If src is a file, dest will be a copy of that file.

Instead of an attrset, you may use a path which will be interpreted the same as if you had specified an attrset with just src.

NOTE: Any paths specified here will not overwrite files in your project directory, unless you set forceVirtualPaths to true.

Example

You can specify dependencies in your flake input, and then use them in your project with something like:

{
  inputs = {
    font-awesome = {
      url = "github:FortAwesome/Font-Awesome";
      flake = false;
    };
  };

  outputs = { typix, font-awesome }: let
    system = "x86_64-linux";
  in {
    apps.${system}.default = typix.lib.${system}.watchTypstProject {
      virtualPaths = [
        {
          dest = "icons";
          src = "${font-awesome}/svgs/regular";
        }
      ];
    };
  };
}

Then, reference the files in Typst:

#image("icons/heart.svg")

Source

Utilities

cleanTypstSource

Filters a source tree to only contain *.typ files and special files such as typst.toml.

Example

{
  outputs = { nixpkgs, typix }: let
    system = "x86_64-linux";
    typixLib = typix.lib.${system};
  in {
    packages.${system}.default = typixLib.mkTypstDerivation {
      src = typixLib.cleanTypstSource ./.;
    };
  };
}