Examples
Create and revise a Descriptor
Publish a typed Approval binding and document schema for the Workshop Note, then observe its domain-form entry.
Example type Recipe
Overview
Create and Change a Descriptor
This recipe makes the same Workshop Note available to Electronic Approval by
adding an ApprovalFormBindingDescriptor and an ApprovalDocumentSchema. It
assumes the previous Contributor recipe added
workshop.note.publish.
Scope of this recipe
Descriptors are the common way Apps expose capabilities to Core runtimes, but
this recipe covers only the binding and document schema required by
Electronic Approval. To execute an App-owned action from a Service Task in
tenant-authored BPMN, publish a separate ProcessWorkActionDescriptor instead
of reusing an Approval descriptor.
ProcessWorkActionDescriptor publishes the action to the BPMN picker
→ the service provider registers a handler factory with ProcessWorkActionRegistrar
→ ProcessWorkActionHandler::handle() receives the restored context and input
→ ProcessWorkActionResult returns the action output
A descriptor declaration publishes a catalog and execution contract; it does
not execute the App's business logic. The App-owned handler performs the actual
work. Process start bindings, user-task forms, and importable Process templates
use the separate ProcessStartBindingDescriptor,
ProcessUserTaskFormDescriptor, and ProcessTemplateDescriptor contracts.
Continue with Contribute to Business Process
for the descriptor, handler, and service-provider registration. See
Business Process and
Business Process contracts for
the complete BPMN integration choices and runtime signatures.
What appears when you finish
| Result | Where to see it |
|---|---|
workshop.note.submit binding | E-Approval → Template management → New template → Business form → Domain form |
| Name-and-status snapshot schema | Public document structure used by preview and approval detail |
| New menu | None. Core owns the E-Approval menu |

This is the actual catalog result. Searching for Workshop shows Submit
Note under Other Apps → Workshop. This stage gives administrators a
binding choice; the next Approval contribution recipe
connects the user-facing composer widget.
Which contract it implements and how Core receives it
WorkshopApprovalDescriptors also does not extend a Core class. It implements
the App SDK AppDescriptorContribution interface and returns an
AppDescriptorSet from appDescriptors(). ApprovalFormBindingDescriptor and
ApprovalDocumentSchema are final SDK descriptor value objects, not base
classes that App code subclasses.
WorkshopApprovalDescriptors implements AppDescriptorContribution
→ NexiaContributionRegistry discovers the implementing class
→ AppDescriptorCatalog calls appDescriptors()
→ catalog rejects duplicate descriptor type + key pairs and records Workshop ownership
├─ ApprovalBindingRegistry consumes ApprovalFormBindingDescriptor
└─ ApprovalDocumentSchemaRegistry consumes ApprovalDocumentSchema
(both registries recheck tenant App installation and descriptor lifecycle)
Adding objects to appDescriptors() does not render UI directly. Core's
Approval registries interpret the static contracts as an available domain
form binding and the shape of document values to freeze. Because
ContributionOwner comes from the Workshop namespace where the class was
discovered, an App cannot publish a descriptor under another App's ownership.
1. Add the Descriptor
cp docs/developers/examples/descriptor/WorkshopApprovalDescriptors.php \
packages/workshop/src/Descriptors/WorkshopApprovalDescriptors.phpThe binding is:
new ApprovalFormBindingDescriptor(
appKey: 'workshop',
resourceKey: 'note',
actionKey: 'submit',
labelKey: 'workshop.approval.note.submit.label',
entryModes: ['link'],
formWidgetKey: 'workshop.note.submit',
documentSchemaKey: 'workshop.note',
descriptionKey: 'workshop.approval.note.submit.description',
submitPermissionKey: 'workshop.note.publish',
)link means that composition selects an existing Note. The submit permission
filters catalog visibility but never replaces record-level backend Policy
checks.
Publish the referenced schema in the same set:
new ApprovalDocumentSchema(
appKey: 'workshop',
resourceKey: 'note',
titleKey: 'workshop.approval.note.document.title',
sections: [[
'title_key' => 'workshop.approval.note.document.summary',
'fields' => [
['key' => 'name', 'label_key' => 'workshop.note.name.label', 'type' => 'string'],
['key' => 'status', 'label_key' => 'workshop.note.status.label', 'type' => 'string'],
],
]],
)The schema does not reflect model columns. It declares only the public
documentValues shape the App freezes at submission.
2. Add locale entries
Add the exact dotted keys to every locale owned by Workshop:
{
"workshop.approval.note.submit.label": "Submit note",
"workshop.approval.note.submit.description": "Link an existing note to Electronic Approval.",
"workshop.approval.note.document.title": "Note",
"workshop.approval.note.document.summary": "Note details"
}3. Discover and observe it
sh docs/developers/examples/descriptor/verify.shThen search for Workshop at the path shown in the screenshot. Installation,
submit authority, lifecycle, and locale resolution all participate in catalog
visibility.
4. Change the Descriptor
For wording-only changes, update locale values and keep identity and version. For a compatible optional extension, confirm consumer compatibility and review the version:
version: '1.1',For breaking changes such as an action key, field type, or requiredness, add a new Active descriptor and deprecate the old one. Remove the old version only after stored templates and consumers have migrated.
Common errors
| Symptom | Cause | Fix | Confirm |
|---|---|---|---|
| Validation succeeds but the domain form is absent | Workshop is not installed for the tenant, the user lacks submit authority, or the descriptor is not Active | Check installation, the workshop.note.publish assignment, and lifecycle | Searching for Workshop in the same tenant and Legal Entity shows Submit Note |
| A raw locale key appears | One of Workshop's supported locale catalogs lacks the same dotted key | Add all four keys to every resources/lang/{locale}.json and refresh runtime caches | The picker shows the translated Submit Note label and description |
| The item is visible but composition cannot open | The Descriptor publishes only the static catalog; the Slot Widget and frontend registry do not exist yet | Complete the Electronic Approval contribution recipe and match all four workshop.note.submit identities | The composer loads the Workshop Widget |
| The schema cannot be resolved | The binding documentSchemaKey differs from the schema's appKey.resourceKey identity | Normalize both sides to workshop.note | verify.sh prints Descriptor contracts are valid. |
Included files
Approval descriptors
docs/developers/examples/descriptor/WorkshopApprovalDescriptors.php<?php
declare(strict_types=1);
namespace Amuzcorp\Nexia\Workshop\Descriptors;
use Nexia\AppDescriptors\AppDescriptorSet;
use Nexia\AppDescriptors\ApprovalDocumentSchema;
use Nexia\AppDescriptors\ApprovalFormBindingDescriptor;
use Nexia\AppDescriptors\Contracts\AppDescriptorContribution;
final class WorkshopApprovalDescriptors implements AppDescriptorContribution
{
public static function appDescriptors(): AppDescriptorSet
{
return AppDescriptorSet::of(
new ApprovalFormBindingDescriptor(
appKey: 'workshop',
resourceKey: 'note',
actionKey: 'submit',
labelKey: 'workshop.approval.note.submit.label',
entryModes: ['link'],
formWidgetKey: 'workshop.note.submit',
documentSchemaKey: 'workshop.note',
descriptionKey: 'workshop.approval.note.submit.description',
submitPermissionKey: 'workshop.note.publish',
),
new ApprovalDocumentSchema(
appKey: 'workshop',
resourceKey: 'note',
titleKey: 'workshop.approval.note.document.title',
sections: [[
'title_key' => 'workshop.approval.note.document.summary',
'fields' => [
[
'key' => 'name',
'label_key' => 'workshop.note.name.label',
'type' => 'string',
],
[
'key' => 'status',
'label_key' => 'workshop.note.status.label',
'type' => 'string',
],
],
]],
),
);
}
}Refresh and validate descriptors
docs/developers/examples/descriptor/verify.sh#!/usr/bin/env sh
set -eu
test -f packages/workshop/src/Descriptors/WorkshopApprovalDescriptors.php
task artisan -- nexia-runtime:refresh-runtime-caches --if-route-cached --no-ansi
task artisan -- nexia-apps:validate-app-descriptors workshop --no-ansi
task artisan -- nexia-apps:doctor-package-app workshop --no-ansi
printf '%s\n' 'Descriptor contracts are valid. Confirm the new binding in the Electronic Approval domain-form catalog.'