Routes and middleware reference
Developer handbook pages use GET /developers/docs/{page?} (developers.docs) with registered nested paths such as build/plugins.
Donor identity
GET|POST /donor/register→donor.register,donor.register.store; registration POST is throttled.GET|POST /donor/login→donor.login,donor.login.store; guest-only and throttled.GET /donor/verify-phone→donor.verification.notice;auth,donor.POST /donor/verify-phone/send→donor.verification.send;auth,donor, throttled.POST /donor/verify-phone→donor.verification.verify;auth,donor, throttled.GET /donor/dashboard→donor.dashboard;auth,donor,phone.verified.POST /donor/logout→donor.logout;auth,donor.
The central /login and /register remain email-based staff/organization flows.
Site
GET /preview/{organizationSlug}/{path?}→site.preview;signed,organization.route.GET /s/{organizationSlug}/{path?}→site.path;organization.route; local mode.GET {organizationSlug}.{central_domain}/{path?}→site.subdomain;organization.route; domain mode.
Charity
Prefix /charity/{organization:slug}, names charity.*, middleware auth, organization.route, organization.member:
GET /→charity.dashboard;GET /preview→charity.site.preview.GET|PUT /appearance→charity.themes.index,charity.themes.update.GET /plugins→charity.plugins.index.GET|PUT /plugins/{plugin}→charity.plugins.show,charity.plugins.update.PATCH /plugins/{plugin}/status→charity.plugins.toggle.
Admin
auth, super.admin: GET /admin (admin.dashboard), GET /admin/organizations (admin.organizations.index), GET /admin/plugins (admin.plugins.index), POST /admin/organizations/{organization}/impersonate (admin.organizations.impersonate). POST /admin/impersonation/stop (admin.impersonation.stop) uses auth.
Plugin Super Admin routes use /admin/plugins/{slug}/manage, plugin.{slug}.admin.*, and web, auth, super.admin, plugin.available:{slug},installed. They must not use organization middleware.
Contact Form
GET /charity/{organization:slug}/plugins/contact-form/manage→plugin.contact-form.dashboard;web, auth, route tenant, member,plugin.available:contact-form,installed.POST /s/{organizationSlug}/_plugins/contact-form→plugin.contact-form.site.path.store;web, route tenant, enabled availability,throttle:5,1.- Equivalent domain POST →
plugin.contact-form.site.subdomain.store; same guards.
Events
Prefix /charity/{organization:slug}/plugins/events/manage, plugin.events.dashboard.*, web, auth, route tenant, member, plugin.available:events,installed:
GET /→plugin.events.dashboard.index;GET /create→plugin.events.dashboard.create;POST /→plugin.events.dashboard.store;GET /{event}/edit→plugin.events.dashboard.edit;PUT /{event}→plugin.events.dashboard.update;DELETE /{event}→plugin.events.dashboard.destroy.
Public Events GET pages use the site catch-all, not plugin routes. Karam Banner has no endpoints.
Fundraising
Settings remain on the Charity routes at /charity/{organization:slug}/plugins/fundraising. Management uses the separate prefix /charity/{organization:slug}/plugins/fundraising/manage to prevent settings/dashboard collisions. Every management route uses web, auth, organization.route, organization.member, and plugin.available:fundraising.
GET /→plugin.fundraising.dashboard.overview.GET /projects→plugin.fundraising.dashboard.projects.index.GET /projects/createandPOST /projects→plugin.fundraising.dashboard.projects.create,plugin.fundraising.dashboard.projects.store.GET /projects/{project}→plugin.fundraising.dashboard.projects.show.GET /projects/{project}/editandPUT /projects/{project}→plugin.fundraising.dashboard.projects.edit,plugin.fundraising.dashboard.projects.update.PATCH /projects/{project}/status→plugin.fundraising.dashboard.projects.status.DELETE /projects/{project}→plugin.fundraising.dashboard.projects.destroy.GET /donationsandGET /donations/review→plugin.fundraising.dashboard.donations.index,plugin.fundraising.dashboard.donations.review.GET /donations/{donation}→plugin.fundraising.dashboard.donations.show.PATCH /donations/{donation}/status→plugin.fundraising.dashboard.donations.status.GET /donations/{donation}/proof→plugin.fundraising.dashboard.donations.proof; policy-protected private download.GET /channels,GET /channels/create,POST /channels→plugin.fundraising.dashboard.channels.index,plugin.fundraising.dashboard.channels.create,plugin.fundraising.dashboard.channels.store.GET /channels/{channel}/edit,PUT /channels/{channel}→plugin.fundraising.dashboard.channels.edit,plugin.fundraising.dashboard.channels.update.PATCH /channels/{channel}/status,DELETE /channels/{channel}→plugin.fundraising.dashboard.channels.toggle,plugin.fundraising.dashboard.channels.destroy.GET /supporters→plugin.fundraising.dashboard.supporters.index.GET /reports→plugin.fundraising.dashboard.reports.index.
Public project GET pages resolve through the site catch-all at projects and projects/{project}. Manual submission uses POST /donate/{organizationSlug}/projects/{project} → plugin.fundraising.donate with web, organization.route, enabled availability, and throttle:20,1.
Fundraising also contributes read-only Super Admin routes:
GET /admin/plugins/fundraising/manage→plugin.fundraising.admin.overview.GET /admin/plugins/fundraising/manage/projects→plugin.fundraising.admin.projects.index.GET /admin/plugins/fundraising/manage/donations→plugin.fundraising.admin.donations.index.
All three use web, auth, super.admin, and plugin.available:fundraising,installed.
EnsurePluginAvailable::handle(Request $request, Closure $next, string $plugin, string $mode = 'enabled'): Response always checks installed/active; enabled mode also checks effective tenant state.