Question

Struct Tags in Go: What They Are and How They’re Used

gostructreflectiontags

Question

In the Go language specification, struct fields may include an optional string literal called a tag. The specification says these tags are exposed through reflection but are otherwise ignored by the language itself.

For example:

// A struct corresponding to the TimeStamp protocol buffer.
// The tag strings define the protocol buffer field numbers.
type TimeStamp struct {
    Microsec  uint64 `field:"1"`
    ServerIP6 uint64 `field:"2"`
    Process   string `field:"3"`
}

What are struct tags used for in Go, and why are they useful in practice?

Short Answer

By the end of this page, you will understand what struct tags are in Go, why the Go compiler mostly ignores them, and how libraries use reflection to read them. You will also see common uses such as JSON encoding, database mapping, validation, and configuration parsing.

Concept

Struct tags in Go are metadata attached to struct fields.

A struct field can be followed by a string literal, usually written with backticks:

type User struct {
    Name string `json:"name"`
}

The Go language itself does not give built-in meaning to most tags. Instead:

  • the tag is stored with the field definition
  • packages can inspect it using the reflect package
  • those packages decide what the tag means

That is why the specification says tags are "made visible through a reflection interface but are otherwise ignored."

Why this matters

Struct tags let you keep data structure definitions and mapping rules together.

Without tags, libraries would need separate configuration to answer questions like:

  • What JSON key should this field use?
  • Should this field be skipped if empty?
  • Which database column matches this field?
  • Is this field required?

Tags make those rules compact and close to the code they affect.

Common examples

JSON serialization

type User struct {
    FirstName string `json:"first_name"`
    Password  string `json:"-"`
}

Here a JSON package can read the tags and decide:

  • FirstName becomes first_name in JSON
  • Password should be omitted completely

Validation

type SignupForm struct {
    Email    string `validate:"required,email"`
    Password string `validate:"required,min=8"`
}

A validation library can read these tags and enforce rules.

Database mapping

type Product struct {
    ID    int     `db:"id"`
    Name  string  `db:"name"`
    Price float64 `db:"price"`
}

A database library can map SQL result columns to struct fields.

Important idea

A tag has meaning only if some code reads it and acts on it. The tag itself does nothing automatically.

For example, json:"name" matters to the encoding/json package. A completely custom tag like ui:"hidden" does nothing unless your own code or a library reads it.

Mental Model

Think of a struct tag like a sticky note attached to a field.

The field is the actual data box. The tag is a note that says something about how another tool should treat that box.

For example:

  • the JSON tool reads the note and says, "I should export this field as first_name"
  • a validator reads the note and says, "This field is required"
  • a database mapper reads the note and says, "This matches the user_id column"

The Go runtime stores the sticky note, but Go itself does not usually act on it. Other packages choose whether to read it.

Take Quiz

Syntax and Examples

Basic syntax

Struct tags are string literals placed after a field declaration.

type Person struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}

The conventional format is a space-separated list of key:"value" pairs inside a raw string literal.

`json:"name,omitempty" db:"full_name" validate:"required"`

Example: using encoding/json

package main

import (
    "encoding/json"
    "fmt"
)

type User struct {
    ID       int    `json:"id"`
    Name     string `json:"name"`
    Password string `json:"-"`
}

func main() {
    user := User{
        ID:       1,
        Name:     "Ava",
        Password: "secret123",
    }

    data, _ := json.Marshal(user)
    fmt.Println(string(data))
}

Output:

{"id":1,"name":"Ava"}

What happened?

  • ID became id
  • Name became name
  • Password was skipped because of json:"-"

Example: reading tags with reflection

package main

import (
    "fmt"
    "reflect"
)

type User struct {
    Name  string `json:"name" validate:"required"`
    Email string `json:"email" validate:"required,email"`
}

func main() {
    t := reflect.TypeOf(User{})

    for i := 0; i < t.NumField(); i++ {
        field := t.Field(i)
        fmt.Println("Field:", field.Name)
        fmt.Println("  json tag:", field.Tag.Get("json"))
        fmt.Println("  validate tag:", field.Tag.Get("validate"))
    }
}

This prints the metadata attached to each field.

Tag format note

Go provides reflect.StructTag to work with tags. The standard convention is:

`key:"value" key2:"value2"`

If you use a non-standard format, some tools may not read it correctly.

Step by Step Execution

Consider this example:

package main

import (
    "encoding/json"
    "fmt"
)

type Product struct {
    Name  string  `json:"name"`
    Price float64 `json:"price,omitempty"`
}

func main() {
    p := Product{Name: "Pen"}
    data, _ := json.Marshal(p)
    fmt.Println(string(data))
}

Step by step

1. The struct type is defined

Product has two fields:

  • Name with tag json:"name"
  • Price with tag json:"price,omitempty"

These tags are stored as metadata.

2. A value is created

p := Product{Name: "Pen"}

At this point:

  • Name is "Pen"
  • Price is 0 because it was not set

3. json.Marshal inspects the struct

The encoding/json package uses reflection to:

  • examine the struct fields
  • read each field's json tag
  • decide what output keys to use

4. The package processes Name

The tag says:

`json:"name"`

So the JSON key becomes name.

5. The package processes Price

The tag says:

`json:"price,omitempty"`

omitempty means: if the field has the zero value, leave it out.

Since Price is 0, it is omitted.

6. Final output is generated

{"name":"Pen"}

If Price were set:

p := Product{Name: "Pen", Price: 1.5}

then the output would be:

{"name":"Pen","price":1.5}

Real World Use Cases

1. JSON APIs

Very common in web servers and REST APIs.

type APIResponse struct {
    UserID   int    `json:"user_id"`
    UserName string `json:"user_name"`
}

This lets your Go naming style stay idiomatic while your API uses a different format.

2. Database row mapping

Libraries often map columns to fields using tags.

type Order struct {
    ID        int    `db:"id"`
    Customer  string `db:"customer_name"`
    CreatedAt string `db:"created_at"`
}

Useful when database column names and Go field names do not match exactly.

3. Validation of incoming data

type LoginRequest struct {
    Email    string `validate:"required,email"`
    Password string `validate:"required"`
}

Validation libraries can reject bad input before business logic runs.

4. XML, YAML, TOML, and configuration files

Different serializers use their own tag keys:

  • xml
  • yaml
  • toml
  • mapstructure

This is common in config-driven applications.

5. ORMs and query builders

Some libraries use tags to declare primary keys, ignored fields, column names, or table behavior.

6. Custom internal tools

Teams often create their own tags for:

  • audit logging
  • UI form generation
  • documentation generation
  • permission rules
  • CLI flag mapping

Again, the tag matters only if custom code reads it.

Take Quiz

Real Codebase Usage

In real Go projects, struct tags are usually combined with predictable patterns.

DTOs and API models

Developers often define request and response structs with JSON tags:

type CreateUserRequest struct {
    Email string `json:"email" validate:"required,email"`
    Name  string `json:"name" validate:"required"`
}

This keeps API parsing and validation close together.

Guard clauses after decoding

A common pattern is:

  1. decode JSON into a tagged struct
  2. validate fields
  3. return early if invalid
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
    return err
}

if req.Email == "" {
    return errors.New("email is required")
}

Multiple tags on one field

Real code often uses several libraries on the same struct:

type User struct {
    ID    int    `json:"id" db:"id"`
    Email string `json:"email" db:"email" validate:"required,email"`
}

This is convenient, but too many responsibilities in one struct can become hard to manage.

Separate structs for separate layers

Large codebases sometimes avoid reusing one tagged struct everywhere. They may use:

  • API structs with json tags
  • DB structs with db tags
  • domain structs with little or no tagging

This avoids coupling one layer too tightly to another.

Reflection cost awareness

Because tags are read with reflection, some performance-sensitive code caches metadata instead of re-reading it repeatedly. Many mature libraries already do this internally.

Custom tag readers

Teams sometimes build utilities like:

  • automatic query parameter binding
  • environment variable loaders
  • CLI option mapping
  • custom serialization rules

These tools inspect tags with reflect.Type and reflect.StructField.

Common Mistakes

1. Thinking tags do something by themselves

A tag alone has no behavior.

type User struct {
    Name string `mytag:"important"`
}

This does nothing unless a package reads mytag.

2. Expecting unexported fields to work with many libraries

Many reflection-based packages only work with exported fields.

Broken example:

type User struct {
    name string `json:"name"`
}

Even with a tag, name is unexported, so encoding/json will ignore it.

Correct:

type User struct {
    Name string `json:"name"`
}

3. Using invalid tag formatting

Broken example:

type User struct {
    Name string `json:name`
}

Correct format:

type User struct {
    Name string `json:"name"`
}

4. Forgetting that omitempty uses zero values

type Product struct {
    Price int `json:"price,omitempty"`
}

If Price is 0, it will be omitted. That may or may not be what you want.

This is a common source of confusion when 0, false, or "" are meaningful values.

5. Overloading one struct with too many concerns

type User struct {
    ID    int    `json:"id" db:"id" xml:"id" yaml:"id" validate:"required"`
    Email string `json:"email" db:"email" validate:"required,email"`
}

This can be practical, but it can also make the struct harder to maintain.

6. Assuming all libraries interpret tags the same way

json:"-" has a specific meaning for the JSON package. Another package may use different options and rules.

Always check the documentation for the specific library.

7. Using spaces or strange punctuation incorrectly in tags

Tags should follow the expected key:"value" structure. If formatting is off, Tag.Get(...) may return an empty string or a library may ignore the tag.

Comparisons

Struct tags vs regular field names

ApproachWhat it doesWhen usefulLimitation
Field name onlyUses Go field name as-isSimple casesExternal names may not match
Struct tagAdds metadata for librariesJSON, DB, validation, configNeeds reflection-aware code

Struct tags vs separate configuration

ApproachProsCons
Struct tagsClose to the code, compact, easy to readCan clutter structs
Separate config files or mappingsKeeps structs cleaner, can be centralizedHarder to track and maintain

Built-in language behavior vs library-defined behavior

FeatureControlled by Go itself?Example
Field typeYesName string
Field visibilityYesName vs name
Struct tag meaningUsually nojson:"name", db:"id"

Tags in different packages

Tag keyCommon useExample
jsonJSON encoding/decodingjson:"user_name,omitempty"
xmlXML encoding/decodingxml:"user"
dbDatabase column mappingdb:"user_id"
validateField validation rulesvalidate:"required"
yamlYAML parsingyaml:"port"

The important point is that the key name and options are package-specific.

Take Quiz

Cheat Sheet

Quick reference

Basic syntax

type Example struct {
    Field string `key:"value"`
}

Multiple tags

type User struct {
    Name string `json:"name" db:"full_name" validate:"required"`
}

Read a tag with reflection

field.Tag.Get("json")

Common JSON tag values

`json:"name"`           // use "name" as JSON key
`json:"name,omitempty"` // omit if zero value
`json:"-"`              // ignore field

Rules to remember

  • Tags are metadata on struct fields.
  • Go stores them, but most meaning comes from libraries.
  • Reflection is used to inspect them.
  • Many libraries only use exported fields.
  • Tag format is usually key:"value".
  • Different packages interpret tags differently.

Common zero values relevant to omitempty

  • 0 for numbers
  • false for booleans
  • "" for strings
  • nil for pointers, slices, maps, interfaces

Best practices

  • Use tags when external names differ from Go field names.
  • Keep tag usage consistent across a codebase.
  • Avoid putting too many unrelated concerns in one struct.
  • Check library docs for exact tag behavior.

FAQ

What are struct tags in Go?

Struct tags are string-based metadata attached to struct fields. Libraries can read them with reflection to control behavior such as JSON encoding, validation, or database mapping.

Does Go itself use struct tags?

Mostly no. Go stores them, but the language does not usually act on them directly. Packages and frameworks decide what they mean.

How do I read a struct tag in Go?

Use the reflect package. A field's tag can be accessed with field.Tag.Get("key").

Why are backticks used for struct tags?

Backticks create a raw string literal, which makes it easier to write quotes inside the tag like json:"name".

Can I define my own struct tags?

Yes. You can create any tag key you want, such as config:"port", as long as your code or a library reads it.

Why is my JSON tag not working?

A common reason is that the field is unexported. For example, name string will be ignored by encoding/json, even if it has a json tag.

Can one field have multiple struct tags?

Yes. A single field can include several tags for different tools, such as json, db, and validate.

Are struct tags required in Go?

No. They are optional. Use them when metadata helps libraries map or process your struct fields.

Take Quiz

Mini Project

Description

Build a small Go program that converts a struct into JSON and also inspects its tags with reflection. This demonstrates the two most important ideas: tags affect library behavior only when a package reads them, and you can also read tags yourself.

Goal

Create a program that prints JSON output from a tagged struct and then lists each field's tag metadata.

Requirements

  • Define a struct with at least three exported fields.
  • Add json tags to control the JSON key names.
  • Ignore one field in JSON output using a tag.
  • Use encoding/json to marshal the struct.
  • Use reflect to print the json tag for each field.
Take Quiz

Keep learning

Related questions

View more