UCloud logo UCloud logo UCloud
v2026.3.0
  1. UCloud/Core
  2. 1. Introduction
  3. 2. Projects
  4. 3. Accounting
  5. 4. Orchestration
  6. UCloud/IM for Slurm-based HPC
  7. 5. Installation
  8. 6. Architecture and Networking
  9. 7. User and Project Management
  10. 8. Filesystem Integration
    1. 8.1. Inter-provider file transfers
  11. 9. Slurm Integration
    1. 9.1. Application Management
    2. 9.2. Built-in Applications
  12. 10. Reference
    1. 10.1. Configuration
    2. 10.2. CLI
  13. 11. Appendix
    1. 11.1. Built-in Application Index
  14. UCloud/IM for Kubernetes
  15. 12. Installation
  16. 13. Architecture and Networking
  17. 14. Filesystem Integration
  18. 15. Compute Jobs
    1. 15.1. Public Links
    2. 15.2. Public IPs
    3. 15.3. License Servers
    4. 15.4. SSH Servers
    5. 15.5. Job Audit Log
    6. 15.6. Virtual machines
  19. 16. Integrated applications
    1. 16.1. Syncthing
    2. 16.2. Integrated terminal
  20. 17. UCX applications
    1. 17.1. Hello world
    2. 17.2. Data binding
    3. 17.3. UI events
    4. 17.4. Component reference
    5. 17.5. API reference
  21. 18. Reference
    1. 18.1. Configuration
    2. 18.2. CLI
  22. Frontend development
  23. 19. Frontend description and development guidelines
  24. Branding for UCloud
  25. 20. Branding and identity for UCloud
  26. H: Procedures
  27. 21. H: Procedures
  28. 22. H: Introduction
  29. 23. H: Auditing
  30. 24. H: Auditing scenario
  31. 25. H: GitHub actions
  32. 26. H: Deployment
  33. 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