gotify/server · warning

unknown plugin

Error message

unknown plugin

What it means

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.

Source

Thrown at api/plugin.go:132

//	    description: Forbidden
//	    schema:
//	        $ref: "#/definitions/Error"
//	  404:
//	    description: Not Found
//	    schema:
//	        $ref: "#/definitions/Error"
//	  500:
//	    description: Internal Server Error
//	    schema:
//	        $ref: "#/definitions/Error"
func (c *PluginAPI) EnablePlugin(ctx *gin.Context) {
	withID(ctx, "id", func(id uint) {
		conf, err := c.DB.GetPluginConfByID(id)
		if success := successOrAbort(ctx, 500, err); !success {
			return
		}
		if conf == nil || !isPluginOwner(ctx, conf) {
			ctx.AbortWithError(404, errors.New("unknown plugin"))
			return
		}
		_, err = c.Manager.Instance(id)
		if err != nil {
			ctx.AbortWithError(404, errors.New("plugin instance not found"))
			return
		}
		if err := c.Manager.SetPluginEnabled(id, true); err == plugin.ErrAlreadyEnabledOrDisabled {
			ctx.AbortWithError(400, err)
		} else if err != nil {
			ctx.AbortWithError(500, err)
		}
	})
}

// DisablePlugin disables a plugin.
// swagger:operation POST /plugin/{id}/disable plugin disablePlugin
//

View on GitHub (pinned to 14bfc25627)

Solutions

  1. List your plugins (GET /plugin) to get the correct ID.
  2. Authenticate as the owner of the plugin configuration.
  3. Reinstall/upload the plugin if it was removed, then retry with the new ID.
  4. Refresh the admin UI to clear stale plugin IDs.

Example fix

// before
DELETE /plugin/12 // owned by another user
// after
GET /plugin // find own plugin id, then DELETE /plugin/<own-id>
Defensive patterns

Strategy: validation

Validate before calling

const plugins = await fetch('/plugin', {headers:{'X-Gotify-Key': token}}).then(r=>r.json());
if (!plugins.some(p => p.id === id)) throw new Error('plugin ' + id + ' not found/not owned; abort');

Type guard

function ownsPlugin(plugins, id) { return plugins.some(p => p.id === id); }

Try / catch

try {
  await gotify.delete(`/plugin/${id}`);
} catch (e) {
  if (e.response?.status === 404) {
    const list = await gotify.get('/plugin');
    id = list[0]?.id; // recover with a valid owned id, or surface to user
  } else throw e;
}

Prevention

When it happens

Trigger: DELETE/PUT /plugin/:id with an ID that was never registered, was removed, or belongs to another user's plugin conf.

Common situations: 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.

Related errors


AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05). Data as JSON: /api/errors/dc4aaa7497b29870. Report an issue: GitHub.