Roles and Permissions

Why Should You Care?

Roles and permissions aren’t just for security nerds. If you want to know who can do what, and why your API key sometimes says "nope," you’re in the right place. They’re your best friends when you want the user to:

  • Only edit their own blog posts (not someone else’s)
  • View all values on a page, but only edit a select few
  • Invite another user to edit blogs in a specific category
  • Allow someone to edit a draft article, but only let the admin publish and further modify the page

The basics

You assign permissions to roles, and then hand out those roles to your users.

Required Permissions vs. Granted Permissions

Suppose you want only certain users to be able to edit blog posts. You can check for a required permission like blogs. If the user has this permission, they can edit all blogs. If not, access is denied. This makes it easy to control who can do what, just by checking for the right permission string.

You can check permissions in your Blade views like this:

@if ($user->can('blogs')) 
    <article></article>
@endif
Required Granted Has Access?
blogs blogs ✔️
blogs homepage ✖️

Path

You can use paths to control access to specific fields or sub-resources. For example, blog/title refers to the title field of a blog. This allows you to grant fine-grained permissions for different fields and resources.

If a user is granted blog, they can access all fields of a blog. If a user is granted blog/title, they can only access or edit the title field.

Example:

  • An admin with Granted Permission blog can create and edit the entire blog, including all fields.
  • An SEO specialist with Granted Permission blog/title can only edit the title of the blog.
@if ($user->can('blog/title'))
    <input name="title" />
@endif
Requested Granted Has Access?
blog/title blog ✔️
blog/title blog/title ✔️
blog/title blog/content ✖️

Extensions: To Read or Not to Read

Permissions can be extended to specify what actions a user can perform on a resource. The path determines which part of the system the user can manage, and the extension on a permission lets you control what actions are allowed.

  • blogs — allows both reading and writing
  • blogs.read — allows only reading
  • blogs.write — allows only writing

This applies to both requested and granted permissions. For example, you can request blogs.write and only get access if your granted permissions include blogs or blogs.write.

@if ($user->can('blogs.write'))
    <input></input>
@endif

This way, both blogs.write and blogs Granted Permission allow editing the blog.

Requested Granted Has Access?
blogs blogs ✔️
blogs blogs.read ✖️
blogs blogs.write ✖️
blogs.write blogs ✔️
blogs.write blogs.read ✖️
blogs.write blogs.write ✔️

You can also define other activities besides 'write', such as 'blog.delete', to control custom actions on your resources.

For example:

  • blog.delete — allows deleting a blog

Query

With a query, you can make your permissions even more fine-grained by specifying parameters for a resource. This allows you to control access to specific subsets of data.

For example:

  • blogs?author_id=123 — only allows access to blogs by author 123
  • blogs?album=photos_2025 — only allows access to blogs in the album 'photos_2025'

You can check for a specific query in Blade like this:

@if ($user->can('blogs', ['author_id' => 123]))
    <button>Edit your own blogs</button>
@endif

Or for a specific album:

@if ($user->can('blogs', ['album' => 'photos_2025']))
    <button>Edit 2025 album blogs</button>
@endif
Requested Granted Has Access?
blogs?author_id=123 blogs?author_id=123 ✔️
blogs?author_id=123 blogs ✔️
blogs?author_id=123 blogs?author_id=456 ✖️
blogs?album=photos_2025 blogs ✔️
blogs?album=photos_2025 blogs?album=photos_2025 ✔️
blogs?album=photos_2025 blogs?album=photos_2024 ✖️

Roles

Here’s an example of how you can assign permissions to roles in a roles.json5 file:

{
  // The marketer can manage and publish homepage and blog content
  "marketer": {
    "permissions": [
      "homepage",
      "blogs",
    ]
  }
  // The blogger can create new blogs and edit their own blogs
  "blogger": {
    "permissions": [
      "blogs?author_id=me",
      "blogs.create",
    ]
  }
}

You can add as many roles and permissions as you need for your project. You can also assign multiple roles to a single user.

Relative permission

In Confetti, you have one account for all your websites. That’s why permissions are always stored as a full path, starting with a / (slash) followed by the repository name (e.g., /ninja-agency/silent-site/blogs). This makes it possible to manage access across multiple projects and organizations with a single user account.

An owner of an agency can have a permission like /ninja-agency (instead of /ninja-agency/silent-site/). In that case, the owner is allowed to do everything within all repositories of the agency. Packages can still have their own restrictions (these are prefixed with their own repository, like /the-pkg-maker/image-uploader). See the package section for more information.

Advanced

Parsing

In some situations, you may want to parse a user's permission. For example, you can use this to show a list of all blogs the user has access to. You can parse a permission just like you would parse a URL. For example, in PHP:

$permission = '/ninja-agency/silent-site/blogs?author_id=123';
$parts = parse_url($permission);
// $parts['path'] = '/ninja-agency/silent-site/blogs'
// $parts['query'] = 'author_id=123'
parse_str($parts['query'], $queryParams);
// $queryParams = ['author_id' => '123']

If you are building your own package in Go, you can use the standard library:

import (
    "net/url"
    "fmt"
)

func main() {
    permission := "/ninja-agency/silent-site/blogs?author_id=123"
    u, _ := url.Parse(permission)
    fmt.Println(u.Path) // '/ninja-agency/silent-site/blogs'
    fmt.Println(u.RawQuery) // 'author_id=123'
    params, _ := url.ParseQuery(u.RawQuery)
    fmt.Println(params.Get("author_id")) // '123'
}

Iterate over permissions

Sometimes you want to loop through all the permissions a user has access to. For example, to show a list of all blogs the user can edit:

$permissions = $user->can('blogs');
$categories = [];
foreach ($permissions as $permission) {
    // Only loop over the blogs where the user has permission.
    // For example, $permission could be 'blogs?category=photos_2025'.
    $parts = parse_url($permission);
    parse_str($parts['query'], $queryParams);
    $categories = array_merge($$categories, $queryParams['category']);
}

// Now you can run a database query to find the blogs:
$blogs = Blogs::whereIn('category', $categories)->get();
Let's Join the Waitlist