Authorization API
Per-option reference for authorization: the initializer options, the resource option, every policy method Avo calls, and the client contract. For task-oriented documentation and worked examples, see the Authorization guide.
Configuration options go in config/initializers/avo.rb:
Avo.configure do |config|
config.authorization_client = :pundit
config.explicit_authorization = true
endPolicy methods go in the policy class for the resource's model, for example app/policies/post_policy.rb.
Configuration
-> authorization_client
The client Avo uses to answer authorization checks.
config.authorization_client = "Avo::ActionPolicyAuthorizationClient"| Value | Behavior |
|---|---|
:pundit | Uses the built-in Pundit client. Requires gem "pundit" in the Gemfile. |
nil | Uses the built-in nil client, which applies no policies. |
String | The class name of a custom client that implements the client contract. |
- Type:
Symbol,String, ornil - Default:
:pundit(the generated initializer sets it tonil) - Without the add-on on your license: a client set in the initializer refuses every resource, and no policy runs. Left unset or
nil, policies are skipped. See When the license lapses.
-> explicit_authorization
How a missing policy class or policy method is treated.
config.explicit_authorization = true| Value | Behavior |
|---|---|
true | A missing policy class or method denies the action. |
false | A missing policy class or method allows the action. |
Proc | Evaluated per request; a truthy result behaves like true, a falsy one like false. |
config.explicit_authorization = -> {
current_user.access_to_admin_panel? && !current_user.admin?
}- Type:
BooleanorProc - Default:
true - Context: the
Procruns in anAvo::ExecutionContext
INFO
Doesn't affect field lists. A policy that declares neither whitelisted_fields nor blacklisted_fields restricts no fields, whatever this option says.
-> raise_error_on_missing_policy
Raises Avo::NoPolicyError when a resource has no policy class, instead of applying explicit_authorization. It doesn't apply to a missing policy method.
config.raise_error_on_missing_policy = true- Type:
Boolean - Default:
false
-> authorization_methods
Maps the actions Avo authorizes to the policy methods it calls. Keys not in the Hash fall back to the action name.
config.authorization_methods = {
index: "avo_index?",
show: "avo_show?",
edit: "avo_edit?",
new: "avo_new?",
update: "avo_update?",
create: "avo_create?",
destroy: "avo_destroy?",
search: "avo_search?"
}- Type:
Hashof actionSymbolto method nameString - Default:
{index: "index?", show: "show?", edit: "edit?", new: "new?", update: "update?", create: "create?", destroy: "destroy?"}
Resource options
-> authorization_policy
The policy class for the resource. When unset, the policy is inferred from the resource's model.
class Avo::Resources::PhotoComment < Avo::BaseResource
self.model_class = "Comment"
self.authorization_policy = PhotoCommentPolicy
end- Type:
Class - Default:
nil
Resource policy methods
Called with the current user and the record (or the model class when there is no record yet). Return truthy to allow.
-> index?
Controls the resource's sidebar entry in the auto-generated menu and access to the Index view. A summarizable column's distribution chart asks it too, or the parent's view_{association}? for a column on an association panel. Since avo >= v4.3.8
- Type:
Boolean
INFO
Not used by the menu editor. Items there are hidden through their visible block.
-> show?
-> new?
Controls the Create new {model} button on the Index view and on association panels, and access to the /new page. The /new page check doesn't raise on denial.
- Type:
Boolean

-> create?
Controls whether a new record can be saved, from the /new page and from association create actions. Checked only on save, so record holds the submitted form values.
- Type:
Boolean

-> edit?
-> update?
Controls whether changes to a record can be saved. Asked twice on a save Since avo >= v4.3.8: once on the record as loaded from the database, then again once the submitted form values are filled in. Both have to allow the write.
- Type:
Boolean record: the stored record on the first check, the record carrying the submitted values on the second

-> destroy?
Controls the delete button and whether a record can be deleted. Per-file deletion is controlled by delete_{FIELD_ID}?.
- Type:
Boolean

-> act_on?
Controls the actions button, and running an action: Avo asks it of the record on the page and of each selected record (the model class when there are none, and for Select all), and refuses the run if any is denied.
- Type:
Boolean

-> reorder?
Controls the record reordering controls on the Index view. It also authorizes the move itself, from any panel.
- Type:
Boolean

-> search?
-> preview?
Controls access to the endpoint the preview field calls.
- Type:
Boolean
INFO
Doesn't hide the preview field itself. Use the field's visible option for that.

Association policy methods
Defined on the parent resource's policy. {association} is the association name exactly as declared, including its pluralization: has_many :comments gives attach_comments?, not attach_comment?.
record is either the parent record or the associated row record, depending on the method:
| Method | record |
|---|---|
attach_, create_, act_on_, view_ | The parent record |
detach_, show_, edit_, destroy_, reorder_ | The associated row record |
-> view_{association}?
Controls whether the association panel is displayed on the parent record. It also answers the requests behind the panel:
The association's show route (
/resources/:resource/:id/:related_name/:related_id), where ahas_onepanel loads its frame from. That route also asks the related record's ownshow?, and looks ahas_manyrow up inside the related resource's policy scope. Sinceavo >= v4.3.4The requests that list the association: the panel's rows, its search, a
summarizablecolumn's chart, and scope counts. Each has to name the resource the field lists with, itsuse_resourcewhen the field sets one. Sinceavo >= v4.3.8Type:
Booleanrecord: the parent recordRefusal:
Avo::NotAuthorizedError. A list requested under a resource the association field does not list with answersActiveRecord::RecordNotFound(404)
-> attach_{association}?
Controls the Attach {model} button.
- Type:
Boolean record: the parent record

-> detach_{association}?
Controls the detach button on each associated row.
- Type:
Boolean record: the associated row record

-> create_{association}?
Controls the Create new {model} button on the association panel. It also controls adding new rows to a nested form: the form hides its "Add" button, and a save with a new row is refused.
- Type:
Boolean record: the parent record

-> show_{association}?
Controls the view button on each associated row.
- Type:
Boolean record: the associated row record
WARNING
Doesn't control access to the associated record's Show view. That's decided by the associated record's own policy.

-> edit_{association}?
Controls the edit button on each associated row.
- Type:
Boolean record: the associated row record
WARNING
Doesn't control access to the associated record's Edit view. That's decided by the associated record's own policy.

-> destroy_{association}?
Controls the delete button on each associated row.
- Type:
Boolean record: the associated row record

-> act_on_{association}?
Controls the Actions dropdown on the association panel. Running an action from the panel also needs act_on? on each selected record's own policy.
- Type:
Boolean record: the parent record

-> reorder_{association}?
Controls the record reordering controls on a has_many association panel. The move itself also needs reorder? on the record's policy.
- Type:
Boolean record: the associated row record

-> inherit_association_from_policy
Class method that defines the association methods above by delegating to another policy. Available after including Avo::Authorization::Concerns::PolicyHelpers.
class PostPolicy < ApplicationPolicy
include Avo::Authorization::Concerns::PolicyHelpers
inherit_association_from_policy :comments, CommentPolicy
end| Defined method | Delegates to |
|---|---|
create_comments? | CommentPolicy#create? |
edit_comments? | CommentPolicy#edit? |
update_comments? | CommentPolicy#update? |
destroy_comments? | CommentPolicy#destroy? |
show_comments? | CommentPolicy#show? |
reorder_comments? | CommentPolicy#reorder? |
act_on_comments? | CommentPolicy#act_on? |
attach_comments? | CommentPolicy#attach? |
detach_comments? | CommentPolicy#detach? |
view_comments? | CommentPolicy#index? |
Each delegate is called as CommentPolicy.new(user, record). A method defined in the policy after the call overrides the generated one.
- Arguments:
association_name(Symbol),policy_class(Class)
Attachment policy methods
{FIELD_ID} is the file field's id. user and record are available. The same methods authorize file fields on actions that run on the resource.
-> upload_{FIELD_ID}?
Controls whether a file can be uploaded to the field.
- Type:
Boolean
-> download_{FIELD_ID}?
Controls whether the field's file can be downloaded.
- Type:
Boolean
-> delete_{FIELD_ID}?
Controls whether the field's file can be deleted.
- Type:
Boolean
Policy scope
-> Scope
Nested policy class whose resolve method filters the records on the Index, Show, and Edit views. scope is the query being run.
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
user.admin? ? scope.all : scope.where(published: true)
end
end
end- Type:
Classwith aresolvemethod returning a relation - Applies to: the resource's own views, and the rows of
has_manyandhas_and_belongs_to_manypanels that list this resource. The association field'sscopeoption runs after it, on the narrowed query.
Field lists
Policy methods that name which of the resource's fields a user may reach. A withheld field is neither rendered nor settable on any Avo surface. See Hide fields from a user for what that covers.
Both lists hold field ids, not column names. An id matching no field the resource declares on the current view is ignored. When both are declared, the allowlist resolves first and the denylist subtracts from it. A private declaration counts as declared.
-> whitelisted_fields
The only field ids this user may reach.
def whitelisted_fields
user.admin? ? :all : [:id, :name, :email]
end- Type:
:all,:none, orArrayof field idSymbols - Default:
:allwhen undeclared
-> blacklisted_fields
Field ids this user may not reach.
def blacklisted_fields
user.admin? ? [] : [:budget, :internal_notes]
end- Type:
:all,:none, orArrayof field idSymbols - Default:
:nonewhen undeclared
-> Avo::Current.interface
The surface the current request came through. Readable in field list methods and anywhere else Avo::Current is, including a field's visible: block.
| Value | Set by |
|---|---|
:ui | The admin panel |
:api | avo-api |
:ai | avo-ai |
:mcp | avo-mcp_server |
- Type:
Symbol - Default:
:ui
Field list errors
A list that can't be resolved raises rather than falling back to no restriction. Every error subclasses Avo::Authorization::FieldResolver::Error:
| Error | Raised when |
|---|---|
InvalidDeclarationError | A method returns something other than :all, :none or an Array. nil counts. |
ResolutionFailedError | A method raised while being read. |
Client contract
Methods a custom authorization_client implements. The built-in :pundit client follows the same contract.
Every method must accept keyword arguments it doesn't use, usually through **. Avo passes keys such as policy_class:, raise_exception: and resource_class:, and a method that doesn't accept them raises ArgumentError, which a UI check can swallow as a denial.
Raise on denial
authorize must raise on denial. Avo ignores its return value, so a client that returns false authorizes the action. When Avo passes raise_exception: false, it still expects the client to raise, and turns the exception into false itself.
-> authorize
Checks whether user can perform action, a String such as "index?", on record. Called for sidebar items, buttons, menu visibility, and controller requests.
def authorize(user, record, action, policy_class: nil, **)
Pundit.authorize(user, record, action, policy_class: policy_class)
rescue Pundit::NotDefinedError => error
raise Avo::NoPolicyError, error.message
rescue Pundit::NotAuthorizedError => error
raise Avo::NotAuthorizedError, error.message
end- Arguments:
user,record,action,policy_class:,** - Raises:
Avo::NotAuthorizedErroron denial,Avo::NoPolicyErrorwhen the policy is missing
-> policy
Returns the policy instance for a record or model class, or nil when none exists.
def policy(user, record, **)
Pundit.policy(user, record)
end- Arguments:
user,record,** - Returns: a policy instance or
nil
-> policy!
Returns the policy instance for a record or model class, raising when none exists.
def policy!(user, record, **)
Pundit.policy!(user, record)
rescue Pundit::NotDefinedError => error
raise Avo::NoPolicyError, error.message
end- Arguments:
user,record,** - Raises:
Avo::NoPolicyErrorwhen the policy is missing
-> apply_policy
Scopes a query to the records user may see. Called on the Index, Show, and Edit views.
def apply_policy(user, model, policy_class: nil, **)
scope_from_policy_class = scope_for_policy_class(policy_class)
if scope_from_policy_class.present?
scope_from_policy_class.new(user, model).resolve
else
Pundit.policy_scope!(user, model)
end
rescue Pundit::NotDefinedError => error
raise Avo::NoPolicyError, error.message
end- Arguments:
user,model(a relation or model class),policy_class:,** - Returns: the scoped query
- Raises:
Avo::NoPolicyErrorwhen the policy is missing
-> permitted_field_ids
Optional. Returns the subset of declared_field_ids this user may reach, for reads and writes alike. A client that implements neither this nor field_reachable? imposes no field restriction.
def permitted_field_ids(user, record, declared_field_ids:, policy_class: nil)
resolved = policy_class ? policy_class.new(user, record) : policy(user, record)
return declared_field_ids if resolved.nil?
Avo::Authorization::FieldResolver.new(
policy: resolved,
declared_field_ids: declared_field_ids
).permitted_field_ids
end- Arguments:
user,record,declared_field_ids:(ArrayofSymbols),policy_class:(the resource'sauthorization_policy, if set) - Returns:
Arrayof field idSymbols - Raises: on resolution failure. Returning the full list on error falls open.
-> field_reachable?
Optional. Returns whether one field id survives the policy's lists, for callers that resolve a single field, such as a record title in a search result or an association picker.
def field_reachable?(user, record, field_id, declared_field_ids: [], policy_class: nil)
resolved = policy_class ? policy_class.new(user, record) : policy(user, record)
return true if resolved.nil?
Avo::Authorization::FieldResolver.new(
policy: resolved,
declared_field_ids: declared_field_ids
).reaches?(field_id)
end- Arguments:
user,record,field_id,declared_field_ids:(empty when the resource hasn't detected its fields),policy_class: - Returns:
Boolean


