Zig Package Manager

Zig's built-in package manager lets you add third-party libraries to your project with a single command. It uses build.zig.zon to declare dependencies, fetches them by URL, verifies their integrity with a content hash, and makes them available in your build. No separate tool to install — it is part of zig build.

The Package Files

  my-project/
  ├── build.zig       ← build instructions (Zig code)
  ├── build.zig.zon   ← package metadata and dependency list
  └── src/
      └── main.zig
  .zon = Zig Object Notation
  Similar to JSON but:
  - Comments allowed
  - Identifiers don't need quotes
  - Used only for build metadata (not general data format)

build.zig.zon Structure

.{
    .name    = "my-project",
    .version = "0.1.0",
    .minimum_zig_version = "0.13.0",

    .dependencies = .{
        .zig_clap = .{
            .url  = "https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz",
            .hash = "12209d2b01...",   // content hash, verified on download
        },
        .httpz = .{
            .url  = "git+https://github.com/karlseguin/http.zig#v0.8.0",
            .hash = "1220abc123...",
        },
    },

    .paths = .{""},  // root of the package for publishing
}

Adding a Dependency

The zig fetch command downloads a package, computes its hash, and prints the entry to add to your build.zig.zon:

  Step 1: Fetch and save the dependency
  zig fetch --save https://github.com/Hejsil/zig-clap/archive/refs/tags/0.9.1.tar.gz

  Output (added to build.zig.zon automatically):
  .zig_clap = .{
      .url  = "https://github.com/.../0.9.1.tar.gz",
      .hash = "12209d2b01...",
  },
  zig fetch workflow:
  URL provided
       │
  Download archive
       │
  Compute SHA256 hash of contents
       │
  Add to build.zig.zon with .hash field
       │
  Store in global cache (~/.cache/zig/p/)

Using the Dependency in build.zig

const std = @import("std");

pub fn build(b: *std.Build) void {
    const target   = b.standardTargetOptions(.{});
    const optimize = b.standardOptimizeOption(.{});

    const exe = b.addExecutable(.{
        .name             = "my-app",
        .root_source_file = b.path("src/main.zig"),
        .target   = target,
        .optimize = optimize,
    });

    // Fetch the dependency by its name in build.zig.zon
    const clap_dep = b.dependency("zig_clap", .{
        .target   = target,
        .optimize = optimize,
    });

    // Add its module so your code can @import it
    exe.root_module.addImport("clap", clap_dep.module("clap"));

    b.installArtifact(exe);
}

Importing a Package in Your Code

// src/main.zig
const std  = @import("std");
const clap = @import("clap");  // name matches what addImport used

pub fn main() !void {
    // Use zig-clap to parse command-line arguments
    const params = comptime clap.parseParamsComptime(
        \\-h, --help    Print help
        \\-n, --name <str>  Your name
    );

    var diag = clap.Diagnostic{};
    var res  = clap.parse(clap.Help, ¶ms, clap.parsers.default, .{
        .diagnostic = &diag,
    }) catch |err| {
        diag.report(std.io.getStdErr().writer(), err) catch {};
        return;
    };
    defer res.deinit();

    if (res.args.help != 0) {
        return clap.help(std.io.getStdErr().writer(), clap.Help, ¶ms, .{});
    }

    const name = res.args.name orelse "World";
    std.debug.print("Hello, {s}!\n", .{name});
}

The Dependency Cache

  First fetch:
  zig fetch URL → download → verify hash → store in cache

  Second fetch (same hash):
  zig fetch URL → hash matches cache → use cached copy
  (no download needed)

  Cache location:
  Linux/macOS: ~/.cache/zig/p/
  Windows:     %LOCALAPPDATA%\zig\p\

  The hash in build.zig.zon guarantees reproducibility:
  Any machine, any time → same code → same binary

Path Dependencies — Local Packages

Reference a local package (a folder on your disk) instead of a URL:

// build.zig.zon
.dependencies = .{
    .my_utils = .{
        .path = "../shared-utils",  // relative path to local package
    },
},
  File structure:
  workspace/
  ├── my-app/
  │   ├── build.zig
  │   ├── build.zig.zon   ← references ../shared-utils
  │   └── src/main.zig
  └── shared-utils/       ← local dependency package
      ├── build.zig
      └── src/lib.zig

Publishing Your Own Package

  To publish your Zig package for others to use:
  1. Host your code on GitHub, GitLab, or any public URL
  2. Tag a release (e.g., v1.0.0)
  3. Users run:
     zig fetch --save https://github.com/you/mylib/archive/v1.0.0.tar.gz
  4. They add it to their build.zig as shown above

  No registry needed — Zig packages are just URLs.
  The hash ensures the code never changes once fetched.

build.zig.zon Fields Reference

  Field                   │ Required │ Purpose
  ────────────────────────┼──────────┼──────────────────────────
  .name                   │ Yes      │ Package name
  .version                │ Yes      │ Semantic version "1.2.3"
  .minimum_zig_version    │ No       │ Oldest Zig version supported
  .dependencies           │ No       │ Map of dependency names to specs
  .paths                  │ Yes      │ Paths included in the package
                          │          │ (use .{""} for everything)

Dependency Spec Fields

  For remote packages:
  .url   → HTTPS or git URL to the archive
  .hash  → Content hash (set by zig fetch --save)

  For local packages:
  .path  → Relative path to the package directory

  Options:
  .lazy  → true = only fetch when explicitly requested
           false = always fetch (default)

Zig's package manager intentionally stays simple. It resolves packages by content hash rather than version ranges, so builds are fully reproducible — the same build.zig.zon produces the exact same binaries on every machine and at any point in the future. No "dependency hell," no version negotiation, no lock files needed beyond the hash already in build.zig.zon.

Leave a Comment

Your email address will not be published. Required fields are marked *