Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

i960's avatar
Level 3

Permission Seeding

I know the topic of permissions has been discussed before, but I couldn't find anything related to this specific issue. There seems to be a piece missing when it comes to using permissions in your code that are also stored in the database. I'll try to explain what I mean as best I can.

I've built several apps over the years that use roles and permissions. I've done this from scratch before, as well as used packages such as Spatie's: https://github.com/spatie/laravel-permission.

In all cases, I've run across this issue of keeping the permissions in the database synced up with the permissions I'm referencing in my code. Let me explain with a short example. Using Spatie's package, I can create a permission like this:

Permission::create(['name' => 'edit articles']);

That gets stored in the database. I can even create a seeder to seed the database with all the permissions I need. Now I can assign that permission to a user like this:

$user->givePermissionTo('edit articles');

The relationship there is stored in the database as well. I can build a UI to help me assign these permissions. So far so good. Now, somewhere in the code I need to check to see if the current user has permission to visit a certain page, or perform an action, or whatever. There are tons of ways to do this in Laravel, via gates, policies, middlewares, etc. None of that is relevant to my question, except for the part where you get to actually check the permission. Something like this:

$user->hasPermissionTo('edit articles');

That works, and is similar to how I've seen permissions done everywhere else. Most articles, packages, forum posts, etc, all use some form of this. But there is a big glaring problem. The permissions name, in this case "edit articles", is hard coded into your code. That's fine, except that it's referencing the name stored in the database. If that permission doesn't exist in the database already, then it won't work. Since I already seeded the database, it does exist, and life goes on. But what happens 6 months down the road, and I build a new widget feature.

So now I have code checking for the permission "edit widgets". Since this app has been in production for 6 months, that permission doesn't exist in the database yet. I can't just add it to my seeder class and run that again. Laravel doesn't have the concept of seed migrations, and it feels wrong to me to seed data into a production database after the fact. The way I've handled this before was to simply create a UI for adding permissions, and now I have to go into that UI after my code is deployed, and create the permission that matches what I'm checking for in the code. To me, this creates a very brittle situation. The code is tied to the data in the database. If I'm the developer and also the administrator of the site, I can fix this up manually. But what if I'm not? What if I am shipping this code off to a customer to deploy on their servers, and for whatever reason, I am not an admin on the site. I do not have permissions to log in and create the permission. I have to give them instructions on how to do it themselves. While that works, it just seems wrong to me. To me, the permissions in the database should reflect the permissions being checked in the code at all times, without any human involvement.

So my question comes down to, logically how would you handle this? Build some class or command or something that will enumerate all the permissions in the code, and update the database to match? Or have some separate config file listing all the permissions, and again enumerate that on a schedule or as part of code deployment, and sync with the database? I don't really need code samples as I can build anything just fine, but I'm struggling to figure out the concept of how this would work, and what would be the best way to resolve this. Am I crazy to think this is an issue?

0 likes
11 replies
Snapey's avatar

I was just thinking about this same issue yesterday. I've not committed anything to code, but was wondering about using a migration file to create the new row in the permissions table.

Its just code in the migrations, there is an up method, whats to stop there being a Permission::create('edit widgets') statement in there?

1 like
Cronix's avatar

@snapey that's what I do. If I have settings that need to be in the app, I put them in migrations.

The other option of course is seeding, but then you have to write logic to see if it's already in the database since it will just keep adding and adding anytime the seeds are run and I only wanted them to run once, so went with the migration route which works great.

1 like
click's avatar
click
Best Answer
Level 35

I personally have all my permissions and roles in a config file that simply says:

  • This app knows these permissions (view-user, create-user, etc.)
  • This app knows these roles (admin, developer, financial, etc.)
  • This role has this permissions

I've made an artisan command that reads this config file and 'syncs' this with the database. It creates new roles if they are added, it removes them when necessary. And the same with permissions and the link between the permission and the role.

This way I have my permissions and roles in version control and I just run the command with each deployment.

Note that in this situation the truth is always in my config file. So I do not have an interface where you can manage permissions per role. If you do want an interface where you can manage permissions this solution won't work because every time you do a new deployment the changes you made through the interface will be overwritten with the state in the config file.

1 like
i960's avatar
Level 3

Hmm, interesting. I never thought about using migrations for this. The only thing that bothers me about it is I'd prefer to keep migrations limited to schema changes, but I guess technically that could work.

I really was leaning towards the config method, where you have an array of permissions, and you have an artisan command that would read the list of permissions and create them if they don't exist. But then that's just the same thing as a seeder. It would look something like this:

$permissions = [
    'admin.dashboard.view'      => 'View Admin Dashboard',
    'admin.roles.view'          => 'View Roles',
    'admin.roles.create'        => 'Create Role',
    'admin.roles.edit'          => 'Edit Role',
    'admin.roles.delete'        => 'Delete Role',
    'admin.permissions.view'    => 'View Permissions',
    'admin.permissions.create'  => 'Create Permission',
    'admin.permissions.edit'    => 'Edit Permission',
    'admin.permissions.delete'  => 'Delete Permission',
    'admin.workflows.view'      => 'View Workflows',
    'admin.workflows.create'    => 'Create Workflow',
    'admin.workflows.edit'      => 'Edit Workflow',
    'admin.workflows.delete'    => 'Delete Workflow',
];

foreach ($permissions as $key => $value) {
    Permission::updateOrCreate(
        ['name' => $key],
        ['display_name' => $value]
    ]);
}

I think that's how eloquent's updateOrCreate method works... I'll need to double check that. But the idea is that it checks for the permissions existence based on the name field, the one I would use in my code, and then it will create if it doesn't exist, or update with the display name if it does exist. So this array becomes the "source of truth" in a way for all permissions, and I just need to keep this list updated and run the seeder on deployment, like this:

php artisan db:seed --class=PermissionsTableSeeder --force
i960's avatar
Level 3

So it sounds like we are all saying the same thing, it's just a matter of where you put this logic, in migrations, a seeder, or artisan command. And that's largely up to opinion. But I'm glad that I'm not the only one that's experienced this. I thought I was going crazy since I couldn't find anyone else talking about it.

i960's avatar
Level 3

@m-rk That's really interesting that you're doing roles in config as well. What do you do when a user is assigned to a role that later gets deleted out of config? Are you just doing a foreign key with cascading deletes and letting the database handle cleanup?

click's avatar

Yes, indeed. The reason why I went for the config file is that it is easier to see all of your permissions and which role is able to do what in one view without creating an web interface for that.

Cronix's avatar

You could use whatever method works and makes the most sense for you. Personally I find the migration strategy cleaner. Just name the migrations appropriately, "insert_permissions", "change_dashboard_permission_name", etc.

This probably isn't something that will be changing often, if at all.

It would be slick if Laravel migrations had some sort of seed() method for migration, that would run code after the up/down method was called on each migration so you could just have it execute a particular seed.

Schema::create('some_table', function (Blueprint $table) {
    $table->increments('id');
    $table->timestamps();
})->seed(YourSeederClass:class);
click's avatar

@i960 that never happened and at this moment I don't see it happening in my application at this moment. But if a role is removed it is automatically removed from the user indeed

And if you want for example to split up an existing role into two new roles you do need a database migration to migrate the users to the new roles. But also this never happened for now. The roles are pretty clear in my applications and won't change that much over time. But I do create new permissions often.

i960's avatar
Level 3

@Cronix They do have this:

php artisan migrate:refresh --seed

But that would run all your seeds and only on a fresh migration. So yeah, I could see that as being cool to have. What would also be cool is versioned seeders in the core. I found a couple of packages that do it, but most didn't seem to be up to date, and I'd rather not pull in another package just for this one thing.

@m-rk I'm marking your suggestion as the answer, as it's the one I was kinda leaning towards anyway when I made this post. It just seems to feel the best to me. I will be keeping the UI for roles and role to permission relationships since I have some users outside of our dev team that will be managing that, but permissions will go in config since they are fairly static and directly tied to code.

i960's avatar
Level 3

@m-rk What's funny is in my situation it's the opposite. Permissions rarely change, and are only added when new features are rolled out. But roles, and their relationship to permissions are constantly changing. Our company has gone through a ton of structural changes over the past few years, so we have people moving around between departments, people within departments who temporarily change roles or need unique permissions that no other role has, or changes to what a role should be allowed to do. We have this same issue with our Active Directory groups. Things are starting to stabilize thankfully, but I have to build this app to allow frequent changes without code deployments in the future.

Please or to participate in this conversation.