Filters

Filters let you serialize a subset of a serializer’s attributes and associations. Narrowing the output reduces both the payload size and the work Panko does, and it lets you reuse one serializer for several endpoints instead of writing a tailored serializer for each.

There are two filters:

  • only: serialize only these attributes / associations.
  • except: serialize everything except these.

only and except

Pass only: or except: when constructing a serializer:

class UserSerializer < Panko::Serializer
  attributes :id, :name, :email
end

# => {"name" => "..."}
UserSerializer.new(only: [:name]).serialize(User.first)

# => {"id" => ..., "email" => "..."}
UserSerializer.new(except: [:name]).serialize(User.first)

The same options work on Panko::ArraySerializer:

Panko::ArraySerializer.new(User.all, each_serializer: UserSerializer, only: [:name])

Filtering associations

An association is filtered by its declared name (the first argument of has_one / has_many), never by its output key.

The two differ only when the association carries a name: alias. For example, given:

has_many :state_transitions, name: :history

the key to use in a filter is :state_transitions, the declared name, not :history:

PostSerializer.new(except: [:state_transitions])

The same declared name keys the association’s entry in a nested filter Hash.

Nested filters

To filter the attributes of associations too, pass a Hash instead of an array. The Hash uses instance for the current serializer’s own fields, and one key per association for that association’s fields.

Say you need posts with only their title, body, the author’s id, and the comments’ id:

posts = Post.all

Panko::ArraySerializer.new(posts, each_serializer: PostSerializer, only: {
  instance: [:title, :body, :author, :comments],
  author: [:id],
  comments: [:id]
})

Reading the only Hash:

  • instance: the attributes and associations to serialize for the current serializer (here, PostSerializer). Associations you want to keep (:author, :comments) must be listed here.
  • author, comments: the attributes to serialize for each association.

Nested filters are recursive: an association’s Hash can itself contain an instance key and further association keys. For example, if CommentSerializer has a has_one :author, you can serialize only each comment author’s name:

Panko::ArraySerializer.new(posts, each_serializer: PostSerializer, only: {
  instance: [:title, :body, :author, :comments],
  author: [:id],
  comments: {
    instance: [:id, :author],
    author: [:name]
  }
})

Inside comments, instance now refers to CommentSerializer’s own fields.

filters_for

When the same filtering logic recurs across your controllers, move it into the serializer as a self.filters_for(context, scope) class method. Panko calls it automatically on every serialization; you never call it yourself.

class UserSerializer < Panko::Serializer
  attributes :id, :name, :email

  def self.filters_for(context, scope)
    {only: [:name]}
  end
end

# => {"name" => "..."}
UserSerializer.new.serialize(User.first)

filters_for receives the context and scope passed to the serializer, so filtering can depend on them, for example exposing more fields to an admin:

class UserSerializer < Panko::Serializer
  attributes :id, :name, :email

  def self.filters_for(context, scope)
    if context[:user_role] == "admin"
      {only: [:id, :name, :email]}
    else
      {only: [:id, :name]}
    end
  end
end

UserSerializer.new(context: {user_role: "admin"}).serialize(User.first)
# => {"id" => ..., "name" => "...", "email" => "..."}

The result of filters_for is combined per key with any only / except passed to the constructor: a filters_for returning {except: [:address]} merges with a constructor only: [:name], applying both.

On the serializer you construct, filters_for runs on every serialization with that call’s context and scope. On a serializer used for an association, it runs once, when the parent is compiled, with nil context and scope, and a nested filter can only narrow its result further.

Filters declared on an association

has_one and has_many accept only: and except: too, fixed at declaration:

class UserSerializer < Panko::Serializer
  attributes :id
  has_many :comments, serializer: CommentSerializer, except: [:status]
end

A runtime nested filter for the same association applies on top of the declared one. It can narrow the association further, but cannot bring back a field the declaration filtered out.

See the original discussion: github.com/yosiat/panko_serializer/issues/16



Copyright © 2026 Panko Serializer. Distributed under the MIT License.

This site uses Just the Docs, a documentation theme for Jekyll.