Schema & dynamic fields
A node's schema() (and advanced() / connection()) returns an array of field
definitions built with the fluent Schema API. These describe the node's config
form — for the interactive builder, and for any UI built on top of the registry.
use Qanna\WorkflowEngine\Engine\Schema\Schema;
public static function schema(): array
{
return [
Schema::text('url')->required()->placeholder('https://…')->help('Supports {{ }}.'),
Schema::select('method')
->options(['get' => 'GET', 'post' => 'POST'])
->default('get'),
Schema::json('body')->when('method', ['post']),
];
}
Field types
| Factory | Type | Extra methods |
|---|---|---|
Schema::text($name) | text | ->multiline(), ->mono() |
Schema::number($name) | number | ->min($v), ->max($v), ->step($v) |
Schema::select($name) | select | ->options([value => label]), ->multiple() |
Schema::checkbox($name) | checkbox | ->options([...]) |
Schema::json($name) | json | free-form JSON value |
Schema::date($name) / Schema::datetime($name) | date / datetime | |
Schema::email($name) / Schema::password($name) | email / password | |
Schema::builder($name) | builder | value is itself a designed schema (used by the Manual trigger's payload) |
Schema::group($name) | group | ->fields([...]), ->resolver(Resolver::class) |
Schema::dynamic($name, Resolver::class) | group | shorthand for a group with a resolver |
Schema::whereBuilder($name) / Schema::filterBuilder($name) | where/filter builder | ->operators([...]) |
Common modifiers
Available on every field:
Schema::text('token')
->label('API token')
->required() // or ->required(false)
->help('Where to find it…')
->placeholder('sk_live_…')
->default('…') // seeds the builder's prompt
->sensitive() // encrypted at rest
->hidden() // not shown in the form
->disabled() // shown, not editable
->rules('required|url') // Laravel validation rules
->when('mode', ['advanced']); // conditional visibility
->when($field, $value)
The field is only shown/prompted when another field in the same form equals
$value (an array means "any of these"). Used throughout the built-in nodes —
e.g. the HTTP node's body only appears for write methods.
->handle($callback) — deferred definition
For values that must be computed at resolve time (a list of the app's filesystem disks, the workflows available to call), pass a closure. It receives the field and can mutate it fluently or return an array to merge:
Schema::select('disk')
->handle(fn ($field) => $field->options(
collect(array_keys(config('filesystems.disks')))
->mapWithKeys(fn ($d) => [$d => $d])->all()
));
Dynamic field groups
A group field with a resolver produces its child fields from answers already
given in the same session — for example, the Model nodes' attributes group
turns the chosen model's fillable attributes into fields.
A resolver implements the marker interface DynamicSchemaResolver and defines a
resolve() method in one of two shapes:
use Qanna\WorkflowEngine\Engine\Contracts\DynamicSchemaResolver;
use Qanna\WorkflowEngine\Engine\Schema\Schema;
class WebhookHeadersResolver implements DynamicSchemaResolver
{
// Shape A: the answers collected so far in this layer
public function resolve(array $answers): array
{
$preset = $answers['preset'] ?? null;
return $preset === 'json'
? [Schema::text('content_type')->default('application/json')]
: [];
}
}
use Qanna\WorkflowEngine\Engine\Context\DynamicSchemaContext;
class ConnectionAwareResolver implements DynamicSchemaResolver
{
// Shape B: cross-layer access (config / connection / advanced answers)
public function resolve(DynamicSchemaContext $context): array
{
$provider = $context->connection('provider');
// ->config($key), ->connection($key), ->advanced($key), ->get('connection.provider')
return [/* fields depending on $provider */];
}
}
The engine picks whichever signature you declared. Attach a resolver with
Schema::group('headers')->resolver(WebhookHeadersResolver::class) or the
Schema::dynamic('headers', WebhookHeadersResolver::class) shorthand.
Custom field prompters (CLI)
The interactive builder knows how to prompt for the built-in field types. To
teach it a type it doesn't handle natively — say a bespoke key-value field
your package's UI renders — register a FieldPrompter from a service provider:
use Qanna\WorkflowEngine\Console\Support\FieldPrompterRegistry;
use Qanna\WorkflowEngine\Console\Support\Contracts\FieldPrompter;
class KeyValuePrompter implements FieldPrompter
{
public function prompt(array $field, mixed $existing): mixed
{
// use laravel/prompts here; return the collected value
}
}
$this->app->make(FieldPrompterRegistry::class)->register('key-value', new KeyValuePrompter());
SchemaFieldPrompter consults the registry for any field type it doesn't handle
itself.