Browse all documentation

Fields

Define document fields, choose their inputs, and add validation, permissions, and custom behavior.

Fields describe the content in a document: a title, a price, a list of links, or a reference to another document. Define them in Go, and Ridu uses that definition to build the admin form, validate saved values, and generate the types your application uses.

Define a field

Start with a field type and a name, then add options. In this example, title is required text and status is a choice that starts as draft:

content/posts.go
package content

import (
  "github.com/riducms/ridu"
  "github.com/riducms/ridu/field"
)

var Posts = ridu.Collection{
  Slug: "posts",
  Fields: field.Fields{
    field.Text("title").Required(),
    field.Select("status", "draft", "published").Default("draft"),
  },
}

Add Posts to your Go config’s Collections list. The API returns properties named title and status. Use .Label(...) to change a label and .Admin(field.Admin{Description: ...}) to add help text without changing those property names.

A clean overview of representative Ridu field controls: text, select, array, relationship, tabs, and rich text.

Choose field names carefully

Start a field name with a lowercase letter, then use letters, numbers, or underscores. Each name must be unique inside its collection, group, or row. Ridu reserves id, createdAt, updatedAt, and deletedAt. Nested fields have paths such as seo.description; an error on the third link’s label has a path such as links.2.label.

Renaming a field changes its API property and can require a data migration. Use .Label(...) when you only want to change the text beside its input.

Choose a built-in field

Choose the type that matches your content. Each linked page shows an example and how it appears in the admin.

Group Field pages Value
Basic fields Text, Textarea, Email, Code, Date, Number, Checkbox, Select, Radio, Slug Text, numbers, true/false values, and fixed choices
Structured JSON, Point, Group, Array, Blocks, Tabs JSON values, objects, repeated rows, and rows with different field layouts
Relationship and media Relationship, Upload Links to other documents or uploaded files
Layout Row, Collapsible, UI Organize the form without adding stored properties
Computed and plugin Join, Virtual, Plugin Computed values or a custom field type

Text, numbers, and choices

Use Text for a short value and Textarea for several lines. Code adds syntax highlighting; Email checks an email address. Number stores a number, and Checkbox stores true or false. Select and Radio offer fixed choices; Select can also allow several choices. Date supports a date, time, or timestamp. Slug creates a URL-friendly value from another field.

Lists of text or numbers

Use field.TextList("sellingPoints").MaxLength(120) for []string and field.NumberList("availableSizes").Min(0) for []float64. Their MinRows and MaxRows constrain item counts; text lengths and numeric bounds apply to each item. Order and duplicates are preserved. Use Array when rows need child fields, or MultiSelect for fixed choices. See Text and number lists for empty/null values, callbacks, and queries.

Slugs

go
field.Text("title").Required(),
field.Slug("slug", "title"),

Slugs are required, unique, indexed, non-localized strings. Generated values follow the source until an author supplies a manual value. Read Slug for ASCII normalization and migration implications.

Add options and behavior

Chain methods to configure a field: field.Number("priority").Label("Priority").Min(0) adds a label and rejects negative numbers. Each method returns an updated copy, so reusing a field does not change its original definition. Put your fields in a field.Fields list.

Configuration Purpose
.Label(...), .LabelTranslations(...) Labels and translations shown in the admin
.Required(), .Unique(), .Index() Required values, uniqueness, and indexes
.Default(...), .DefaultFrom(...) Fixed or calculated initial values
.Localized() A separate value for each content language
.Access(field.Access{...}) Who can create, read, or change the field
.Hooks(field.Hooks[T]{...}), .AfterRead(callbacks...) Change a value or run code when a document is read or changed
.Validate(rule) Check a value before saving
.Admin(field.Admin{...}) Input components, row labels, visibility, and layout

Calling .Admin(...), .Hooks(...), or .Access(...) replaces that group of settings. To adjust a reusable field without losing its existing settings, use .EditAdmin(...), .AppendHooks(...), or .RestrictAccess(...). Hiding an input does not disable validation or change who can read or write it.

Custom inputs

Use your own Svelte component when a field needs a different input, a character counter, or extra information beside the control. For example, this text field uses a component named app:titleCounter:

content/posts.go
field.Text("title").
  Required().
  Admin(field.Admin{
    Editor: field.Component("app:titleCounter"),
  })

The Field components tutorial shows the complete component, where to register it, and how to try it in the admin. It starts with a title that counts characters as you type.

You can customize text, textarea, email, date, code, number, checkbox, text-list, and number-list inputs this way. Your component changes the editing UI; the field’s Go validation and permissions still apply.

More ways to customize fields

  • Use Row labels to show a useful summary on an array or block row.
  • Use Table cells to change how a saved value appears in a collection list.
  • Build a field plugin when you need a new data format, server validation, or an editor for a field that cannot use an application component.

A plugin can provide a renderer selected with field.PluginComponent, or a new field type selected with field.Plugin. The plugin guide explains the Go and Svelte parts together. For other admin changes, start with Custom components.

Defaults and validation

Use .Default(...) for a fixed initial value, such as "draft", or .DefaultFrom(callback) to choose one from request information, such as the content locale. Defaults fill omitted fields when creating a document and can also fill children of a new object or array row during an update. They leave supplied values and retained fields alone. Dynamic defaults run on save and do not prefill unsaved forms. See Set default field values for examples and the rules for nested and translated content.

Options such as .Required(), .Min(...), and .MaxLength(...) check values before saving, regardless of whether the change comes from the admin or an API. Documents saved before a field became required are checked once, by the migration or ridu dev synchronization that makes it required: it stops while a published or unversioned document has no value. See Making a field required.

For a rule specific to your application, attach .Validate(yourFunction). The Custom validation guide shows a complete URL validator, explains what to return, and walks through testing the result in the admin.

To show server feedback while the author edits, attach .LiveValidate(...) as well. Live server validation explains when checks run and how to share a rule between live feedback and save validation.

Show validation messages on child fields

A validator normally puts its message on the field it checks. A rule comparing several children or rows can point to the child that needs attention with operation.Issue.Target. See Put a message on a child field for group, array, block, and translation examples.

Use other field values in a callback

A field validator, hook, or access rule receives a context, usually called ctx. Use ctx.Siblings to read nearby fields, ctx.Root for top-level fields, and ctx.Prior for previously saved values. In an array, Siblings and Prior refer to the same row even when it has moved.

Start with Using other field values, which shows how to prevent a locked product code from changing.

Know which values a callback receives

Validators see the values that would be saved. Hooks can run before input is checked or while a response is being prepared. See Which values does a callback see? for the differences, including how to distinguish omitted input from an explicit null.

Identify the request and user

Use ctx.Operation, ctx.ID, and ctx.Actor for the operation, document, and signed-in user. See Identify the request and signed-in user for cancellation and logging details.

Compare translations

During a French update, the current and previously saved values both refer to French. An English fallback is not a previously saved French value. See Work with translations for single-locale and all-locales reads.

Look up a related document

Use ctx.Local.FindByID to check a document already selected in a field. It uses the callback’s user and transaction and checks read permissions. See Look up a related document.

Nested fields

Use Group for one known object, Array for ordered rows with the same fields, and Blocks for rows with different layouts. Named Tabs also create an object; unnamed tabs affect only presentation.

go
field.Group("seo", field.Fields{
  field.Text("title"),
  field.Textarea("description"),
}),
field.Array("links", field.Fields{
  field.Text("label").Required(),
  field.Text("url").Required(),
}).MaxRows(12),

Ridu builds the nested form and generates matching types. Use these fields when you know the properties your content needs; use JSON for data whose structure you cannot define in advance.

Arrange the edit form

Rows, collapsibles, unnamed tabs, and UI fields organize the form without nesting the stored values. Named tabs and Groups create objects in the saved document. Admin ReadOnly and Hidden settings control the form; use access rules to protect the data.

go
field.Row(field.Fields{
  field.Text("firstName").Admin(field.Admin{Columns: 6}),
  field.Text("lastName").Admin(field.Admin{Columns: 6}),
}),
field.Collapsible("advanced", field.Fields{
  field.JSON("providerMetadata"),
}).Admin(field.Admin{InitiallyCollapsed: true}),

The stored paths remain firstName, lastName, and providerMetadata.

Link to other documents

Use Relationship to select one or more documents, such as a post’s category. Use Upload to select a file from a media collection. Ridu checks that the user can read the selected documents. Option filters can further limit the choices.

The API normally returns the selected IDs. Ask for populated results when you need the related documents too. A Join finds documents that point back to this one, such as all posts in a category, without making you maintain a second list of IDs.

See Relationships for filtering, related-document queries, and deletion rules.

Add files and rich text

An Upload field selects a media document. Upload the actual file through the admin or an upload API call, then use that document’s ID in the field.

The official Rich-text plugin provides formatted text, links, images, and content blocks. It includes both Go validation and the Svelte editor.

Return computed values

Use a Virtual field for a value calculated by Go code, such as a display name assembled from other fields. The field appears in API responses but is not stored and cannot be set by a create or update request. Its resolver’s return type sets the output type: string, float64, bool, or store.Value for any JSON value.

Joins and virtual fields run when the response requests them. They are not editable inputs.

Translate field values

Configure your application’s content languages, then add .Localized() to fields that need a separate value in each language. Put it on a Group, Array, or Blocks field when the whole object or list should vary by language. This keeps fallback from mixing parts of different translations.

Translating the admin’s labels and descriptions is a separate setting from translating stored content. See Content localization and Admin language.

Reuse fields with their rules

Attach access rules and hooks to a field so they travel with it when you reuse it. In this example, both the primary link and footer link require a signed-in user to update the URL:

go
func Link(name string) field.GroupField {
  return field.Group(name, field.Fields{
    field.Text("label").Required(),
    field.Text("url").Required().Access(field.Access{
      Update: func(ctx operation.Context) (bool, error) {
        return ctx.Actor.ID != "", nil
      },
    }),
  })
}

primary := Link("primary")
footer := Link("footer").Label("Footer link")

To change one child of a reusable group, use field.EditChild:

go
func CompactLink(name string) (field.GroupField, error) {
  return field.EditChild(Link(name), "label", field.AsText,
    func(label field.TextField) field.TextField {
      return label.MaxLength(32)
    })
}

Text, Code, and Textarea share field.TextField and field.AsText. Select and Radio share field.SelectField and field.AsSelect. Each constructor retains its schema kind, control, and validation. A child editor can deliberately replace presentation within its family, such as returning field.Code(label.Name()); it must preserve the name and pass final schema validation. Date, Email, lists, and references have separate builders.

field.EditChild keeps the child’s existing settings while applying your change. field.ReplaceChild replaces the child entirely; field.AppendChild adds a child. These helpers return an updated group or array and an error if the edit is invalid. See Customize a reusable group for more examples.

Plugins that need to change several fields can use field.Fields.Edit and field.ChildrenDraft. For permission checks and code that runs during saves, continue with Access control and Hooks.

Change fields after saving data

Adding an optional field usually leaves existing data usable. Renaming or removing a field, changing its type, or making it required may need a data migration.

Run ridu migrate create, review the proposed changes, and test them against an isolated copy of your data before applying them. Development database sync does not replace production migrations. See Migrations for the workflow.

Common content examples

Need Prefer
Short or multi-line plain text Text or Textarea
Formatted portable document Official Rich text plugin
One known object / repeated one-shape rows / mixed rows Group / Array / Blocks
Fixed values / managed records Select / Relationship
Media reference Upload to an upload-enabled collection
Form layout without nesting API values Row, Collapsible, unnamed Tabs, Admin.Columns
Derived response value Virtual field with a Go function
Reusable new value kind Plugin with Go validation and an admin component

Choose the simplest field that fits your content. See the individual field pages for examples and the field API reference for every option.