Framework
mpociot/teamwork avatar
mpociot/teamwork

teamwork: user and team associations with invitations for Laravel

User to Team associations with invitation system for the Laravel Framework

1,100 stars180 forksPHPMIT

At a glance

What is it?
teamwork is a Laravel package that adds teams to a User model, with a many-to-many pivot, a pending invite table, a current_team_id column and a scaffold command that generates the views, routes and controllers. It supports Laravel 6 and above, installs with a single Composer require, and its own API has grown across eleven major versions.
Who is it for?
teamwork suits a Laravel application that needs multi-tenant grouping, per-team ownership rules and an invitation flow, and that can accept the design of storing the current team as a column on the users table. It is also a poor fit if you want a full permissions system, since the package supplies one middleware, teamowner, which checks ownership of the current team rather than a role model.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 50 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What the package models, and the three tables it adds

teamwork is described as the fastest and easiest method to add a User and Team association with Invites to a Laravel 6 and above project, and the repository description calls it User to Team associations with invitation system for the Laravel Framework. The data model is three tables. The teams table stores team records. The team_user table stores many-to-many relations between users and teams. The team_invites table stores pending invites for email addresses to teams. On top of those, a new column current_team_id is added to the users table, and that column defines the team the user is currently assigned to. The primary language is PHP, the licence is MIT with the text in LICENSE.md, and the last push to the repository was on 2026-09-03, the same day as the 11.0.0 release.

Installation is a Composer require, then two artisan commands

The install path is short. You run composer require mpociot/teamwork, and the Teamwork Facade will be discovered by Laravel automatically, so there is no manual provider registration. To publish the configuration and migration files you run the vendor:publish command with the provider named, and that creates a teamwork.php in your config directory. The default configuration should work fine for you, but you can look at it if you want to customize the table or model names Teamwork will use. Then you run php artisan migrate to generate all the tables Teamwork needs. The documentation flags one thing here that is easy to miss, that if your users are stored in a different table other than users you must modify the published migration before running it, since the package assumes the conventional table name.

bash
composer require mpociot/teamwork
bash
php artisan vendor:publish --provider="Mpociot\Teamwork\TeamworkServiceProvider"
bash
php artisan migrate

The UserHasTeams trait is the whole API surface

The package attaches itself to your existing User model with a trait, and the trait brings the relations and the methods. You add use UserHasTeams to the class, and you get the relation with Team plus the methods teams(), ownedTeams(), currentTeam(), invites(), isTeamOwner(), isOwnerOfTeam($team), attachTeam($team, $pivotData = []), detachTeam($team), attachTeams($teams), detachTeams($teams) and switchTeam($team). That list is the package's contract with your application, and it is worth reading as a design statement. A single current team is a column, membership is a relation, ownership is a check, and switching is an explicit call. After adding the trait the documentation tells you to dump the composer autoload with composer dump-autoload, which is a Laravel-era detail that still appears in package setup instructions.

php
<?php namespace App;

use Mpociot\Teamwork\Traits\UserHasTeams;

class User extends Model {

    use UserHasTeams; // Add this trait to your model
}

The Team model, and the one config key you must change

You create your own Team model in app/Team.php extending TeamworkTeam, and it has two main attributes. owner_id is a reference to the User model that owns the team, and name is the human readable name. owner_id is an optional attribute and is nullable in the database, so a team can exist without an owner, which is the sensible choice given that an account can be deleted. The step that people forget is told explicitly: when extending TeamworkTeam you must change the team_model variable in config/teamwork.php to point at your new model, for instance 'team_model' => App\Team::class. The configuration file and the model class are the two halves of the same thing, and the package cannot infer the second from the first, so if you skip the config change your application silently uses the package's own model rather than yours.

php
<?php namespace App;

use Mpociot\Teamwork\TeamworkTeam;

class Team extends TeamworkTeam
{
}

Scaffolding: make:teamwork generates the views, routes and controllers

The easiest way to give a new Laravel project team abilities is the make:teamwork command, which creates all views, routes and controllers needed to make the project team-ready. Out of the box it creates a team listing, team creation, editing and deletion, and the ability to invite new members to teams. The documentation compares it to the make:auth command for Teamwork, and tells you to look at the newly installed /teams route to get started. The pragmatic point here is that the scaffolding is a starting point rather than a design. You get working PHP and Blade to modify, not a design system, and teams are usually the part of an application that gets customised more than any other.

bash
php artisan make:teamwork

One middleware, and it checks ownership of the current team

The permission model is deliberately minimal. There is a single middleware, TeamOwner, which you register in your app\Http\Kernel.php by adding a teamowner entry to the route middleware array, pointing at the Mpociot\Teamwork\Middleware\TeamOwner class. You then apply it to a route alongside auth, and only if the authenticated user is the owner of the current team can they access it. The documentation states the intent clearly, that this middleware is aimed to protect routes where only the owner of the team can edit, create or delete that model. So there is no role table, no permission list and no per-action granularity, and a team owner is the only distinction the package draws. If your application needs a member, a manager and an admin, you build that on top rather than configuring it here.

php
protected $routeMiddleware = [
    ...
    'teamowner' => \Mpociot\Teamwork\Middleware\TeamOwner::class,
    ...
];

The API for creating, attaching and switching teams

The usage examples show the shape of the API, and one behaviour in them is worth highlighting. You create a Team by setting owner_id and name and saving it, and the first parameter of attachTeam can be a Team object, an array or an id, which means you can attach from several shapes of input without converting first. There is also the Eloquent original technique of $user->teams()->attach($team->id), which takes an id only. The behaviour to note is that by using the attachTeam method, if the user has no teams assigned, the current_team_id column will automatically be set. And there is a createOwnedTeam method on the trait, which creates the team for the user as owner, attaches the user to it and switches to the newly created team, and if the user has another current team active you pass a second parameter as true to force the switch. The function returns the newly created instance of your team model.

php
// Create user owned team and switch the current team to this new team.
$team = $user->createOwnedTeam(['name' => 'My awesome team']);

// If user has another current team active, you should pass second parameter as true to force switch to the new team.
$team = $user->createOwnedTeam(['name' => 'My awesome team'], true);

Maintenance, versioning and the package's own repository layout

The release history is the interesting part of the maintenance picture. The listed releases are 9.0.0 on 2024-06-18, 10.0.0 on 2025-05-21 and 11.0.0 on 2026-09-03, so the package is on its eleventh major version and has shipped one a year. That cadence is what a long-lived Laravel package looks like when the framework itself moves, and it also means the API you read about may not match the version you install, so a team should pin the major and read that version's documentation. The repository layout supports that reading, with a CHANGELOG.md for the version history, a src/ directory for the package code, a database/ directory for the migrations, a config/ directory for the published configuration, a stubs/ directory for the scaffolding templates, a tests/ directory, and composer.json, phpunit.xml.dist, .php_cs and .styleci.yml for style. The CI badges show a run-tests workflow, codecov coverage, a SensioLabs Insight badge and a Scrutinizer quality score.

Editorial conclusion

teamwork suits a Laravel application that needs multi-tenant grouping, per-team ownership rules and an invitation flow, and that can accept the design of storing the current team as a column on the users table. It is also a poor fit if you want a full permissions system, since the package supplies one middleware, teamowner, which checks ownership of the current team rather than a role model. Before adopting it, read the migration before running it, because the documentation tells you to modify the published migration if your users are stored in a table other than users, and note that the scaffolded views are generated for you rather than designed by you.

Frequently asked questions

What does the teamwork Laravel package add?

It adds User to Team associations with an invitation system. The migration creates three tables, teams, team_user for the many-to-many relations and team_invites for pending invites, and it adds a current_team_id column to the users table that defines the team a user is currently assigned to.

How do I install and set up teamwork in Laravel?

Run composer require mpociot/teamwork, then php artisan vendor:publish --provider="Mpociot\Teamwork\TeamworkServiceProvider" to publish the config and migrations, and then php artisan migrate. The Teamwork Facade is discovered by Laravel automatically, and the package supports Laravel 6 and above.

What does the UserHasTeams trait give me?

It adds the relation with Team plus the methods teams(), ownedTeams(), currentTeam(), invites(), isTeamOwner(), isOwnerOfTeam($team), attachTeam($team, $pivotData = []), detachTeam($team), attachTeams($teams), detachTeams($teams) and switchTeam($team). Remember to run composer dump-autoload afterwards.

How do I add teams to a new Laravel project?

Run php artisan make:teamwork, which creates all the views, routes and controllers needed, out of the box providing a team listing, team creation, editing and deletion, and the ability to invite new members. It is described as the make:auth command for Teamwork.

How does teamwork handle permissions?

With a single middleware, teamowner, which you register in app\Http\Kernel.php and which only allows access if the authenticated user owns the current team. The documentation says it is aimed at protecting routes where only the team owner can edit, create or delete a model.

Official sources

  1. Issues
  2. License: MIT
  3. mpociot/teamwork on GitHub
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/mpociot-teamwork.svg)](https://hysenlabs.com/projects/mpociot-teamwork)

Was this useful?