zoobzio December 26, 2025 Edit this page

Tag Reference

Transforms are configured through boundary-specific struct tags.

Tag Format

boundary.operation:"algorithm"
  • boundary - When the transform applies (receive, load, store, send)
  • operation - What transform to apply (hash, decrypt, encrypt, mask, redact)
  • algorithm - Which registered handler to use

Boundaries and Operations

BoundaryDirectionAvailable Operations
receiveExternal → Apphash
loadStorage → Appdecrypt
storeApp → Storageencrypt
sendApp → Externalmask, redact

receive.hash

Hashes the field when receiving external input. One-way, not reversible.

type User struct {
    Password string `receive.hash:"sha256"`
    Token    string `receive.hash:"argon2"`
}

Tag values:

ValueConstantHandler
sha256HashSHA256Built-in
sha512HashSHA512Built-in
argon2HashArgon2Built-in
bcryptHashBcryptBuilt-in

Behavior:

  • Hash field value
  • Store result (hex or PHC format)
  • Original value lost

store.encrypt

Encrypts the field when storing to database. Reversible with load.decrypt.

type User struct {
    Email string `store.encrypt:"aes"`
    SSN   string `store.encrypt:"rsa"`
}

Tag values:

ValueConstantHandler
aesEncryptAESRequires SetEncryptor
rsaEncryptRSARequires SetEncryptor
envelopeEncryptEnvelopeRequires SetEncryptor

Behavior:

  • Encrypt field value
  • Base64 encode for string fields
  • Store ciphertext

Registration required:

enc, _ := cereal.AES(key)
proc.SetEncryptor(cereal.EncryptAES, enc)

load.decrypt

Decrypts the field when loading from storage. Reverses store.encrypt.

type User struct {
    Email string `load.decrypt:"aes"`
}

Tag values: Same as store.encrypt.

Behavior:

  • Base64 decode for string fields
  • Decrypt ciphertext
  • Restore plaintext

Common pattern: Pair with store.encrypt:

Email string `store.encrypt:"aes" load.decrypt:"aes"`

send.mask

Partially masks the field when sending to external destinations.

type User struct {
    Email string `send.mask:"email"`
    SSN   string `send.mask:"ssn"`
    Phone string `send.mask:"phone"`
}

Tag values:

ValueConstantExample Output
ssnMaskSSN***-**-6789
emailMaskEmaila***@example.com
phoneMaskPhone(***) ***-4567
cardMaskCard************1111
ipMaskIP192.168.xxx.xxx
uuidMaskUUID550e8400-****-****-...
ibanMaskIBANGB82**************5432
nameMaskNameJ*** S****

Behavior:

  • Apply content-aware partial masking
  • Original value preserved (not reversible)
  • Built-in maskers require no registration

send.redact

Replaces the entire field value when sending to external destinations.

type User struct {
    Password string `send.redact:"***"`
    Token    string `send.redact:"[REDACTED]"`
    Secret   string `send.redact:""`
}

Tag value: The replacement string (can be empty).

Behavior:

  • Replace entire value with tag value
  • Original value lost
  • No registration required

Multiple Tags

Combine tags for different boundaries:

type User struct {
    // Full lifecycle: encrypt for storage, decrypt on load, mask for API
    Email string `store.encrypt:"aes" load.decrypt:"aes" send.mask:"email"`

    // Hash on receive, redact on send
    Password string `receive.hash:"argon2" send.redact:"***"`
}

Each tag applies at its specific boundary. They don't conflict.

Field Type Support

Tagstring[]byte
receive.hashYesYes
store.encryptYesYes
load.decryptYesYes
send.maskYesNo
send.redactYesNo

For string fields, encrypted values are base64 encoded.

Nested Structs

Tags apply to nested struct fields:

type Address struct {
    Street string `json:"street"`
    City   string `json:"city" send.redact:"[HIDDEN]"`
}

type User struct {
    Name    string  `json:"name" send.mask:"name"`
    Address Address `json:"address"` // City will be redacted
}

Both direct fields and nested fields are processed.

Pointer Fields

Tags work on pointer fields:

type User struct {
    Email *string `send.mask:"email"`
}

Nil pointers are skipped.

Slice and Map Fields

Tags apply to elements within slices and maps:

type User struct {
    Emails []string          `send.mask:"email"`
    Tags   map[string]string `send.redact:"***"`
}

Each element is transformed individually.

Validation

Call Validate() to check all tags have registered handlers:

type User struct {
    Email string `store.encrypt:"custom"` // Unknown algorithm
}

proc, _ := cereal.NewProcessor[User](json.New())
err := proc.Validate()
// err: missing encryptor for algorithm "custom" (field Email)

Validation checks:

  • store.encrypt and load.decrypt values have registered encryptors
  • receive.hash values have registered hashers (sha256/sha512 built-in)
  • send.mask values are valid built-in types
  • send.redact can be any string (no validation)

Override Interface Bypass

When override interfaces are implemented, corresponding tags are ignored:

type User struct {
    Email string `store.encrypt:"aes"` // Ignored if Encryptable implemented
}

func (u *User) Encrypt(encryptors map[cereal.EncryptAlgo]cereal.Encryptor) error {
    // Your implementation
}

The interface takes full responsibility for that transform type.

Invalid Tag Handling

Unknown boundaries or operations are ignored:

type User struct {
    Email string `unknown.operation:"value"` // Ignored
    Name  string `send.unknown:"value"`      // Ignored
}

Only valid boundary.operation combinations are processed.