Building a Custom Block Plugin
A complete Drupal block plugin using attributes, dependency injection, configuration, and cache metadata.
Block plugins are ideal for reusable, placeable UI backed by application logic. A production block needs more than build(): access, configuration, dependencies, and cacheability all matter.
Use an attributed plugin
Modern Drupal discovers block plugins through the Block attribute. Give the plugin a stable machine ID, translated label, and meaningful category.
Inject the service
Implement ContainerFactoryPluginInterface when the block needs services. Keep storage queries in a repository or domain service rather than embedding them in the plugin.
Return correct cache metadata
If output depends on configuration, entities, routes, roles, or users, declare the corresponding tags and contexts. Correct caching is part of functional correctness.
Working example
#[Block(id: 'journal_featured', admin_label: new TranslatableMarkup('Featured journal article'))]
final class FeaturedArticleBlock extends BlockBase {
public function build(): array {
return [
'#theme' => 'journal_featured',
'#article' => $this->repository->featured(),
'#cache' => ['tags' => ['node_list:journal_article']],
];
}
}Key Takeaways
- Keep plugins thin and inject application services.
- Make configuration explicit.
- Treat cache metadata as required output.