> ## Documentation Index
> Fetch the complete documentation index at: https://openfga.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Custom Roles

> Modeling custom and dynamically changing roles in your system

export const WriteRequestViewer = ({relationshipTuples, deleteRelationshipTuples, authorizationModelId, skipSetup, conflictOptions, allowedLanguages}) => {
  const [runtime, setRuntime] = useState(null);
  const [loadError, setLoadError] = useState(null);
  const [selectedLanguage, setSelectedLanguage] = useState(null);
  useEffect(() => {
    if (window.openfgaViewer) {
      setLoadError(null);
      setRuntime(window.openfgaViewer);
      return;
    }
    let script = document.querySelector('script[src="/openfga-viewer.js"]');
    const loaded = () => {
      clearTimeout(timeout);
      if (window.openfgaViewer) {
        setLoadError(null);
        setRuntime(window.openfgaViewer);
      } else setLoadError('The OpenFGA example helper did not initialize. Reload this page to retry.');
    };
    const failed = () => {
      clearTimeout(timeout);
      setLoadError('Unable to load OpenFGA examples. Reload this page to retry.');
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/openfga-viewer.js';
    }
    script.addEventListener('load', loaded);
    script.addEventListener('error', failed);
    const timeout = setTimeout(() => {
      if (!window.openfgaViewer) failed();
    }, 10000);
    if (!script.isConnected) document.head.appendChild(script);
    return () => {
      clearTimeout(timeout);
      script.removeEventListener('load', loaded);
      script.removeEventListener('error', failed);
    };
  }, []);
  if (loadError) return <div className="openfga-code-viewer__status" role="alert">
        {loadError}
      </div>;
  if (!runtime) return <div className="openfga-code-viewer__status" role="status">
        Loading examples...
      </div>;
  const {languageLabels: LANG_LABEL, languageGrammars: LANG_CODE} = runtime;
  const langs = runtime.selectLanguages('WriteRequestViewer', allowedLanguages);
  const activeLang = langs.includes(selectedLanguage) ? selectedLanguage : langs[0];
  const setupLangs = langs.filter(runtime.hasSetup);
  const buildSetupCode = lang => runtime.buildSdkSetup(lang, 'WriteRequestViewer');
  const buildCode = lang => runtime.buildRequestCode(lang, 'WriteRequestViewer', {
    relationshipTuples,
    deleteRelationshipTuples,
    authorizationModelId,
    conflictOptions
  });
  return <div data-openfga-viewer="WriteRequestViewer">
      {!skipSetup && runtime.hasSetup(activeLang) && <Accordion title="Initialize the SDK">
          <p>
            Install the <a href="https://openfga.dev/docs/getting-started/install-sdk">SDK or CLI</a> and deploy an OpenFGA server. Set
            FGA_API_URL, FGA_STORE_ID, and optionally FGA_MODEL_ID. These examples use no authentication; see{' '}
            <a href="https://openfga.dev/docs/getting-started/setup-sdk-client">client setup</a> for authentication and runtime
            prerequisites. Go snippets run inside main; Python requests run inside an async function and the client must
            be closed afterward.
          </p>
          <CodeGroup key={setupLangs.join(',')} onChange={index => setSelectedLanguage(setupLangs[index])}>
            {setupLangs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
                {buildSetupCode(lang)}
              </code>)}
          </CodeGroup>
        </Accordion>}
      <CodeGroup key={langs.join(',')} onChange={index => setSelectedLanguage(langs[index])}>
        {langs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
            {buildCode(lang)}
          </code>)}
      </CodeGroup>
    </div>;
};

export const CheckRequestViewer = ({user, relation, object, allowed, contextualTuples, context, headers, authorizationModelId, skipSetup, allowedLanguages, consistency}) => {
  const [runtime, setRuntime] = useState(null);
  const [loadError, setLoadError] = useState(null);
  const [selectedLanguage, setSelectedLanguage] = useState(null);
  useEffect(() => {
    if (window.openfgaViewer) {
      setLoadError(null);
      setRuntime(window.openfgaViewer);
      return;
    }
    let script = document.querySelector('script[src="/openfga-viewer.js"]');
    const loaded = () => {
      clearTimeout(timeout);
      if (window.openfgaViewer) {
        setLoadError(null);
        setRuntime(window.openfgaViewer);
      } else setLoadError('The OpenFGA example helper did not initialize. Reload this page to retry.');
    };
    const failed = () => {
      clearTimeout(timeout);
      setLoadError('Unable to load OpenFGA examples. Reload this page to retry.');
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/openfga-viewer.js';
    }
    script.addEventListener('load', loaded);
    script.addEventListener('error', failed);
    const timeout = setTimeout(() => {
      if (!window.openfgaViewer) failed();
    }, 10000);
    if (!script.isConnected) document.head.appendChild(script);
    return () => {
      clearTimeout(timeout);
      script.removeEventListener('load', loaded);
      script.removeEventListener('error', failed);
    };
  }, []);
  if (loadError) return <div className="openfga-code-viewer__status" role="alert">
        {loadError}
      </div>;
  if (!runtime) return <div className="openfga-code-viewer__status" role="status">
        Loading examples...
      </div>;
  const {languageLabels: LANG_LABEL, languageGrammars: LANG_CODE} = runtime;
  const langs = runtime.selectLanguages('CheckRequestViewer', allowedLanguages);
  const activeLang = langs.includes(selectedLanguage) ? selectedLanguage : langs[0];
  const setupLangs = langs.filter(runtime.hasSetup);
  const buildSetupCode = lang => runtime.buildSdkSetup(lang, 'CheckRequestViewer');
  const buildCode = lang => runtime.buildRequestCode(lang, 'CheckRequestViewer', {
    user,
    relation,
    object,
    allowed,
    contextualTuples,
    context,
    headers,
    authorizationModelId,
    consistency
  });
  return <div data-openfga-viewer="CheckRequestViewer">
      {!skipSetup && runtime.hasSetup(activeLang) && <Accordion title="Initialize the SDK">
          <p>
            Install the <a href="https://openfga.dev/docs/getting-started/install-sdk">SDK or CLI</a> and deploy an OpenFGA server. Set
            FGA_API_URL, FGA_STORE_ID, and optionally FGA_MODEL_ID. These examples use no authentication; see{' '}
            <a href="https://openfga.dev/docs/getting-started/setup-sdk-client">client setup</a> for authentication and runtime
            prerequisites. Go snippets run inside main; Python requests run inside an async function and the client must
            be closed afterward.
          </p>
          <CodeGroup key={setupLangs.join(',')} onChange={index => setSelectedLanguage(setupLangs[index])}>
            {setupLangs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
                {buildSetupCode(lang)}
              </code>)}
          </CodeGroup>
        </Accordion>}
      <CodeGroup key={langs.join(',')} onChange={index => setSelectedLanguage(langs[index])}>
        {langs.map(lang => <code key={lang} className={`language-${LANG_CODE[lang]}`} language={LANG_CODE[lang]} filename={LANG_LABEL[lang]}>
            {buildCode(lang)}
          </code>)}
      </CodeGroup>
    </div>;
};

export const AuthzModelSnippetViewer = ({configuration, syntaxesToShow = ['dsl', 'json'], skipVersion}) => {
  const [codegenReady, setCodegenReady] = useState(false);
  const [dslReady, setDslReady] = useState(false);
  const [activeTab, setActiveTab] = useState(syntaxesToShow[0] || 'dsl');
  useEffect(() => {
    if (window.fgaCodegen) {
      setCodegenReady(true);
      return;
    }
    let script = document.querySelector('script[src="/fga-codegen.js"]');
    const loaded = () => {
      if (window.fgaCodegen) setCodegenReady(true);
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/fga-codegen.js';
    }
    script.addEventListener('load', loaded);
    if (!script.isConnected) document.head.appendChild(script);
    return () => script.removeEventListener('load', loaded);
  }, []);
  useEffect(() => {
    if (window.openfgaDsl) {
      setDslReady(true);
      return;
    }
    let script = document.querySelector('script[src="/openfga-dsl-highlight.js"]');
    const loaded = () => {
      if (window.openfgaDsl) setDslReady(true);
    };
    if (!script) {
      script = document.createElement('script');
      script.src = '/openfga-dsl-highlight.js';
    }
    script.addEventListener('load', loaded);
    if (!script.isConnected) document.head.appendChild(script);
    return () => script.removeEventListener('load', loaded);
  }, []);
  if (!codegenReady) {
    return <div className="openfga-code-viewer__status" role="status">Loading model viewer...</div>;
  }
  const {transformer} = window.fgaCodegen;
  const getDsl = () => {
    try {
      const isTypeDefinition = !configuration.type_definitions && Boolean(configuration.type);
      const model = isTypeDefinition ? {
        schema_version: '1.1',
        type_definitions: [configuration]
      } : configuration;
      const dsl = transformer.transformJSONToDSL(model);
      return skipVersion || isTypeDefinition ? dsl.replace('model\n  schema 1.1\n', '') : dsl;
    } catch (e) {
      return `// error converting to DSL: ${e.message}`;
    }
  };
  const getJson = () => {
    const json = {
      ...configuration
    };
    delete json.id;
    if (skipVersion) delete json.schema_version;
    return JSON.stringify(json, null, 2);
  };
  const showTabs = syntaxesToShow.length > 1;
  const DSL_COLORS = dslReady && window.openfgaDsl ? window.openfgaDsl.colors : {};
  const THEME_STYLE = {
    '--openfga-dark-background': DSL_COLORS.background,
    '--openfga-dark-default': DSL_COLORS.default,
    '--openfga-dark-accent': DSL_COLORS.green
  };
  const renderDsl = () => {
    const code = getDsl().replace(/^\n/, '').replace(/\n$/, '');
    const hasError = code.startsWith('// error converting to DSL:');
    const tokens = hasError ? [{
      text: code,
      type: 'error'
    }] : dslReady && window.openfgaDsl ? window.openfgaDsl.tokenize(code) : [{
      text: code,
      type: 'default'
    }];
    return <div className="openfga-code-viewer__code" data-language="dsl" data-state={hasError ? 'error' : undefined} role={hasError ? 'alert' : undefined}>
        {tokens.map((t, i) => <span className="openfga-code-token" data-token={t.type || 'default'} key={i} style={{
      '--openfga-dark-token-color': t.color || DSL_COLORS.default
    }}>
            {t.text}
          </span>)}
      </div>;
  };
  const tokenizeJson = text => {
    const strRanges = [];
    const claims = [];
    let m;
    const strRe = /"(?:[^"\\]|\\.)*"/g;
    while (m = strRe.exec(text)) {
      const s = m.index, e = s + m[0].length;
      strRanges.push({
        s,
        e
      });
      const isKey = (/^\s*:/).test(text.slice(e));
      claims.push({
        s,
        e,
        type: isKey ? 'json-key' : 'json-string'
      });
    }
    const inStr = (s, e) => strRanges.some(r => s >= r.s && e <= r.e);
    const numRe = /-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g;
    while (m = numRe.exec(text)) {
      const s = m.index, e = s + m[0].length;
      if (!inStr(s, e)) claims.push({
        s,
        e,
        type: 'json-number'
      });
    }
    const kwRe = /\b(true|false|null)\b/g;
    while (m = kwRe.exec(text)) {
      const s = m.index, e = s + m[0].length;
      if (!inStr(s, e)) claims.push({
        s,
        e,
        type: 'json-keyword'
      });
    }
    const puncRe = /[{}\[\]:,]/g;
    while (m = puncRe.exec(text)) {
      const s = m.index;
      if (!inStr(s, s + 1)) claims.push({
        s,
        e: s + 1,
        type: 'json-punctuation'
      });
    }
    claims.sort((a, b) => a.s - b.s);
    const tokens = [];
    let cursor = 0;
    for (const c of claims) {
      if (c.s > cursor) tokens.push({
        text: text.slice(cursor, c.s),
        type: 'default'
      });
      tokens.push({
        text: text.slice(c.s, c.e),
        type: c.type
      });
      cursor = c.e;
    }
    if (cursor < text.length) tokens.push({
      text: text.slice(cursor),
      type: 'default'
    });
    return tokens;
  };
  const renderJson = () => {
    const code = getJson();
    const tokens = tokenizeJson(code);
    return <div className="openfga-code-viewer__code" data-language="json">
        {tokens.map((t, i) => <span className="openfga-code-token" data-token={t.type} key={i}>{t.text}</span>)}
      </div>;
  };
  return <div className="openfga-code-viewer" style={THEME_STYLE}>
      {showTabs && <div className="openfga-code-viewer__tabs">
          {syntaxesToShow.map(fmt => <button aria-pressed={activeTab === fmt} className="openfga-code-viewer__tab" data-state={activeTab === fmt ? 'active' : 'inactive'} key={fmt} onClick={() => setActiveTab(fmt)}>
              {fmt.toUpperCase()}
            </button>)}
        </div>}
      {(!showTabs || activeTab === 'dsl') && syntaxesToShow.includes('dsl') && renderDsl()}
      {(!showTabs || activeTab === 'json') && syntaxesToShow.includes('json') && renderJson()}
    </div>;
};

In this guide you'll learn how to model custom roles in your system using OpenFGA.

For example, a Business-to-Business (B2B) application could allow customers to create their own custom roles on the application to grant their users.

<Note>
  **When to use**

  In many cases, roles would fit in well as relations on an object type, as seen in [Modeling Roles and Permissions](/docs/modeling/roles-and-permissions). In some cases, however, they may not be enough.

  Custom roles are useful when:

  * Users of the application are able to create arbitrary sets of roles with different permissions that govern the users' access to objects.
  * It is not known beforehand (at the time of Authorization Model creation) what the application roles are.
  * The team responsible for building the authorization model is different from the teams responsible for defining roles and access to the application.
</Note>

## Before you start

Before you start this guide, make sure you're familiar with some [OpenFGA Concepts](/docs/concepts) and know how to develop the things listed below.

To start, let's say there is an application with a [type](/docs/concepts#what-is-a-type) called `asset-category`. Users can have view and/or edit access to assets in that category. Any user who can edit can also view.

<Accordion title="Initial Model" id="initial-model">
  We'll start with the following authorization model showing a system with an `asset-category` type. This type allows users to have view and edit access to it.

  <AuthzModelSnippetViewer
    configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'asset-category',
    relations: {
      viewer: {
        union: {
          child: [
            {
              this: {},
            },
            {
              computedUserset: {
                relation: 'editor',
              },
            },
          ],
        },
      },
      editor: {
        this: {},
      },
    },
    metadata: {
      relations: {
        editor: { directly_related_user_types: [{ type: 'user' }] },
        viewer: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
  />

  <hr />

  In addition, you'll need to know the following:

  <h3 id="modeling-roles-and-permissions">Modeling Roles and Permissions</h3>

  You need to know how to add users to groups and grant groups access to resources. [Learn more →](/docs/modeling/user-groups)

  <h3 id="modeling-object-to-object-relationships">Modeling Object-to-Object Relationships</h3>

  You need to know how to create relationships between objects and how that might affect a user's relationships to those objects. [Learn more →](/docs/modeling/building-blocks/object-to-object-relationships)

  <h3 id="concepts--configuration-language">Concepts & Configuration Language</h3>

  * [OpenFGA Concepts](/docs/concepts)
  * [Configuration Language](/docs/configuration-language)
</Accordion>

## Step By Step

Starting with the authorization model mentioned above, we want to enable users to create their own custom roles, and tie permissions to those roles to our two users and to the permissions on the logo asset category.

For this guide, we'll model a scenario where a certain organization using our app has created an `asset-category` called "logos", and another called "text content".

The company administrator would like to create:

* a **media-manager** role that allows users to **edit** assets in the **logos asset category**
* a **media-viewer** role that allows users to **view** all assets in the **logos asset category**
* a **blog-editor** role that allows users to **edit** all assets in the **text content asset category**
* a **blog-viewer** role that allows users to **view** all assets in the **text content asset category**

Imagine these are what the permissions the roles in one organization using our service are like:

<img src="https://mintcdn.com/fga/sFng5HcsvGPlDD3_/modeling/assets/custom-roles-roles-and-permissions.svg?fit=max&auto=format&n=sFng5HcsvGPlDD3_&q=85&s=a801bda480f285618d9595522175244b" alt="Image showing custom roles and permissions" width="1213" height="253" data-path="modeling/assets/custom-roles-roles-and-permissions.svg" />

Finally, the administrator wants to assign **Anne** the **media-manager** role and **Beth** the **media-viewer** role.

At the end, we'll verify our model by ensuring the following access [check](/docs/concepts#what-is-a-check-request) requests return the expected result.

<img src="https://mintcdn.com/fga/sFng5HcsvGPlDD3_/modeling/assets/custom-roles-expectations.svg?fit=max&auto=format&n=sFng5HcsvGPlDD3_&q=85&s=b391576b3b3d6223e7ce5b5bd5bcf0d0" alt="Image showing expected results" width="1213" height="448" data-path="modeling/assets/custom-roles-expectations.svg" />

In order to do this, we need to:

1. Update the Authorization Model to add a Role Type
2. Use Relationship Tuples to tie the Users to the Roles
3. Use Relationship Tuples to associate Permissions with the Roles
4. Verify that the Authorization Model works

### 01. Update The Authorization Model To Add A Role Type

Because our roles are going to be dynamic and might change frequently, we represent them in a new type instead of as relations on that same type. We'll create new type called `role`, where users can be related as assignee to it.

The authorization model becomes this:

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'asset-category',
    relations: {
      viewer: {
        union: {
          child: [
            {
              this: {},
            },
            {
              computedUserset: {
                relation: 'editor',
              },
            },
          ],
        },
      },
      editor: {
        this: {},
      },
    },
    metadata: {
      relations: {
        viewer: { directly_related_user_types: [{ type: 'user' }, { type: 'role', relation: 'assignee' }] },
        editor: { directly_related_user_types: [{ type: 'user' }, { type: 'role', relation: 'assignee' }] },
      },
    },
  },
  {
    type: 'role',
    relations: {
      assignee: {
        this: {},
      },
    },
    metadata: {
      relations: {
        assignee: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
/>

With this change we can add relationship tuples indicating that a certain user is `assigned` a certain `role`.

<h3 id="02use-relationship-tuples-to-tie-the-users-to-the-roles">02.Use Relationship Tuples To Tie The Users To The Roles</h3>

Once we've added the `role` type, we can assign roles to Anne and Beth. Anne is assigned the "media-manager" role and Beth is assigned the "media-viewer" role. We can do that by adding relationship tuples as follows:

<WriteRequestViewer
  relationshipTuples={[
{
  _description: 'Anne is assigned the media-manager role',
  user: 'user:anne',
  relation: 'assignee',
  object: 'role:media-manager',
},
{
  _description: 'Beth is assigned the media-viewer role',
  user: 'user:beth',
  relation: 'assignee',
  object: 'role:media-viewer',
},
]}
/>

We can verify they are members of said roles by issuing the following check requests:

<img src="https://mintcdn.com/fga/sFng5HcsvGPlDD3_/modeling/assets/custom-roles-membership-checks.svg?fit=max&auto=format&n=sFng5HcsvGPlDD3_&q=85&s=fbce7576e34b1936c5d1ec376facbf68" alt="Image showing expected membership checks" width="1213" height="253" data-path="modeling/assets/custom-roles-membership-checks.svg" />

<CheckRequestViewer user={'user:anne'} relation={'assignee'} object={'role:media-manager'} allowed={true} />

### 03. Use Relationship Tuples To Associate Permissions With The Roles

With our users and roles set up, we still need to tie members of a certain role to it's corresponding permission(s).

<WriteRequestViewer
  relationshipTuples={[
{
  _description: 'Users assigned the media-manager role can edit in the Logos assets category',
  user: 'role:media-manager#assignee',
  relation: 'editor',
  object: 'asset-category:logos',
},
{
  _description: 'Users assigned the media-viewer role can view from the Logos assets category',
  user: 'role:media-viewer#assignee',
  relation: 'viewer',
  object: 'asset-category:logos',
},
]}
/>

### 04. Verify That The Authorization Model Works

To ensure our model works, it needs to match our expectations:

<img src="https://mintcdn.com/fga/sFng5HcsvGPlDD3_/modeling/assets/custom-roles-expectations.svg?fit=max&auto=format&n=sFng5HcsvGPlDD3_&q=85&s=b391576b3b3d6223e7ce5b5bd5bcf0d0" alt="Image showing expected results" width="1213" height="448" data-path="modeling/assets/custom-roles-expectations.svg" />

<CheckRequestViewer user={'user:anne'} relation={'editor'} object={'asset-category:logos'} allowed={true} />

The checks come back as we expect, so our model is working correctly.

## Related Sections

Check the following sections for more on how to model with OpenFGA.

<CardGroup>
  <Card title="Modeling Roles and Permissions" href="/docs/modeling/roles-and-permissions">
    Learn how to remove the direct relationship to indicate nonassignable permissions.
  </Card>

  <Card title="Modeling Concepts: Object to Object Relationships" href="/docs/modeling/building-blocks/object-to-object-relationships">
    Learn about how to model object to object relationships in OpenFGA.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.