zig-dotenv: Lightweight .env file parser for Zig

zig-dotenv

I just published a lightweight .env file parser for Zig! It’s designed to be simple, fast, and dependency-free.

What it does

  • Parses .env files into a key-value map
  • Handles comments (# comment)
  • Supports quoted values ("hello world")
  • Trims whitespace automatically
  • Zero external dependencies

Key features

  • Tiny: Under 100 lines of pure Zig
  • Fast: Minimal allocations and overhead
  • Simple API: loadFile() and get() - that’s it
  • Memory safe: Proper allocation and cleanup

Example usage

const std = @import("std");
const dotenv = @import("dotenv");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();
    
    var env = try dotenv.DotEnv.loadFile(allocator, ".env");
    defer env.deinit();
    
    const db_url = env.get("DATABASE_URL") orelse "default";
    std.debug.print("Database: {s}\n", .{db_url});
}

Example .env file

# Database configuration
DATABASE_URL=postgres://localhost:5432/myapp
API_KEY=your_secret_key_here

# Feature flags
DEBUG=true
WELCOME_MESSAGE="Hello, World!"

Repository

The library is MIT licensed and ready to use. Feedback and contributions welcome!

Would love to hear what you think or if you find it useful in your projects.

5 Likes

note that there’s a project on github with the exact same name: GitHub - xcaeser/zig-dotenv: ⚡️A powerful Zig library for loading, parsing, and managing environment variables from .env files

Hey! Appreciate the heads-up. I’m aware of xcaeser/zig-dotenv — and that’s totally cool.

There’s room for more than one .env loader in the Zig ecosystem. Different projects, different priorities.

This one’s gonna stay active, and I’ll keep packing it with features, utilities, and improvements as Zig evolves.
More libraries means more innovation, not competition.

Let’s build the ecosystem, not limit it.

Nice work

Here is another old one in around 20 lines of code

Yours is a bit more robust with edge cases (which hasn’t been a problem for me yet) … I might update mine eventually to cover the bits that you catch too (trim input strings, etc)

Excellentl

1 Like

wasn’t meant as critique, I’m not opposed to multiple people having their own go on something, I do just the same ^^ Just wanted to point out the matching names.

3 Likes

Thanks for the feedback on my initial lightweight version. That was me testing the waters to see how you all would react to another dotenv library.

I’ve been working on a more comprehensive version for the past month, and based on your responses, I’m finally pushing v2.0 to GitHub. This is the version I want to be the official dotenv solution for Zig.

What v2.0 brings

The lightweight version was intentionally basic. This is the full-featured library:

Runtime modification - Change environment variables while your app runs. Add, remove, or update config without restarts.

Type-safe getters - Get integers, booleans, floats, and arrays directly. No more string parsing boilerplate.

Variable expansion - Use ${VAR} syntax in .env files. Useful for building complex configurations.

Multiple file loading - Load multiple .env files with proper precedence for different environments.

Comprehensive testing - 36 tests covering real-world edge cases, memory safety, and error handling. Took forever to get all the edge cases right.

Making this the official solution

I want this to become the standard dotenv library for Zig. Looking at what’s available:

  • xcaeser/zig-dotenv - exists, but basic features
  • zigster64/dotenv.zig - 20 lines, minimal functionality
  • dying-will-bullet/dotenv - just loads into std.os.environ
  • Various other minimal parsers

None of them has the feature set that real projects need. This one does.

Help me make this official

If you think Zig needs a proper dotenv library, help me get this recognized:

  • Star the repo if you find it useful
  • Try it in your projects and report issues
  • Let me know if there are other good libraries I should be aware of
  • Tell the community if you think this should be the standard

I want this to be the library that gets recommended when people ask, “How do I handle environment variables in Zig?”

Repository

GitHub: GitHub - BeigeHornet151/zig-dotenv: A lightweight .env file parser for Zig - Load environment variables from .env files with zero dependencies

This is actively maintained, thoroughly tested, and built for real-world use over the past month. If there’s another library out there that’s comprehensive, please let me know. Otherwise, let’s make this the official solution.

1 Like

Looks like a nice library!

A few notes/comments for things I would expect from a “standard” .env library for Zig:

  • process environment support: I would expect support for loading loading variables from the OS process environment too. Ideally in a way that allows .env files to possibly have higher or lower priority than the process’ own environment. Correct me if I’m wrong, but that doesn’t seem to be available at the moment. Also, I would expect to be able to pass the resultant enviroment variable collection to a subprocess as it’s standard OS process enviroment.
  • serialization support: It would be good to be able to serialize the dotenv structure back to a .env compatible format. Possible without variable expansions. This is useful for debuggin/logging and also for persisting the resultant configuration which can be very useful in CI systems.
  • good integration with the zig standard library: Perhaps quite closely related to the previous two points. I would expect at least some conversion support to/from std.process.EnvMap. If zig-dotenv is lightweight enough it could/should probably even be integrated directly into std.process.

And, because Zig is considered a “true” systems programming language:

  • null-delimited environment variable block support: For advanced usage it would be good to have support for reading to/from standard posix environment variables blocks. This allows inheriting variables from any process that you have read access for in /proc/, also it my be used for fetching variables by calling out to other processes (for example /bin/sh -c "source some-script.sh && cat /proc/self/environ").

A note about the API, getFloat, getBool, etc. are ok, but it would also be nice to have a fully generic API. eg. get(self: *const Self, T: type, key: []const u8, allocator: Allocator) ?T. With @compileErrors for types that do not have a parser. Possibly with and without an allocator.

3 Likes

It will come soon and thank you

1 Like

FYI, you accidentally committed .zig-cache and zig-out to the repo. Might be a bit late to remove them from your Git history, but I’d recommend at least removing them going forward.

1 Like
  • Memory safe - Proper allocation and cleanup as always

There’s a memory leak on error in loadFiles function. While leaking is not unsafe, I’d be rather skeptical of code that proclaims memory safety and then trivially leaks stuff.

3 Likes

If you’ve got time, mind forking and patching it in? and thank you

I fixed it and thank you!