go-gorm/gorm · error
conflicting callback %s with before %s
Error message
conflicting callback %s with before %s
What it means
When a processor sorts its callbacks (sortCallback in callbacks.go), each callback's Before constraint is checked against the already-sorted list. If the callback itself is already in the sorted list at a position AFTER the callback it declared Before (curIdx > sortedIdx), the ordering constraints are contradictory and GORM returns fmt.Errorf("conflicting callback %s with before %s", c.name, c.before).
Source
Thrown at callbacks.go:295
// show warning message the callback name already exists
if idx := getRIndex(names, c.name); idx > -1 && !c.replace && !c.remove && !cs[idx].remove {
c.processor.db.Logger.Warn(context.Background(), "duplicated callback `%s` from %s\n", c.name, utils.FileWithLineNum())
}
names = append(names, c.name)
}
sortCallback = func(c *callback) error {
if c.before != "" { // if defined before callback
if c.before == "*" && len(sorted) > 0 {
if curIdx := getRIndex(sorted, c.name); curIdx == -1 {
sorted = append([]string{c.name}, sorted...)
}
} else if sortedIdx := getRIndex(sorted, c.before); sortedIdx != -1 {
if curIdx := getRIndex(sorted, c.name); curIdx == -1 {
// if before callback already sorted, append current callback just after it
sorted = append(sorted[:sortedIdx], append([]string{c.name}, sorted[sortedIdx:]...)...)
} else if curIdx > sortedIdx {
return fmt.Errorf("conflicting callback %s with before %s", c.name, c.before)
}
} else if idx := getRIndex(names, c.before); idx != -1 {
// if before callback exists
cs[idx].after = c.name
}
}
if c.after != "" { // if defined after callback
if c.after == "*" && len(sorted) > 0 {
if curIdx := getRIndex(sorted, c.name); curIdx == -1 {
sorted = append(sorted, c.name)
}
} else if sortedIdx := getRIndex(sorted, c.after); sortedIdx != -1 {
if curIdx := getRIndex(sorted, c.name); curIdx == -1 {
// if after callback sorted, append current callback to last
sorted = append(sorted, c.name)
} else if curIdx < sortedIdx {
return fmt.Errorf("conflicting callback %s with after %s", c.name, c.after)View on GitHub (pinned to 1d6ce99528)
Solutions
- Print db.Callback().Create().Get(...) / the registered names+constraints and identify the mutually contradictory pair.
- Change one side's constraint (swap Before for After, or target a different anchor callback).
- Update the plugin(s) to versions with compatible constraint sets.
- In tests, use a fresh gorm.Session/DB or remove callbacks before re-registering with new constraints.
Example fix
// before: plugin A
c.Create().After("my_cb").Register("audit", fn)
// plugin B
c.Create().Before("audit").Register("my_cb", fn2) // contradictory
// after: single consistent direction
c.Create().Before("gorm:create").Register("my_cb", fn2)
c.Create().Before("gorm:create").Register("audit", fn) Defensive patterns
Strategy: validation
Validate before calling
// startup self-check: register all callbacks, then force a sort by starting a session
func initCallbacks(db *gorm.DB) error {
// ... register plugins ...
if err := db.Callback().Create().Replace("gorm:create", db.Callback().Create().Register /* or keep */); err != nil { return err }
return nil // sort errors surface on first registration; catch them here at boot
} Try / catch
if err := c.Register("my_cb", fn); err != nil {
if strings.Contains(err.Error(), "conflicting callback") {
// adjust Before/After anchor and retry registration once
}
return err
} Prevention
- Anchor plugin callbacks to core callbacks (gorm:create, gorm:query), not to other plugins' names.
- Register each callback exactly once at startup; avoid re-registration in tests.
- Fail fast at boot: exercise one Create/Query per processor after plugin init.
When it happens
Trigger: Registering callbacks via db.Callback().Create().Before("gorm:create").Register("my_cb", ...) where earlier registrations already placed my_cb after gorm:create (e.g. via another callback's After("my_cb") constraint), producing a cycle/contradiction; typically happens when multiple plugins register callbacks with mutual constraints.
Common situations: Two plugins (audit hooks, soft-delete, caching) each registering Before/After constraints that interact badly; upgrading a plugin that changed its constraint targets; re-registering a callback after Remove with different constraints in tests.
Related errors
- conflicting callback %s with after %s
- violates check constraint
- unsupported relationship
- record not found
- failed to get schema
AI-assisted analysis of go-gorm/gorm@1d6ce99528 (2026-08-15).
Data as JSON: /api/errors/7a9066445fd4a5cb.
Report an issue: GitHub.