
Drupal 11 Custom Form Workflows

In Drupal 11, forms can do much more than just collect a few fields. At Arudhra IT Techs, we build multi-step workflows, dynamic field changes, and secure file uploads — all without a single page reload. Here's our blueprint for modern, AJAX-powered form systems.
Drupal 11 Custom Form Workflows: Multi-Step, AJAX Validation, and File Upload Logic
Want to build a multi-step application form? A survey with conditional logic? Or a secure file upload wizard? With Drupal 11’s Form API + AJAX + FormState workflows — we can do it all.
📦 Step 1: Basic Form Structure
Let’s create a form class that extends FormBase
or ConfirmFormBase
:
// src/Form/MultiStepForm.php
class MultiStepForm extends FormBase {
protected $step = 1;
public function getFormId() {
return 'multi_step_form';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$step = $form_state->get('step') ?? 1;
$form_state->set('step', $step);
if ($step === 1) {
$form['first_name'] = [
'#type' => 'textfield',
'#title' => 'First Name',
'#required' => TRUE,
];
$form['next'] = [
'#type' => 'submit',
'#value' => 'Next',
];
}
elseif ($step === 2) {
$form['upload_cv'] = [
'#type' => 'managed_file',
'#title' => 'Upload CV',
'#upload_location' => 'public://uploads/',
'#required' => TRUE,
];
$form['prev'] = [
'#type' => 'submit',
'#value' => 'Back',
'#submit' => ['::prevStep'],
];
$form['submit'] = [
'#type' => 'submit',
'#value' => 'Submit',
];
}
return $form;
}
public function submitForm(array &$form, FormStateInterface $form_state) {
if ($form_state->get('step') === 1) {
$form_state->set('step', 2);
$form_state->setRebuild();
} else {
// Final submit
\Drupal::messenger()->addMessage('Form submitted ✅');
}
}
public function prevStep(array &$form, FormStateInterface $form_state) {
$form_state->set('step', 1);
$form_state->setRebuild();
}
}
🔁 Step 2: Enable AJAX for Instant Interaction
Add AJAX to your buttons:
$form['next'] = [
'#type' => 'submit',
'#value' => 'Next',
'#ajax' => [
'callback' => '::ajaxRebuildForm',
'wrapper' => 'multi-step-form-wrapper',
],
];
$form['#prefix'] = '<div id="multi-step-form-wrapper">';
$form['#suffix'] = '</div>';
This lets us change steps without reloading the page!
📁 Step 3: Handle File Uploads Properly
- Use
managed_file
- Set
file_usage_add()
if needed - Restrict extensions + size
$form['upload_cv'] = [
'#type' => 'managed_file',
'#title' => $this->t('Upload CV'),
'#upload_location' => 'public://uploads',
'#upload_validators' => [
'file_validate_extensions' => ['pdf doc docx'],
'file_validate_size' => [25600000], // 25MB
],
];
🧠 Real-World Use Cases
- Vendor onboarding with dynamic step-by-step logic
- Loan application with file upload and real-time field validation
- Multi-language registration with conditional fields
✅ Form Validation + Conditional Logic
Use validateForm()
to dynamically throw errors:
public function validateForm(array &$form, FormStateInterface $form_state) {
if ($form_state->get('step') === 2) {
$file = $form_state->getValue('upload_cv');
if (empty($file)) {
$form_state->setErrorByName('upload_cv', 'CV is required.');
}
}
}
Need a Complex Form Flow Built in Drupal 10 or 11?
We specialize in multi-step, AJAX-driven, file-uploading Drupal forms for enterprise clients — fast, secure, and fully dynamic.
Let’s architect your next smart form 🔥