NuGet packages
Workspace code — custom endpoints, AI tools, scheduled tasks, code indexes, queries and the admin Shell — compiles against a fixed set of assembly references. The Packages page adds NuGet packages to that set: a package's assemblies are loaded into the running workspace, and the namespaces they expose are added to the imports every one of those code surfaces compiles against.
Where the page lives
Manage → Configure → Workspace → Packages (#/manage/configure/packages).
Listing packages requires a system administrator; installing, removing and reloading require a system administrator with write access. Installs and removals are written to the admin audit log.
Adding a package
Click Add package. The dialog has two tabs — From nuget.org and Upload a file — and both end in the same install path, so a package installed either way behaves identically.
From nuget.org
Paste the reference and the workspace downloads the package from api.nuget.org and installs it. The intended input is the <PackageReference /> snippet from the package's page on nuget.org, but the other copy buttons are accepted too:
| Pasted | Reads as |
|---|---|
<PackageReference Include="NodaTime" Version="3.2.0" /> |
NodaTime, version 3.2.0 |
<PackageReference Include="NodaTime" Version="3.2.0"></PackageReference> |
NodaTime, version 3.2.0 |
dotnet add package NodaTime --version 3.2.0 |
NodaTime, version 3.2.0 |
dotnet add package NodaTime --version=3.2.0 |
NodaTime, version 3.2.0 |
Install-Package NodaTime -Version 3.2.0 |
NodaTime, version 3.2.0 |
NodaTime 3.2.0 |
NodaTime, version 3.2.0 |
NodaTime@3.2.0 |
NodaTime, version 3.2.0 |
NodaTime |
NodaTime, newest version |
The attributes may appear in either order and may use single or double quotes, and surrounding whitespace is ignored.
When no version is given, the newest published version is resolved from nuget.org. A prerelease is only used when the package has no stable release at all. A floating or range version — 2.*, [3.2.0,) — is not a single package to fetch, so it is treated the same as no version and resolves to the newest release.
Anything that carries more than a package id and a version is rejected rather than guessed at, with a message asking for the <PackageReference /> snippet.
Air-gapped and restricted-egress workspaces
This tab is the only part of the feature that reaches the internet. If the workspace cannot reach api.nuget.org, the install fails with a message saying so — fetch the .nupkg elsewhere and use the upload tab instead.
Uploading a .nupkg
The Upload a file tab takes a .nupkg directly. Use it for packages built in-house, packages that are not published to nuget.org, and workspaces with no outbound access to nuget.org.
Replacing a package
Installing a package whose id is already installed replaces it — the same id is never installed twice, because that would load two copies of the same assembly names. This is how you move between versions of a package.
Using the package's types
Every namespace that has public types in the package's assemblies is imported automatically. No using directive is needed, and nothing has to be redeployed or restarted: after the install, the next compile of any endpoint, AI tool, scheduled task, code index or query sees the new types. The package card lists the imported namespaces.
The code editor compiles against the same reference set, so completion, hover documentation and inline errors pick up package types as soon as the package is installed.
An endpoint body using NodaTime after installing the NodaTime package — note the absence of using NodaTime;:
public record ScheduleRequest(string TimeZone, string LocalTime);
var request = Body.FromJson<ScheduleRequest>();
var zone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(request.TimeZone);
if (zone is null) return BadRequest($"Unknown time zone '{request.TimeZone}'.");
var local = LocalDateTime.FromDateTime(DateTime.Parse(request.LocalTime));
var instant = zone.AtLeniently(local).ToInstant();
return Ok(new { utc = instant.ToDateTimeUtc() });
The same applies inside an AI tool, a scheduled task, a code index or the Shell.
Removing a package
Removing a package takes its references and imports away first, so nothing new can compile against it; then everything that was compiled against it is dropped and its load context unloaded, the in-memory assembly images are disposed, and the stored .nupkg is deleted. Code that was running at that moment finishes normally.
Once the last reference is released the assemblies are collected, so a different version of the same package can be installed without restarting the workspace.
Code that used the package stops compiling
Any endpoint, AI tool, scheduled task, index or query still referencing the removed package's types fails to compile the next time it runs — the types are simply gone from the reference set. Change or remove that code before removing the package, or install a replacement package that provides the same types.
Assemblies the workspace already ships
A package is not allowed to replace an assembly the workspace itself ships. Each candidate assembly's real name is read from its PE metadata — not from its file name — and compared against the assemblies already loaded in the workspace:
- A matching assembly is skipped: it is not loaded, and it is named on the package's card under a "Part of this package was not loaded" notice. The rest of the package installs normally, and the skipped assembly's types are already available to your code anyway.
- A package whose assemblies are all workspace assemblies is refused outright, with a message naming them.
Without this, a package's own copy of a shipped library would compile and run in place of the one the rest of the workspace was built against, and the resulting failures would surface far away from the package. The check runs at install time and again on every load, because the set of shipped assemblies changes when the product is upgraded.
What is read out of a package
| Constraint | Behaviour |
|---|---|
| Target framework | Only one lib/<tfm> folder is used — the best of net10.0, net9.0, net8.0, netstandard2.1, netstandard2.0, in that order. A package that ships none of them is refused, with a message listing the frameworks it does ship. |
| Assembly location | Only assemblies directly under lib/<tfm>/ are read. |
| Everything else in the package | Ignored — native assets under runtimes/, content files, MSBuild targets and analyzers. A package that needs a native library at run time will not work. |
| Dependencies | Not resolved and not fetched. If the types you use need the package's dependencies, install those as packages too. |
| Size | 128 MB per package, enforced on both the upload and the nuget.org route. |
| On disk | The .nupkg is stored under Models/Packages in the workspace's storage folder. The assemblies themselves are read out of the archive into memory and loaded from there — nothing is extracted to disk. |
Integrity and the "Not loaded" state
The SHA-256 of the .nupkg is recorded when the package is installed, and re-checked every time the package is loaded. If the stored file is missing or its bytes no longer match, nothing is read out of it: the package's card shows Not loaded instead of Loaded, and its types are unavailable to your code.
A card in that state gets a refresh action that retries the load — the way back for a package whose file was temporarily unreadable, or that failed to load at boot. If the file itself is damaged, remove the package and install it again.
Packages travel with workspace definitions
A definitions bundle carries the workspace's packages including their binaries, since naming them would not restore a workspace — a package built in-house is not fetchable from anywhere. Each package contributes two files under config/packages/: a <name>.<uid>.json declaration and the <uid>.<name>.nupkg itself.
On import the declaration is not trusted. Its recorded hash is checked against the .nupkg that arrived, and then everything is read back out of the package through the same install path the upload and nuget.org routes use, so an imported package is indistinguishable from one installed by hand. A missing or mismatched .nupkg is reported as an error rather than half-imported. A package already installed under the same id is updated in place; otherwise the package keeps the identity it had in the workspace it came from.
Packages are imported before any code, because their namespaces are part of the imports every endpoint and AI tool compiles against. In the comparison screen a .nupkg is not shown as a diff — a binary has no readable diff — and selecting a package's declaration for import pulls its .nupkg along automatically.
See export-workspace-definitions and import-workspace-definitions for moving definitions between workspaces from the CLI.
Cross-links
- Code editor — the editing surface that picks up package types.
- Creating endpoints — where the example above is pasted.
- AI tools and scheduled tasks — the other code surfaces that see package namespaces.