> ## 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.

# Blocklists

> Preventing certain users from accessing objects

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 see how to model preventing users from accessing objects using OpenFGA. For example, `blocking` users from accessing a `document`, even if it has been already shared with them.

<Note>
  **When to use**

  Exclusion is useful while building applications. You may need to support access patterns like granting access to some users, but excluding specific people or groups, similar to how users can block others from following them on social media, or prevent them from sharing documents on Google Drive.

  This is useful when:

  * Implementing the "blocking" feature, such as the profile blocking commonly present on social media platforms (e.g. Instagram and Twitter).
  * Reduce a user's access if they are part of a particular group (e.g. restricting access to members who are also guests, or restricting access to users in a certain locality).
</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.

<Accordion title="Prerequisites and starting model">
  You will start with the *[authorization model](/docs/concepts#what-is-an-authorization-model)* below, it represents a `document` *[type](/docs/concepts#what-is-a-type)* that can have users **[related](/docs/concepts#what-is-a-relation)** as `editor`, and `team` type that can have users related as `member`.

  Let us also assume that we have a `document` called "planning", shared for editing within the product `team` (comprised of becky and carl).

  <AuthzModelSnippetViewer
    configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'document',
    relations: {
      editor: {
        this: {},
      },
    },
    metadata: {
      relations: {
        editor: { directly_related_user_types: [{ type: 'user' }, { type: 'team', relation: 'member' }] },
      },
    },
  },
  {
    type: 'team',
    relations: {
      member: {
        this: {},
      },
    },
    metadata: {
      relations: {
        member: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
  />

  The current state of the system is represented by the following relationship tuples being in the system already:

  1. Members of the product team can edit the planning document
  2. Becky is a member of the product team
  3. Carl is a member of the product team

  ```json theme={"system"}
  [
    {
      "user": "team:product#member",
      "relation": "editor",
      "object": "document:planning"
    },
    {
      "user": "user:becky",
      "relation": "member",
      "object": "team:product"
    },
    {
      "user": "user:carl",
      "relation": "member",
      "object": "team:product"
    }
  ]
  ```

  ***

  In addition, you will need to know the following:

  ### Modeling user groups

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

  <h3 id="-concepts">OpenFGA Concepts</h3>

  * A **[Type](/docs/concepts#what-is-a-type)**: a class of objects that have similar characteristics
  * A **[User](/docs/concepts#what-is-a-user)**: an entity in the system that can be related to an object
  * A **[Relation](/docs/concepts#what-is-a-relation)**: is a string defined in the type definition of an authorization model that defines the possibility of a relationship between an object of the same type as the type definition and a user in the system
  * An **[Object](/docs/concepts#what-is-an-object)**: represents an entity in the system. Users' relationships to it can be define through relationship tuples and the authorization model
  * A **[Relationship Tuple](/docs/concepts#what-is-a-relationship-tuple)**: a grouping consisting of a user, a relation and an object stored in OpenFGA
  * [Exclusion Operator](/docs/configuration-language#the-exclusion-operator): the exclusion operator can be used to exclude certain usersets from being related to an object
</Accordion>

## Step by step

With the above authorization model and relationship tuples, OpenFGA will correctly respond with `{"allowed":true}` when [check](/docs/concepts#what-is-a-check-request) is called to see if Carl and Becky can edit this `document`.

We can verify that by issuing two check requests:

<CheckRequestViewer user={'user:becky'} relation={'editor'} object={'document:planning'} allowed={true} />

<CheckRequestViewer user={'user:carl'} relation={'editor'} object={'document:planning'} allowed={true} />

We want to share a document with the product team and also have the ability to deny certain users access, even if they have the document shared with them already. We can verify this by blocking Carl (who we have seen already has edit access) from editing the document.

In order to do that, we need to:

1. [Modify our model to allow indicating that users can be blocked from accessing a document](#01-modify-our-model-so-users-can-be-blocked-from-accessing-a-document)
2. [Modify our model to indicate that users who are blocked can no longer edit the document](#02-modify-our-model-so-users-who-are-blocked-can-no-longer-edit-the-document)
3. [Verify that our solution works](#03-verify-our-solution-works):

a. [Indicate that Carl is blocked from the planning document](#a-indicate-that-carl-is-blocked-from-the-planning-document)

b. [Carl (now blocked) can no longer edit the document](#b-carl-now-blocked-can-no-longer-edit-the-document)

c. [Becky still has edit access](#c-becky-still-has-edit-access)

### 01. Modify our model so users can be blocked from accessing a document

To allow users to be "blocked" from accessing a `document`, we first need to allow this relation. We'll update our store model to add a `blocked` [relation](/docs/concepts#what-is-a-relation) to the `document` type.

The authorization model becomes this:

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'document',
    relations: {
      blocked: {
        this: {},
      },
      editor: {
        this: {},
      },
    },
    metadata: {
      relations: {
        blocked: { directly_related_user_types: [{ type: 'user' }] },
        editor: { directly_related_user_types: [{ type: 'user' }, { type: 'team', relation: 'member' }] },
      },
    },
  },
  {
    type: 'team',
    relations: {
      member: {
        this: {},
      },
    },
    metadata: {
      relations: {
        member: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
/>

Now we can add relationship tuples indicating that a certain user is `blocked` from editing a `document`.

### 02. Modify our model so users who are blocked can no longer edit the document

Now that we can mark users as `blocked` from editing documents, we need to support denying the `editor` relationship when a user is `blocked`. We do that by modifying the relation definition of `editor`, and making use of the [**exclusion operator**](/docs/configuration-language#the-exclusion-operator) to exclude the set of `blocked` users, as we can see here:

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'user',
  },
  {
    type: 'document',
    relations: {
      blocked: {
        this: {},
      },
      editor: {
        difference: {
          base: {
            this: {},
          },
          subtract: {
            computedUserset: {
              relation: 'blocked',
            },
          },
        },
      },
    },
    metadata: {
      relations: {
        blocked: { directly_related_user_types: [{ type: 'user' }] },
        editor: { directly_related_user_types: [{ type: 'user' }, { type: 'team', relation: 'member' }] },
      },
    },
  },
  {
    type: 'team',
    relations: {
      member: {
        this: {},
      },
    },
    metadata: {
      relations: {
        member: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
],
}}
/>

### 03. Verify our solution works

To check if our new model works, we'll add a relationship tuple with Carl as `blocked` from `document:planning` and then verify that Carl no longer has `editor` access to that document.

#### a. Indicate that Carl is blocked from the planning document

With our modified authorization model, we can indicate that Carl is blocked by adding this *[relationship tuple](/docs/concepts#what-is-a-relationship-tuple)*.

<WriteRequestViewer
  relationshipTuples={[
{
  _description: 'Carl is blocked from editing the planning document',
  user: 'user:carl',
  relation: 'blocked',
  object: 'document:planning',
},
]}
/>

#### b. Carl (now blocked) can no longer edit the document

We have modified the authorization model and added relationship tuples to indicate that Carl is `blocked`. Now let's make sure our solution works as expected.

To check if Carl still has access to the document, we can issue a check request with Carl as the user.

<CheckRequestViewer user={'user:carl'} relation={'editor'} object={'document:planning'} allowed={false} />

The response is `false`, so our solution is working as expected.

#### c. Becky still has edit access

To check if Becky still has access to the document, we'll issue another check request with Becky as the user.

<CheckRequestViewer user={'user:becky'} relation={'editor'} object={'document:planning'} allowed={true} />

The response is `true`, indicating our model change did not inadvertently deny access for users who have access but are not blocked.

<Warning>
  When creating tuples for OpenFGA make sure to use unique ids for each object and user within your application domain. We are using first names and human-readable identifiers to make this task easier to read.
</Warning>

## Related Sections

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

<CardGroup>
  <Card title="Modeling: Getting Started" href="/docs/modeling/getting-started">
    Learn about how to get started with modeling.
  </Card>

  <Card title="Configuration Language" href="/docs/configuration-language">
    Learn about OpenFGA Configuration Language.
  </Card>

  <Card title="Public Access" href="/docs/modeling/public-access">
    Learn about model public access.
  </Card>
</CardGroup>


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