All formats

archive/zip

LanguageGo
LicenseBSD 3-clause (Go standard library)
Repositorygo.googlesource.com/go
Documentationpkg.go.dev/archive/zip
Depends onnothing (standard library)
Read / writeyes / yes, ZIP64, stored and DEFLATE; other methods with RegisterCompressor; no encryption

archive/zip reads and writes ZIP archives with stored and DEFLATE entries and handles ZIP64 automatically. zip.Reader (from zip.OpenReader or zip.NewReader on any io.ReaderAt) implements fs.FS, so fs.WalkDir, fs.ReadFile and http.FS work on an archive. zip.Writer writes to any io.Writer, and Writer.AddFS (Go 1.22+) adds a whole directory tree.

It doesn’t support encryption, and it can’t modify an archive in place: create a new one and copy entries with Writer.Copy, which copies compressed data without recompressing.

Installation#

Nothing to install: import "archive/zip".

Basic usage: extract a ZIP file#

Go
package main

import (
	"archive/zip"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	r, err := zip.OpenReader("in.zip")
	if err != nil {
		log.Fatal(err)
	}
	defer r.Close()

	target := "out"
	for _, f := range r.File {
		fmt.Printf("%-40s %10d -> %10d\n", f.Name, f.UncompressedSize64, f.CompressedSize64)

		dest := filepath.Join(target, f.Name)
		// Zip Slip protection: the path must stay inside target
		if !strings.HasPrefix(dest, filepath.Clean(target)+string(os.PathSeparator)) {
			log.Fatalf("bad entry name: %s", f.Name)
		}
		if f.FileInfo().IsDir() {
			os.MkdirAll(dest, 0o755)
			continue
		}
		if err := extractFile(f, dest); err != nil {
			log.Fatal(err)
		}
	}
}

func extractFile(f *zip.File, dest string) error {
	if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
		return err
	}
	rc, err := f.Open()
	if err != nil {
		return err
	}
	defer rc.Close()
	out, err := os.OpenFile(dest, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, f.Mode().Perm())
	if err != nil {
		return err
	}
	defer out.Close()
	_, err = io.Copy(out, rc)  // the reader returns zip.ErrChecksum on a CRC mismatch
	return err
}

How to create a ZIP file#

Go
package main

import (
	"archive/zip"
	"io"
	"log"
	"os"
	"time"
)

func main() {
	out, err := os.Create("out.zip")
	if err != nil {
		log.Fatal(err)
	}
	defer out.Close()

	w := zip.NewWriter(out)

	// simple entry: DEFLATE, no modification time
	f, err := w.Create("hello.txt")
	if err != nil {
		log.Fatal(err)
	}
	io.WriteString(f, "hello from Go\n")

	// entry with metadata
	hdr := &zip.FileHeader{
		Name:     "docs/report.pdf",
		Method:   zip.Store,  // already compressed, don't deflate again
		Modified: time.Now(),
	}
	hdr.SetMode(0o644)
	f, err = w.CreateHeader(hdr)
	if err != nil {
		log.Fatal(err)
	}
	src, err := os.Open("report.pdf")
	if err != nil {
		log.Fatal(err)
	}
	io.Copy(f, src)
	src.Close()

	if err := w.Close(); err != nil {  // writes the central directory
		log.Fatal(err)
	}
}

Only the most recently created entry can be written to; Create / CreateHeader finish the previous one.

How to zip a whole directory#

Go
w := zip.NewWriter(out)
if err := w.AddFS(os.DirFS("photos")); err != nil {  // Go 1.22+
	log.Fatal(err)
}
w.Close()

How to read one file from a ZIP#

Go
r, err := zip.OpenReader("in.zip")
if err != nil {
	log.Fatal(err)
}
defer r.Close()
data, err := fs.ReadFile(r, "docs/readme.txt")  // zip.ReadCloser is an fs.FS
if err != nil {
	log.Fatal(err)
}
fmt.Println(string(data))

How to use better or other compression#

Register a compressor for the Writer, for example a faster DEFLATE at maximum level or Zstandard from klauspost/compress:

Go
import (
	"archive/zip"
	"compress/flate"
	"io"

	"github.com/klauspost/compress/zstd"
)

w := zip.NewWriter(out)
w.RegisterCompressor(zip.Deflate, func(out io.Writer) (io.WriteCloser, error) {
	return flate.NewWriter(out, flate.BestCompression)
})
// Zstandard: method 93; readers must register the matching decompressor
w.RegisterCompressor(zstd.ZipMethodWinZip, zstd.ZipCompressor())

For reading, zip.RegisterDecompressor(zstd.ZipMethodWinZip, zstd.ZipDecompressor()).

Tips#