Создаёшь структуру и реализуешь метод Error() string — всё, тип удовлетворяет интерфейс error. Можно добавить метод Unwrap() error, чтобы ошибка поддерживала цепочки. Например: type AppError struct { Code int; Msg string; Err error } с методами Error() и Unwrap(). Потом можешь проверять через errors.As, доставать Code и Msg. Это стандартный подход для бизнес-ошибок в Go-приложениях.