Skip to main content
Home
Drupal life hacks

Main navigation

  • Drupal
  • React
  • WP
  • Contact
  • About
User account menu
  • Log in

Breadcrumb

  1. Home

Object-Oriented Form API in Drupal 11

By admin, 4 July, 2025

✅ Disclaimer

This article summarizes exploratory ideas regarding an object-oriented approach to form/render arrays in Drupal.

⚠ Important Notes

  • This content is not an official description of Drupal 11 features.

  • The information presented here may:

    • Not exist in Drupal core

    • Be incomplete or experimental

    • Change or be removed in the future

  • Code samples should be treated as illustrative only, not production-ready.

✅ How to Read This Article

  1. Verify all claims against official sources, especially Drupal Change Records and relevant issues.

  2. Consider this material conceptual rather than prescriptive.

  3. If any statement conflicts with official documentation, the official documentation takes precedence.

Author’s Intent

This article is intended to help developers understand discussion and experimentation around object-oriented wrappers for form/render arrays.
It does not attempt to define finalized or adopted APIs.
Where applicable, references to current core behavior should be checked against:

  • Official change records

  • Conversations authored by core contributors

  • Source code

If inaccuracies are found, corrections and feedback are welcome.

 

A Full Guide with Comparison to the Old Approach and Real Examples

With the release of Drupal 11, a new Object-Oriented API for forms and render elements has been introduced in core. It’s a major architectural improvement that brings cleaner, more maintainable, and reusable code.

In this post, we’ll cover:

  • Why the new API was introduced
  • Key differences from the traditional array-based method
  • Practical examples of both approaches
  • When and why to switch

🚀 Why a New API?

For years, Drupal forms were defined using nested associative arrays:

$form['element'] = [
  '#type' => 'textfield',
  '#title' => 'Example',
];

This system is flexible, but:

  • Hard to read
  • Difficult to reuse
  • Lacks IDE support
  • Error-prone in complex forms

The new object-oriented approach solves these problems:

  • You use classes instead of nested arrays
  • Methods replace verbose properties like #title, #ajax, #validate
  • Easy to extend and reuse form logic
  • Much better IDE/autocompletion support

🔍 Comparison Table: Old vs. New

FeatureTraditional Form API (arrays)New Object-Oriented Form API
StructureNested arraysChainable objects
IDE supportMinimalExcellent (type-safe)
ExtendabilityHooks, callbacksInheritance, composition
ValidationIn validateForm()Constraints or method overrides
ReusabilityLowHigh
TestabilityHardEasy (unit testable classes)
ReadabilityLowHigh

📦 Example 1: Simple Textfield

✅ Old Approach

$form['name'] = [
  '#type' => 'textfield',
  '#title' => $this->t('Your name'),
  '#required' => TRUE,
];

✅ New Approach

use Drupal\Core\Render\Element\Textfield;

$field = Textfield::create()
  ->setTitle($this->t('Your name'))
  ->setRequired(TRUE);

$form['name'] = $field->toArray();

📦 Example 2: Submit Button with AJAX

🔁 Old Approach

$form['submit'] = [
  '#type' => 'submit',
  '#value' => $this->t('Submit'),
  '#ajax' => [
    'callback' => '::ajaxCallback',
    'wrapper' => 'result-wrapper',
  ],
];

🔁 New Approach

use Drupal\Core\Render\Element\Submit;

$submit = Submit::create()
  ->setValue($this->t('Submit'))
  ->setAjaxCallback('::ajaxCallback')
  ->setAjaxWrapper('result-wrapper');

$form['submit'] = $submit->toArray();

Now you get proper IDE hints for all AJAX settings.


📦 Example 3: Field Validation

👴 Old Style

$form['age'] = [
  '#type' => 'number',
  '#title' => $this->t('Age'),
  '#min' => 0,
];

public function validateForm(array &$form, FormStateInterface $form_state) {
  if ($form_state->getValue('age') < 18) {
    $form_state->setErrorByName('age', $this->t('You must be 18+'));
  }
}

🔥 New Style

use Drupal\Core\Render\Element\Number;

$age = Number::create()
  ->setTitle($this->t('Age'))
  ->setMin(0)
  ->addConstraint('GreaterThanOrEqual', 18);

$form['age'] = $age->toArray();

Validation becomes declarative — no need to manually write error checks.


📦 Example 4: Full Custom Form Class

namespace Drupal\my_module\Form;

use Drupal\Core\Form\FormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\Textfield;
use Drupal\Core\Render\Element\Submit;

class ContactForm extends FormBase {

  public function getFormId() {
    return 'contact_form';
  }

  public function buildForm(array $form, FormStateInterface $form_state) {
    $form['name'] = Textfield::create()
      ->setTitle($this->t('Name'))
      ->setRequired(TRUE)
      ->toArray();

    $form['submit'] = Submit::create()
      ->setValue($this->t('Send'))
      ->toArray();

    return $form;
  }

  public function submitForm(array &$form, FormStateInterface $form_state) {
    $name = $form_state->getValue('name');
    \Drupal::messenger()->addMessage($this->t('Hello @name!', ['@name' => $name]));
  }
}

This looks and feels more like modern OOP code — and your IDE will thank you.


🧠 When Should You Use the New API?

✅ Recommended when:

  • You're building a new module or form
  • Forms are complex or reused
  • You want clean, testable code
  • You care about DX (Developer Experience)

❌ Stick with the old way if:

  • The form is trivial or temporary
  • You're maintaining legacy code
  • You don't want to add complexity for small things

🧪 What About Compatibility?

The new system wraps around the old Render and Form APIs. That means:

  • You can mix object elements with regular arrays
  • Backward compatibility is preserved
  • You’re free to gradually migrate

🧾 Conclusion

Drupal’s new Object-Oriented Form API is a game-changer for developer experience. It modernizes how we build and manage forms, while keeping everything compatible with the powerful Render API under the hood.

If you're serious about:

  • Clean architecture
  • Reusability
  • IDE-friendliness
  • Reducing cognitive load —

then it’s time to start adopting this new API.


🗨️ Have you used the new Form API in your projects? Share your experience or challenges in the comments!

 

 

✅ What could be implemented (and makes sense)

Although the article incorrectly describes current Drupal core functionality, many of the ideas it mentions could become real improvements on top of the new wrapper classes introduced in Drupal 11.

✅ 1) Chainable setters

Example:

Textfield::create()
  ->setTitle('...')
  ->setRequired();

This could be implemented as syntactic sugar.
The current wrapper allows set(), so helper methods like setTitle() can be added.

→ Possible as contrib, and could later be proposed to core.


✅ 2) Better IDE / autocompletion support

This is actually the main motivation behind the wrapper classes.
Adding PHPDoc + typed sugar methods would significantly improve DX.

→ Very realistic.


✅ 3) Helper / convenience methods for AJAX

Example:

->setAjaxCallback('::callback')
->setAjaxWrapper('id')

This is feasible — just sugar around #ajax.


✅ 4) Declarative validation via constraints

Example:

->addConstraint('GreaterThanOrEqual', 18);

This could be mapped to Symfony Validator (since Drupal already uses it).
Would require design work, but can be done in contrib.


✅ 5) Reusable element subclasses

You could do:

class RequiredName extends Textfield {
  public function __construct() {
    $this->setTitle('Name')->setRequired();
  }
}

→ Possible right now.

Good path for DX enhancement.


✅ 6) Better unit-testability

Once elements are encapsulated, they become easier to test outside a full Form API environment.

→ Feasible.


✅ 7) Gradual adoption / hybrid mode

The current wrapper approach already supports mixing array + object, so incremental adoption is realistic.


⚠️ What is theoretically possible, but harder

⚠️ 1) A complete OOP replacement for render arrays

Render arrays are deeply embedded in Drupal (themes, preprocess, caching, etc.).
A full rewrite would take years.

→ Possible long-term, but not soon.


⚠️ 2) Automatic constraint integration w/ form submission

Mapping Symfony Constraint validation directly into Form API submission is non-trivial and would require coordination across components.

→ Possible with major design work.


❌ What does not make sense (or is dangerous)

❌ Removing render arrays

Render arrays are what make Drupal flexible, theme-able, and cache-aware.
OO wrappers should supplement, not replace.


❌ Forcing migration

Replacing existing Form API code would break virtually all modules.
OO API must remain optional.


✅ What could be done now in contrib

If someone wants to evolve the idea into a real project, the following are realistic:

✅ Fluent chains (setTitle(), etc.)
✅ PHPDoc & typed helper methods
✅ Constraint integration
✅ Reusable element subclasses
✅ Factory utilities (Element::textfield())

This can live in contrib and later be proposed for core adoption.

A contrib module could be named e.g.:

FormOO / RenderOO / ElementBuilder


✅ Realistic roadmap

StageScope
1Contrib PoC with sugar methods
2Add PHPDoc + typing for IDE
3Constraint integration
4Reusable subclasses
5Advanced DX helpers
6RFC discussion in core

✅ Conclusion

The blog post is incorrect about what exists right now,
but many of its ideas are actually reasonable DX improvements and could be implemented on top of the new wrapper classes.

So:
✅ The article is misleading as documentation.
✅ But its direction is not nonsense — it can inspire real evolution.

Tags

  • #Drupal Planet
  • Add new comment

Comments3

About text formats

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • Lines and paragraphs break automatically.
  • Web page addresses and email addresses turn into links automatically.

Mr D (not verified)

10 months 1 week ago

Good Idea

I was unable to find any documentation specifying which version of Drupal you are working on. I tried running the code example on the latest version, 11.2.2, but it did not work.

  • Reply

chx (not verified)

10 months 1 week ago

It doesn't work that way

Textfield::create() doesn't exist and it just doesn't work that way. You want $submit = $elementInfoManager->fromClass(Submit::class))

The documentation is at https://www.drupal.org/node/3532720 currently.

  • Reply

chx (not verified)

10 months 1 week ago

also there are no set methods

setAjaxCallback this method does not exist. none of this exist. is this page dreamed up by AI or what is this?

  • Reply
Powered by Drupal