{"record":{"id":"5fa29c468664eca4","repo":"JosefNemec/Playnite","slug":"cannot-package-extension-id-is-missing","errorCode":null,"errorMessage":"Cannot package extension, ID is missing!","messagePattern":"Cannot package extension, ID is missing!","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"source/Tools/Playnite.Toolbox/Extensions.cs","lineNumber":195,"sourceCode":"            File.Move(Path.Combine(outDir, baseProjectName + \".sln\"), outSolutionFile);\r\n            FileSystem.ReplaceStringInFile(outProjectFile, baseProjectName, normalizedName);\r\n            FileSystem.ReplaceStringInFile(outSolutionFile, baseProjectName, normalizedName);\r\n            return outDir;\r\n        }\r\n\r\n        public static string PackageExtension(string extDirectory, string targetPath)\r\n        {\r\n            var dirInfo = new DirectoryInfo(extDirectory);\r\n            var manifestPath = Path.Combine(extDirectory, PlaynitePaths.ExtensionManifestFileName);\r\n            if (!File.Exists(manifestPath))\r\n            {\r\n                throw new Exception($\"Manifest file ({PlaynitePaths.ExtensionManifestFileName}) not found!\");\r\n            }\r\n\r\n            var extInfo = ExtensionInstaller.GetExtensionManifest(manifestPath);\r\n            if (extInfo.Id.IsNullOrEmpty())\r\n            {\r\n                throw new Exception(\"Cannot package extension, ID is missing!\");\r\n            }\r\n\r\n            extInfo.VerifyManifest();\r\n\r\n            var packedPath = Path.Combine(targetPath, $\"{Common.Paths.GetSafePathName(extInfo.Id).Replace(' ', '_')}_{extInfo.Version.ToString().Replace(\".\", \"_\")}{PlaynitePaths.PackedExtensionFileExtention}\");\r\n            FileSystem.PrepareSaveFile(packedPath);\r\n            var ignoreFiles = File.ReadAllLines(Paths.ExtFileIgnoreListPath);\r\n\r\n            using (var zipStream = new FileStream(packedPath, FileMode.Create))\r\n            {\r\n                using (var zipFile = new ZipArchive(zipStream, ZipArchiveMode.Create))\r\n                {\r\n                    foreach (var file in Directory.GetFiles(extDirectory, \"*.*\", SearchOption.AllDirectories))\r\n                    {\r\n                        var subName = file.Replace(extDirectory, \"\").TrimStart(Path.DirectorySeparatorChar);\r\n                        if (ignoreFiles.ContainsString(subName, StringComparison.OrdinalIgnoreCase))\r\n                        {\r\n                            continue;\r","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/JosefNemec/Playnite/blob/5911f4e964e628aa7a69c2030ab35101afa63067/source/Tools/Playnite.Toolbox/Extensions.cs#L177-L213","documentation":"Thrown by Extensions.PackageExtension after the manifest is loaded but its Id field is null or empty. ExtensionInstaller.GetExtensionManifest(manifestPath) deserializes extension.yaml into an ExtensionManifest; if the Id property is absent or blank in the YAML, packaging stops because the packed filename (Id_Version.pext) and add-on identity both depend on it. Note this runs before VerifyManifest, so a bad Version alone will not reach here.","triggerScenarios":"extension.yaml exists and parses, but the top-level `Id:` key is missing, commented out, or set to an empty string. Also occurs if YAML indentation put Id under the wrong node so the deserializer never binds it.","commonSituations":"Authors copy a template and forget to fill in Id; an editor auto-removed a perceived-duplicate key; YAML uses a tab where a space is required, silently mis-indenting Id; Id is spelled `id:` or `ID:` and the YamlDotNet binding is case-sensitive.","solutions":["Open extension.yaml and confirm a top-level `Id: <unique_identifier>` line exists with correct two-space YAML indentation and no surrounding quotes that collapse to empty.","Use the exact casing `Id` (PascalCase) — the manifest model property is `Id`, and YamlDotNet is case-sensitive here.","Run a quick parse check: deserialize via ExtensionInstaller.GetExtensionManifest and assert `!string.IsNullOrEmpty(manifest.Id)` before invoking PackageExtension.","Ensure Id is non-empty after any GenerateScriptExtension/GeneratePluginExtension run that may have left a placeholder."],"exampleFix":"// before\nId:\nName: My Plugin\n\n// after\nId: MyUniquePlugin_7c3f\nName: My Plugin","handlingStrategy":"validation","validationCode":"var man = ExtensionInstaller.GetExtensionManifest(manifestPath);\nif (man.Id.IsNullOrEmpty())\n    throw new InvalidOperationException($\"extension.yaml at {manifestPath} has no Id\");","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep the exact key name `Id` (PascalCase) in extension.yaml.","After scaffolding or editing the manifest, deserialize and assert a non-empty Id before packaging.","Lint YAML indentation (spaces, not tabs) to ensure Id binds at the top level."],"tags":["extensions","packaging","manifest","toolbox","validation","yaml"],"backgroundTag":null,"analyzedSha":"5911f4e964e628aa7a69c2030ab35101afa63067","analyzedAt":"2026-08-13T17:38:25.713Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}