NUPKG File Documentation
Summary
A .nupkg file is a NuGet Package, the standard way .NET libraries are distributed and installed. It is a ZIP archive built on Open Packaging Conventions, holding compiled .dll assemblies plus a .nuspec manifest. Its MIME type is application/octet-stream. You rarely open one by hand: Visual Studio or dotnet add package installs it into a project. To inspect it, use NuGet Package Explorer or rename it to .zip. It holds compiled libraries, not source code.
Technical details
| Feature | Value |
|---|---|
| Full name | NuGet Package |
| File extension | .nupkg |
| MIME type | application/octet-stream |
| Format type | ZIP archive (Open Packaging Conventions) |
| Developer | Microsoft / .NET Foundation (NuGet) |
| Introduced | 2010 (NuGet 1.0) |
| Container / base format | ZIP + OPC (same family as .docx, .xlsx, .vsix) |
| Byte order | Little-endian (ZIP structures) |
| Magic number | 50 4B 03 04 (PK\x03\x04, ZIP local file header) |
| Manifest | .nuspec XML at the archive root |
| Assemblies | lib/<tfm>/*.dll per target framework |
| OPC parts | [Content_Types].xml, _rels/, package/ |
| Open standard | Yes (OPC = ECMA-376 / ISO/IEC 29500 Part 2) |
| Symbol package sibling | .snupkg (source + debug symbols) |
| Primary registry | nuget.org (or a private feed) |
| Create with | dotnet pack / nuget pack |
| Inspect with | NuGet Package Explorer, or rename to .zip + 7-Zip |
| Related extensions | .snupkg, .nuspec, .zip, .dll, .vsix |
| Specification | Microsoft Learn — .nuspec reference |
What is a .nupkg file?
A .nupkg file is a NuGet Package, the package format of the .NET ecosystem, introduced with NuGet 1.0 in 2010 and now maintained by Microsoft and the .NET Foundation. NuGet is to .NET what npm is to JavaScript or pip is to Python: a way to publish a reusable library once and let other projects pull it in, with its dependencies resolved automatically. The file itself is a ZIP archive built on Microsoft’s Open Packaging Conventions (OPC), the same packaging model behind .docx and .xlsx, so it carries the standard OPC housekeeping parts alongside NuGet-specific content.
A widespread misconception, carried by some older file-type databases that call it a “source code package”, is that a .nupkg contains source. A normal one does not: it holds compiled assemblies. Source code and debug symbols ship separately in a .snupkg symbol package. The sections below open the ZIP and describe each part, then explain the code that a package can run at install time, which is the reason package trust matters.
A ZIP under Open Packaging Conventions
Because a .nupkg is a ZIP, it starts with the ZIP local file header 50 4B 03 04 (PK\x03\x04) and ends with a ZIP central directory, exactly like any .zip. That is why renaming a .nupkg to .zip and opening it in 7-Zip or Windows Explorer just works, and why unzip package.nupkg on any OS extracts it. All the ZIP internals (local headers, central directory, little-endian sizes and CRC-32s) are unchanged; the .nupkg extension is only a hint to NuGet tooling.
OPC then adds a required layer on top of plain ZIP. Two housekeeping parts make an archive a valid OPC package:
[Content_Types].xml maps file extensions to MIME content types (required)
_rels/.rels the package-level relationships part
package/services/ digital-signature parts (for signed packages)
The [Content_Types].xml part at the archive root declares the content type of every extension used inside, and the _rels/ folder holds OPC relationship XML. NuGet does not use these for much beyond OPC conformance and package signing, but they must be present for a package to be well-formed, which is one structural way to tell a real .nupkg from a plain ZIP someone renamed.
The .nuspec manifest, field by field
At the root of the archive sits a single <id>.nuspec file, the package manifest. It is XML, and it is what NuGet reads to know the package’s identity and what it depends on.
<?xml version="1.0"?>
<package>
<metadata>
<id>Newtonsoft.Json</id> <!-- unique package id -->
<version>13.0.3</version> <!-- SemVer 2.0 version -->
<authors>James Newton-King</authors>
<license type="expression">MIT</license>
<dependencies>
<group targetFramework="net8.0" />
</dependencies>
</metadata>
</package>
The id is the globally unique package name on the feed, and the version follows Semantic Versioning 2.0 (so 1.2.3-beta.1 sorts before 1.2.3). The dependencies element lists other packages this one needs, grouped by target framework, and this is what lets NuGet build the full dependency graph and pick compatible versions. The manifest also carries the license (as an SPDX expression or a bundled license file), a description, project and repository URLs, and a readme and icon reference. When you run dotnet pack, most of these fields are generated from the project’s .csproj rather than hand-written.
The lib folder and target framework monikers
The compiled code lives under lib/, organised into subfolders named by target framework moniker (TFM). Each TFM folder holds the assemblies built for that framework, and NuGet picks the best-matching folder for the consuming project at restore time.
lib/
├─ net8.0/ MyLib.dll (built for .NET 8)
├─ net6.0/ MyLib.dll (built for .NET 6)
└─ netstandard2.0/ MyLib.dll (broadest compatibility)
A project targeting .NET 8 gets lib/net8.0/; a project on an older runtime falls back to the most compatible folder available, often netstandard2.0/, which a wide range of runtimes can load. This per-TFM layout is how a single package supports many framework versions from one file. The assemblies themselves are ordinary .NET DLLs: PE files carrying CIL and metadata. To pull one out without installing the package, open the .nupkg as a ZIP and copy the .dll out of the matching lib/ folder, which is the file most people who “want the DLL” are really after.
build/, tools/ and content parts
Beyond lib/, a package can carry several optional folders that change how a consuming project builds. Each has a defined role:
| Folder | What it does |
|---|---|
build/, buildTransitive/ | MSBuild .props/.targets auto-imported into the consuming project |
tools/ | PowerShell install scripts, or the payload of a dotnet tool |
content/, contentFiles/ | files copied into the consuming project (legacy / source packages) |
ref/ | reference-only assemblies used at compile time, not at runtime |
runtimes/ | platform-specific native and managed assets (win-x64, linux-x64, …) |
The build/ folder is the important one to understand, because a .props or .targets file it contains is imported into your project’s MSBuild graph automatically when the package restores. That is a feature many packages rely on (setting compiler options, wiring up source generators), but it also means installing a package can inject build logic that runs on your machine, which is the basis for the trust discussion below.
Install-time code execution and supply-chain trust
A .nupkg is passive data, and merely unzipping one to look inside is completely safe. The risk appears when you install and build against it, because a package can run code as part of that process. The two vectors are the MSBuild .props/.targets in build/, which execute inside your build, and the PowerShell scripts in tools/ that older package-manager flows run during install. Either can execute arbitrary commands with your permissions the moment a restore or build happens.
This makes NuGet a supply-chain surface. Malicious and typosquatted packages, whose ids differ from a popular package by a character or two, have been used on nuget.org to ship malware to developers who mistyped a dependency name. Practical defences are concrete: install only from authors you trust, read the id carefully to avoid typosquats, prefer packages with high download counts and a linked source repository, enable package signing verification, and commit a lock file (packages.lock.json) so the exact resolved versions cannot silently change. None of this affects inspection, only installation, so when in doubt, open the package as a ZIP and read its .nuspec, build/ and tools/ contents before you add it to a project.
Creating a package and inspecting one
You create a .nupkg with dotnet pack, which reads the metadata from your .csproj, compiles the assemblies, lays out the lib/ folders and writes the package into bin/Release; nuget pack yourpackage.nuspec does the same from a hand-written manifest. Publishing to a feed is dotnet nuget push. To install one you almost never touch the file directly: dotnet add package <id> or Visual Studio’s NuGet Package Manager pulls it from a feed and wires it into the project, and for an offline file you add its folder as a package source.
To look inside without any of that, the canonical GUI is NuGet Package Explorer (open-source, available in the Microsoft Store and as a web app at nuget.info), which shows the metadata on one side and the file tree on the other. Because the file is just a ZIP, the low-tech route is equally valid: rename to .zip, or open it straight in 7-Zip, and browse the .nuspec and the DLLs by hand.
References
- Microsoft Learn — What is NuGet?
- Microsoft Learn — .nuspec reference
- NuGet Package Explorer (GitHub)
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.