# Blueprinter gives a Rails app JSON views without the view files

> Blueprinter is a Ruby library that turns business objects into JSON through blueprint classes, with the central idea being views, so the same object can be rendered differently for different consumers. It positions itself as a simpler and faster replacement for JBuilder or ActiveModelSerializers, and the repository has recently moved to the procore-oss organisation.

**procore-oss/blueprinter** — Simple, Fast, and Declarative Serialization Library for Ruby

- Repository: https://github.com/procore-oss/blueprinter
- Stars: 1,313 · Forks: 118
- Language: Ruby
- License: MIT
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/procore-oss-blueprinter

## A presenter layer, and what it replaces

Blueprinter is a JSON object presenter for Ruby that takes business objects and breaks them down into simple hashes and serializes them to JSON. It can be used in Rails in place of other serializers, and the readme names JBuilder and ActiveModelSerializers as the ones it is meant to replace.

The design commitment is that it is simple, direct and performant, and it heavily relies on the idea of views, which the documentation compares to Rails views, describing them as ways of predefining output for data in different contexts. That is the design in one sentence, and it is the reason the library is interesting rather than merely convenient: the hard part of serialization is rarely the first shape, it is the second and third shapes that every team ends up needing and that most serializers handle with conditionals.

The audience is therefore a Rails application whose models are rendered to more than one consumer. A public API, an internal admin tool and a mobile client all want the same user, and they want different things from it, and the view mechanism is how one blueprint serves all three.

The project is a Ruby gem, MIT licensed, with the documentation published on rubydoc.info under the gem name, a test workflow on GitHub Actions, a Discord channel, and a repository that carries the usual set of community files: a rubocop configuration, a Rakefile, a spec/ directory with the tests, a lib/ directory, a CHANGELOG, a contributing guide, a code of conduct, a security policy, and an EMERITUS.md file that records people who have stepped away from maintaining it.

## The repository moved, and your remote is now wrong

The readme opens with something that is not documentation but housekeeping, and it matters for anyone with a fork or a clone.

There is a recent organization move, and the instruction is to change your local remote so that you pull from the new repository:

```bash
git remote set-url [previous-remote-name] git@github.com:procore-oss/blueprinter.git
```

and to see what your previous upstream remote was called, you run:

```bash
git remote -v
```

The specifics are that the project now lives under the procore-oss organisation, and the README badge, the gemspec repository field and the topic list all point at the new location. For a user of the gem, nothing changes, since the gem is fetched from the package registry. For a contributor with a fork, it is a one line fix, and the alternative is the quiet failure mode where your fork keeps working against an upstream that no longer receives commits.

The gemspec is the other place this shows up, since the homepage, the code URL and the issues URL in the project metadata are all the procore-oss URLs now.

The move itself is a signal about governance. A library that has been adopted widely enough for a company to move it under its own organisation has a different kind of maintenance backing than an individual project, and the EMERITUS.md file, which records people who have stepped away, suggests the project has a history of maintainers rather than a single owner. Neither fact is a guarantee, but both are the kind of thing worth knowing when you are choosing a dependency for an API surface.

## Blueprints, identifiers and fields

The basic usage is a class and a render call. Given a user record with a list of attributes, you write a blueprint that subclasses Blueprinter::Base:

```ruby
class UserBlueprint < Blueprinter::Base
  identifier :uuid

  fields :first_name, :last_name, :email
end
```

and then render an instance of it:

```ruby
puts UserBlueprint.render(user) # Output is a JSON string
```

which produces a hash with the identifier and the three fields, and deliberately not the password attribute that was on the model. The whitelist nature of this is the point: what appears in the output is what the blueprint says, not what the model happens to expose, and a new column on the model cannot leak into the API by accident.

The identifier has two properties that set it apart from a field, and the documentation lists them. Identifiers are always rendered and are considered their own view, the identifier view. And when rendering, identifier fields are always sorted first, before the other fields. If either of those conveniences is not wanted, the documentation notes that you can simply create the identifier fields as regular fields instead.

That is a small but useful design decision, because a stable key at the front of a JSON document is the kind of thing clients depend on, and making it automatic means a client can index into the first field without knowing which field that is.

For collections, the same render method takes an array or a collection object, and the output is a JSON array of the same shapes. And the library can be configured to treat other classes as collections, for example Mongoid::Criteria objects, or a Set.

## Views are the idea the library is built on

Views are how one blueprint produces several outputs, and they are the feature to understand first.

A view is a named block inside the blueprint that declares fields and associations:

```ruby
class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end

  view :extended do
    include_view :normal
    field :address
    association :projects
  end
end
```

The extended view pulls in the normal view with include_view, and a view can include fields from another view with include_view and include_views. Rendering picks the view by name:

```ruby
puts UserBlueprint.render(user, view: :extended)
```

which gives a document with the identifier, the address, the first and last names and the renamed email, and no password and no other model attributes.

The mechanism has a consequence that is worth stating, because it is where these libraries differ. A view is a scope, not a filter, so the normal view and the extended view are not the same document with extra keys, they are two separately declared documents that happen to share a blueprint. The extended view explicitly includes the normal one, which makes the relationship visible in the code and makes the derived shape auditable. A serializer based on conditionals would give you the same result with the relationship hidden inside the conditions.

That is the difference between a view and a filter, and it is the reason the project leans on the idea of views so heavily.

## Renaming, root keys and metadata

Three more features sit on top of the blueprint, and each answers a question about the shape of the response.

Renaming is done with the name option, on both fields and associations:

```ruby
class UserBlueprint < Blueprinter::Base
  identifier :uuid

  field :email, name: :login

  association :user_projects, name: :projects
end
```

so the JSON key is login rather than email, and projects rather than user_projects. The renaming is the part of serialization that most often drifts between a model, a database column and a client, and having it in one place is the argument for a presenter layer at all.

A root key wraps the result in an object:

```ruby
puts UserBlueprint.render(user, view: :normal, root: :user)
```

which produces a document with a single user key holding the fields.

Metadata is added with the meta option, and it sits beside the root rather than inside it:

```ruby
json = UserBlueprint.render(user, view: :normal, root: :user, meta: {links: [
  'https://app.mydomain.com',
  'https://alternate.mydomain.com'
]})
```

so the output has a user object and a meta object holding an array of links. There is one rule here that is easy to trip over, and the documentation states it plainly: for meta attributes, a root is mandatory. The reasoning is structural, since the meta object is a sibling of the root, and without a root there is nothing for it to sit beside.

So the three features are complementary. Root for a single object envelope, meta for the pagination and links that an envelope carries, and renaming for the keys inside.

## Excluding fields per view, and what the library does not cover

The last feature in the documentation is exclusion, and it is per view:

```ruby
class UserBlueprint < Blueprinter::Base
  identifier :uuid
  field :email, name: :login

  view :normal do
    fields :first_name, :last_name
  end

  view :extended do
    include_view :normal
    field :address
    exclude :last_name
  end
end
```

so the extended view drops last name, and the excludes option excludes multiple fields at once inline. Exclusion is a field level operation scoped to a view, which is the right place for it, since the decision to omit a field is a decision about that particular output.

What the library does not cover is worth stating, because the readme's scope is precise. Blueprinter is a JSON object presenter. There is no mention of XML, MessagePack, YAML or any other output format, so if your application needs a second serialisation format this is not the library for it, or it is the JSON half of a pair of libraries. There is also no schema validation layer described: the shapes are declared in Ruby classes, and nothing checks that they match a published schema or a client's expectations.

The other limitation is the one every Rails project has with a serializer layer. Blueprints are code, so they have a lifecycle, they need review, and they tend to accumulate as the number of views grows. The readme's own example of a user with a normal and an extended view is a small case, and a system with a dozen views per resource is a different maintenance story. The counterweight is that the views are explicit and declarative, so the surface is readable and a diff shows what changed in the output.

And the migration note from the top of the readme applies to contributors and anyone vendoring the source rather than to gem users, which is a small but useful distinction to keep in mind when you read an organisation move notice in a project you merely depend on.

## Conclusion

Blueprinter suits a Rails application with several consumers of the same model, such as a web app, a mobile client and an admin tool, because views let each of them get its own shape from one blueprint. It is the wrong choice if you need a format other than JSON, since the library is a JSON object presenter and the documentation covers nothing else, and a poor match if you want the output to be guaranteed against a schema, because the shapes are declared in Ruby and nothing validates them. Before adopting, decide where your blueprint classes live, since they are code with a lifecycle, and check the migration note, because the project has moved to a new organisation and a stale remote in your fork will silently stop receiving upstream work.

## FAQ

### How do I define and use a Blueprinter blueprint?

Subclass Blueprinter::Base, declare an identifier and your fields, and call the render method on the class with the object. The output is a JSON string containing only the fields the blueprint declared, and the identifier is always rendered and always sorted first.

### What are views in Blueprinter and how do they differ from filters?

A view is a named block inside a blueprint declaring its own fields and associations, and a view can include another with include_view. It is a scope rather than a filter, so the extended view in the example is a separately declared document that deliberately includes the normal one, rather than the same document with extra keys.

### How do I add a root key and metadata to the JSON output?

Pass root with a key name to wrap the result in an object, and meta with a hash to add a sibling object. The documentation notes that a root is mandatory when using meta attributes, since the meta object sits beside the root.

### Can Blueprinter render objects other than arrays and ActiveRecord relations?

You can configure other classes to be treated as collections through the custom_array_like_classes setting, and the documentation shows Mongoid::Criteria and Set as examples. The render method also accepts a collection object or an array directly.

## Sources

- [Issues](https://github.com/procore-oss/blueprinter/issues)
- [License: MIT](https://github.com/procore-oss/blueprinter/blob/main/LICENSE)
- [procore-oss/blueprinter on GitHub](https://github.com/procore-oss/blueprinter)
- [README](https://github.com/procore-oss/blueprinter/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/procore-oss-blueprinter
