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

# Migrating Relations

> Migrating relations

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 the lifecycle of software development, you will need to make updates or changes to the [authorization model](/docs/concepts#what-is-an-authorization-model). In this guide, you will learn best practices for changing your existing authorization model. With these recommendations, you will minimize downtime and ensure your relationship models stay up to date.

## Before you start

This guide assumes you are familiar with the following OpenFGA concepts:

* 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 defined 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
* [Intersection Operator](/docs/configuration-language#the-intersection-operator): the intersection operator can be used to indicate a relationship exists if the user is in all the sets of users

## Step by step

The document below is an example of a relational authorization model. In this model, you can assign users to the `editor` relation. The `editor` relation has write privileges that regular users do not.

In this scenario, you will migrate the following model:

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

There are existing [relationship tuples](/docs/concepts#what-is-a-relationship-tuple) associated with editor relation.

```json theme={"system"}
[
  {
    "user": "user:anne",
    "relation": "editor",
    "object": "document:roadmap"
  },
  {
    "user": "user:charles",
    "relation": "editor",
    "object": "document:roadmap"
  }
]
```

This is the authorization model that you will want to migrate to:

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'document',
    relations: {
      writer: {
        this: {},
      },
      can_write: {
        computedUserset: {
          object: '',
          relation: 'writer',
        },
      },
    },
    metadata: {
      relations: {
        writer: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
  {
    type: 'user',
  },
],
}}
/>

***

### 01. Create a backwards compatible model

To avoid service disruption, you will create a backwards compatible model. The backwards compatible model ensures the existing relationship tuple will still work.

In the example below, `user:Anne` still has write privileges to the `document:roadmap` resource.

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'document',
    relations: {
      editor: {
        this: {},
      },
      writer: {
        union: {
          child: [
            {
              this: {},
            },
            {
              computedUserset: {
                relation: 'editor',
              },
            },
          ],
        },
      },
      can_write: {
        computedUserset: {
          object: '',
          relation: 'writer',
        },
      },
      can_edit: {
        computedUserset: {
          object: '',
          relation: 'writer',
        },
      },
    },
    metadata: {
      relations: {
        editor: { directly_related_user_types: [{ type: 'user' }] },
        writer: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
  {
    type: 'user',
  },
],
}}
/>

Test the `can_edit` definition. It should produce a value of `true`.

<CheckRequestViewer user={'user:anne'} relation={'can_write'} object={'document:roadmap'} allowed={true} />

<CheckRequestViewer user={'user:anne'} relation={'can_edit'} object={'document:roadmap'} allowed={true} />

### 02. Create a new relationship tuple

Now that you have a backwards compatible model, you can create new relationship tuples with a new relation.

In this example, you will add Bethany to the `writer` relationship.

<WriteRequestViewer
  relationshipTuples={[
{
  _description: 'Bethany assigned writer instead of editor',
  user: 'user:bethany',
  relation: 'writer',
  object: 'document:roadmap',
},
]}
/>

Run a check in the API for Bethany to ensure correct access.

<CheckRequestViewer user={'user:bethany'} relation={'can_write'} object={'document:roadmap'} allowed={true} />

### 03. Migrate the existing relationship tuples

Next, migrate the existing relationship tuples. The new relation makes this definition obsolete.

Use the `read` API to look up all relationship tuples.

<Accordion title="Initialize the SDK for Read">
  Set `FGA_API_URL` for your service (for example, `https://api.fga.example`) and `FGA_STORE_ID` for your store. Use the [Read SDK initialization examples](/docs/interacting/relationship-queries#read), then place this request in the same scope as the client. For Python, use the `async with` block; for Go, .NET, and Java, use the method that initializes the client. See [SDK client setup](/docs/getting-started/setup-sdk-client) for authentication options.
</Accordion>

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={"system"}
    const { tuples } = await fgaClient.read({});
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={"system"}
    options := ClientReadOptions{}
    body := ClientReadRequest{}
    data, err := fgaClient.Read(context.Background()).
        Body(body).
        Options(options).
        Execute()
    ```
  </Tab>

  <Tab title=".NET">
    ```csharp theme={"system"}
    var options = new ClientReadOptions {};
    var body = new ClientReadRequest {};
    var response = await fgaClient.Read(body, options);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"system"}
    options = {}
    body = ReadRequestTupleKey()
    response = await fga_client.read(body, options)
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={"system"}
    var body = new ClientReadRequest();
    var response = fgaClient.read(body).get();
    ```
  </Tab>

  <Tab title="CLI">
    ```bash theme={"system"}
    fga tuple read --store-id=${FGA_STORE_ID}
    ```
  </Tab>

  <Tab title="curl">
    ```bash theme={"system"}
    # Remove Authorization if your service does not require it.
    curl -X POST "$FGA_API_URL/stores/$FGA_STORE_ID/read" \
      -H "Authorization: Bearer $FGA_API_TOKEN" \
      -H "content-type: application/json" \
      -d '{}'
    ```
  </Tab>

  <Tab title="Pseudocode">
    ```text theme={"system"}
    read();
    ```
  </Tab>
</Tabs>

```json Response theme={"system"}
{
  "tuples": [
    {
      "key": {
        "user": "user:anne",
        "relation": "editor",
        "object": "document:planning"
      },
      "timestamp": "2021-10-06T15:32:11.128Z"
    },
    {
      "key": {
        "user": "user:charles",
        "relation": "editor",
        "object": "document:planning"
      },
      "timestamp": "2021-10-06T15:32:11.128Z"
    }
  ]
}
```

Then filter out the tuples that do not match the object type or relation (in this case, `document` and `editor` respectively), and update the new tuples with the `write` relationship.

<WriteRequestViewer
  relationshipTuples={[
{
  user: 'user:anne',
  relation: 'writer',
  object: 'document:roadmap',
},
{
  user: 'user:charles',
  relation: 'writer',
  object: 'document:roadmap',
},
]}
/>

Finally, remove the old relationship tuples.

<WriteRequestViewer
  deleteRelationshipTuples={[
{
  user: 'user:anne',
  relation: 'editor',
  object: 'document:roadmap',
},
{
  user: 'user:charles',
  relation: 'editor',
  object: 'document:roadmap',
},
]}
/>

<Note>
  Perform a `write` operation before a `delete` operation to ensure Anne still has access.
</Note>

Confirm the tuples are correct by running a check on the user.

<CheckRequestViewer user={'user:anne'} relation={'can_write'} object={'document:roadmap'} allowed={true} />

The old relationship tuple no longer exists.

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

### 04. Remove obsolete relationship from the model

After you remove the previous relationship tuples, update your authorization model to remove the obsolete relation.

<AuthzModelSnippetViewer
  configuration={{
schema_version: '1.1',
type_definitions: [
  {
    type: 'document',
    relations: {
      writer: {
        this: {},
      },
      can_write: {
        computedUserset: {
          object: '',
          relation: 'writer',
        },
      },
    },
    metadata: {
      relations: {
        writer: { directly_related_user_types: [{ type: 'user' }] },
      },
    },
  },
  {
    type: 'user',
  },
],
}}
/>

Now, the `write` API will only accept the new relation name.

## Related Sections

Review the following sections for more information on managing relationship tuples.

<CardGroup>
  <Card title="Relationship Queries" href="/docs/interacting/relationship-queries">
    Understand the differences between check, read, expand and list objects.
  </Card>

  <Card title="Production Best Practices" href="/docs/best-practices/running-in-production">
    Learn the best practices of running OpenFGA in a production environment
  </Card>
</CardGroup>


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