Optional String Validation
Learn how optional string validation works in NLG Form using OptionalString, conditional rule execution, blank value skipping, and reusable validation pipelines.
OptionalString allows string validation rules to run only when the string contains a meaningful value.
This makes it possible to validate optional text input without forcing users to provide the field.
It is especially useful for:
- profile updates
- PATCH APIs
- optional descriptions
- nicknames
- social links
- optional metadata
- partial onboarding
- admin forms
- settings pages
Validation Philosophy
Unlike required validation, optional validation skips nested rules entirely when the value is considered empty.
This allows schemas to naturally support optional fields without additional conditional logic.
Example:
optional.OptionalString(
UserForm.Nickname,
rules.MinLen(UserForm.Nickname, 3),
)Validation behavior:
| Input | Result |
|---|---|
"" | skipped |
" " | skipped |
"ab" | invalid |
"john" | valid |
TL;DR
| Helper | Description |
|---|---|
optional.OptionalString(field, rules...) | Runs nested rules only when the string is non-empty |
optional.OptionalStringTrim(field, rules...) | Trims whitespace before evaluating emptiness |
optional.OptionalStringWith(field, fn) | Runs custom conditional string validation |
optional.OptionalStringValue(field, value, rules...) | Runs rules only when the string equals a specific value |
Defining Optional String Fields
Optional string validation starts with regular string field definitions.
OptionalForm := struct {
Nickname form.StringField[OptionalStringRequest]
Bio form.StringField[OptionalStringRequest]
Email form.StringField[OptionalStringRequest]
}{
Nickname: form.Str[OptionalStringRequest]("nickname", func(r *OptionalStringRequest) string {
return r.Nickname
}),
Bio: form.Str[OptionalStringRequest]("bio", func(r *OptionalStringRequest) string {
return r.Bio
}),
Email: form.Str[OptionalStringRequest]("email", func(r *OptionalStringRequest) string {
return r.Email
}),
}Optional validation wraps normal validation rules.
OptionalString
OptionalString skips nested validation rules when the string is blank.
Example:
optional.OptionalString(
OptionalForm.Nickname,
rules.MinLen(OptionalForm.Nickname, 3),
)Validation flow:
empty string
↓
validation skippednon-empty string
↓
nested rules executedTypical use cases:
- optional nicknames
- optional profile fields
- optional labels
- optional comments
OptionalStringTrim
OptionalStringTrim trims whitespace before checking whether the value is empty.
Example:
optional.OptionalStringTrim(
OptionalForm.Bio,
rules.MaxLen(OptionalForm.Bio, 200),
)Validation behavior:
| Input | Result |
|---|---|
"" | skipped |
" " | skipped |
"hello" | validated |
This is useful for frontend form input where users may accidentally submit whitespace-only values.
OptionalStringWith
OptionalStringWith allows fully custom conditional validation logic.
Example:
optional.OptionalStringWith(
OptionalForm.Email,
func(v string) bool {
return strings.Contains(v, "@company.com")
},
rules.Email(OptionalForm.Email),
)This helper is useful for:
- feature-flagged validation
- domain-restricted input
- custom conditional validation flows
- advanced business rules
OptionalStringValue
OptionalStringValue only executes validation rules when the string matches a specific value.
Example:
optional.OptionalStringValue(
OptionalForm.Type,
"business",
rules.Required(OptionalForm.CompanyName),
)Typical use cases:
- conditional form sections
- account type validation
- business onboarding flows
- feature-dependent validation
Combining Optional Rules
Optional validation can wrap multiple validation rules.
Example:
optional.OptionalString(
OptionalForm.Nickname,
rules.MinLen(OptionalForm.Nickname, 3),
rules.MaxLen(OptionalForm.Nickname, 20),
rules.Regex(OptionalForm.Nickname, nicknameRegex),
)This creates reusable optional validation pipelines.
Complete Example
schema.go
package main
import (
"regexp"
"github.com/netlifeguru/form"
"github.com/netlifeguru/form/optional"
"github.com/netlifeguru/form/rules"
)
const (
CodeNicknameMinLen = form.Code("nickname_min_len")
CodeBioMaxLen = form.Code("bio_max_len")
CodeSlugRegex = form.Code("slug_regex")
)
type OptionalStringRequest struct {
Nickname string `json:"nickname"`
Bio string `json:"bio"`
Email string `json:"email"`
Slug string `json:"slug"`
}
func OptionalStringSchema() form.Schema[OptionalStringRequest] {
OptionalForm := struct {
Nickname form.StringField[OptionalStringRequest]
Bio form.StringField[OptionalStringRequest]
Email form.StringField[OptionalStringRequest]
Slug form.StringField[OptionalStringRequest]
}{
Nickname: form.Str[OptionalStringRequest]("nickname", func(r *OptionalStringRequest) string {
return r.Nickname
}),
Bio: form.Str[OptionalStringRequest]("bio", func(r *OptionalStringRequest) string {
return r.Bio
}),
Email: form.Str[OptionalStringRequest]("email", func(r *OptionalStringRequest) string {
return r.Email
}),
Slug: form.Str[OptionalStringRequest]("slug", func(r *OptionalStringRequest) string {
return r.Slug
}),
}
slugRegex := regexp.MustCompile(`^[a-z0-9-]+$`)
return form.Schema[OptionalStringRequest]{
optional.OptionalString(
OptionalForm.Nickname,
rules.MinLenWithCode(OptionalForm.Nickname, 3, CodeNicknameMinLen),
),
optional.OptionalStringTrim(
OptionalForm.Bio,
rules.MaxLenWithCode(OptionalForm.Bio, 20, CodeBioMaxLen),
),
optional.OptionalString(
OptionalForm.Email,
rules.Email(OptionalForm.Email),
),
optional.OptionalString(
OptionalForm.Slug,
rules.RegexWithCode(OptionalForm.Slug, slugRegex, CodeSlugRegex),
),
}
}main.go
package main
import (
"encoding/json"
"fmt"
"log/slog"
"net/http"
"os"
"github.com/netlifeguru/form"
"github.com/netlifeguru/form/httpform"
"github.com/netlifeguru/router"
)
func main() {
r := router.New()
r.HandleFunc("/optional-string", "POST", func(w http.ResponseWriter, req *http.Request, ctx *router.Context) {
var in OptionalStringRequest
if !httpform.BindAndValidate(w, req, &in, OptionalStringSchema(), 1<<20) {
fmt.Println("optional string validation failed")
return
}
fmt.Println("optional string validation passed:", in)
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]any{
"message": "optional string validation passed",
"data": in,
})
})
validPayload := map[string]any{
"nickname": "john",
"bio": "short bio",
"email": "john@example.com",
"slug": "hello-world",
}
invalidPayload := map[string]any{
"nickname": "ab",
"bio": "this bio is definitely too long",
"email": "invalid-email",
"slug": "Hello World!",
}
skippedPayload := map[string]any{
"nickname": "",
"bio": "",
"email": "",
"slug": "",
}
fmt.Println("\n--- Valid request ---")
form.SendTestPost(":8080/optional-string", validPayload)
fmt.Println("\n--- Invalid request ---")
form.SendTestPost(":8080/optional-string", invalidPayload)
fmt.Println("\n--- Skipped validation request ---")
form.SendTestPost(":8080/optional-string", skippedPayload)
if err := r.ListenAndServe(8080); err != nil {
slog.Error("failed to start server", "error", err)
os.Exit(1)
}
}Notes
- Optional validation skips nested rules when the string is blank.
OptionalStringTrimtreats whitespace-only values as empty.- Optional validation is useful for PATCH APIs and partial updates.
- Optional validation can wrap any regular string validation rule.
- Optional validation keeps schemas reusable and transport-independent.
- Empty values are skipped intentionally and are not considered validation failures.
- Optional validation is composable and works naturally with conditional validation pipelines.
Optional Validation
Learn how optional validation works in NLG Form using OptionalString, OptionalPtr, OptionalSlice, nullable fields, and conditional validation pipelines.
Optional Integer Validation
Learn how optional integer validation works in NLG Form using nullable integer fields, OptionalInt, minimum, maximum, positivity, and range validation.