diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..3afeb43
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,322 @@
+# `ZipEntry`
+A single entry (a file or a directory) in a ZIP file, and its properties.
+
+```luau
+export type ZipEntry = {
+	name: string,
+	versionMadeBy: { software: string, os: MadeByOS },
+	compressedSize: number,
+	size: number,
+	offset: number,
+	timestamp: number,
+	method: CompressionMethod,
+	crc: number,
+	isDirectory: boolean,
+	isText: boolean,
+	attributes: number,
+	parent: ZipEntry?,
+	children: { ZipEntry },
+}
+```
+## Properties
+- **name** - File path within ZIP, '/' suffix indicates directory
+- **versionMadeBy** - Version of software and OS that created the ZIP
+- **compressedSize** - Compressed size in bytes
+- **size** - Uncompressed size in bytes
+- **offset** - Absolute position of local header in ZIP
+- **timestamp** - MS-DOS format timestamp
+- **method** - Method used to compress the file
+- **crc** - CRC32 checksum of the uncompressed data
+- **isDirectory** - Whether the entry is a directory or not
+- **isText** - Whether the entry is plain ASCII text or binary
+- **attributes** - File attributes
+- **parent** - Parent directory entry, `nil` if entry is root
+- **children** - Children of the entry, if it was a directory, empty array for files
+
+## API
+### `new`
+> [!IMPORTANT]
+> This is a private API. It may be exported publically, but try to avoid
+> using this API, since it can have breaking changes at any time without
+> warning.
+
+
+```luau
+ZipEntry.new(
+	offset: number, -- Offset of the entry in the ZIP file
+	name: string, -- File path within ZIP, '/' suffix indicates directory
+	properties: ZipEntryProperties, -- Properties of the entry
+): ZipEntry
+
+```
+
+[ZipEntry.new]: #new
+### `isSymlink`
+Returns whether the entry is a symlink.
+```luau
+ZipEntry:isSymlink(): boolean
+
+```
+
+[ZipEntry:isSymlink]: #isSymlink
+### `getPath`
+Resolves the path of the entry based on its relationship with other entries. It is recommended to use this 
+method instead of accessing the `name` property directly, although they should be equivalent.
+
+:::warning
+Never use this method when extracting files from the ZIP, since it can contain absolute paths
+(say `/etc/passwd`) referencing directories outside the current directory (say `/tmp/extracted`),
+causing unintended overwrites of files.
+:::
+```luau
+ZipEntry:getPath(): string
+
+```
+
+[ZipEntry:getPath]: #getPath
+### `getSafePath`
+Resolves the path of the entry based on its relationship with other entries and returns it
+only if it is safe to use for extraction, otherwise returns `nil`.
+```luau
+ZipEntry:getSafePath(): string?
+
+```
+
+[ZipEntry:getSafePath]: #getSafePath
+### `sanitizePath`
+Sanitizes the path of the entry, potentially losing information, but ensuring the path is
+safe to use for extraction.
+```luau
+ZipEntry:sanitizePath(): string
+
+```
+
+[ZipEntry:sanitizePath]: #sanitizePath
+### `compressionEfficiency`
+Calculates the compression efficiency of the entry, or `nil` if the entry is a directory.
+
+Uses the formula: `round((1 - compressedSize / size) * 100)` and outputs a percentage.
+```luau
+ZipEntry:compressionEfficiency(): number?
+
+```
+
+[ZipEntry:compressionEfficiency]: #compressionEfficiency
+### `isFile`
+Returns whether the entry is a file, i.e., not a directory or symlink.
+```luau
+ZipEntry:isFile(): boolean
+
+```
+
+[ZipEntry:isFile]: #isFile
+### `unixMode`
+Parses the entry's attributes to extract a UNIX mode, represented as a [UnixMode].
+```luau
+ZipEntry:unixMode(): UnixMode?
+
+```
+
+[ZipEntry:unixMode]: #unixMode
+
+## Types
+### `MadeByOS`
+The OS that created the ZIP.
+```luau
+export type MadeByOS = "FAT" | "AMIGA" | "VMS" | "UNIX" | "VM/CMS" | "Atari ST" | "OS/2" | "MAC" | "Z-System" | "CP/M" | "NTFS" | "MVS" | "VSE" | "Acorn RISCOS" | "VFAT" | "Alternate MVS" | "BeOS" | "TANDEM" | "OS/400" | "OS/X" | "Unknown"
+```
+
+[MadeByOS]: #MadeByOS
+### `CompressionMethod`
+The method used to compress the file:
+- `STORE` - No compression
+- `DEFLATE` - Compressed raw deflate chunks
+```luau
+export type CompressionMethod = "STORE" | "DEFLATE"
+```
+
+[CompressionMethod]: #CompressionMethod
+### `ZipEntryProperties`
+> [!IMPORTANT]
+> This is a private type. It may be exported publically, but try to avoid
+> using it, since its definition can have a breaking change at any time
+> without warning.
+
+A set of properties that describe a ZIP entry. Used internally for construction of 
+[ZipEntry] objects.
+```luau
+export type ZipEntryProperties = {
+	versionMadeBy: number,
+	compressedSize: number,
+	size: number,
+	attributes: number,
+	timestamp: number,
+	method: CompressionMethod?,
+	crc: number,
+}
+```
+- **versionMadeBy** - Version of software and OS that created the ZIP
+- **compressedSize** - Compressed size in bytes
+- **size** - Uncompressed size in bytes
+- **attributes** - File attributes
+- **timestamp** - MS-DOS format timestamp
+- **method** - Method used
+- **crc** - CRC32 checksum of the uncompressed data
+
+[ZipEntryProperties]: #ZipEntryProperties
+### `UnixMode`
+A object representation of the UNIX mode.
+```luau
+export type UnixMode = {
+	perms: string,
+	typeFlags: string,
+}
+```
+- **perms** - The permission octal
+- **typeFlags** - The type flags octal
+
+[UnixMode]: #UnixMode
+
+[ZipEntry]: #ZipEntry
+# `ZipReader`
+The main class which represents a decoded state of a ZIP file, holding references
+to its entries. This is the primary point of interaction with the ZIP file's contents.
+
+```luau
+export type ZipReader = {
+	data: buffer,
+	comment: string,
+	entries: { ZipEntry },
+	directories: { [string]: ZipEntry },
+	root: ZipEntry,
+}
+```
+## Properties
+- **data** - The buffer containing the raw bytes of the ZIP
+- **comment** - Comment associated with the ZIP
+- **entries** - The decoded entries present
+- **directories** - The directories and their respective entries
+- **root** - The entry of the root directory
+
+## API
+### `new`
+Creates a new ZipReader instance from the raw bytes of a ZIP file.
+
+**Errors if the ZIP file is invalid.**
+```luau
+ZipReader.new(
+	data: buffer, -- The buffer containing the raw bytes of the ZIP
+): ZipReader
+
+```
+
+[ZipReader.new]: #new
+### `parseCentralDirectory`
+> [!IMPORTANT]
+> This is a private API. It may be exported publically, but try to avoid
+> using this API, since it can have breaking changes at any time without
+> warning.
+
+Parses the central directory of the ZIP file and populates the `entries` and `directories` 
+fields. Used internally during initialization of the [ZipReader].
+
+**Errors if the ZIP file is invalid.**
+```luau
+ZipReader:parseCentralDirectory()
+```
+
+[ZipReader:parseCentralDirectory]: #parseCentralDirectory
+### `buildDirectoryTree`
+> [!IMPORTANT]
+> This is a private API. It may be exported publically, but try to avoid
+> using this API, since it can have breaking changes at any time without
+> warning.
+
+Builds the directory tree from the entries. Used internally during initialization of the 
+[ZipReader].
+```luau
+ZipReader:buildDirectoryTree()
+```
+
+[ZipReader:buildDirectoryTree]: #buildDirectoryTree
+### `findEntry`
+Finds a [ZipEntry] by its path in the ZIP archive.
+```luau
+ZipReader:findEntry(
+	path: string, -- Path to the entry to find
+): ZipEntry?
+
+```
+
+[ZipReader:findEntry]: #findEntry
+### `extract`
+Extracts the specified [ZipEntry] from the ZIP archive. See [ZipReader:extractDirectory] for 
+extracting directories.
+```luau
+ZipReader:extract(
+	entry: ZipEntry, -- The entry to extract
+	options: ExtractionOptions?, -- Options for the extraction
+): buffer | string
+
+```
+
+[ZipReader:extract]: #extract
+### `extractDirectory`
+Extracts all the files in a specified directory, skipping any directory entries.
+
+**Errors if [ZipReader:extract] errors on an entry in the directory.**
+```luau
+ZipReader:extractDirectory(
+	path: string, -- The path to the directory to extract
+	options: ExtractionOptions?, -- Options for the extraction
+): { [string]: buffer } | { [string]: string }
+
+```
+
+[ZipReader:extractDirectory]: #extractDirectory
+### `listDirectory`
+Lists the entries within a specified directory path.
+```luau
+ZipReader:listDirectory(
+	path: string, -- The path to the directory to list
+): { ZipEntry }
+
+```
+
+[ZipReader:listDirectory]: #listDirectory
+### `walk`
+Recursively walks through the ZIP file, calling the provided callback for each entry
+with the current entry and its depth.
+```luau
+ZipReader:walk(
+	callback: (entry: ZipEntry, depth: number) -> (), -- The function to call for each entry
+)
+```
+
+[ZipReader:walk]: #walk
+### `getStats`
+Retrieves statistics about the ZIP file.
+```luau
+ZipReader:getStats(): ZipStatistics
+
+```
+
+[ZipReader:getStats]: #getStats
+
+## Types
+### `ZipStatistics`
+
+```luau
+export type ZipStatistics = {
+	fileCount: number,
+	dirCount: number,
+	totalSize: number,
+}
+```
+- **fileCount** - The number of files in the ZIP
+- **dirCount** - The number of directories in the ZIP
+- **totalSize** - The total size of all files in the ZIP
+
+[ZipStatistics]: #ZipStatistics
+
+[ZipReader]: #ZipReader