Visibility

With GraphQL-Ruby, it’s possible to hide parts of your schema from some users. This isn’t exactly part of the GraphQL spec, but it’s roughly within the bounds of the spec.

Here are some reasons you might want to hide parts of your schema:

Hiding Parts of the Schema

To start limiting visibility of your schema, add the plugin:

class MySchema < GraphQL::Schema
  # ...
  use GraphQL::Schema::Visibility # see below for options
end

Then, you can customize the visibility of parts of your schema by reimplementing various visible? methods:

These methods are called with the query context, based on the hash you pass as context:. If the method returns false, then that member of the schema will be treated as though it doesn’t exist for the entirety of the query. That is:

Visibility Profiles

You can use named profiles to cache your schema’s visibility modes. For example:

use GraphQL::Schema::Visibility, profiles: {
  # mode_name => example_context_hash
  public: { public: true },
  beta: { public: true, beta: true },
  internal_admin: { internal_admin: true }
}

Then, you can run queries with context[:visibility_profile] equal to one of the pre-defined profiles. When you do, GraphQL-Ruby will use a precomputed set of types and fields for that query.

Preloading profiles

By default, GraphQL-Ruby will preload all named visibility profiles when Rails.env.production? is present and true. You can manually set this option by passing use ... preload: true (or false). Enable preloading in production to reduce latency of the first request to each visibility profile. Disable preloading in development to speed up application boot.

Dynamic profiles

When you provide named visibility profiles, context[:visibility_profile] is required for query execution. You can also permit dynamic visibility for queries which don’t have that key set by passing use ..., dynamic: true. You could use this to support backwards compatibility or when visibility calculations are too complex to predefine.

When no named profiles are defined, all queries use dynamic visibility.

Object Visibility

Let’s say you’re working on a new feature which should remain secret for a while. You can implement .visible? in a type:

class Types::SecretFeature < Types::BaseObject
  def self.visible?(context)
    # only show it to users with the secret_feature enabled
    super && context[:viewer].feature_enabled?(:secret_feature)
  end
end

(Always call super to inherit the default behavior.)

Now, the following bits of GraphQL will return validation errors:

And in introspection:

Field Visibility

class Types::BaseField < GraphQL::Schema::Field
  # Pass `field ..., require_admin: true` to hide this field from non-admin users
  def initialize(*args, require_admin: false, **kwargs, &block)
    @require_admin = require_admin
    super(*args, **kwargs, &block)
  end

  def visible?(ctx)
    # if `require_admin:` was given, then require the current user to be an admin
    super && (@require_admin ? ctx[:viewer]&.admin? : true)
  end
end

For this to work, the base field class must be configured with other GraphQL types.

Argument Visibility

class Types::BaseArgument < GraphQL::Schema::Argument
  # If `require_logged_in: true` is given, then this argument will be hidden from logged-out viewers
  def initialize(*args, require_logged_in: false, **kwargs, &block)
    @require_logged_in = require_logged_in
    super(*args, **kwargs, &block)
  end

  def visible?(ctx)
    super && (@require_logged_in ? ctx[:viewer].present? : true)
  end
end

For this to work, the base argument class must be configured with other GraphQL types.

Opting Out

By default, GraphQL-Ruby always runs visibility checks. You can opt out of this by adding to your schema class:

class MySchema < GraphQL::Schema
  # ...
  # Opt out of GraphQL-Ruby's visibility feature:
  use GraphQL::Schema::AlwaysVisible
end

For big schemas, this can be a worthwhile speed-up.

Migration Notes

GraphQL::Schema::Visibility is a new implementation of visibility in GraphQL-Ruby. It has some slight differences from the previous implementation (GraphQL::Schema::Warden):

Migration Mode

You can use use GraphQL::Schema::Visibility, ... migration_errors: true to enable migration mode. In this mode, GraphQL-Ruby will make visibility checks with both Visibility and Warden and compare the result, raising a descriptive error when the two systems return different results. As you migrate to Visibility, enable this mode in test to find any unexpected discrepancies.

Sometimes, there’s a discrepancy that is hard to resolve but doesn’t make any real difference in application behavior. To address these cases, you can use these flags in context:

You can use these flags to conditionally handle edge cases that should be ignored in testing.