Skip to content

Nested resources

Mount child resources below authorized parent records and Lucid relations.

A nested resource is addressable only below a parent row. Declare the parent on the child resource; Adonia mounts parent-qualified routes, resolves and authorizes the parent, scopes every child lookup through the declared Lucid relation, and makes breadcrumbs and mutation redirects parent-aware.

import { BaseResource, type SchemaBuilder, type SchemaComponent } from '@adonia/core'
import Post from '#models/post'

export default class LessonResource extends BaseResource<typeof Post> {
  static override model = Post
  static override slug = 'lessons'
  static override parent = {
    resource: 'courses',
    relation: 'lessons',
  }

  override schema(_schema: SchemaBuilder): SchemaComponent[] {
    return []
  }
}

resource is the registered parent resource slug. relation is the relation name on the parent model that yields child rows. Both parent and child resources must be registered in the same panel. Boot fails when the parent is missing, the relation is invalid, or its related model does not match the child model.

The resulting shape is:

/courses/:parentId/lessons
/courses/:parentId/lessons/create
/courses/:parentId/lessons/:id
/courses/:parentId/lessons/:id/edit

Names use the adonia.<panel>.resources.nested.* family. Generate URLs through descriptor capability URLs or Adonia’s URL builders; do not hand-assemble route names in UI components.

Scope and authorization

Parent resolution happens before the child query. The base scope composes panel scope, tenant scope, parent relation scope, and lens scope before soft-delete, search, filters, sort, eager loading, pagination, and serialization. A nested child queried without an active parent context returns no rows.

The framework authorizes the parent and child independently. Every detail, edit, update, delete, restore, action, relation, options, search, and widget path retains the parent context. Create and mutation flows save through the parent relation when nesting is active, preventing a submitted foreign key from selecting a different parent.

When tenancy is also enabled, both resources still need static tenantScope (or an intentional false). Parent relation scope does not replace tenant scope.

The panel envelope includes parent context for generic pages. The UI renders a trail from the parent resource and record into the child resource. Create, update, delete, restore, and action redirects remain inside the same parent path. Prefer urls.index, urls.create, and the row capability URLs supplied by the descriptor so custom pages preserve this behavior.

Verification checklist

Exercise two parents containing different children. Confirm that list, detail, edit, update, delete, restore, actions, search, widgets, relation endpoints, and JSON routes cannot read a child through the wrong parent id. Also verify create/update redirects and breadcrumbs. A relation manager test alone is insufficient: relation managers and nested-resource routes have different URL and lifecycle contracts.

See Tenancy for the second fail-closed scope and Authorization for parent/child policy responsibilities.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close