Skip to content

Instantly share code, notes, and snippets.

@phenaproxima
Last active August 3, 2025 14:13
Show Gist options
  • Save phenaproxima/2eb81182eb8322472d76b8aa89d87329 to your computer and use it in GitHub Desktop.
Save phenaproxima/2eb81182eb8322472d76b8aa89d87329 to your computer and use it in GitHub Desktop.
Project Browser and its API

Let's learn a little bit about how Project Browser works.

You can trust me. Together with @chrisfromredfin, @tim.plunkett, and a lot of other contributors, I have spent the better part of the last year refactoring, rearchitecting, and rewriting most of Project Browser, to try and lift it out of its proof-of-concept roots and turn it into a modernized extension browsing and installation system for Drupal. This, then, is a tour of Project Browser from a developer's perspective.

The thing you need to know about Project Browser is that, despite being a decoupled app, it has no JavaScript API at all. The UI, which is written in Svelte, is completely and totally locked down and cannot be extended. What API surface it has is entirely on the PHP side. So let's talk about what those parts are.

The Project class

The most important class in Project Browser is Project. It is a value object that represents a single project that you can see in the UI. Project Browser takes a pretty expansive definition of what a "project" is -- it could be a module, a recipe, or really anything else. A project could even be a bundle of packages (what's known in Composer-ese as a metapackage).

Every project needs to have certain metadata -- those are the required parameters of Project's constructor. That metadata is used in certain ways by the UI, and by other parts of the API. As long as a project has the required metadata, Project Browser doesn't really care about what, exactly, it is.

Source plugins

Project Browser introduces the idea of a source plugin. A source plugin is just a plugin that, well, returns information about projects that are available in some location. A few examples:

  • There's a source plugin that returns information on modules from drupal.org (drupalorg_jsonapi).
  • There's one that scans the local filesystem for recipes, and represents each one as its own project (recipes).
  • In 2.x-beta3 and up, there's one that reads project information from an external list and shows those projects to the user (recommended).
  • There's one that only shows core modules (drupal_core).

Source plugins are just regular Drupal plugins, and you can write your own. Project Browser treats its built-in source plugins as internal, and won't let you extend them -- if you want to extend a built-in Project Browser source plugin with your own behavior, you should decorate it. (That's generally a safer and better practice than extending plugins anyway.) How to do that is beyond the scope of this gist, but maybe I'll write about it more generally at another time.

You can create your own source plugins by extending ProjectBrowserSourceBase, or implementing ProjectBrowserSourceInterface directly if you're hardcore.

Activators

The final crucial piece of Project Browser's API are activators. First, a little background...

Here's the thing about projects. They have absolutely no idea what their status is in the Drupal site. You can have a project that shows information about a module, and it does not know if it's installed on the Drupal site, or even if it's available in the code base for Drupal to install. That responsibility falls to the activators.

Activators are tagged services, implementing ActivatorInterface and tagged with project_browser.activator, which can examine a project and do a few important things:

  • Determine if project is installed on the current site, or even available to be installed (i.e., already composer required). Activators use the more general term "activated", rather than "installed", since a project might not be something "installable" as you normally think of it. Activators can tell you if a given project is active on the current site, and it's up to the activator to decide what that means.
  • Activators are responsible for activating projects. For example, Project Browser has an activator for modules -- and as far as it's concerned, activating a module means installing it. Similarly, the recipe activator applies a recipe. You could have a totally custom activator that does something else when asked to activate a particular project. An activator can return AJAX commands when it activates a project, which the UI will execute.
  • Activators can provide lists of tasks, which are just links, that a user can take for a specific project. For example, for an installed module, the module activator will provide links to the module's configuration form and its permissions list.
  • Activators can provide helpful instructions or help text explaining how to activate a project manually if needed. (This is generally not needed if you have Package Manager installed.)
  • Activators have no concept of de-activation. They cannot uninstall modules, or un-apply recipes, or generally reverse the process of activating a project. That's not to say you can't uninstall a module you installed with Project Browser, or undo a recipe that was applied by Project Browser; it just means that the activators have nothing to do with that process.

You can write a custom activator if you want to -- it may be useful in certain cases. But generally you can rely on Project Browser's built-in activators.

Conclusion

These are the points which you can use to extend Project Browser and modify its behavior. Pretty much everything else in Project Browser is internal (which is enforced by the classes being final) and shouldn't be touched. But you can get a surprising amount done with just these pieces!

And bear in mind...the default JavaScript UI is internal and can't be extended, sure: but Project Browser is decoupled and exposes endpoints. There's no real reason you couldn't write a whole new UI for Project Browser if you wanted to!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment