UCloud logo UCloud logo UCloud
v2026.3.0
  1. UCloud/Core
  2. 1. Introduction
  3. 2. Projects
  4. 3. Accounting
  5. 4. Orchestration
  6. 5. Frontend
  7. UCloud/IM for Slurm-based HPC
  8. 6. Installation
  9. 7. Architecture and Networking
  10. 8. User and Project Management
  11. 9. Filesystem Integration
    1. 9.1. Inter-provider file transfers
  12. 10. Slurm Integration
    1. 10.1. Application Management
    2. 10.2. Built-in Applications
  13. 11. Reference
    1. 11.1. Configuration
    2. 11.2. CLI
  14. 12. Appendix
    1. 12.1. Built-in Application Index
  15. UCloud/IM for Kubernetes
  16. 13. Installation
  17. 14. Architecture and Networking
  18. 15. Filesystem Integration
  19. 16. Compute Jobs
    1. 16.1. Public Links
    2. 16.2. Public IPs
    3. 16.3. License Servers
    4. 16.4. SSH Servers
    5. 16.5. Job Audit Log
    6. 16.6. Virtual machines
  20. 17. Integrated applications
    1. 17.1. Syncthing
    2. 17.2. Integrated terminal
  21. 18. UCX applications
    1. 18.1. Hello world
    2. 18.2. Data binding
    3. 18.3. UI events
    4. 18.4. Component reference
    5. 18.5. API reference
  22. 19. Reference
    1. 19.1. Configuration
    2. 19.2. CLI
  23. Branding for UCloud
  24. 20. Branding and identity for UCloud
  25. H: Procedures
  26. 21. H: Procedures
  27. 22. H: Introduction
  28. 23. H: Auditing
  29. 24. H: Auditing scenario
  30. 25. H: GitHub actions
  31. 26. H: Deployment
  32. 27. H: 3rd party dependencies (risk assesment)
  1. Links
  2. Source Code
  3. Releases

Data binding

UCX binds frontend input to exported Go fields using model paths.

Field mapping rules

By default, exported field names are mapped to lower camel case:

  • JobName -> jobName
  • ValidationMessage -> validationMessage

You can override this with ucx tags:

type appModel struct {
    JobName string
    Errors  map[string]string

    // Excluded from model serialization
    NextTodoId int64 `ucx:"-"`

    // Custom key in model
    StackName string `ucx:"stack.id"`
}

Binding inputs and output

Bind paths point into your serialized model:

ucx.InputText("jobName", "Job name", "Name your job", "jobName")
ucx.TextBound("errors.jobName")
ucx.TextBound("validationMessage")

Routing can also be model-bound:

ucx.Router("routePath")
ucx.TextBound("routePath")

In this case routePath is synchronized with query parameter p on the current page.

For list rendering, use relative paths inside row templates:

ucx.List("todos", "No items yet.").Children(
    ucx.TextBoundEx("todoItemText", "./text"),
    ucx.ButtonEx("removeTodo", "Remove", ucx.ColorErrorMain, ucx.IconHeroTrash, "", "./id"),
)

./text and ./id resolve relative to the current list item.

Handling model input

UCX sends model edits as OpModelInput. In most apps you:

  1. normalize model values,
  2. run validation,
  3. clear stale status messages.
func (app *myApp) OnMessage(msg ucx.Frame) {
    switch msg.Opcode {
    case ucx.OpModelInput:
        app.JobName = strings.TrimSpace(app.JobName)
        app.Errors = validateState(app)
        app.SubmissionMessage = ""
    }
}

ucx.AppServe(...) already applies model input into your struct (ApplyModelInput) before OnMessage(...) is called.

Validation pattern

A simple and effective approach is map[string]string for errors:

func validateState(app *myApp) map[string]string {
    errors := map[string]string{}
    if len(strings.TrimSpace(app.JobName)) < 3 {
        errors["jobName"] = "Job name must be at least 3 characters"
    }
    if app.CPU < 1 || app.CPU > 128 {
        errors["cpu"] = "CPU must be between 1 and 128"
    }
    return errors
}

Then render with TextBound("errors.jobName"), TextBound("errors.cpu"), etc.

Previous Hello world
Next UI events