Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

User Bundle Laravel Package

kimai/user-bundle

Symfony bundle that adds user management to Kimai, including user entities, roles, authentication integration, and admin screens for creating, editing, and organizing users within the time-tracking application.

View on GitHub
Deep Wiki
Context7

Getting Started

Know what you're installing. kimai/user-bundle is not a general-purpose user library — it is a subtree split of the kimai/kimai monorepo (the src/UserBundle directory), published as a standalone repo for packaging/autoloading reasons. It is archived, has essentially no independent consumer base, and is only meaningful alongside kimai/core-bundle, kimai/timesheet-bundle, and the Kimai application skeleton. Treat it as a component of Kimai, not a dependency you reach for in a new Symfony app.

Minimal setup

  1. In practice you get it via the monorepo:

    git clone https://github.com/kimai/kimai.git && cd kimai
    composer install
    bin/console kimai:install
    

    If you are on a Kimai version that consumed split packages, composer require kimai/user-bundle:^2.0 works — but only inside that app's dependency graph. Standalone installs will immediately collide on Doctrine entity mappings, symfony/security-bundle version, and the Kimai\CoreBundle\ classes it type-hints against.

  2. The bundle registers itself as Kimai\UserBundle\KimaiUserBundle in config/bundles.php. Services, routes, and entity mappings are loaded from Resources/config/.

  3. Configuration lives under the kimai: key of config/packages/kimai.yaml, with the canonical schema in src/DependencyInjection/Configuration.php. Read that file before guessing at keys — the public docs lag the code.

Where to look first

  • src/Entity/User.php, Team.php, Role.php — the domain model. User is your UserInterface, holds roles, locale/timezone, enabled flag, and a collection of UserPreference.
  • src/Repository/UserRepository.php — also the UserLoaderInterface backing the Symfony security provider.
  • src/Events/ — the real extension surface (UserCreateEvent, UserUpdateEvent, team events).
  • src/Security/ — voter/permission wiring; this is where "can this user see that timesheet" is decided.
  • src/Controller/ — UserController, TeamController, RoleController, RegistrationController, ProfileController. These show you the intended admin UX.

First use case: provision an admin and a teamlead, then confirm scoping.

bin/console kimai:user:create        # interactive; add --help for non-interactive flags
bin/console kimai:user:promote <username> ROLE_TEAMLEAD

Then log in as the teamlead, create a team in /en/team/, add a member, and verify the timesheet listing only exposes data for that team's customers/projects. That single loop exercises the entity model, the permission voter, and the team scoping you'll be debugging constantly.

Implementation Patterns

CLI-first provisioning. For production, script user lifecycle rather than clicking. kimai:user:create, kimai:user:promote, and kimai:user:demote are the stable surface; wrap them in deploy/onboarding scripts and treat the admin UI as a fallback. This survives UI refactors and gives you an auditable path.

Event subscribers are the intended extension point. Because the entity is Doctrine-mapped inside a bundle you don't own, the supported way to react to user lifecycle is the event dispatcher.

namespace App\EventListener;

use Kimai\UserBundle\Event\UserCreateEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;

final class ProvisionUserSubscriber implements EventSubscriberInterface
{
    public function __construct(private CrmClient $crm) {}

    public static function getSubscribedEvents(): array
    {
        return [UserCreateEvent::class => 'onCreate'];
    }

    public function onCreate(UserCreateEvent $event): void
    {
        $user = $event->getUser();
        $this->crm->createContact($user->getEmail(), $user->getUsername());
        // seed defaults, e.g. preferences / language / timezone
    }
}

Tag it kernel.event_subscriber (autoconfigured in Kimai's service config). The same pattern applies to update events for syncing display names, disabled flags, or external identity mappings. This is exactly how the LDAP/SAML-style identity plugins hook in — read their source for the reference implementation.

Teams are the authorization unit, roles are the coarse switch. Kimai has effectively two axes:

  • Roles (ROLE_USER, ROLE_TEAMLEAD, ROLE_ADMIN, ROLE_SUPER_ADMIN) set the ceiling.
  • Team membership sets the scope — which customers, projects, activities, and timesheets a user can touch.

Model your organisation as one team per customer (or per department), put the customer-facing consultant in the relevant team(s), and give one person per team ROLE_TEAMLEAD. Do not try to encode scoping in roles; you'll fight the voter.

Programmatic creation when you need more than the command offers:

$user = new User();
$user->setUsername('jane');
$user->setEmail('[email protected]');
$user->setPlainPassword($generated);        // hashed by the bundle's listener/service
$user->setEnabled(true);
$user->setRoles(['ROLE_USER']);

$user->setPreference('language', 'de');     // per-user preference key/value
// timezone: leave null to fall back to the app default

$em->persist($user);
$em->flush();

If you bypass whatever service orchestrates creation, remember to dispatch the create event yourself — otherwise your CRM subscriber silently doesn't fire.

Registration and password reset are controlled through the kimai: config tree (registration on/off, mailer behaviour) plus the RegistrationController routes. If you need a bespoke signup (SSO pre-provisioning, invitation tokens), override the controller route in your app's routing rather than patching the bundle.

Permission checks in your own controllers/plugins go through the standard voter:

if (!$this->isGranted('view_other_timesheet')) { throw new AccessDeniedException(); }

Never assume isGranted('ROLE_ADMIN') implies access to a specific record — the voter combines role ceiling with team scope, and ROLE_SUPER_ADMIN is the one bypass.

Preferences over schema changes. When you need "extra field on the user", first try a UserPreference key. It's indexed, serialised through the profile form's extension hooks in plugins, and avoids the migration problem described below.

Gotchas and Tips

  • Archived + subtree split means PRs don't land here. Fixes go to kimai/kimai; this repo is a read-only mirror. File issues upstream, and pin the app version rather than the split package version — the two drift.
  • Do not extend the User entity by inheritance. Doctrine single-table inheritance across a vendor bundle mapping is a migration nightmare and the bundle's forms, validators, and repository queries assume the concrete class. Use events, preferences, or a side table with a one-to-one relation you own.
  • Unique constraint violations are not form errors. Duplicate username/email throws a Doctrine UniqueConstraintViolationException at flush time. If you build a custom creation controller, validate with the bundle's constraints (or a UniqueEntity) before persisting, or you'll hand users a 500.
  • Role changes and sessions. Depending on the firewall's user-refresh configuration, promoted/demoted users may keep old permissions until their next request or a re-login. When a support ticket says "I gave them admin and it didn't work", suspect the session before the voter — and have them log out and back in.
  • Deleting users is often the wrong move. Users with timesheets are historical data; prefer setEnabled(false). Hard deletes will either be blocked by relations or orphan reporting. Disable, don't delete, and let a retention policy handle the rest.
  • Mailer misconfiguration fails quietly. Registration confirmation and password reset depend on MAILER_DSN. The default (sendmail) is frequently absent in containers — set a real DSN and test the reset flow explicitly, because the failure surfaces as "no email arrived", not as an exception.
  • Timezone and locale are per user and affect arithmetic, not just display. A user with a null timezone inherits the app default; changing it later reinterprets stored timestamps in reports. Decide the policy before go-live, not after the first invoicing cycle.
  • Cache after config changes. Kimai compiles the kimai: config and container; bin/console cache:clear after editing kimai.yaml or you'll debug phantom behaviour for twenty minutes.
  • Read DependencyInjection/Configuration.php for every config key. The documentation is thin for this bundle, and several options changed shape between 1.x and 2.x. The schema file is the only reliable contract.
  • Test against fixtures, not production. The bundle ships Doctrine fixtures under tests/; use them in integration tests for anything touching scoping, because the permission matrix (role × team × ownership) has more edge cases than you'll enumerate by hand.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons