gotify/server · warning
cannot delete internal application
Error message
cannot delete internal application
What it means
This 400 error is returned by the DELETE /applications/:id endpoint when the application being deleted is marked as internal. Internal applications are system-managed records that the API forbids users from deleting, regardless of ownership. The check runs only after confirming the requester owns the application (app.UserID == auth.GetUserID(ctx)), so this error means 'you own it, but it is protected'.
Source
Thrown at api/application.go:197
// schema:
// $ref: "#/definitions/Error"
// 403:
// description: Forbidden
// schema:
// $ref: "#/definitions/Error"
// 404:
// description: Not Found
// schema:
// $ref: "#/definitions/Error"
func (a *ApplicationAPI) DeleteApplication(ctx *gin.Context) {
withID(ctx, "id", func(id uint) {
app, err := a.DB.GetApplicationByID(id)
if success := successOrAbort(ctx, 500, err); !success {
return
}
if app != nil && app.UserID == auth.GetUserID(ctx) {
if app.Internal {
ctx.AbortWithError(400, errors.New("cannot delete internal application"))
return
}
if success := successOrAbort(ctx, 500, a.DB.DeleteApplicationByID(id)); !success {
return
}
if app.Image != "" {
os.Remove(a.ImageDir + app.Image)
}
} else {
ctx.AbortWithError(404, fmt.Errorf("app with id %d doesn't exists", id))
}
})
}
// UpdateApplication updates an application info by its id.
// swagger:operation PUT /application/{id} application updateApplication
//
// Update an application.View on GitHub (pinned to 14bfc25627)
Solutions
- Pick a different (non-internal) application to delete, or leave the internal one in place
- If the record truly must go, flip internal=false in the database (UPDATE applications SET internal=false WHERE id=...) and retry as an admin
- Filter internal applications out client-side before issuing DELETE calls (e.g. skip apps where internal is true)
- Patch the handler/seed so internal apps are not owned by regular users
Example fix
// before apps.forEach(a => deleteApplication(a.id)); // after apps.filter(a => !a.internal).forEach(a => deleteApplication(a.id));
Defensive patterns
Strategy: validation
Validate before calling
if (app.internal) { throw new SkipError('internal applications cannot be deleted'); }
await api.delete(`/applications/${app.id}`); Type guard
function isDeletable(app) { return app != null && app.userid === currentUser.id && !app.internal; } Prevention
- Filter out internal applications in list views before offering delete actions
- Treat 400 'cannot delete internal application' as an expected skip, not a bug
- Never mark user-facing records as internal unless they are meant to be immutable
- When cleaning up, check the internal flag from GET responses first
When it happens
Trigger: Calling DELETE on an application whose DB record has internal=true. Typically the app was created by seed data, an admin, or a system bootstrap routine rather than by the user.
Common situations: Developers trying to clean up seed/demo applications; environments where an internal app got assigned to a user's account; scripts iterating over all owned applications and deleting each one, hitting the protected internal entry.
Related errors
- file with key 'file' must be present
- invalid file extension
- file must be an image
- sort key is not unique
- client not found
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/d7888ecd94d7584e.
Report an issue: GitHub.