NuGet Package Metadata Best Practices: README, Icon, Tags, and License
Publishing a NuGet package is just the first step. The details you fill in -- the README, icon, license expression, and tags -- are what separates packages that get adopted from packages that get ignored. NuGet package metadata is the difference between a package that looks professional and one that raises eyebrows before anyone reads a single line of docs.
If you have already created your NuGet package with dotnet pack, you know the mechanics of packaging. Getting the package to the finish line -- making it trustworthy and discoverable on NuGet.org -- takes a few more fields in your .csproj. This guide walks through each nuget package metadata property, what NuGet.org actually displays for it, and the common mistakes that undermine even well-written libraries.
Why NuGet Package Metadata Matters for Adoption
Think about the last time you picked a NuGet package from a search result. You probably glanced at the icon first. Then the description. Then whether it had a README. Then the license. Only after all of that did you decide whether to look deeper.
Every package author is competing for that split-second evaluation. A package without an icon looks abandoned. One without a README leaves developers guessing about usage. And one with a missing or incorrect license can get rejected outright by enterprise security teams -- automatically, before a human even looks at it.
Good nuget package metadata does three things:
- It signals that the package is actively maintained and professionally built
- It helps NuGet.org surface the package in relevant searches
- It removes friction for potential adopters by answering their key questions upfront
The complete guide to creating NuGet packages in .NET covers the full picture of NuGet packaging. Here, we focus specifically on nuget package metadata -- the fields that make your package shine once it lands on NuGet.org.
Adding a README to Your NuGet Package
NuGet.org renders a package's README as the first thing visitors see on its page. If it's missing, visitors see a blank area where documentation should be. That is not a great first impression.
Starting with NuGet 5.10 and the .NET 6 SDK, you can embed a README.md directly inside your .nupkg file. Here is how to configure it in your .csproj:
<PropertyGroup>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<ItemGroup>
<None Include="README.md">
<Pack>true</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
A few things to note. The <PackageReadmeFile> property tells NuGet the name of the README file to display. The <None Include="README.md"> item is what actually packs the file into the .nupkg. The <PackagePath></PackagePath> value places the file at the root of the package -- which is exactly where NuGet.org expects to find it.
A common mistake here is adding <PackageReadmeFile>README.md</PackageReadmeFile> but forgetting the matching <None Include> item. The build succeeds, but the package ships without the README. Always verify by renaming your .nupkg to .zip and inspecting its contents.
Your README should serve two audiences: developers reading the package page on NuGet.org, and developers who have already installed the package and are reading it in their IDE. Keep it practical. Cover installation, basic usage, and a quick code sample. Link to full docs if you have them. Twenty useful lines beats a thousand words of boilerplate.
What NuGet.org displays: The README tab on the package page renders the full Markdown file. Images in the README load from external URLs -- embedded binary images are not supported.
Including a Package Icon
The icon is the thumbnail for your package. It shows up in Visual Studio's Package Manager, in search results on NuGet.org, and in command-line tooling. A missing icon is immediately visible -- the default grey placeholder stands out in a list of packages that all have real icons.
The recommended approach is to embed the icon directly in the package rather than referencing an external URL. This ensures the icon is always available, even if your hosting goes offline.
<PropertyGroup>
<PackageIcon>icon.png</PackageIcon>
</PropertyGroup>
<ItemGroup>
<None Include="icon.png">
<Pack>true</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
NuGet.org icon requirements:
- Must be a PNG file
- Maximum size is 1 MB
- Recommended dimensions are 128×128 pixels or 512×512 pixels
- Must use a square aspect ratio
If you are designing an icon for a reusable library -- the kind other developers will use as a building block in their own systems -- think about what the library represents conceptually. Simple, clear icons read better at small sizes than complex ones. If you are building extensibility tooling, something like a plugin architecture in C#, a recognizable icon contributes to the polished feel of your whole package ecosystem.
What NuGet.org displays: The icon appears as a square image on the package detail page and in every search result card.
Specifying a License with SPDX Expressions
Every NuGet package should declare its license. Enterprise teams run automated compliance checks that flag packages with no license declared. Skipping this field is one of the fastest ways to get your package blocked by corporate security policies -- automatically, with no appeal.
The modern way to declare a license is <PackageLicenseExpression>, which accepts a SPDX license identifier. SPDX is an open standard for communicating license information. The full list lives at spdx.org/licenses, but here are the identifiers you will use most often:
| License | SPDX Identifier |
|---|---|
| MIT License | MIT |
| Apache License 2.0 | Apache-2.0 |
| GNU GPL v3 (only) | GPL-3.0-only |
| Microsoft Public License | MS-PL |
In your .csproj:
<PropertyGroup>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
</PropertyGroup>
<PackageRequireLicenseAcceptance> controls whether NuGet shows a license acceptance prompt during installation. The default is false, and you should leave it that way unless you have a specific legal requirement to force explicit acceptance. Setting it to true was historically problematic in older NuGet clients, where it triggered an interactive acceptance dialog that broke automated restores. Modern dotnet CLI tooling handles the flag silently, but it still adds complexity and friction for no practical gain unless you have a specific legal requirement to document explicit acceptance.
Do not use <PackageLicenseUrl> -- that property is deprecated and NuGet.org shows a warning when it encounters it. Use <PackageLicenseExpression> for standard open-source licenses, or <PackageLicenseFile> if your license text is not in the SPDX list.
What NuGet.org displays: The license identifier appears as a clickable badge on the package page. SPDX identifiers link directly to the license text on spdx.org.
Tags for Discoverability
Tags are how NuGet.org categorizes packages and how developers find them when searching by topic rather than by exact name. This is one of the most overlooked pieces of nuget package metadata.
In a .csproj file, tags are separated by semicolons:
<PropertyGroup>
<PackageTags>dependency-injection;dotnet;csharp;ioc;fluent;di</PackageTags>
</PropertyGroup>
Note that this is the opposite of what you might expect. Some documentation and older examples show comma-separated tags, but the .csproj format uses semicolons. If you use commas, NuGet treats the entire comma-separated string as a single tag. That is a surprisingly common mistake.
Guidelines for effective tags:
- Include the primary technology --
dotnet,csharp,aspnetcore - Include the problem domain --
logging,serialization,caching,dependency-injection - Include related framework names if relevant --
aspnet,blazor,maui - Use both full names and abbreviations --
dianddependency-injection - Avoid generic words like
libraryorutilitythat add no search signal - NuGet.org limits the total
<PackageTags>value to 4,000 characters. In practice, 20 or fewer focused tags are more effective than exhaustive lists -- search relevance degrades with overly broad tagging.
Tags influence how NuGet.org surfaces your package in category browsing and search, but they are not a magic ranking lever. The real value is helping developers quickly scan search results and recognize that your package solves the exact problem they have right now.
Repository and Project URLs
These two fields point developers to where your source lives and where your documentation lives. They are easy to fill in and often left completely blank.
<PropertyGroup>
<RepositoryUrl>https://github.com/yourname/your-repo</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://yourname.github.io/your-repo</PackageProjectUrl>
</PropertyGroup>
<RepositoryUrl> is the URL to the source code repository -- typically a GitHub, GitLab, or Azure DevOps URL. This powers the "Source Repository" link on NuGet.org. It is also the URL used by SourceLink if you add it later to map PDB debug symbols back to source lines.
<RepositoryType> is almost always git. NuGet can often infer this from common platform URLs, but being explicit is cleaner.
<PackageProjectUrl> is the URL to your project's documentation or website. If you do not have a separate documentation site, pointing this at your GitHub repository README is a completely valid choice. The field exists to give developers a single destination for "where do I go to learn more about this?"
Setting these fields matters for trust. Developers want to see that a package has an accessible source repository before they install it in a production application. Open-source packages without a repository URL raise questions about maintenance, history, and transparency. Real-world packages like those described in getting started with Needlr and assembly scanning in Needlr demonstrate exactly this pattern -- the repository URL is front and center, making it easy to audit, contribute, and file issues.
Release Notes
Release notes attach a changelog to each individual version of your package. They appear on the version history tab of the NuGet.org package page and in package manager tooling when developers are evaluating whether to upgrade.
<PropertyGroup>
<PackageReleaseNotes>
v1.2.0: Added support for .NET 9. Fixed null reference in FooBuilder. Improved tag parsing performance.
v1.1.0: Added PackageTags helper methods.
v1.0.0: Initial release.
</PackageReleaseNotes>
</PropertyGroup>
Keep release notes concise and specific. Developers scanning version history want to know whether upgrading is safe and what changed. Vague notes like "bug fixes and improvements" are nearly useless. Specific notes like "Fixed ArgumentNullException when processing empty collections" tell developers exactly what to expect.
For projects with longer changelogs, consider maintaining a CHANGELOG.md and extracting the latest version's notes programmatically during the build -- or just linking to it from the property value. Either approach beats empty release notes.
Copyright Notice
The <Copyright> field appears in the package metadata and in the generated .nuspec. It is a small field but it rounds out the professional presentation of your nuget package metadata.
<PropertyGroup>
<Copyright>Copyright © 2025 Your Name or Company</Copyright>
</PropertyGroup>
Use the copyright symbol (©) or the text Copyright (c). Include the year or year range and the legal entity that owns the package. For personal open-source packages, your own name works fine. For company packages, use the legal company name.
Full .csproj Example with All Metadata
Here is a complete .csproj showing all the metadata fields discussed in this article, along with the item group entries needed for README and icon embedding. This is your starting template for professional NuGet package metadata.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<!-- Package identity -->
<PackageId>YourCompany.YourLibrary</PackageId>
<Version>1.0.0</Version>
<Authors>Your Name</Authors>
<Company>Your Company</Company>
<Copyright>Copyright © 2025 Your Name</Copyright>
<!-- NuGet.org display -->
<Title>Your Library Title</Title>
<Description>A short, clear description of what your library does in one or two sentences.</Description>
<PackageTags>dotnet;csharp;your-domain;your-topic</PackageTags>
<!-- Files displayed on the package page -->
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageIcon>icon.png</PackageIcon>
<!-- License -->
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<!-- Links -->
<RepositoryUrl>https://github.com/yourname/your-repo</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageProjectUrl>https://github.com/yourname/your-repo</PackageProjectUrl>
<!-- Release notes -->
<PackageReleaseNotes>v1.0.0: Initial release.</PackageReleaseNotes>
</PropertyGroup>
<!-- Pack README and icon into the package root -->
<ItemGroup>
<None Include="README.md">
<Pack>true</Pack>
<PackagePath></PackagePath>
</None>
<None Include="icon.png">
<Pack>true</Pack>
<PackagePath></PackagePath>
</None>
</ItemGroup>
</Project>
Every property here maps directly to something visible on NuGet.org or consumed by tooling. Run dotnet pack after configuring these, inspect the output .nupkg by renaming it to .zip, and confirm README.md and icon.png are present at the root.
How .csproj Metadata Maps to .nuspec
If you work with older packages or need to understand how .csproj properties correspond to the underlying .nuspec format, here is that mapping. NuGet generates the .nuspec from your .csproj properties automatically -- you never need to write it by hand for modern SDK-style projects, but understanding it helps when debugging packaging issues.
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>YourCompany.YourLibrary</id>
<version>1.0.0</version>
<authors>Your Name</authors>
<copyright>Copyright © 2025 Your Name</copyright>
<description>A short, clear description of what your library does.</description>
<!-- Note: .nuspec uses space-separated tags; .csproj uses semicolons -->
<tags>dotnet csharp your-domain your-topic</tags>
<readme>README.md</readme>
<icon>icon.png</icon>
<license type="expression">MIT</license>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<repository type="git" url="https://github.com/yourname/your-repo" />
<projectUrl>https://github.com/yourname/your-repo</projectUrl>
<releaseNotes>v1.0.0: Initial release.</releaseNotes>
</metadata>
<files>
<file src="README.md" target="" />
<file src="icon.png" target="" />
</files>
</package>
One important difference: in .nuspec format, tags are space-separated. In .csproj, they are semicolon-separated. NuGet handles the conversion automatically. Knowing this matters if you are migrating an older package that was originally built with a hand-crafted .nuspec file.
Common NuGet Package Metadata Mistakes to Avoid
Even experienced .NET developers make these mistakes when first publishing packages. Knowing them upfront saves you from shipping a package that immediately looks incomplete.
Missing README. The most common mistake. A package without a README tells potential users nothing about how to use it. Even a 20-line README with a single code sample is vastly better than a blank package description tab on NuGet.org.
No icon. The grey placeholder icon makes your package look unmaintained. Designing a simple icon takes 15 minutes and pays dividends for every developer who encounters your package in search results or the Visual Studio Package Manager.
Wrong license identifier. MIT is the correct SPDX identifier. MIT License or mit are not. Check spdx.org/licenses when unsure. Using the wrong identifier means automated compliance tools may not recognize your license and will treat it as undeclared.
Comma-separated tags. The .csproj format uses semicolons. <PackageTags>dotnet,csharp</PackageTags> creates one tag named dotnet,csharp rather than two separate searchable tags. This is a silent failure that NuGet will not warn you about.
Using the deprecated <PackageLicenseUrl>. This field has been deprecated since NuGet 4.9. NuGet.org shows a validation warning when it encounters it. Migrate to <PackageLicenseExpression> for SPDX identifiers or <PackageLicenseFile> for custom license text.
Forgetting the <None Include> item group. It is easy to add <PackageReadmeFile>README.md</PackageReadmeFile> and forget the matching item group entry. The build will succeed, but the package will not contain the file. Test with dotnet pack and inspect the .nupkg contents before publishing.
Vague descriptions. The <Description> field (equally important alongside everything discussed here) should say specifically what the package does. "A utility library" tells nobody anything. "A fluent API for configuring .NET dependency injection containers" tells developers exactly what they are getting.
Frequently Asked Questions
What file formats does NuGet support for package icons?
Only PNG files are supported for embedded NuGet package icons. The file must be 1 MB or smaller, and a square aspect ratio is required. Recommended dimensions are 128×128 or 512×512 pixels. JPEG, SVG, and GIF formats are not supported.
Can I reference an external image URL for my NuGet package icon?
Embedding the icon directly in the package using <PackageIcon> and a <None Include> item group is strongly preferred. External URL references via the older <PackageIconUrl> property are deprecated. Embedded icons are always available regardless of whether your website or hosting is online.
What happens if I leave PackageLicenseExpression blank?
NuGet.org marks your package as having no license information. Automated security scanning tools used in enterprise environments will typically flag this as a policy violation. Many developers actively avoid packages with no declared license. Always declare a license, even for small or personal packages.
What is the correct tag separator in a .csproj file?
Semicolons. The property looks like <PackageTags>dotnet;csharp;logging</PackageTags>. Using commas makes NuGet treat the entire string as a single tag. Semicolons are the only reliable separator in a .csproj file. Spaces are the separator in .nuspec format -- if you are migrating from a hand-crafted .nuspec, that is the one format difference to watch for.
How do I verify my README and icon were actually packed into the .nupkg?
After running dotnet pack, change the .nupkg file extension to .zip and open it with any archive tool (Windows Explorer, 7-Zip, etc.). You should see README.md and icon.png at the root of the archive. If they are missing, double-check that both <Pack>true</Pack> and <PackagePath></PackagePath> are set in your <None Include> item group entries.
Should I set PackageRequireLicenseAcceptance to true?
Almost never. Setting it to true forces a license acceptance dialog in older NuGet tooling, which breaks automated restores and CI pipelines. Only set it to true if you have a specific legal requirement to document explicit user acceptance -- which is rare for standard open-source packages.
What is the difference between RepositoryUrl and PackageProjectUrl?
<RepositoryUrl> should point to your source code repository (GitHub, GitLab, Azure DevOps). <PackageProjectUrl> should point to your project's documentation or website. They can point to the same URL if you only have a GitHub repository. The distinction matters for tooling: SourceLink uses RepositoryUrl for symbol mapping, while PackageProjectUrl is purely for human navigation.
Wrapping Up
NuGet package metadata is the packaging around your packaging. It is what determines whether a developer investigating your library in a search result decides to click through -- or scrolls past. The fields covered in this guide -- README, icon, license expression, tags, repository URL, release notes, and copyright -- take about 30 minutes to set up properly on the first package. After that, updating them for each release is a few lines of change per version.
Get these right from your very first published version. First impressions matter enormously in the open-source ecosystem, and most developers make their initial evaluation within a few seconds of landing on your NuGet.org package page. Solid nuget package metadata removes doubt, signals professionalism, and makes it easier for the right developers to find and adopt your work.
For the complete picture of NuGet packaging from first creation to publishing workflow, start with the complete guide to creating NuGet packages in .NET.

