PageSourceSearch

https://openfga.dev/assets/js/49f3d900.c73eebda.js

js openfga.dev collected 2026-10-02 03:03:24 UTC 29,852 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkopenfga_dev=globalThis.webpackChunkopenfga_dev||[]).push([[2022],{2465(e,n,t){t.r(n),t.d(n,{assets:()=>c,contentTitle:()=>l,default:()=>d,frontMatter:()=>r,metadata:()=>i,toc:()=>p});var i=t(1581),o=t(4848),s=t(8453),a=t(7968);const r={title:"Ignore Duplicate Tuples On Write",description:"OpenFGA now supports ignoring duplicate writes and missing deletes, making data imports much easier and more efficient.",slug:"ignore-duplicate-writes-announcement",date:new Date("2025-10-31T00:00:00.000Z"),last_update:{date:"2025-10-31"},authors:"tylernix",tags:["openfga","features","api"],image:"https://openfga.dev/img/og-rich-embed.png",hide_table_of_contents:!1},l='Announcing "Ignore Duplicate Writes" in OpenFGA',c={authorsImageUrls:[void 0]},p=[{value:"The Problem",id:"the-problem",level:2},{value:"The Solution",id:"the-solution",level:2},{value:"See it in Action",id:"see-it-in-action",level:2},{value:"Get Started",id:"get-started",level:2},{value:"We want your feedback!",id:"we-want-your-feedback",level:2}];function u(e){const n={a:"a",code:"code",h2:"h2",li:"li",p:"p",strong:"strong",ul:"ul",...(0,s.R)(),...e.components};return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsxs)(n.p,{children:["We've added two new optional parameters to the Write API endpoint to improve the experience of writing data to FGA. You can now gracefully ",(0,o.jsx)(n.a,{href:"https://openfga.dev/docs/getting-started/update-tuples#05-ignoring-duplicate-or-missing-tuples",children:'"ignore" duplicate writes and missing deletes'}),"."]}),"\n",(0,o.jsx)(n.h2,{id:"the-problem",children:"The Problem"}),"\n",(0,o.jsxs)(n.p,{children:["When you're writing tuples to OpenFGA, it's almost inevitable that you'll try to write a relationship tuple that already exists (e.g., ",(0,o.jsx)(n.code,{children:"user:anne"})," is already a ",(0,o.jsx)(n.code,{children:"viewer"})," of ",(0,o.jsx)(n.code,{children:"document:123"}),") or try to delete one that's already gone. In the past, OpenFGA would reject the entire Write request containing that single duplicate operation."]}),"\n",(0,o.jsx)(n.p,{children:"This forced developers to build complex error-handling and retry logic on the client-side, just to filter out the single problematic tuple and resend the rest of the batch. This adds latency and operational overhead."}),"\n",(0,o.jsx)(n.h2,{id:"the-solution",children:"The Solution"}),"\n",(0,o.jsx)(n.p,{children:"The Write API now accepts two new optional parameters to gracefully handle these use cases:"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:(0,o.jsx)(n.code,{children:'on_duplicate: "ignore"'})}),": When included in the ",(0,o.jsx)(n.code,{children:"writes"})," section, this tells OpenFGA to simply skip any tuples that already exist instead of failing the request."]}),"\n"]}),"\n",(0,o.jsxs)(n.li,{children:["\n",(0,o.jsxs)(n.p,{children:[(0,o.jsx)(n.strong,{children:(0,o.jsx)(n.code,{children:'on_missing: "ignore"'})}),": When included in the ",(0,o.jsx)(n.code,{children:"deletes"})," section, this tells OpenFGA to skip any tuples that don't exist."]}),"\n"]}),"\n"]}),"\n",(0,o.jsx)(n.p,{children:"Now, you can send large batches of writes and deletes without worrying about these common conditions breaking your import."}),"\n",(0,o.jsx)(n.h2,{id:"see-it-in-action",children:"See it in Action"}),"\n",(0,o.jsx)(n.p,{children:"For writes:"}),"\n",(0,o.jsx)(a.dp,{relationshipTuples:[{user:"user:anne",relation:"viewer",object:"document:roadmap"}],conflictOptions:{onDuplicateWrites:"ignore"},skipSetup:!0,allowedLanguages:[a.NH.JS_SDK,a.NH.GO_SDK,a.NH.DOTNET_SDK,a.NH.PYTHON_SDK,a.NH.JAVA_SDK,a.NH.CLI,a.NH.CURL]}),"\n",(0,o.jsx)(n.p,{children:"And deletes:"}),"\n",(0,o.jsx)(a.dp,{deleteRelationshipTuples:[{user:"user:anne",relation:"owner",object:"document:roadmap"}],conflictOptions:{onMissingDeletes:"ignore"},skipSetup:!0,allowedLanguages:[a.NH.JS_SDK,a.NH.GO_SDK,a.NH.DOTNET_SDK,a.NH.PYTHON_SDK,a.NH.JAVA_SDK,a.NH.CLI,a.NH.CURL]}),"\n",(0,o.jsx)(n.h2,{id:"get-started",children:"Get Started"}),"\n",(0,o.jsx)(n.p,{children:"This is supported in the latest versions of the OpenFGA API, SDKs and CLI. Try it out and let us know what you think!"}),"\n",(0,o.jsxs)(n.ul,{children:["\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"https://openfga.dev/docs/api/service/relationship-tuples/add-or-delete-tuples-from-the-store",children:"API Docs"})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"https://github.com/openfga/js-sdk?tab=readme-ov-file#conflict-options-for-write-operations",children:"JavaScript SDK"})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"https://github.com/openfga/go-sdk?tab=readme-ov-file#conflict-options-for-write-operations",children:"Go SDK"})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"https://github.com/openfga/d
1otnet-sdk?tab=readme-ov-file#conflict-options-for-write-operations",children:".NET SDK"})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"https://github.com/openfga/python-sdk?tab=readme-ov-file#conflict-options-for-write-operations",children:"Python SDK"})}),"\n",(0,o.jsx)(n.li,{children:(0,o.jsx)(n.a,{href:"https://github.com/openfga/java-sdk?tab=readme-ov-file#conflict-options-for-write-operations",children:"Java SDK"})}),"\n"]}),"\n",(0,o.jsxs)(n.p,{children:["Special thanks to ",(0,o.jsx)(n.a,{href:"https://github.com/phamhieu",children:"@phamhieu"})," for his ",(0,o.jsx)(n.a,{href:"https://github.com/openfga/js-sdk/pull/276",children:"contribution"})," to the JavaScript SDK! \ud83d\ude4f"]}),"\n",(0,o.jsxs)(n.p,{children:["Learn more about ",(0,o.jsx)(n.a,{href:"https://openfga.dev/docs/getting-started/update-tuples#05-ignoring-duplicate-or-missing-tuples",children:"Writing Tuples in OpenFGA"}),"."]}),"\n",(0,o.jsx)(n.h2,{id:"we-want-your-feedback",children:"We want your feedback!"}),"\n",(0,o.jsxs)(n.p,{children:["Please reach out through our ",(0,o.jsx)(n.a,{href:"https://openfga.dev/community",children:"community channels"})," with any questions or feedback."]})]})}function d(e={}){const{wrapper:n}={...(0,s.R)(),...e.components};return n?(0,o.jsx)(n,{...e,children:(0,o.jsx)(u,{...e})}):u(e)}},8740(e,n){function t(e,n){if("string"==typeof n)throw new Error(n);if("function"==typeof n)throw new Error(n(e));if(n)return e;throw new Error("Unhandled discriminated union member: ".concat(JSON.stringify(e)))}n.Ay=t},7968(e,n,t){t.d(n,{NH:()=>E,dp:()=>J});t(1418);var i=t(8537);const{SchemaVersion:o}=i.constants.enums;var s=t(6540),a=(t(8774),t(4164)),r=t(7559),l=t(6347),c=t(205),p=t(7485),u=t(1682),d=t(679),h=t(4848);function g(e){const{values:n,children:t}=e;return(0,s.useMemo)((()=>{const e=n??function(e){return s.Children.toArray(e).flatMap((e=>{if(!e)return[];if((0,s.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return[e];const n="string"==typeof e.type?e.type:e.type.name;throw new Error(`Docusaurus error: Bad <Tabs> child <${n}>: all children of the <Tabs> component should be <TabItem>, and every <TabItem> should have a unique "value" prop.\nIf you do not want to pass on a "value" prop to the direct children of <Tabs>, you can also pass an explicit <Tabs values={...}> prop.`)})).map((({props:{value:e,label:n,attributes:t,default:i}})=>({value:e,label:n,attributes:t,default:i})))}(t);return function(e){const n=(0,u.XI)(e,((e,n)=>e.value===n.value));if(n.length>0)throw new Error(`Docusaurus error: Duplicate values "${n.map((e=>`'${e.value}'`)).join(", ")}" found in <Tabs>. Every value needs to be unique.`)}(e),e}),[n,t])}function f({value:e,tabValues:n}){return n.some((n=>n.value===e))}function m({queryString:e=!1,groupId:n}){const t=(0,l.W6)(),i=function({queryString:e=!1,groupId:n}){if("string"==typeof e)return e;if(!1===e)return null;if(!0===e&&!n)throw new Error('Docusaurus error: The <Tabs> component groupId prop is required if queryString=true, because this value is used as the search param name. You can also provide an explicit value such as queryString="my-search-param".');return n??null}({queryString:e,groupId:n});return[(0,p.aZ)(i),(0,s.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(t.location.search);n.set(i,e),t.replace({...t.location,search:n.toString()})}),[i,t])]}function b(e){const{defaultValue:n,queryString:t=!1,groupId:i}=e,o=g(e),[a,r]=(0,s.useState)((()=>function({defaultValue:e,tabValues:n}){if(0===n.length)throw new Error("Docusaurus error: the <Tabs> component requires at least one <TabItem> children component");if(e){if(!f({value:e,tabValues:n}))throw new Error(`Docusaurus error: The <Tabs> has a defaultValue "${e}" but none of its children has the corresponding value. Available values are: ${n.map((e=>e.value)).join(", ")}. If you intend to show no default tab, use defaultValue={null} instead.`);return e}const t=n.find((e=>e.default))??n[0];if(!t)throw new Error("Unexpected error: 0 tabValues");return t.value}({defaultValue:n,tabValues:o}))),[l,p]=m({queryString:t,groupId:i}),[u,h]=function({groupId:e}){const n=function(e){return e?`docusaurus.tab.${e}`:null}(e),[t,i]=(0,d.Dv)(n);return[t,(0,s.useCallback)((e=>{n&&i.set(e)}),[n,i])]}({groupId:i}),b=(()=>{const e=l??u;return f({value:e,tabValues:o})?e:null})();(0,c.A)((()=>{b&&r(b)}),[b]);return{selectedValue:a,selectValue:(0,s.useCallback)((e=>{if(!f({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);r(e),p(e),h(e)}),[p,h,o]),tabValues:o,lazy:e.lazy??!1,block:e.block??!1}}const $=(0,s.createContext)(null);function _(){const e=s.useContext($);if(!e)throw new Error("useTabsContext() must be used within a Tabs component");return e}function D(e){return(0,h.jsx)($.Provider,{value:e.value,children:e.children})}var T=t(3104),w=t(2303);const O="tabList__CuJ",S="tabItem_LNqP";function j({className:e}){const{selectedValue:n,selectValue:t,tabValues:i,block:o}=_(),s=[],{blockElementScrollPositionUntilNextRender:r}=(0,T.a_)(),l=e=>{const o=e.currentTarget,a=s.indexOf(o),l=i[a].value;l!==n&&(r(o),t(l))},c=e=>{let n=null;switch(e.key){case"Enter":l(e);break;case"ArrowRight":{const t=s.indexOf(e.currentTarget)+1;n=s[t]??s[0];break}case"ArrowLeft":{const t=s.indexOf(e.currentTarget)-1;n=s[t]??s[s.length-1];break}}n?.focus()};return(0,h.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,a.A)("tabs",{"tabs--block":o},e),children:i.map((({value:e,label:t,attributes:i})=>(0,h.jsx)("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,ref:e=>{s.push(e)},onKeyDown:c,onClick:l,...i,className:(0,a.A)("tabs__item",S,i?.className,{"tabs__item--active":n===e}),children:t??e},e)))})}function v({children:e}){return(0,h.jsx)("div",{className:"margin-top--md",children:e})}function x({className:e,children:n}){return(0,h.jsxs)("div",{className:(0,a.A)(r.G.tabs.container,"tabs-container",O),children:[(0,h.jsx)(j,{className:e}),(0,h.jsx)(v,{children:n})]})}function C(e){const n=(0,w.A)(),t=b(e);return(0,h.jsx)(D,{value:t,children:(0,h.jsx)(x,{className:e.className,children:(i=e.children,s.Children.toArray(i).filter((e=>"\n"!==e)))})},String(n));var i}const A="tabItem_Ymn6";function y({children:e,className:n,hidden:t}){return(0,h.jsx)("div",{role:"tabpanel",className:(0,a.A)(A,n),hidden:t,children:e})}function R({children:e,className:n,value:t}){const{selectedValue:i,lazy:o}=_(),s=t===i;return!s&&o?null:(0,h.jsx)(y,{className:n,hidden:!s,children:e})}t(7293);var I=t(5920);var N=t(4586);let E=function(e){return e.CLI="cli",e.JS_SDK="js-sdk",e.GO_SDK="go-sdk",e.DOTNET_SDK="dotnet-sdk",e.PYTHON_SDK="python-sdk",e.JAVA_SDK="java-sdk",e.CURL="curl",e.RPC="rpc",e.PLAYGROUND="playground",e}({});
1const K=new Map([[E.CLI,"CLI"],[E.JS_SDK,"Node.js"],[E.GO_SDK,"Go"],[E.DOTNET_SDK,".NET"],[E.PYTHON_SDK,"Python"],[E.JAVA_SDK,"Java"],[E.CURL,"curl"],[E.RPC,"Pseudocode"],[E.PLAYGROUND,"Playground"]]),L=new Map([[E.CLI,"shell"],[E.JS_SDK,"javascript"],[E.GO_SDK,"go"],[E.DOTNET_SDK,"dotnet"],[E.PYTHON_SDK,"python"],[E.JAVA_SDK,"java"],[E.CURL,"shell"],[E.RPC,"shell"],[E.PLAYGROUND,"shell"]]);function G(e){const{allowedLanguages:n}=e;return n.map((e=>({label:K.get(e),value:e})))}var k=t(1622),M=t(8740);function W(e,n){switch(e){case E.JS_SDK:return n.js;case E.GO_SDK:return n.go;case E.DOTNET_SDK:return n.dotnet;case E.PYTHON_SDK:return n.python;case E.JAVA_SDK:return n.java}return{importStatement:"",apiName:"",setupNote:""}}function P(e,n){return W(e,n).importStatement}function U(e,n){switch(e){case E.CURL:case E.CLI:return"Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)\n";case E.JS_SDK:return`// import the SDK\n${P(e,n)}\n\n// Initialize the SDK with no auth - see "How to setup SDK client" for more options\nconst fgaClient = new ${n.js.apiName}({\n  apiUrl: process.env.FGA_API_URL, // required, e.g. https://api.fga.example\n  storeId: process.env.FGA_STORE_ID,\n  authorizationModelId: process.env.FGA_MODEL_ID, // Optional, can be overridden per request\n});`;case E.GO_SDK:return`import (\n    "os"\n\n    ${P(e,n)}\n)\n\nfunc main() {\n    // Initialize the SDK with no auth - see "How to setup SDK client" for more options\n    fgaClient, err := NewSdkClient(&ClientConfiguration{\n        ApiUrl:               os.Getenv("FGA_API_URL"), // required, e.g. https://api.fga.example\n        StoreId:              os.Getenv("FGA_STORE_ID"), // optional, not needed for \`CreateStore\` and \`ListStores\`, required before calling for all other methods\n        AuthorizationModelId: os.Getenv("FGA_MODEL_ID"),  // Optional, can be overridden per request\n    })\n\n    if err != nil {\n    // .. Handle error\n    }\n}`;case E.DOTNET_SDK:return`// import the SDK\n${P(e,n)}\nusing Environment = System.Environment;\n\nnamespace Example;\n\nclass Example {\n    public static async Task Main() {\n        // Initialize the SDK with no auth - see "How to setup SDK client" for more options\n        var configuration = new ClientConfiguration() {\n          ApiUrl = Environment.GetEnvironmentVariable("FGA_API_URL"), ?? "http://localhost:8080", // required, e.g. https://api.fga.example\n          StoreId = Environment.GetEnvironmentVariable("FGA_STORE_ID"), // optional, not needed for \`CreateStore\` and \`ListStores\`, required before calling for all other methods\n          AuthorizationModelId = Environment.GetEnvironmentVariable("FGA_MODEL_ID"), // Optional, can be overridden per request\n        };\n        var fgaClient = new ${n.js.apiName}(configuration);\n    }\n}`;case E.PYTHON_SDK:return`\n${P(e,n)}\n\nasync def main():\n    configuration = ClientConfiguration(\n        api_url = os.environ.get('FGA_API_URL'), # required, e.g. https://api.fga.example\n        store_id = os.environ.get('FGA_STORE_ID'), # optional, not needed for \`CreateStore\` and \`ListStores\`, required before calling for all other methods\n        authorization_model_id = os.environ.get('FGA_MODEL_ID'), # Optional, can be overridden per request\n    )\n\n    # Enter a context with an instance of the OpenFgaClient\n    async with OpenFgaClient(configuration) as fga_client:\n        api_response = await fga_client.read_authorization_models()\n        await fga_client.close()\n\nasyncio.run(main())\n`;case E.JAVA_SDK:return`\n${P(e,n)}\n\npublic class Example {\n  public static void main(String[] args) throws Exception {\n      var config = new ClientConfiguration()\n              .apiUrl(System.getenv("FGA_API_URL")) // If not specified, will default to "https://localhost:8080"\n              .storeId(System.getenv("FGA_STORE_ID")) // Not required when calling createStore() or listStores()\n              .authorizationModelId(System.getenv("FGA_AUTHORIZATION_MODEL_ID")); // Optional, can be overridden per request\n\n      var fgaClient = new OpenFgaClient(config);\n  }\n}\n`;case E.RPC:case E.PLAYGROUND:throw new Error(`Lang ${e}
1 support has not been implemented`);default:(0,M.Ay)(e)}}function F(e,n){const{siteConfig:t}=(0,N.A)(),i=t.customFields.languageMapping;if(n)return;let o,s="Initialize the SDK";const a=L.get(e);switch(e){case E.JS_SDK:case E.GO_SDK:case E.DOTNET_SDK:case E.PYTHON_SDK:case E.JAVA_SDK:o=W(e,i).setupNote+U(e,i);break;case E.CLI:o=U(E.CLI,i),s="Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)";break;case E.CURL:o=U(E.CURL,i),s="Set FGA_API_URL according to the service you are using (e.g. https://api.fga.example)";break;case E.PLAYGROUND:case E.RPC:return;default:(0,M.Ay)(e)}return(0,h.jsx)(h.Fragment,{children:(0,h.jsx)(k.A,{summary:(0,h.jsx)("summary",{children:s}),children:(0,h.jsx)(I.A,{className:`language-${a}`,children:o})})})}function V({allowedLanguage:e,content:n}){return(0,h.jsx)(I.A,{className:`language-${L.get(e)}`,children:n})}function q({allowedLanguages:e,opts:n,tabViewFn:t,langMappings:i}){const[o,a]=(0,s.useState)(!1),r=e.filter((e=>e!==E.RPC)),l=o?"View pseudocode":"View code";return(0,h.jsxs)("div",{className:"snippet-mode-toggle-wrapper",children:[(0,h.jsx)("div",{className:"snippet-mode-toggle",children:(0,h.jsx)("button",{type:"button","aria-pressed":o,"aria-label":l,className:"snippet-mode-toggle__button",onClick:()=>a((e=>!e)),children:l})}),o?r.length>0&&(0,h.jsx)(C,{groupId:"languages",values:G({allowedLanguages:r}),children:r.map((e=>(0,h.jsxs)(R,{value:e,children:[F(e,n.skipSetup),V({allowedLanguage:e,content:t(e,n,i)})]},e)))}):(0,h.jsx)("div",{style:{paddingTop:"1.5rem"},children:V({allowedLanguage:E.RPC,content:t(E.RPC,n,i)})})]})}function H(e,n){const t=n.authorizationModelId?n.authorizationModelId:"01HVMMBCMGZNT3SED4Z17ECXCA";switch(e){case E.CLI:return`${n.relationshipTuples?.length?n.relationshipTuples.map((e=>`fga tuple write --store-id=\${FGA_STORE_ID} --model-id=${t} ${e.user} ${e.relation} ${e.object} ${e.condition?`--condition-name ${e.condition.name} --condition-context '${JSON.stringify(e.condition.context)}'`:""} ${n.conflictOptions?.onDuplicateWrites?`--on-duplicate ${n.conflictOptions.onDuplicateWrites}`:""}`)).join("\n"):""}\n\n${n.deleteRelationshipTuples?.length?n.deleteRelationshipTuples.map((e=>`fga tuple delete --store-id=\${FGA_STORE_ID} ${e.user} ${e.relation} ${e.object}  ${n.conflictOptions?.onMissingDeletes?`--on-missing ${n.conflictOptions.onMissingDeletes}`:""}`)).join("\n"):""}`;case E.CURL:{const e={};n.relationshipTuples?.length&&(e.writes={tuple_keys:n.relationshipTuples.map((e=>{const{_description:n,...t}=e;return t}))},n.conflictOptions?.onDuplicateWrites&&(e.writes.on_duplicate=n.conflictOptions.onDuplicateWrites)),n.deleteRelationshipTuples?.length&&(e.deletes={tuple_keys:n.deleteRelationshipTuples.map((e=>{const{_description:n,...t}=e;return t}))},n.conflictOptions?.onMissingDeletes&&(e.deletes.on_missing=n.conflictOptions.onMissingDeletes)),e.authorization_model_id=t;return`curl -X POST $FGA_API_URL/stores/$FGA_STORE_ID/write \\\n  -H "Authorization: Bearer $FGA_API_TOKEN" \\ # Not needed if service does not require authorization\n  -H "content-type: application/json" \\\n  -d '${JSON.stringify(e,null,2)}'`}case E.JS_SDK:{const e=n.relationshipTuples?n.relationshipTuples.map((e=>`\n      ${e._description?`// ${e._description}\n      `:""}${JSON.stringify(e)}`)).join(","):"",i=n.deleteRelationshipTuples?n.deleteRelationshipTuples.map((({user:e,relation:n,object:t,_description:i})=>`\n      ${i?`// ${i}\n      `:""}{ user: '${e}', relation: '${n}', object: '${t}'}`)).join(","):"",o="writes: ["+(e.length>0?`${e}\n  ]`:"]"),s="deletes: ["+(i.length>0?`${i}\n  ]`:"]"),a=""+(n.deleteRelationshipTuples&&n.relationshipTuples?",\n  ":"");return`\nconst options = {${t?`\n  authorizationModelId: "${t}",`:""}${n.conflictOptions?`\n  conflict: {${n.conflictOptions.onDuplicateWrites?`\n    onDuplicateWrites: OnDuplicateWrites.${n.conflictOptions.onDuplicateWrites.charAt(0).toUpperCase()+n.conflictOptions.onDuplicateWrites.slice(1)},`:""}${n.conflictOptions.onMissingDeletes?`\n    onMissingDeletes: OnMissingDeletes.${n.conflictOptions.onMissingDeletes.charAt(0).toUpperCase()+n.conflictOptions.onMissingDeletes.slice(1)}`:""}\n  }`:""}\n};\n\nawait fgaClient.write({\n  ${n.relationshipTuples?o:""}${a}${n.deleteRelationshipTuples?s:""},\n}, options);`}case E.GO_SDK:{const e=n.relationshipTuples?n.relationshipTuples.map((({user:e,relation:n,object:t,condition:i,_description:o})=>`        {${o?`\n             // ${o}`:""}\n             User: "${e}",\n             Relation: "${n}",\n             Object: "${t}",${i?`\n             Condition: &RelationshipCondition{\n                 Name: "${i.name}",\n                 Context: &map[string]interface{}${JSON.stringify(i.context)},\n             },`:""}\n        }, `)).join(""):"",i=n.deleteRelationshipTuples?n.deleteRelationshipTuples.map((({user:e,relation:n,object:t,_description:i})=>`        {${i?`\n             // ${i}`:""}\n             User: "${e}",\n             Relation: "${n}",\n             Object: "${t}",\n        }, `)).join(""):"",o="\n    Writes: []ClientTupleKey{"+(e.length>0?`\n${e}\n    },`:"},"),s="\n    Deletes: []ClientTupleKeyWithoutCondition{"+(i.length>0?`\n${i}\n    },`:"},");return`\noptions := ClientWriteOptions{${t?`\n    AuthorizationModelId: openfga.PtrString("${t}"),`:""}${n.conflictOptions?`\n    Conflict: ClientWriteConflictOptions{${n.conflictOptions.onDuplicateWrites?`\n        OnDuplicateWrites: CLIENT_WRITE_REQUEST_ON_DUPLICATE_WRITES_${n.conflictOptions.onDuplicateWrites.toUpperCase()},`:""}${n.conflictOptions.onMissingDeletes?`\n        OnMissingDeletes: CLIENT_WRITE_REQUEST_ON_MISSING_DELETES_${n.conflictOptions.onMissingDeletes.toUpperCase()},`:""}\n},`:""}\n}\n\nbody := ClientWriteRequest{${n.relationshipTuples?o:""}${n.deleteRelationshipTuples?s:""} \n}
1\n\ndata, err := fgaClient.Write(context.Background()).\n    Body(body).\n    Options(options).\n    Execute()\n\nif err != nil {\n    // .. Handle error\n}\n\n_ = data // use the response\n`}case E.DOTNET_SDK:{const e=`Writes = new List<ClientTupleKey>() {\n${n.relationshipTuples?n.relationshipTuples.map((({user:e,relation:n,object:t,_description:i,condition:o})=>`${i?`    // ${i}\n`:""}       new() {\n                  User = "${e}",\n                  Relation = "${n}",\n                  Object = "${t}"${o?`,\n                  Condition = new RelationshipCondition(){\n                    Name = "${o.name}",\n                    Context = new { ${Object.entries(o.context).map((([e,n])=>`\n                        ${e}="${n}"`)).join(",")}\n                    }\n                  }`:""}\n              }`)).join(",\n"):""}\n  }`,i=`Deletes = new List<ClientTupleKeyWithoutCondition>() {\n${n.deleteRelationshipTuples?n.deleteRelationshipTuples.map((({user:e,relation:n,object:t,_description:i})=>`${i?`    // ${i}\n`:""}    new() { User = "${e}", Relation = "${n}", Object = "${t}" }`)).join(",\n"):""}\n  }`,o=""+(n.deleteRelationshipTuples&&n.relationshipTuples?",\n  ":"");return`\nvar options = new ClientWriteOptions {${t?`\n    AuthorizationModelId = "${t}",`:""}${n.conflictOptions?`\n    Conflict = new ConflictOptions {${n.conflictOptions.onDuplicateWrites?`\n        OnDuplicateWrites = OnDuplicateWrites.${n.conflictOptions.onDuplicateWrites.charAt(0).toUpperCase()+n.conflictOptions.onDuplicateWrites.slice(1)},`:""}${n.conflictOptions.onMissingDeletes?`\n        OnMissingDeletes = OnMissingDeletes.${n.conflictOptions.onMissingDeletes.charAt(0).toUpperCase()+n.conflictOptions.onMissingDeletes.slice(1)}`:""}\n    }`:""}\n};\nvar body = new ClientWriteRequest() {\n    ${n.relationshipTuples?e:""}${o}${n.deleteRelationshipTuples?i:""},\n};\nvar response = await fgaClient.Write(body, options);`}case E.PYTHON_SDK:{const e=`    writes=[${n.relationshipTuples?n.relationshipTuples.map((({user:e,relation:n,object:t,_description:i,condition:o})=>`\n                ClientTuple(\n${i?`                    # ${i}\n                    `:"                    "}user="${e}",\n                    relation="${n}",\n                    object="${t}",${o?`\n                    condition=RelationshipCondition(\n                        name='${o.name}',\n                        context=dict(${Object.entries(o.context).map((([e,n])=>`\n                            ${e}="${n}"`)).join(",")}\n                        )\n                    )`:""}\n                ),`)).join(""):""}\n        ],`,i=`    deletes=[${n.deleteRelationshipTuples?n.deleteRelationshipTuples.map((({user:e,relation:n,object:t,_description:i})=>`\n                ClientTuple(\n${i?`                    # ${i}\n                    `:"                    "}user="${e}",\n                    relation="${n}",\n                    object="${t}",\n                ),`)).join(""):""}\n        ],`;return`options = {${t?`\n    "authorization_model_id": "${t}"`:""}${n.conflictOptions?`,\n    "conflict": ConflictOptions(${n.conflictOptions.onDuplicateWrites?`\n        on_duplicate_writes=ClientWriteRequestOnDuplicateWrites.${n.conflictOptions.onDuplicateWrites.toUpperCase()},`:""}${n.conflictOptions.onMissingDeletes?`\n        on_missing_deletes=ClientWriteRequestOnMissingDeletes.${n.conflictOptions.onMissingDeletes.toUpperCase()}`:""}\n    )`:""}\n}\nbody = ClientWriteRequest(\n    ${n.relationshipTuples?e:""}${n.deleteRelationshipTuples?i:""}\n)\n\nresponse = await fga_client.write(body, options)\n`}case E.RPC:{const e=n.relationshipTuples?.map((({user:e,relation:n,object:t,_description:i})=>`${i?`\n    // ${i}`:""}\n    {\n      "user":"${e}",\n      "relation":"${n}",\n      "object":"${t}"\n    }`)).join(","),t=n.deleteRelationshipTuples?.map((({user:e,relation:n,object:t,_description:i})=>`${i?`\n    // ${i}`:""}\n    {\n      "user":"${e}",\n      "relation":"${n}",\n      "object":"${t}"\n    }`)).join(","),i=`write([${e}\n])`,o=`delete([${t}\n])`,s=""+(n.deleteRelationshipTuples&&n.relationshipTuples?",":"");return`${n.relationshipTuples?i:""}${s}\n${n.deleteRelationshipTuples?o:""}`}case E.JAVA_SDK:{const e=`\n        .writes(List.of(${n.relationshipTuples?n.relationshipTuples.map((({user:e,relation:n,object:t,condition:i,_description:o})=>`\n    ${o?`            // ${o}\n    `:""}            new ClientTupleKey()\n                        .user("${e}")\n                        .relation("${n}")\n                        ._object("${t}")${i?`\n                        .condition(new ClientRelationshipCondition()\n                                .name("${i.name}")\n                                .context(Map.of(${Object.entries(i.context).map((([e,n])=>`"${e}", "${n}"`)).join(",")}))\n                        )\n                        `:""}`)).join(","):""}\n        ))`,i=`\n        .deletes(List.of(${n.deleteRelationshipTuples?n.deleteRelationshipTuples.map((({user:e,relation:n,object:t,_description:i})=>`\n    ${i?`            // ${i}\n    `:""}            new ClientTupleKey()\n                        .user("${e}")\n                        .relation("${n}")\n                        ._object("${t}")`)).join(","):""}\n        ))`;return`var options = new ClientWriteOptions()${t?`\n        .authorizationModelId("${t}")`:""}${n.conflictOptions?.onDuplicateWrites?`\n        .onDuplicate(WriteRequestWrites.OnDuplicateEnum.${n.conflictOptions.onDuplicateWrites.toUpperCase()})`:""}${n.conflictOptions?.onMissingDeletes?`\n        .onMissing(WriteRequestDeletes.OnMissingEnum.${n.conflictOptions.onMissingDeletes.toUpperCase()})`:""};\n\nvar body = new ClientWriteRequest()${n.relationshipTuples?e:" "}${n.deleteRelationshipTuples?i:""};\n\nvar response = fgaClient.write(body, options).get();`}case E.PLAYGROUND:return"";default:(0,M.Ay)(e)}}function J(e){const n=[E.JS_SDK,E.GO_SDK,E.DOTNET_SDK,E.PYTHON_SDK,E.JAVA_SDK,E.CURL,E.CLI,E.RPC];var t,i;return function(e,n,t){const{siteConfig:i}=(0,N.A)(),o=i.customFields?.languageMapping;if(!o)throw new Error("Missing required siteConfig.customFields.languageMapping configuration.");const s=o,a=n.pseudoCodeMode??!1,r=e.includes(E.RPC),l=e.some((e=>e!==E.RPC));return a&&r&&l?(0,h.jsx)("div",{style:{marginTop:-20},children:(0,h.jsx)(q,{allowedLanguages:e,opts:n,tabViewFn:t,langMappings:s})}):(0,h.jsx)("div",{style:{marginTop:-5},children:(0,h.jsx)(C,{groupId:"languages",values:G({allowedLanguages:e}),children:e.map((e=>(0,h.jsxs)(R,{value:e,children:[F(e,n.skipSetup),V({allowedLanguage:e,content:t(e,n,s)})]},e)))})})}((t=e.allowedLanguages,i=n,t?i?t.filter((e=>i.includes(e))):t:i||[]),e,H)}t(8969)},1581(e){e.exports=JSON.parse('{"permalink":"/blog/ignore-duplicate-writes-announcement","source":"@site/blog/ignore-duplicate-writes-announcement.md","title":"Ignore Duplicate Tuples On Write","description":"OpenFGA now supports ignoring duplicate writes and missing deletes, making data imports much easier and more efficient.","date":"2025-10-31T00:00:00.000Z","tags":[{"inline":true,"label":"openfga","permalink":"/blog/tags/openfga"},{"inline":true,"label":"features","permalink":"/blog/tags/features"},{"inline":true,"label":"api","permalink":"/blog/tags/api"}],"readingTime":2.25,"hasTruncateMarker":true,"authors":[{"name":"Tyler Nix","title":"Product Manager","url":"https://github.com/tylernix","imageURL":"/img/blog/authors/tyler.jpg","key":"tylernix","page":null}],"frontMatter":{"title":"Ignore Duplicate Tuples On Write","description":"OpenFGA now supports ignoring duplicate writes and missing deletes, making data imports much easier and more efficient.","slug":"ignore-duplicate-writes-announcement","date":"2025-10-31T00:00:00.000Z","last_update":{"date":"2025-10-31"},"authors":"tylernix","tags":["openfga","features","api"],"image":"https://openfga.dev/img/og-rich-embed.png","hide_table_of_contents":false},"unlisted":false,"prevItem":{"title":"OpenFGA Accepted into CNCF Incubation \ud83c\udf89","permalink":"/blog/incubation-announcement"},"nextItem":{"title":"Fine-Grained News - October 2025","permalink":"/blog/fine-grained-news-2025-10"}}')}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.