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
reflectpackage - 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:
FirstNamebecomesfirst_namein JSONPasswordshould 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_idcolumn"
The Go runtime stores the sticky note, but Go itself does not usually act on it. Other packages choose whether to read it.
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?
IDbecameidNamebecamenamePasswordwas skipped because ofjson:"-"
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:
Namewith tagjson:"name"Pricewith tagjson:"price,omitempty"
These tags are stored as metadata.
2. A value is created
p := Product{Name: "Pen"}
At this point:
Nameis"Pen"Priceis0because 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
jsontag - 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:
xmlyamltomlmapstructure
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.
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:
- decode JSON into a tagged struct
- validate fields
- 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
jsontags - DB structs with
dbtags - 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
| Approach | What it does | When useful | Limitation |
|---|---|---|---|
| Field name only | Uses Go field name as-is | Simple cases | External names may not match |
| Struct tag | Adds metadata for libraries | JSON, DB, validation, config | Needs reflection-aware code |
Struct tags vs separate configuration
| Approach | Pros | Cons |
|---|---|---|
| Struct tags | Close to the code, compact, easy to read | Can clutter structs |
| Separate config files or mappings | Keeps structs cleaner, can be centralized | Harder to track and maintain |
Built-in language behavior vs library-defined behavior
| Feature | Controlled by Go itself? | Example |
|---|---|---|
| Field type | Yes | Name string |
| Field visibility | Yes | Name vs name |
| Struct tag meaning | Usually no | json:"name", db:"id" |
Tags in different packages
| Tag key | Common use | Example |
|---|---|---|
json | JSON encoding/decoding | json:"user_name,omitempty" |
xml | XML encoding/decoding | xml:"user" |
db | Database column mapping | db:"user_id" |
validate | Field validation rules | validate:"required" |
yaml | YAML parsing | yaml:"port" |
The important point is that the key name and options are package-specific.
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
0for numbersfalsefor booleans""for stringsnilfor 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.
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
jsontags to control the JSON key names. - Ignore one field in JSON output using a tag.
- Use
encoding/jsonto marshal the struct. - Use
reflectto print thejsontag for each field.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.