uandi / ui-permissions
Deployable TYPO3 backend user permissions via YAML configuration files
Package info
github.com/uandi/ui-permissions
Type:typo3-cms-extension
pkg:composer/uandi/ui-permissions
Requires
- typo3/cms-core: ^12.4.8 || ^13.4 || ^14.3
Requires (Dev)
- a9f/typo3-fractor: ^0.5
- ergebnis/composer-normalize: ^2.49
- friendsofphp/php-cs-fixer: ^3.93
- helmich/typo3-typoscript-lint: ^3.3
- php-parallel-lint/php-parallel-lint: ^1.4
- phpstan/extension-installer: ^1.4
- phpstan/phpstan: ^2.1
- phpstan/phpstan-strict-rules: ^2.0
- seld/jsonlint: ^1.11
- spaze/phpstan-disallowed-calls: ^4.7
- squizlabs/php_codesniffer: ^4.0
- ssch/typo3-rector: ^3.11
- typo3/coding-standards: ^0.8
Suggests
None
Provides
None
Conflicts
None
Replaces
- typo3-ter/ui-permissions: 14.1.1
README
Deployable TYPO3 Permissions
TYPO3 extension to declaratively manage backend user group permissions as code.
If you have a TYPO3 project with any level of permissions for backend users you probably ran into one of the following problems:
- Creating the permissions in the TYPO3 backend takes a lot of time and clicks
- If you have different environments like INT, PPE and PROD, you would have to create the permissions on each environment
- You deployed something to PROD and forgot about the permissions until the customer calls because he can't see the new feature
This extension introduces deployable YAML descriptions of permissions that are gathered from all active extensions, merged, and written into the database via a CLI command. This makes permission changes reviewable (VCS), testable, and reproducible across environments.
What it does
- Collects all files matching
*.permissions.yamlfrom every active TYPO3 extension (recursive scan)- These YAML files basically contain abstractions of
be_groupsandsys_filemountsrecords.
- These YAML files basically contain abstractions of
- Merges all found configurations deterministically
- Writes/updates corresponding records in:
sys_filemountsbe_groups
- Resolves relations (e.g.,
be_groups.subgroup,be_groups.file_mountpoints) by referencing other items by their logical keys
Goals
- Treat backend permissions as configuration-as-code
- Enable safe deployments without manual backend tweaking
- Keep environments (INT/PPE/PROD) in sync
- Provide a clear, readable schema that is easy to review in pull requests
- If you know how TYPO3 permissions work and how they are stored in the DB, you'll have no trouble writing the YAML abstractions
Installation
composer require uandi/ui-permissions
Extension configuration (Settings → Extension Configuration)
pidBeGroups(int): PID to store/updatebe_groupsrecordspidSysFilemounts(int): PID to store/updatesys_filemountsrecordscreateFilemountDirectories(bool): Create directories for filemounts if missing
These values are used by the repositories when inserting/updating records.
CLI usage
Run via TYPO3 CLI. This command is ideally executed during deployments.
# Apply the current YAML permissions to the database
vendor/bin/typo3 ui_permissions:update
# Generate YAML files from the permissions that already exist in the database
vendor/bin/typo3 ui_permissions:export
Migrating an existing project
ui_permissions:export is the inverse of ui_permissions:update: it reads the existing be_groups and
sys_filemounts records and writes them as *.permissions.yaml files. This is meant to bootstrap the
configuration of a project whose permissions were built in the TYPO3 backend.
# Print the whole permission setup as YAML to review it first vendor/bin/typo3 ui_permissions:export # Store the derived permission keys in the database, then write one file per permission key vendor/bin/typo3 ui_permissions:export --write-permission-keys --output packages/my_sitepackage/Configuration/Permissions
| Option | Description |
|---|---|
-o, --output |
Directory to write the files to. Relative paths are resolved from the project root, EXT: paths are supported. Without this option the YAML is printed to stdout. |
--single-file |
Write everything into one Permissions.permissions.yaml instead of one file per permission key. |
-g, --group |
Limit the export to the given be_groups, identified by uid, permission key or title. Repeatable. Only the filemounts that these groups reference are exported. |
--include-hidden |
Also export disabled records. They are skipped by default, because the hidden state has no representation in the YAML abstraction. |
--write-permission-keys |
Store the generated permission keys in the database (see below). No other field is touched. |
-f, --force |
Overwrite existing files. Without it, existing files are reported as skipped. |
Permission keys of existing records
Records that were created through the TYPO3 backend have no permission_key, so the export derives one
from the record title (Redakteure Süd becomes Redakteure_Sued, duplicates get a numeric suffix).
ui_permissions:update matches a record by its permission_key and falls back to matching a record whose
title equals the permission key. A derived key that differs from the title would therefore create a second
record instead of updating the existing one. The export lists exactly those records and recommends a re-run
with --write-permission-keys, which stores the derived keys on the existing records first.
What the export covers
Without --group every non-deleted group and filemount is exported, and subgroup and file_mountpoints
are written back as permission keys instead of uids. Fields with no representation in the YAML abstraction
(for example hidden or category_perms) are not exported, but every record that uses one of them is
reported so it can be handled manually. The same applies to references that cannot be
resolved and, on installations upgraded from TYPO3 v11 or below, to denied values in explicit_allowdeny.
The generated files are a starting point, not a finished configuration. Review the permission keys against
the naming conventions below, move the files into the extensions they belong to, and replace environment
specific values such as db_mountpoints page uids before committing.
Where to place YAML files
While the collector scans the whole extension path, the recommended location is:
EXT:your_ext/Configuration/Permissions/YourName.permissions.yamlfor general purposesEXT:your_ext/ContentBlocks/ContentElements/*/permissions/YourName.permissions.yamlfor ContentBlock elements.
Any filename ending with .permissions.yaml is picked up.
Naming conventions (permission files and keys)
Based on the “TYPO3 Backend User Management” concept (naming-for-purpose), use clear, prefixed keys. The map key of each item becomes its durable permission_key (and should also inform the filename):
- R_<Name>: Role groups that aggregate capabilities via
subgroup, e.g.R_Editors,R_EditorsInChief - ACL_<Name>: Access control scopes for single tables/features/CTypes, e.g.
ACL_tt_content_common,ACL_contentelement_gallery - FM_<Name>: Filemount definitions and helper groups that attach filemounts, e.g.
FM_ProjeectIcons - DB_<Name>: Database (page tree) mounts, e.g.
DB_Projeect - PG_<Name>: Page-related presets, e.g.
PG_Projeect - L_<Name>: Language-related presets for accessible languages, e.g.
L_Spanish
Further reading
- https://archive-2019.typo3worx.eu/2017/02/typo3-backend-user-management/
- https://punkt.de/de/blog/2017/typo3-backend-berechtigungen.html
Recommendations
- Keep keys unique and stable; they act as IDs across environments
- Name files accordingly, e.g.
R_Editors.permissions.yamlcontaining abe_groups: R_Editors: ...definition - Compose roles (R_) from ACL_ and FM_* using
subgroupandfileMountpoints
YAML schema
As was already noted, the YAML files are abstractions of the database records. So the top level keys describe which table we're dealing with:
sys_filemounts: map of filemounts by a unique key ("permission key")be_groups: map of backend groups by a unique key ("permission key")
Each item’s map key becomes its permission_key in the database and is used to reference between items.
sys_filemounts item fields
permission_key(string, optional - map key will be used if missing)title(string, optional - map key will be used if missing)description(string, optional)identifier(string, e.g.1:/assets)readOnly(bool, default: false)
Legacy support: If identifier is missing, the pair base + path will be converted to identifier (base:path).
be_groups item fields (selection)
permission_key(string, optional - map key will be used if missing)title(string, optional - map key will be used if missing)description(string, optional)tablesModify(array|string CSV)tablesSelect(array|string CSV)pagetypesSelect(array|int|string CSV)nonExcludeFields(maptable: [field, ...]or strings/CSV)explicitAllowdeny(map of tables → fields → [values])- Legacy support:
explicitAllowdeny: { allow: { table: { field: [values] } } }also accepted
- Legacy support:
dbMountpoints(array|int|string CSV of page IDs)fileMountpoints(array of filemount permission keys)filePermissions(array|string CSV)subgroup(array|string CSV of other be_group permission keys)groupMods(array|string CSV)availableWidgets(array|string CSV of dashboard widget identifiers, requires EXT:dashboard)TSconfig(string)tsconfigIncludes(array|string CSV ofEXT:-paths to TSconfig files)allowedLanguages(array|string CSV of sys_language uids)customOptions(array|string CSV)mfaProviders(array|string CSV)workspacePerms(bool|int, default: false - grants editing in the live workspace)
Only fields that are present in the YAML are written to the database. Omitting a field does not reset it, the value that is already stored in the record is kept. Unknown keys are silently ignored.
Fields whose database column does not exist in the current installation are skipped instead of letting the
whole record fail, and ui_permissions:update reports them at the end of its run. Optional system
extensions add their own columns to the permission tables, for example availableWidgets which only
exists when EXT:dashboard is installed. This way the same configuration can be shared across projects
that do not all install the same system extensions.
Some fields are resolved after initial persistence (e.g., subgroup, fileMountpoints). See Classes/Domain/Repository/BackendUserGroupRepository.php for some insights.
Merge behavior
Merging is custom (see ConfigurationCollector::mergeConfiguration()):
- Associative arrays are merged recursively; later values override earlier scalars
- List arrays are concatenated and
array_unique()-ed
This enables layering configurations across multiple extensions.
Examples
Examples of permissions files
Example ACL for a content element (CType)
be_groups: ACL_contentelement_gallery: tables_select: tt_content tables_modify: tt_content explicit_allowdeny: tt_content: CType: - contentelement_gallery
Example role composed of many ACLs
be_groups: R_Editors: pagetypes_select: 1, 3, 4, 199, 254 subgroup: - ACL_form_formframework - ACL_pages_common - ACL_sys_file_metadata - ACL_sys_file_reference - ACL_tt_content_common - ACL_tt_content_shortcut # Content Elements - ACL_contentelement_gallery - ACL_contentelement_media - ACL_contentelement_quote - ACL_contentelement_stage - ACL_contentelement_teaser - ACL_contentelement_textmedia # Grids - ACL_contentelement_grid2columns50-50 - ACL_contentelement_grid3columns33-33-33 - ACL_contentelement_grid4columns25-25-25-25 groupMods: - web_layout - media_management - user_setup
Example filemounts (FM_*) and their respective be_groups
sys_filemounts: FM_ProjeectIcons: title: 'Assets' description: 'Base Fileadmin Folder for Icons' identifier: '1:/user_upload/Projeect/Icons/' FM_ProjeectIcons_ReadOnly: title: 'Icons' description: 'Base Fileadmin Folder for Icons (Read Only)' identifier: '1:/user_upload/Projeect/Icons/' read_only: 1 be_groups: FM_ProjeectIcons: file_mountpoints: - FM_ProjeectIcons FM_ProjeectIcons_ReadOnly: file_mountpoints: - FM_ProjeectIcons_ReadOnly
Example database mount (DB_*)
be_groups: DB_Projeect: db_mountpoints: 1
Example TSconfig includes
Instead of inlining everything into TSconfig, reference deployable TSconfig files. The
paths are stored as given, so they stay valid across environments.
be_groups: R_Editors: tsconfig_includes: - 'EXT:ui_sitepackage/Configuration/user.tsconfig' - 'EXT:ui_sitepackage/Configuration/TsConfig/User/Editors.tsconfig'
Note that TYPO3 stores tsconfig_includes in a varchar(255) column, so the resulting
comma-separated list of paths has to stay below that limit.
Tips
- Keep permission keys unique and stable; they become the durable identifier (
permission_key) across environments - Split large setups into multiple files; merging will combine them
- Prefer arrays over CSV for readability, but both are supported in many fields
What's next
- Maybe try to introduce some level of plausibility/error checks to make it easier to find misconfigurations in the YAML abstraction files