Skip to content

Plugin API Reference

Plugin Interface

go
type Plugin interface {
    Name() string        // Unique name, e.g. "order-email"
    Version() string     // Semver, e.g. "1.0.0"
    Description() string // Short description
    Init(app *AppContext) error
    Shutdown() error
}

AppContext

go
type AppContext struct {
    DB     *pgxpool.Pool
    Router chi.Router
    Hooks  *HookRegistry
    Config map[string]interface{}
    Logger zerolog.Logger
}

HookRegistry

Registering a handler

go
app.Hooks.On(sdk.HookAfterOrderCreate, func(ctx context.Context, event *sdk.HookEvent) error {
    // ...
    return nil
})

Dispatching a hook (from webhook handlers, etc.)

go
app.Hooks.Dispatch(ctx, &sdk.HookEvent{
    Name:   sdk.HookAfterPaymentComplete,
    Entity: transaction,
})

HookEvent

go
type HookEvent struct {
    Name     string                 // Hook name constant
    Entity   interface{}            // The affected entity (type depends on hook)
    Changes  map[string]interface{} // Changed fields (before-update hooks)
    Metadata map[string]interface{} // Arbitrary extra data
}

Cast Entity to the concrete type for the hook you are handling:

go
o := event.Entity.(*order.Order)
p := event.Entity.(*product.Product)
c := event.Entity.(*customer.Customer)

Hook Constants

All constants are in pkg/sdk/hooks.go.

Products

ConstantValueEntity typeCan cancel
HookBeforeProductCreateproduct.before_create*product.ProductYes
HookAfterProductCreateproduct.after_create*product.ProductNo
HookBeforeProductUpdateproduct.before_update*product.ProductYes
HookAfterProductUpdateproduct.after_update*product.ProductNo
HookBeforeProductDeleteproduct.before_delete*product.ProductYes
HookAfterProductDeleteproduct.after_delete*product.ProductNo

Categories

ConstantValueCan cancel
HookBeforeCategoryCreatecategory.before_createYes
HookAfterCategoryCreatecategory.after_createNo
HookBeforeCategoryUpdatecategory.before_updateYes
HookAfterCategoryUpdatecategory.after_updateNo
HookBeforeCategoryDeletecategory.before_deleteYes
HookAfterCategoryDeletecategory.after_deleteNo

Orders

ConstantValueEntity typeCan cancel
HookBeforeOrderCreateorder.before_create*order.OrderYes
HookAfterOrderCreateorder.after_create*order.OrderNo
HookBeforeOrderUpdateorder.before_update*order.OrderYes
HookAfterOrderUpdateorder.after_update*order.OrderNo

Cart

ConstantValueCan cancel
HookBeforeCartAddcart.before_add_itemYes
HookAfterCartAddcart.after_add_itemNo
HookBeforeCartUpdatecart.before_update_itemYes
HookAfterCartUpdatecart.after_update_itemNo
HookBeforeCartRemovecart.before_remove_itemYes
HookAfterCartRemovecart.after_remove_itemNo

Customers

ConstantValueCan cancel
HookBeforeCustomerCreatecustomer.before_createYes
HookAfterCustomerCreatecustomer.after_createNo
HookBeforeCustomerUpdatecustomer.before_updateYes
HookAfterCustomerUpdatecustomer.after_updateNo

Checkout & Payment

ConstantValueCan cancel
HookBeforeCheckoutcheckout.beforeYes
HookAfterCheckoutcheckout.afterNo
HookAfterPaymentCompletepayment.after_completeNo
HookAfterPaymentFailedpayment.after_failedNo

BaseEntity

Shared fields available on all entities via sdk.BaseEntity:

go
type BaseEntity struct {
    ID           uuid.UUID
    CreatedAt    time.Time
    UpdatedAt    time.Time
    CustomFields JSONB     // map[string]interface{}
    Metadata     JSONB
}

Custom Endpoints

Plugins can register routes on the Chi router:

go
func (p *Plugin) Init(app *sdk.AppContext) error {
    app.Router.Route("/api/v1/my-plugin", func(r chi.Router) {
        r.Get("/", p.handleList)
        r.Post("/", p.handleCreate)
        r.Delete("/{id}", p.handleDelete)
    })
    return nil
}

The router is the same instance used by Stoa core, so middleware (auth, logging, etc.) applies automatically for paths under /api/v1/admin/* and /api/v1/store/*.

Released under the MIT License.