Skip to main content
Plugins can’t add their own Doctrine entities or migrations, because the ORM maps only the core src/ directory and the migration paths are fixed. That’s less limiting than it sounds: most plugins need settings and a little state, and both have a home.

Choose a storage strategy

Options

Options are key-value rows holding JSON. They’re loaded once per request and exposed to the container, so reading one is free after boot.

Reading

In Twig, the same values are under option:

Writing

Writes merge, they don’t replace. Associative arrays are merged recursively, so the call above leaves option.crm.api_key untouched. Lists are replaced wholesale, which is what you want for an array of configured items.
One top-level key per plugin. Two pages writing different subtrees of the same key is fine, but two plugins sharing a key will overwrite each other’s structure.
Options are global to the installation, not per workspace or per user. For per-workspace state, key the data inside the option by workspace ID, or store it on a core entity.

Core entities

Your plugin can read and write the core’s own data through its commands and repositories, which keeps validation and events intact:
Entities are persisted at the end of the request: the application flushes the entity manager once, after the response is emitted. Call the entity’s methods and let the flush happen.
Several core entities support metadata, which is a practical place for a small amount of plugin-owned data attached to an existing record. Check the entity for an addMeta() or setMeta() method before inventing storage of your own.

When you really need tables

If your plugin genuinely needs its own tables, own them end to end:
  • Create the schema from an install() hook, using the DBAL connection from the entity manager, and drop it in uninstall().
  • Query with DBAL rather than the ORM, since Doctrine won’t map your entities.
  • Namespace table names with your vendor, such as acme_notes, so you never collide with a future core table.
Aikeedo won’t migrate, back up or delete tables you create, and nothing validates them against a future release. Prefer options or an external service unless you’re prepared to maintain the schema yourself.

Caching

For values that are expensive to compute or fetch, use the PSR-6 cache pool instead of a new option:
Clearing the application cache also clears this pool, so never treat it as durable storage.

Cleaning up

Uninstalling a plugin removes its files, but not its data. Delete what you own in the uninstall() hook: