Golang fmt.Formatter Interface
last modified May 8, 2025
This tutorial explains how to use the fmt.Formatter interface in Go.
We'll cover interface basics with practical examples of custom formatting.
The fmt.Formatter interface allows types to control their formatting. It provides more flexibility than the Stringer interface by supporting format verbs and flags.
In Go, fmt.Formatter is used when you need custom formatting
behavior beyond simple string conversion. It works with all fmt printing
functions like Printf and Sprintf.
Basic fmt.Formatter interface definition
The fmt.Formatter interface requires implementing one method.
This example shows the interface definition and basic implementation.
Note: The Format method gives you full control over formatting.
package main
import "fmt"
type Point struct {
X, Y int
}
func (p Point) Format(f fmt.State, verb rune) {
switch verb {
case 'v':
if f.Flag('+') {
fmt.Fprintf(f, "Point{X: %d, Y: %d}", p.X, p.Y)
} else {
fmt.Fprintf(f, "{%d %d}", p.X, p.Y)
}
case 's':
fmt.Fprintf(f, "(%d,%d)", p.X, p.Y)
default:
fmt.Fprintf(f, "%%!%c(Point=%d,%d)", verb, p.X, p.Y)
}
}
func main() {
p := Point{3, 4}
fmt.Printf("%v\n", p) // {3 4}
fmt.Printf("%+v\n", p) // Point{X: 3, Y: 4}
fmt.Printf("%s\n", p) // (3,4)
fmt.Printf("%d\n", p) // %!d(Point=3,4)
}
The Point type implements Format to handle different format verbs. It supports 'v' with + flag and 's' verb with custom formatting.
Handling width and precision
The fmt.State parameter provides access to formatting flags. This example shows how to handle width and precision specifications.
package main
import (
"fmt"
"strings"
)
type Banner string
func (b Banner) Format(f fmt.State, verb rune) {
switch verb {
case 's', 'v':
width, hasWidth := f.Width()
if hasWidth {
padded := strings.Repeat(string(b), width/len(b)+1)
fmt.Fprint(f, padded[:width])
} else {
fmt.Fprint(f, string(b))
}
default:
fmt.Fprintf(f, "%%!%c(Banner=%s)", verb, b)
}
}
func main() {
b := Banner("Go")
fmt.Printf("%s\n", b) // Go
fmt.Printf("%5s\n", b) // GoGoG
fmt.Printf("%.3s\n", b) // Go
fmt.Printf("%5.3s\n", b) // Go
}
The Banner type uses Width() to check for width specification. It repeats the banner text to fill the requested width when printing.
Custom flag handling
The fmt.State interface provides flag information. This example demonstrates custom flag handling in a formatter.
package main
import "fmt"
type Temperature float64
func (t Temperature) Format(f fmt.State, verb rune) {
switch verb {
case 'f', 'F':
prec, hasPrec := f.Precision()
if !hasPrec {
prec = 1
}
if f.Flag('+') {
fmt.Fprintf(f, "%+.*f°F", prec, float64(t))
} else {
fmt.Fprintf(f, "%.*f°C", prec, float64(t)*5/9)
}
default:
fmt.Fprintf(f, "%%!%c(Temperature=%f)", verb, t)
}
}
func main() {
temp := Temperature(32.0)
fmt.Printf("%f\n", temp) // 0.0°C
fmt.Printf("%+.2f\n", temp) // +32.00°F
fmt.Printf("%5.1f\n", temp) // 0.0°C
}
The Temperature type checks for '+' flag to switch between Fahrenheit and Celsius. It also handles precision specifications for decimal places.
Combining with Stringer interface
A type can implement both fmt.Stringer and fmt.Formatter. This example shows how they work together.
package main
import "fmt"
type Color struct {
R, G, B uint8
}
func (c Color) String() string {
return fmt.Sprintf("RGB(%d,%d,%d)", c.R, c.G, c.B)
}
func (c Color) Format(f fmt.State, verb rune) {
switch verb {
case 'v':
if f.Flag('#') {
fmt.Fprintf(f, "Color{R:0x%02x, G:0x%02x, B:0x%02x}", c.R, c.G, c.B)
} else {
fmt.Fprint(f, c.String())
}
case 's':
fmt.Fprint(f, c.String())
case 'x', 'X':
fmt.Fprintf(f, "%02x%02x%02x", c.R, c.G, c.B)
default:
fmt.Fprintf(f, "%%!%c(Color=%s)", verb, c.String())
}
}
func main() {
c := Color{255, 0, 128}
fmt.Println(c) // RGB(255,0,128)
fmt.Printf("%v\n", c) // RGB(255,0,128)
fmt.Printf("%#v\n", c) // Color{R:0xff, G:0x00, B:0x80}
fmt.Printf("%x\n", c) // ff0080
}
The Color type uses String() for simple string conversion. The Format method provides additional formatting options including hex output and struct-style printing with the # flag.
Formatting complex numbers
The fmt.Formatter can be used to create custom complex number formatting. This example shows polar coordinate representation.
package main
import (
"fmt"
"math"
)
type Polar complex128
func (p Polar) Format(f fmt.State, verb rune) {
r := real(p)
i := imag(p)
switch verb {
case 'v', 'f', 'g':
mag := math.Hypot(r, i)
ang := math.Atan2(i, r) * 180 / math.Pi
prec, hasPrec := f.Precision()
if !hasPrec {
prec = 2
}
fmt.Fprintf(f, "%.*f∠%.*f°", prec, mag, prec, ang)
default:
fmt.Fprintf(f, "%%!%c(Polar=%f+%fi)", verb, r, i)
}
}
func main() {
p := Polar(1 + 1i)
fmt.Printf("%v\n", p) // 1.41∠45.00°
fmt.Printf("%.3f\n", p) // 1.414∠45.000°
fmt.Printf("%.1g\n", p) // 1.4∠45.0°
}
The Polar type formats complex numbers in polar notation. It calculates magnitude and angle, then formats them with the specified precision.
Custom date formatting
Implementing fmt.Formatter allows flexible date formatting. This example shows custom date output with different verbs.
package main
import (
"fmt"
"time"
)
type MyDate time.Time
func (d MyDate) Format(f fmt.State, verb rune) {
t := time.Time(d)
switch verb {
case 'd':
fmt.Fprintf(f, "%02d/%02d/%04d", t.Day(), t.Month(), t.Year())
case 't':
fmt.Fprintf(f, "%02d:%02d:%02d", t.Hour(), t.Minute(), t.Second())
case 'v':
if f.Flag('+') {
fmt.Fprintf(f, time.Time(d).String())
} else {
fmt.Fprintf(f, "%s", t.Format("2006-01-02"))
}
default:
fmt.Fprintf(f, "%%!%c(MyDate=%s)", verb, t.Format(time.RFC3339))
}
}
func main() {
d := MyDate(time.Date(2023, 5, 15, 14, 30, 0, 0, time.UTC))
fmt.Printf("%d\n", d) // 15/05/2023
fmt.Printf("%t\n", d) // 14:30:00
fmt.Printf("%v\n", d) // 2023-05-15
fmt.Printf("%+v\n", d) // 2023-05-15 14:30:00 +0000 UTC
}
The MyDate type provides custom formatting for dates. It supports 'd' for date, 't' for time, and 'v' with + flag for detailed output.
Binary data formatting
The fmt.Formatter can format binary data in different representations. This example shows hex dump and binary output.
package main
import (
"fmt"
"strings"
)
type Binary []byte
func (b Binary) Format(f fmt.State, verb rune) {
switch verb {
case 'x', 'X':
for _, byteVal := range b {
fmt.Fprintf(f, "%02x", byteVal)
}
case 'b':
for _, byteVal := range b {
fmt.Fprintf(f, "%08b ", byteVal)
}
case 'v':
if f.Flag('#') {
fmt.Fprintf(f, "Binary{0x%x}", b)
} else {
fmt.Fprintf(f, "%x", b)
}
default:
fmt.Fprintf(f, "%%!%c(Binary=%x)", verb, b)
}
}
func main() {
data := Binary{0xDE, 0xAD, 0xBE, 0xEF}
fmt.Printf("%x\n", data) // deadbeef
fmt.Printf("%X\n", data) // DEADBEEF
fmt.Printf("%b\n", data) // 11011110 10101101 10111110 11101111
fmt.Printf("%#v\n", data) // Binary{0xdeadbeef}
}
The Binary type formats byte slices as hex or binary. The '#' flag triggers Go-syntax representation when used with 'v' verb.
Source
This tutorial covered the fmt.Formatter interface in Go with
practical examples of custom formatting for different types.
Author
List all Golang tutorials.