Extending forge
forge is designed to be fully extensible. You can extend almost every part of the framework to fit your needs.
Why extend forge?
You might want to extend forge to:
- Add custom field types for your domain
- Create reusable components
- Integrate with third-party services
- Customize admin interface
- Add custom validators
- Build plugins
Extension points
forge provides several extension points:
1. Custom field types
Create custom field types for specialized data:
type ColorFieldBuilder struct {
*schema.BaseFieldBuilder
}
func Color(name string) *ColorFieldBuilder {
return &ColorFieldBuilder{
BaseFieldBuilder: &schema.BaseFieldBuilder{
field: schema.Field{
Name: name,
Type: schema.TypeString,
},
},
}
}
func (b *ColorFieldBuilder) Build() schema.Field {
// Add custom validation
b.field.Validators = append(b.field.Validators, &ColorValidator{})
return b.field
}
2. Custom validators
Add custom validation logic:
type PhoneValidator struct{}
func (v *PhoneValidator) Validate(value interface{}) error {
str, ok := value.(string)
if !ok {
return fmt.Errorf("expected string")
}
// Validate phone format
matched, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, str)
if !matched {
return fmt.Errorf("invalid phone number format")
}
return nil
}
// Usage
schema.String("phone").
Required().
Validators(&PhoneValidator{}).
Build()
3. Custom admin widgets
Create custom form widgets:
type RichTextWidget struct {
*admin.BaseWidget
}
func (w *RichTextWidget) Render(field *admin.Field, value interface{}) string {
// Render rich text editor
return fmt.Sprintf(`<textarea class="rich-text">%v</textarea>`, value)
}
// Register widget
admin.RegisterWidget("richtext", &RichTextWidget{})
4. Custom QuerySet methods
Add custom query methods:
func (qs *PostQuerySet) Published(ctx context.Context) ([]*Post, error) {
return qs.Filter(Post.Fields.Published.Equals(true)).All(ctx)
}
func (qs *PostQuerySet) ByAuthor(ctx context.Context, authorID int64) ([]*Post, error) {
return qs.Filter(Post.Fields.AuthorID.Equals(authorID)).All(ctx)
}
5. Custom middleware
Create custom middleware:
func CustomMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Your middleware logic
log.Printf("Request: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
})
}
// Use it
router.Use(CustomMiddleware)
6. Plugin system
Build reusable plugins:
package myplugin
import "github.com/forgego/forge/registry"
func init() {
registry.RegisterPlugin(&MyPlugin{})
}
type MyPlugin struct{}
func (p *MyPlugin) Name() string {
return "myplugin"
}
func (p *MyPlugin) Initialize(app *App) error {
// Plugin initialization
return nil
}
Extension patterns
Builder pattern
Use builder pattern for field types:
type CustomFieldBuilder struct {
*schema.BaseFieldBuilder
customOption string
}
func (b *CustomFieldBuilder) CustomOption(value string) *CustomFieldBuilder {
b.customOption = value
return b
}
Strategy pattern
Use strategy pattern for interchangeable components:
type AuthenticationStrategy interface {
Authenticate(r *http.Request) (*User, error)
}
type TokenAuth struct{}
type JWTAuth struct{}
type SessionAuth struct{}
Factory pattern
Use factory pattern for creating instances:
type WidgetFactory interface {
CreateWidget(widgetType string) Widget
}
func NewWidgetFactory() WidgetFactory {
return &DefaultWidgetFactory{}
}
Best practices
1. Keep extensions focused
Each extension should do one thing well:
// ✅ Good: Focused validator
type EmailValidator struct{}
// ❌ Bad: Does too much
type EmailAndPhoneAndAddressValidator struct{}
2. Document your extensions
Document how to use your extensions:
// EmailValidator validates email addresses.
// Usage:
// schema.String("email").Validators(&EmailValidator{}).Build()
type EmailValidator struct{}
3. Test your extensions
Write tests for your extensions:
func TestEmailValidator(t *testing.T) {
v := &EmailValidator{}
err := v.Validate("test@example.com")
assert.NoError(t, err)
err = v.Validate("invalid")
assert.Error(t, err)
}
4. Follow forge patterns
Match forge's existing patterns:
- Use builder pattern for field types
- Use interfaces for extensibility
- Follow naming conventions
- Match code style
Example: Complete extension
Here's a complete example of extending forge:
package myfields
import "github.com/forgego/forge/schema"
// PhoneField creates a phone number field with validation
func PhoneField(name string, opts ...schema.FieldOpt) schema.Field {
field := schema.StringField(name, opts...)
field.Validators = append(field.Validators, PhoneValidator{})
return field
}
type PhoneValidator struct{}
func (v PhoneValidator) Validate(value interface{}) error {
str, ok := value.(string)
if !ok {
return fmt.Errorf("expected string")
}
matched, _ := regexp.MatchString(`^\+?[1-9]\d{1,14}$`, str)
if !matched {
return fmt.Errorf("invalid phone number format")
}
return nil
}
// Usage
func (User) Fields() []schema.Field {
return []schema.Field{
PhoneField("phone", schema.Required()),
}
}
Next steps
- Custom fields - Create custom field types
- Plugins - Build plugins
- API Reference - Complete API documentation