Skip to content
| Marketplace
Sign in
Visual Studio>Tools>Synology DSM Package Template
Synology DSM Package Template

Synology DSM Package Template

Jordan Duerksen

| (0) | Free
Build Synology NAS packages from Visual Studio, with no prior knowledge of DSM packaging. A guided wizard asks about your app in plain terms — which project to ship, whether it has a web page, which C
Download

Synology DSM Package Template (Visual Studio extension)

A .vsix that adds a "Synology DSM Package (.spk)" entry to File → New → Project. The generated project publishes a .NET app self-contained for a Synology NAS and assembles a DSM 7 .spk with the pure-.NET spkpack tool — buildable entirely from Visual Studio, no Git Bash.

Prerequisites to build this extension

  • Visual Studio 2022/2026 with the "Visual Studio extension development" workload.
  • This is a classic VSIX project — build it with Visual Studio's MSBuild, not dotnet build.

Build & install

  1. Open Synology.PackageTemplate.Vsix.csproj in Visual Studio and build it (or run MSBuild.exe Synology.PackageTemplate.Vsix.csproj /p:Configuration=Release).
  2. The build emits bin\Release\Synology.PackageTemplate.Vsix.vsix. Double-click it to install into VS.
  3. Make the packaging package available: build/pack Projects/Tungsten.Synology.Packaging so Tungsten.Synology.Packaging lands on a feed the new project can restore from (this repo publishes to \Source\Local NuGet). Run dotnet pack Projects/Tungsten.Synology.Packaging -c Release.

Use

File → New → Project → search "Synology" → Synology DSM Package (.spk). Then follow the generated project's README.md: point SpkPublishProject at your app, set SpkAppExecutable, and build Release.

The creation wizard

The point of the wizard is that the developer needs to know nothing about DSM packaging. It asks about the application, and derives the DSM concepts from the answers:

You are asked What it decides
Display name / description / maintainer / version The INFO manifest and Package Center listing
Which project to ship — picked from a dropdown of the solution's projects (or "create a starter app for me") SpkPublishProject (as a relative path) and SpkAppExecutable (the linux apphost name)
"Does it have a web interface?" + port Web → DSM menu icon, pkg/ui/config, adminport/adminurl, and ASPNETCORE_URLS in the daemon script. Headless → none of that is generated at all
"What CPU does your Synology have?" SpkArch + SpkRuntime (x86_64/linux-x64 vs aarch64/linux-arm64)
What DSM should ask at install time WIZARD_UIFILES/install_uifile and the matching postinst that saves the answers to settings.env

Implementation lives in Wizard/:

  • SpkModel — the answers plus the derived DSM values (arch, RID, app id) and validation.
  • SolutionScanner — walks the DTE solution (recursing solution folders) for candidate projects, skipping package projects, and computes the relative path to the chosen one.
  • WizardWindow — the five-step WPF dialog.
  • GeneratedContent — writes the tailored start-stop-status, postinst, install_uifile, pkg/ui/config and README.md. Always LF, never a BOM — DSM runs these with /bin/sh and CRLF makes the shebang fail with a confusing "not found" on the NAS.
  • StarterApp — generates a working ASP.NET or worker project when asked, and adds it to the solution.
  • SpkProjectWizard — the IWizard: injects the $spk*$ replacements, uses ShouldAddProjectItem to suppress boilerplate the chosen shape doesn't need, and writes the generated content in RunFinished. Cancelling throws WizardBackoutException so VS cleans up.

Because the assembly now ships, IncludeAssemblyInVSIXContainer is true and the manifest carries a Microsoft.VisualStudio.Assembly asset whose AssemblyName must match the .vstemplate's <WizardExtension><Assembly> string exactly — otherwise VS cannot bind the wizard and project creation fails.

The settings designer

Right-click a package project → Synology Package Settings… to edit its DSM settings as a form instead of hand-editing Spk* properties. Same plain language as the wizard (web vs headless, NAS processor), with a live "Builds to <id>-<version>-<rev>.spk" readout and a warning when SpkPublishProject doesn't resolve to a real file.

The command is registered by a VSPackage (SynologyPackageVsPackage + SynologyPackage.vsct), auto-loads on SolutionExists, and is hidden unless the selected project is actually a DSM package (BeforeQueryStatus checks for SpkPackageId/SpkPublishProject).

Saving is surgical. SpkProjectFile loads the .csproj with PreserveWhitespace, changes only the targeted property elements, and writes back with the original BOM/declaration/line-ending shape:

  • XDocument.Save would add a UTF-8 BOM and an <?xml?> declaration that SDK-style project files don't have — which makes line 1 differ and turns every settings edit into a whole-file change in source control. Both are matched to what the file actually had (OmitXmlDeclaration, UTF8Encoding(hadBom), NewLineHandling.None).
  • Comments, ordering, other properties and item groups are untouched. Verified against the real Planetspite.Synology.csproj: editing five values produced a five-hunk diff and nothing else.
  • Clearing "has a web interface" blanks rather than deletes SpkDsmUiDir/SpkAdminPort/etc., so the comments survive and switching back is one word. The packer omits empty INFO fields anyway.

The install-questions designer

Right-click a package project → DSM Install Questions… to design the form the NAS admin sees during installation, without writing DSM's JSON. Add/remove/reorder questions, each with a label, a variable name, a kind (Text, Password, Number), a default and a required flag.

Two things it does beyond editing JSON:

  • Keeps scripts/postinst in step. A question is useless if nothing saves the answer, so postinst is regenerated to write every variable into settings.env. It is only rewritten while it still carries the generator marker (GeneratedContent.GeneratedMarker) — a hand-customised script is left alone and the dialog says so.
  • Warns before it drops anything. Opening a file with rules the designer can't model (extra steps, select lists, a custom regex, a max length) shows a warning that saving will replace them. Files the designer itself wrote always reload losslessly.

Clearing every question deletes WIZARD_UIFILES entirely, so the package installs silently rather than showing a blank page.

DSM validator spelling (important)

DSM's wizard validators are snake_case with a bare regex:

"validator": { "allow_blank": false, "min": 4, "regex": "^[0-9]+$" }

The ExtJS-style allowBlank / minLength / /^\d+$/ spellings are silently ignored by DSM — the questions still render, but nothing is validated. This was found by parsing the shipping Planetspite package (which uses the correct form) and was wrong in versions 1.1.0–1.2.0 of this extension.

Build notes (why the csproj looks the way it does)

Four non-obvious things are required to make a template-only VSIX build; don't "simplify" them away:

  • VSToolsPath is set explicitly from $(MSBuildExtensionsPath). VS 2022+ runs a 64-bit MSBuild, where the classic MSBuildExtensionsPath32 default resolves to Program Files (x86) and misses VSSDK\Microsoft.VsSDK.targets (error MSB4226).
  • Microsoft.CSharp.targets is imported — without it the project has no Build target (MSB4057) — and Properties/AssemblyInfo.cs exists so the compiler has an input. The compiled assembly is not shipped (IncludeAssemblyInVSIXContainer=false); it only exists to satisfy the C# build.
  • Per-configuration OutputPath blocks are declared, as classic csproj requires.
  • IncludeVSTemplatesInVSIXContainer (the custom target at the bottom) bridges the template into the container. The VS SDK's d:Source="Project" asset flow assumes the VSIX references a separate template project, so with the template in this same project the .vsix builds successfully but silently without it. The target feeds TemplateProjectOutputGroup's zips into VSIXSourceItem before GetVsixSourceItems runs.

Verify a build by listing the container — the template must be present, not just the manifest:

unzip -l bin\Release\Synology.PackageTemplate.Vsix.vsix
# expect ProjectTemplates/CSharp/SynologyDsmPackage/1033/... plus templateManifest0.1033.vstman

Other notes

  • The generated project must stay a .csproj. Visual Studio picks a project system purely by file extension, and a custom extension (.spkproj) has no registered project type — a project template does not register one. A solution referencing such a project fails to open entirely. Microsoft.Build.NoTargets works fine under .csproj; Site/Planetspite.Site.csproj is the same pattern. Giving the package project its own extension would require shipping a full CPS project-type registration (pkgdef + project factory), which is far more than this is worth.
  • The DSM control scripts are intentionally minimal. See Server/synology/ (Planetspite Metrics) for a production example with an install wizard, a supervisor/restart loop, and reverse-proxy support.
  • To prompt for the package id/port/arch at creation time instead of editing the .csproj afterward, add an IWizard implementation and reference it from the .vstemplate <WizardExtension>.
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
© 2026 Microsoft