Custom zones · 0.8.1

Backend: hooks, dependencies and custom APIs

Add business rules and integrations through the generated contracts.

On this page

Use backend custom zones for business rules: validating an order, calculating a value, calling an external service or exposing a dedicated API. The generator continues to maintain the model, ordinary CRUD operations and extension contracts.

In the paths below, replace ENTITY_NAME with the generated entity directory, such as product. Replace the Go type EntityName with the real type, such as Product. These are placeholders, not filenames to create literally.

Where the code belongs

File Purpose
backend/internal/custom/ENTITY_NAME/hooks.go Logic within one entity's use cases
backend/internal/custom/app/composition.go Project dependencies, clients and typed hooks
backend/internal/custom/app/routes.go Custom public and protected routes
backend/internal/app/ENTITY_NAME/service.go Generated contract to read; implement your code in custom
backend/internal/generated/customcontracts/ Types used to connect custom composition

Change a value before saving

Open the existing hooks.go. It already declares NewHooks() and methods for the entity's enabled CRUD operations. Change the existing method body rather than adding a second declaration.

This example assumes an ordinary, non-localized string field named Name:

func (hooks) BeforeCreate(_ context.Context, value domain.EntityName) (domain.EntityName, error) {
    value.Name = strings.TrimSpace(value.Name)
    return value, nil
}

Use the actual entity type and add the strings import. A nullable or Localized field may be *string; handle nil first. Read the generated model instead of inferring a Go type from the UI label.

Hook Execution point
BeforeCreate, BeforeUpdate Before the repository write; may change the value or return an error
AfterCreate, AfterUpdate After the repository operation
BeforeDelete Before deletion; receives the record key
AfterList After a page of records is read
AfterGetByID After an individual record is read

The available methods depend on enabled operations. A BeforeCreate error prevents creation. An AfterCreate error does not by itself undo the completed write; operations requiring a shared transaction need an explicit custom design. Classify invalid user input through the app package's error contract, for example by wrapping ErrInvalidInput, so the HTTP boundary can return the appropriate response.

Connect an external dependency

Implement Configure(ctx, dependencies) in composition.go. The returned composition can supply typed entity hooks and protected routes. A non-nil hook replaces the legacy NewHooks() fallback; nil retains it.

dependencies.DB is the application's borrowed pool. Use it through application interfaces and do not close it. Return Cleanup(ctx) for clients you own. Configure's context is startup-only; use r.Context() for requests and a separate cancellable context, stopped by Cleanup, for your background work.

Add a custom API

Project routes use RegisterPublicRoutes and RegisterProtectedRoutes in custom/app/routes.go; entities also have RegisterRoutes. Choose the appropriate extension point without editing generated bootstrap.

The custom directory does not grant authorization automatically. Route dependencies expose RequireAuthenticated, RequireAdminOperation, RequireExternalUser and CurrentPrincipal. Apply the appropriate server-side guard. Signing in does not grant every operation, and custom record-selection rules must also run on the server.

Build the backend and check the changed use case. Custom routes do not automatically appear in generated OpenAPI; your team maintains their contract.

Continue with Admin extensions and The custom development workflow.