# How to Implement Custom Codec Serializers in Kratos for Non-Standard Formats

> Implement custom Kratos codec serializers to support non-standard formats like applicationvndmyappprotobuf. Register your codec for automatic content negotiation.

- Repository: [Kratos/kratos](https://github.com/go-kratos/kratos)
- Tags: how-to-guide
- Published: 2026-03-02

---

**You can implement custom codec serializers in Kratos by creating a type that satisfies the `encoding.Codec` interface and registering it with `encoding.RegisterCodec`, enabling automatic content negotiation for any non-standard media type such as `application/vnd.myapp+protobuf` or `application/custom`.**

The `go-kratos/kratos` framework abstracts serialization behind a pluggable codec system defined in [`encoding/encoding.go`](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go). By implementing custom codec serializers, you can transparently handle proprietary binary formats, specialized JSON variants, or legacy wire protocols without modifying core transport logic in the HTTP or gRPC layers.

## Understanding the Codec Interface and Registry

The foundation of Kratos serialization is the **`encoding.Codec`** interface. This contract requires three methods: `Marshal(v any) ([]byte, error)` for serialization, `Unmarshal(data []byte, v any) error` for deserialization, and `Name() string` which returns the content subtype (e.g., "json", "protobuf", "custom").

The global registry in [`encoding/encoding.go`](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go) stores codec instances in a map keyed by the lower-cased result of `Name()`. The **`RegisterCodec`** function validates the codec and adds it to `registeredCodecs`, making it available to all transports.

## How Kratos Looks Up Custom Codecs

Kratos transports automatically select codecs based on HTTP headers or gRPC content types. The lookup mechanism extracts the subtype from headers and queries the registry.

### Request Decoding via CodecForRequest

In [`transport/http/codec.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go), the **`CodecForRequest`** function scans the `Content-Type` header, extracts the subtype using `httputil.ContentSubtype`, and calls `encoding.GetCodec` to obtain the matching codec. If none is found, it falls back to the built-in JSON codec.

```go
// transport/http/codec.go
func CodecForRequest(r *http.Request, name string) (encoding.Codec, bool) {
    for _, accept := range r.Header[name] {
        codec := encoding.GetCodec(httputil.ContentSubtype(accept))
        if codec != nil {
            return codec, true
        }
    }
    return encoding.GetCodec("json"), false
}

```

### Response Encoding via CodecForResponse

The **`CodecForResponse`** function performs the same lookup against the `Content-Type` or `Accept` headers to determine the appropriate encoder for responses.

```go
// transport/http/codec.go
func CodecForResponse(r *http.Response) encoding.Codec {
    codec := encoding.GetCodec(httputil.ContentSubtype(r.Header.Get("Content-Type")))
    if codec != nil {
        return codec
    }
    return encoding.GetCodec("json")
}

```

### Codec Registration Mechanism

The central registry implementation in [`encoding/encoding.go`](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go) handles the mapping:

```go
// encoding/encoding.go
var registeredCodecs = make(map[string]Codec)

func RegisterCodec(codec Codec) {
    if codec == nil {
        panic("cannot register a nil Codec")
    }
    if codec.Name() == "" {
        panic("cannot register Codec with empty string result for Name()")
    }
    contentSubtype := strings.ToLower(codec.Name())
    registeredCodecs[contentSubtype] = codec
}

```

## Step-by-Step: Creating a Custom Codec Serializer

Follow these steps to integrate a non-standard format into your Kratos application:

1. **Implement the Codec Interface** – Create a type satisfying `encoding.Codec` with `Marshal`, `Unmarshal`, and `Name` methods.
2. **Register the Codec** – Call `encoding.RegisterCodec` in an `init` function or during application bootstrap.
3. **Configure Content Negotiation** – Set the appropriate `Content-Type` or `Accept` headers to trigger automatic codec selection.
4. **Extend to gRPC (Optional)** – For gRPC transports, additionally register with `google.golang.org/grpc/encoding`.

## Complete Implementation Example

Below is a minimal custom codec that handles a fictional `application/custom` format.

```go
// custom_codec.go
package mycodec

import (
    "errors"
    "github.com/go-kratos/kratos/v2/encoding"
)

// customCodec implements the encoding.Codec interface.
type customCodec struct{}

// Marshal converts a Go value into the custom wire format.
func (customCodec) Marshal(v any) ([]byte, error) {
    s, ok := v.(string)
    if !ok {
        return nil, errors.New("customCodec only supports string payloads")
    }
    // prepend a marker to identify the format
    return []byte("CUST:" + s), nil
}

// Unmarshal parses the custom wire format back into a Go value.
func (customCodec) Unmarshal(data []byte, v any) error {
    ptr, ok := v.(*string)
    if !ok {
        return errors.New("customCodec only supports *string destination")
    }
    // strip the marker
    if len(data) < 5 || string(data[:5]) != "CUST:" {
        return errors.New("invalid custom payload")
    }
    *ptr = string(data[5:])
    return nil
}

// Name returns the media subtype used in HTTP headers.
func (customCodec) Name() string { return "custom" }

// init registers the codec when the package is imported.
func init() {
    encoding.RegisterCodec(customCodec{})
}

```

### Using the Codec in HTTP Handlers

Once registered, the codec activates automatically based on headers:

```go
func MyHandler(w http.ResponseWriter, r *http.Request) {
    // Expect the client to send Content-Type: application/custom
    var payload string
    if err := http.DefaultRequestDecoder(r, &payload); err != nil {
        http.DefaultErrorEncoder(w, r, err)
        return
    }

    // Echo back using the same format (Accept header influences response)
    resp := "received:" + payload
    if err := http.DefaultResponseEncoder(w, r, resp); err != nil {
        http.DefaultErrorEncoder(w, r, err)
    }
}

```

When the client sends:

```http
POST /my-endpoint HTTP/1.1
Content-Type: application/custom
Accept: application/custom

CUST:hello

```

the server decodes via `customCodec.Unmarshal` and encodes replies via `customCodec.Marshal`.

### Registering for gRPC Transport

For gRPC, register the codec with both Kratos and the standard gRPC encoding package:

```go
// grpc_custom.go
package mygrpc

import (
    "google.golang.org/grpc/encoding"
    kratosencoding "github.com/go-kratos/kratos/v2/encoding"
    "myproject/mycodec"
)

// init registers the codec for both transports.
func init() {
    // Kratos registration (already performed by mycodec init)
    // Register with gRPC using the same name.
    encoding.RegisterCodec(&grpcCodec{})
}

type grpcCodec struct{}

func (grpcCodec) Name() string { return mycodec.customCodec{}.Name() }

func (grpcCodec) Marshal(v any) ([]byte, error) {
    return mycodec.customCodec{}.Marshal(v)
}

func (grpcCodec) Unmarshal(data []byte, v any) error {
    return mycodec.customCodec{}.Unmarshal(data, v)
}

```

Now gRPC services can exchange the custom format via the same subtype (`custom`).

## Key Source Files in go-kratos/kratos

| File | Purpose |
|------|---------|
| [[`encoding/encoding.go`](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go)](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go) | Defines `Codec` interface and the global registry (`RegisterCodec`, `GetCodec`). |
| [[`transport/http/codec.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go)](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go) | Shows how HTTP transports query the registry based on `Content-Type` / `Accept`. |
| [[`transport/http/codec_test.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/codec_test.go)](https://github.com/go-kratos/kratos/blob/main/transport/http/codec_test.go) | Demonstrates registration of a mock codec and the effect on encoding/decoding. |
| [[`transport/grpc/codec.go`](https://github.com/go-kratos/kratos/blob/main/transport/grpc/codec.go)](https://github.com/go-kratos/kratos/blob/main/transport/grpc/codec.go) | Shows integration of Kratos codecs with the gRPC encoding subsystem. |

## Summary

- **Custom codec serializers** in Kratos implement the `encoding.Codec` interface with `Marshal`, `Unmarshal`, and `Name` methods.
- Register implementations via `encoding.RegisterCodec` in [`encoding/encoding.go`](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go) to make them available globally.
- HTTP transports in [`transport/http/codec.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go) automatically select codecs by inspecting `Content-Type` and `Accept` headers using `httputil.ContentSubtype`.
- For gRPC support, additionally register with `google.golang.org/grpc/encoding` to bridge Kratos codecs with the gRPC transport.
- This architecture enables transparent handling of non-standard formats without modifying core framework code.

## Frequently Asked Questions

### What is the Codec interface in Kratos?

The **Codec interface** is defined in [`encoding/encoding.go`](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go) and requires three methods: `Marshal(v any) ([]byte, error)` for serialization, `Unmarshal(data []byte, v any) error` for deserialization, and `Name() string` which returns the content subtype (e.g., "json", "custom"). All custom serializers must satisfy this contract to integrate with Kratos transports.

### How does Kratos determine which codec to use for an HTTP request?

Kratos determines the codec by examining the `Content-Type` header for requests and the `Accept` header for responses. In [`transport/http/codec.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go), the `CodecForRequest` function extracts the subtype using `httputil.ContentSubtype` and queries the registry via `encoding.GetCodec`. If no registered codec matches, it falls back to the built-in JSON codec.

### Can the same custom codec be used for both HTTP and gRPC transports?

Yes, but **gRPC requires additional registration**. While Kratos uses its own registry in [`encoding/encoding.go`](https://github.com/go-kratos/kratos/blob/main/encoding/encoding.go), the gRPC transport relies on the standard `google.golang.org/grpc/encoding` package. You must create a wrapper that implements both interfaces and register it with both `encoding.RegisterCodec` (for Kratos) and `grpc.encoding.RegisterCodec` (for gRPC) to ensure the custom format works across all transports.

### What happens if no codec is found for the requested content type?

If `encoding.GetCodec` returns nil because no registered codec matches the extracted subtype, the HTTP transport in [`transport/http/codec.go`](https://github.com/go-kratos/kratos/blob/main/transport/http/codec.go) falls back to the built-in JSON codec. This ensures the server can always respond, but it may result in encoding errors if the client expects a different format. Always verify that your custom codec is properly registered and the content type headers are correctly set to avoid unexpected fallback behavior.