How to Extend or Modify Gitea Modules: A Complete Developer's Guide
You can extend Gitea by creating new packages under modules/ for reusable utilities, implementing business logic in services/ with dependency injection, and registering HTTP handlers in routers/web/, while modifying existing behavior requires overriding service implementations in the global container or patching handler registrations.
Gitea’s self-hosted Git service architecture is built around a clean modular design that separates concerns into distinct layers, making it straightforward to add custom functionality or alter core behavior without forking the entire codebase. Understanding how to extend or modify Gitea modules allows developers to integrate proprietary workflows, custom validations, or specialized webhook payloads while maintaining compatibility with upstream updates. The codebase follows strict conventions in the go-gitea/gitea repository, where components communicate through Go interfaces and a simple global registry enables clean dependency injection.
Understanding Gitea's Layered Architecture
Gitea organizes code into four primary layers, each with specific responsibilities and entry points. This separation ensures that low-level utilities remain independent from HTTP handling and database operations.
-
Modules (
modules/): Re-usable utilities, helpers, and low-level abstractions. Examples includemodules/utilfor helper functions,modules/webfor HTTP routing infrastructure, andmodules/validationfor input checking. The core router implementation lives inmodules/web/router.go. -
Services (
services/): Business-logic components that operate on models and expose higher-level APIs. For instance,services/webhook/webhook.gohandles webhook delivery logic, while issue and repository management live in their respective service packages. -
Routers (
routers/web/androuters/api/): HTTP handlers that map URLs to service calls, enforce middleware chains, and render templates. Handler files likerouters/web/user/setting/webhooks.goextract parameters and invoke the appropriate service methods. -
Models (
models/): Database entities and ORM helpers defining structs likeUserinmodels/user/user.goand repository metadata inmodels/repo/.
The application bootstrap occurs in main.go at the repository root, where all modules, services, and routers initialize and wire together.
Request Flow Through the System
When a request hits the Gitea server, it flows through a predictable pipeline that demonstrates how these layers interact:
-
The router in
modules/web/router.gomatches the request path and dispatches to a handler function inrouters/web/. -
The handler executes middleware from
modules/web/middleware/for logging, authentication, CSRF protection, and locale detection. -
The handler invokes a service method (e.g.,
webhook.CreateWebhook), passing a context object. -
The service manipulates models and may call other modules for utility functions, completing the business operation.
Because each layer communicates through well-defined Go interfaces, you can inject alternative implementations at any stage without modifying upstream code.
How to Extend Gitea with New Modules
Adding functionality follows a bottom-up approach: build utilities, wrap them in services, expose via HTTP, and register globally.
Create a New Module
For code reusable across multiple features, create a package under modules/yourmodule/. Modules should be pure libraries with no dependency on database models or HTTP contexts. Expose a public API through clearly named functions and structs.
Implement Business Logic as a Service
Encapsulate domain operations in services/yourservice/. Define a Go interface describing the operations (e.g., type YourService interface { DoSomething(ctx context.Context, ...) error }), then create an unexported struct implementing it. This pattern, visible throughout services/webhook/ and services/issue/, enables easy mocking and replacement.
Register Your Service
Gitea uses a simple global container initialized at startup. In services/init.go (or a dedicated init.go within your package), call Register() with your service implementation:
func init() {
Register(myservice.New())
}
Retrieve the service elsewhere via the global getter, typically service.GetYourService() or similar patterns found in the existing codebase.
Add HTTP Endpoints
Create handler files in routers/web/yourfeature/ that accept *context.Context (Gitea's extended context wrapping http.ResponseWriter and *http.Request). Extract parameters, call your service, and render JSON or HTML. Register routes in routers/web/init.go or the feature's own init() function using the router group pattern:
web.Group("/api/v1").GET("/yourfeature/:id", yourhandler.GetItem)
Wire Custom Middleware
Reuse existing middleware from modules/web/middleware/ or create new middleware functions that accept and return http.Handler. Apply these at the route group level or individual handler level to enforce cross-cutting concerns like custom authentication headers or rate limiting.
How to Modify Existing Gitea Modules
When you need to alter existing behavior rather than add new features, three techniques allow surgical modifications without extensive rewrites.
Override Functions with Wrappers
For changes to utility functions in modules/util/ or validation logic, create a wrapper function in a new package that calls the original and modifies the result. Adjust the call site in the router or service to use your wrapper instead of the original import.
Replace Service Implementations
Gitea's global service container keeps the last registration for any interface type. To replace an existing service, create a new implementation that embeds the original (for method reuse) or implements the interface from scratch, then register it in an init() function that executes after the original registration:
// services/init.go
func init() {
// Original registration happens first
Register(webhook.New())
// Your override wins
Register(customwebhook.New())
}
This pattern allows you to modify webhook payloads, alter issue processing logic, or change repository initialization sequences while maintaining the existing API contract.
Patch Router Handlers
Copy the existing handler function from routers/web/ to your own file, modify the logic as needed, and update the route registration to point to your version instead of the original. This approach works best for one-off behavioral changes in the presentation layer.
Practical Code Examples
Example: Creating a Utility Module
Create modules/hello/hello.go for simple, stateless utilities:
package hello
import "fmt"
// Greet returns a friendly greeting.
func Greet(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}
Reference existing utilities in modules/util/ for patterns on error handling and input validation.
Example: Building a Service Layer
Define the interface and implementation in services/greeting/greeting.go:
package greeting
import (
"context"
"github.com/go-gitea/gitea/modules/hello"
)
// Service describes greeting-related operations.
type Service interface {
SayHello(ctx context.Context, user string) (string, error)
}
type serviceImpl struct{}
func New() Service { return &serviceImpl{} }
func (s *serviceImpl) SayHello(_ context.Context, user string) (string, error) {
return hello.Greet(user), nil
}
Register in services/init.go:
package services
import (
"github.com/go-gitea/gitea/services/greeting"
)
func init() {
Register(greeting.New())
}
Example: Adding an HTTP Endpoint
Implement the handler in routers/web/greeting/greeting.go:
package greeting
import (
"net/http"
"github.com/go-gitea/gitea/modules/context"
"github.com/go-gitea/gitea/services/greeting"
)
// GetGreeting handles GET /api/v1/greeting/:name
func GetGreeting(ctx *context.Context) {
name := ctx.Params("name")
msg, err := greeting.GetService().SayHello(ctx, name)
if err != nil {
ctx.JSON(http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
ctx.JSON(http.StatusOK, map[string]string{"message": msg})
}
Wire the route in routers/web/api/v1/init.go or equivalent:
func init() {
web.Group("/api/v1").GET("/greeting/:name", greeting.GetGreeting)
}
Example: Overriding an Existing Service
To customize webhook payloads, create services/webhook/custom.go:
package webhook
import (
"context"
"github.com/go-gitea/gitea/models/webhook"
)
type customImpl struct {
*serviceImpl // Embed original to inherit unmodified methods
}
func NewCustom() Service {
return &customImpl{serviceImpl: &serviceImpl{}}
}
func (c *customImpl) Deliver(ctx context.Context, hook *webhook.Webhook) error {
// Modify payload before delegating to original logic
hook.Payload = []byte(`{"custom":"value"}`)
return c.serviceImpl.Deliver(ctx, hook)
}
Ensure your registration in services/init.go executes last:
func init() {
Register(NewCustom()) // Replaces the default webhook service
}
Key Files for Module Development
Understanding these critical entry points accelerates development when you extend or modify Gitea modules:
main.go: Application bootstrap that initializes the global service container and starts the HTTP server.modules/web/router.go: Core HTTP routing implementation defining how URLs map to handlers.modules/web/middleware/: Common middleware for authentication, CSRF protection, logging, and locale detection.services/webhook/webhook.go: Exemplary service implementation showing business logic separation from transport concerns.routers/web/user/setting/webhooks.go: Reference for handler patterns that bridge HTTP requests to service calls.models/user/user.go: Database model definition showing ORM struct conventions used throughout services.
Summary
- Gitea's architecture separates concerns into modules (utilities), services (business logic), routers (HTTP handling), and models (data), enabling clean extension points.
- Extend functionality by creating packages under
modules/, implementing interfaces inservices/, and registering routes inrouters/web/. - Modify existing behavior by leveraging the global service container—later registrations override earlier ones—or by wrapping functions and patching handler registrations.
- Always follow the interface-based patterns found in
services/webhook/webhook.goandmodules/web/router.goto ensure your code remains compatible with upstream Gitea updates.
Frequently Asked Questions
How do I ensure my custom module survives Gitea updates?
Place your code in dedicated directories under modules/ and services/ rather than modifying existing files. Use the service override pattern (registering your implementation after the default in services/init.go) so that git merge operations from upstream apply cleanly without conflicts. Avoid editing files in routers/web/ that you don't own; instead, add new handler files and register additional routes.
Can I modify Gitea's database models when extending modules?
While you can add new tables and models in separate packages under models/, avoid modifying existing model files like models/user/user.go or models/repo/repo.go directly, as this breaks database migrations and upstream compatibility. Instead, create join tables or extension tables linked by foreign keys, and access them through your custom service layer.
What is the difference between a module and a service in Gitea?
Modules (modules/) are low-level, reusable libraries with no application state—think of them as internal packages providing utilities, validation, or HTTP abstractions. Services (services/) contain business logic, operate on database models, and typically manage transactions. Services depend on modules, but modules should never import services or models, maintaining a strict dependency direction.
How do I debug my custom service implementation during development?
Add your service initialization to services/init.go with verbose logging, then run Gitea with the debug log level enabled in app.ini. Since services are singletons registered at startup in main.go, you can add log.Info() calls in your service's constructor and methods to trace execution. Use go test in your package directories for unit testing, following the patterns in services/webhook/webhook_test.go.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →