{"record":{"id":"dc4aaa7497b29870","repo":"gotify/server","slug":"unknown-plugin","errorCode":null,"errorMessage":"unknown plugin","messagePattern":"unknown plugin","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"api/plugin.go","lineNumber":132,"sourceCode":"//\t    description: Forbidden\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\n//\t  404:\n//\t    description: Not Found\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\n//\t  500:\n//\t    description: Internal Server Error\n//\t    schema:\n//\t        $ref: \"#/definitions/Error\"\nfunc (c *PluginAPI) EnablePlugin(ctx *gin.Context) {\n\twithID(ctx, \"id\", func(id uint) {\n\t\tconf, err := c.DB.GetPluginConfByID(id)\n\t\tif success := successOrAbort(ctx, 500, err); !success {\n\t\t\treturn\n\t\t}\n\t\tif conf == nil || !isPluginOwner(ctx, conf) {\n\t\t\tctx.AbortWithError(404, errors.New(\"unknown plugin\"))\n\t\t\treturn\n\t\t}\n\t\t_, err = c.Manager.Instance(id)\n\t\tif err != nil {\n\t\t\tctx.AbortWithError(404, errors.New(\"plugin instance not found\"))\n\t\t\treturn\n\t\t}\n\t\tif err := c.Manager.SetPluginEnabled(id, true); err == plugin.ErrAlreadyEnabledOrDisabled {\n\t\t\tctx.AbortWithError(400, err)\n\t\t} else if err != nil {\n\t\t\tctx.AbortWithError(500, err)\n\t\t}\n\t})\n}\n\n// DisablePlugin disables a plugin.\n// swagger:operation POST /plugin/{id}/disable plugin disablePlugin\n//","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/api/plugin.go#L114-L150","documentation":"The plugin update/delete handler returns 404 'unknown plugin' when no plugin config exists for the given ID or the authenticated user is not the plugin's owner (isPluginOwner fails). Like message deletion, ownership mismatches are masked as not-found.","triggerScenarios":"DELETE/PUT /plugin/:id with an ID that was never registered, was removed, or belongs to another user's plugin conf.","commonSituations":"Operating on a plugin ID from another account; referencing a plugin ID after Gotify restarted and plugin confs changed; stale UI state after the plugin was uninstalled; typo in the id path parameter.","solutions":["List your plugins (GET /plugin) to get the correct ID.","Authenticate as the owner of the plugin configuration.","Reinstall/upload the plugin if it was removed, then retry with the new ID.","Refresh the admin UI to clear stale plugin IDs."],"exampleFix":"// before\nDELETE /plugin/12 // owned by another user\n// after\nGET /plugin // find own plugin id, then DELETE /plugin/<own-id>","handlingStrategy":"validation","validationCode":"const plugins = await fetch('/plugin', {headers:{'X-Gotify-Key': token}}).then(r=>r.json());\nif (!plugins.some(p => p.id === id)) throw new Error('plugin ' + id + ' not found/not owned; abort');","typeGuard":"function ownsPlugin(plugins, id) { return plugins.some(p => p.id === id); }","tryCatchPattern":"try {\n  await gotify.delete(`/plugin/${id}`);\n} catch (e) {\n  if (e.response?.status === 404) {\n    const list = await gotify.get('/plugin');\n    id = list[0]?.id; // recover with a valid owned id, or surface to user\n  } else throw e;\n}","preventionTips":["Discover plugin IDs via GET /plugin instead of hardcoding.","Refresh UI state after plugin install/uninstall.","Perform plugin admin actions as the owning user.","Treat 404 as 'gone or not owned'."],"tags":["http-404","authorization","plugins","gotify"],"backgroundTag":"resource-not-found-or-not-owned","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}