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

FeatureValue
Full nameNuGet Package
File extension.nupkg
MIME typeapplication/octet-stream
Format typeZIP archive (Open Packaging Conventions)
DeveloperMicrosoft / .NET Foundation (NuGet)
Introduced2010 (NuGet 1.0)
Container / base formatZIP + OPC (same family as .docx, .xlsx, .vsix)
Byte orderLittle-endian (ZIP structures)
Magic number50 4B 03 04 (PK\x03\x04, ZIP local file header)
Manifest.nuspec XML at the archive root
Assemblieslib/<tfm>/*.dll per target framework
OPC parts[Content_Types].xml, _rels/, package/
Open standardYes (OPC = ECMA-376 / ISO/IEC 29500 Part 2)
Symbol package sibling.snupkg (source + debug symbols)
Primary registrynuget.org (or a private feed)
Create withdotnet pack / nuget pack
Inspect withNuGet Package Explorer, or rename to .zip + 7-Zip
Related extensions.snupkg, .nuspec, .zip, .dll, .vsix
SpecificationMicrosoft Learn — .nuspec reference
File signature (magic bytes)
50 4B 03 04

A .nupkg is a ZIP archive, so it begins with the ZIP local file header 50 4B 03 04 (ASCII PK followed by 0x03 0x04) at offset 0. It is not a bespoke format. What marks it as a NuGet package rather than a plain ZIP is the internal layout: a .nuspec XML manifest at the root, a [Content_Types].xml part, the OPC folders _rels/ and package/, and lib/<framework>/ folders holding the .dll assemblies. The signed and symbol variants (.snupkg) use the same container.

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:

FolderWhat 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