Skip to content

Abilities ​

Abilities handle named authorization decisions that are not tied to a single model instance.

Ability shape ​

ts
import { defineAbility, allow, deny } from '@holo-js/authorization'

export default defineAbility('reports.export', (context, input: {
  reportId: string
  format: 'csv' | 'pdf'
}) => {
  if (!context.user) {
    return deny('You must be signed in to export reports.')
  }

  if (context.user.role === 'admin') {
    return allow()
  }

  return input.format === 'csv'
    ? allow()
    : deny('Only CSV export is allowed.')
})

The ability name is the registry key. Holo uses it to generate typed discovery output and autocomplete.

When to use abilities ​

Use abilities when the action is not naturally a policy on a record.

Examples:

  • report exports
  • publishing workflows
  • batch operations
  • custom business actions

If you are checking a model instance, use a policy instead.

Typed inputs ​

Ability inputs are inferred from the handler. That means the payload you pass into authorize(...), can(...), or inspect(...) stays safe and autocompleted.

ts
const decision = await authorization.forUser(user).ability('reports.export').inspect({
  reportId: 'rpt-1',
  format: 'csv',
})

if (!decision.allowed) {
  return Response.json({ message: decision.message }, { status: decision.status })
}

Discovery and typing ​

Ability files belong in server/abilities. The CLI discovers them, generates typed registry artifacts, and gives you autocomplete for:

  • ability names
  • ability input shapes

Continue ​

Holo owns backend runtime concerns. The host framework owns SSR and routing.