PageSourceSearch

https://openfeature.dev/assets/js/417afd39.f890690b.js

js openfeature.dev collected 2026-09-24 08:38:06 UTC 12,453 bytes, 1 lines download raw bytes

1"use strict";(globalThis.webpackChunkdocs||=[]).push([[2260],{61288(e,n,a){a.r(n),a.d(n,{assets:()=>u,contentTitle:()=>l,default:()=>h,frontMatter:()=>c,metadata:()=>t,toc:()=>d});const t=JSON.parse('{"id":"reference/concepts/tracking","title":"Tracking","description":"Tracking in OpenFeature is the process of associating metrics or KPIs with feature flag evaluation contexts.","source":"@site/docs/reference/concepts/07-tracking.mdx","sourceDirName":"reference/concepts","slug":"/reference/concepts/tracking","permalink":"/docs/reference/concepts/tracking","draft":false,"unlisted":false,"editUrl":"https://github.com/open-feature/openfeature.dev/edit/main/docs/reference/concepts/07-tracking.mdx","tags":[],"version":"current","sidebarPosition":7,"frontMatter":{"sidebar_position":7,"id":"tracking"},"sidebar":"docs","previous":{"title":"SDK Paradigms","permalink":"/docs/reference/concepts/sdk-paradigms"},"next":{"title":"SDK Technologies","permalink":"/docs/reference/sdks/"}}');var r=a(74848),i=a(28453),o=a(4865),s=a(19365);const c={sidebar_position:7,id:"tracking"},l="Tracking",u={},d=[{value:"Use Cases",id:"use-cases",level:2},{value:"Performance Monitoring",id:"performance-monitoring",level:3},{value:"Experimentation",id:"experimentation",level:3},{value:"Providers, Hooks and Integration",id:"providers-hooks-and-integration",level:2},{value:"Track Event Implementation",id:"track-event-implementation",level:2}];function p(e){const n={a:"a",code:"code",h1:"h1",h2:"h2",h3:"h3",header:"header",mermaid:"mermaid",p:"p",pre:"pre",...(0,i.R)(),...e.components};return(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(n.header,{children:(0,r.jsx)(n.h1,{id:"tracking",children:"Tracking"})}),"\n","\n",(0,r.jsx)(n.p,{children:"Tracking in OpenFeature is the process of associating metrics or KPIs with feature flag evaluation contexts.\nThis association helps measure the impact of feature changes on both business KPIs and system performance, making it particularly valuable for experimentation."}),"\n",(0,r.jsx)(n.h2,{id:"use-cases",children:"Use Cases"}),"\n",(0,r.jsx)(n.p,{children:"Tracking events associated with flag evaluations serves two primary purposes:"}),"\n",(0,r.jsx)(n.h3,{id:"performance-monitoring",children:"Performance Monitoring"}),"\n",(0,r.jsx)(n.p,{children:"When changes are made to an application via feature flags, tracking helps measure their impact on performance.\nBy associating events with flag evaluation contexts\nand sending this data to telemetry or analytics platforms, teams can determine whether specific flag configurations improve or degrade measured performance,\nwhether business metrics or system performance."}),"\n",(0,r.jsx)(n.h3,{id:"experimentation",children:"Experimentation"}),"\n",(0,r.jsx)(n.p,{children:"Tracking creates a crucial link between flag evaluations and business outcomes, enabling robust experimentation.\nExperimentation differs from generalized\nPerformance Monitoring in its execution.\nThe most common form of Experimentation being A/B testing which is when two variations of an application or feature\nare distributed randomly to similar groups and the differences in metrics are evaluated.\nFor example, if a feature flag controls the order\nof items in a menu, tracking events can be emitted when users click on menu items.\nThe feature flag provider can typically be set to distribute the different variations\nequally to your audience, making this an A/B test, helping to validate hypotheses about user behavior in a statistically relevant manner."}),"\n",(0,r.jsx)(n.h2,{id:"providers-hooks-and-integration",children:"Providers, Hooks and Integration"}),"\n",(0,r.jsxs)(n.p,{children:["In order to accomplish the use cases above, it's required that the flag provider in use reports events or metrics about the flag evaluations and ",(0,r.jsx)(n.a,{href:"/specification/sections/providers/#27-tracking-support",children:"tracking calls"})," it facilitates.\nIf your vendor or home-grown solution does not support these functionalities, the OpenFeature SDK offers various integration and extension points to help.\nYou can implement your own provider ",(0,r.jsx)(n.a,{href:"/specification/sections/tracking#61-tracking-api",children:"track"})," function (by extending or encapsulating your provider) and use custom ",(0,r.jsx)(n.a,{href:"/docs/reference/concepts/hooks",children:"hooks"}),", to collect and export the appropriate event data to your customer data or analytics platform."]}),"\n",(0,r.jsx)(n.mermaid,{value:"sequenceDiagram\n  Evaluation API->>+Tracking Hook: evaluate\n  Tracking Hook->>Analytics Platform: before\n  Tracking Hook->>Analytics Platform: after\n  Tracking Hook->>-Evaluation API: evaluate\n  Evaluation API->>
1Analytics Platform: track"}),"\n",(0,r.jsx)(n.h2,{id:"track-event-implementation",children:"Track Event Implementation"}),"\n",(0,r.jsxs)(n.p,{children:["The ",(0,r.jsx)(n.code,{children:"track"})," function requires only a label parameter.\nYou don\u2019t need to pass an identifier or flag evaluation context since the OpenFeature provider already maintains this information."]}),"\n",(0,r.jsx)(n.p,{children:"Optionally, you can associate additional metadata with each track event if your feature flagging tool supports it."}),"\n",(0,r.jsxs)(o.A,{groupId:"code",children:[(0,r.jsx)(s.A,{value:"js",label:"TypeScript",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-ts",children:"// example tracking event recording a conversion\nclient.track('conversion-tracked');\n\n// optionally additional data can be associated with the event\nclient.track('conversion-tracked', {\n  value: 99.77,\n  currencyCode: 'USD',\n});\n"})})}),(0,r.jsx)(s.A,{value:"java",label:"Java",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-java",children:'// example tracking event recording a conversion\nclient.track("conversion-tracked");\n\n// optionally additional data can be associated with the event\nclient.track("conversion-tracked", new TrackingEventDetails(99.77).add("currencyCode", "USD"));\n'})})}),(0,r.jsx)(s.A,{value:"csharp",label:"C#",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-csharp",children:'// example tracking event recording a conversion\nclient.Track("visited-promo-page");\n\n// optionally additional data can be associated with the event\nclient.Track("visited-promo-page", new TrackingEventDetailsBuilder().SetValue(99.77).Set("currencyCode", "USD").Build());\n'})})}),(0,r.jsx)(s.A,{value:"go",label:"Go",children:(0,r.jsx)(n.pre,{children:(0,r.jsx)(n.code,{className:"language-go",children:'client.Track(\n  context.Background(),\n  "visited-promo-page",\n  openfeature.EvaluationContext{},\n  openfeature.NewTrackingEventDetails(99.77).Add("currencyCode", "USD"),\n  )\n'})})})]})]})}function h(e={}){const{wrapper:n}={...(0,i.R)(),...e.components};return n?(0,r.jsx)(n,{...e,children:(0,r.jsx)(p,{...e})}):p(e)}},19365(e,n,a){a.d(n,{A:()=>c});a(96540);var t=a(34164),r=a(47751);const i="tabItem_Ymn6";var o=a(74848);function s({children:e,className:n,hidden:a}){return(0,o.jsx)("div",{role:"tabpanel",className:(0,t.A)(i,n),hidden:a,children:e})}function c({children:e,className:n,value:a}){const{selectedValue:t,lazy:i}=(0,r.uc)(),c=a===t;return!c&&i?null:(0,o.jsx)(s,{className:n,hidden:!c,children:e})}},4865(e,n,a){a.d(n,{A:()=>f});a(96540);var t=a(34164),r=a(17559),i=a(47751),o=a(23104),s=a(92303);const c="tabList__CuJ",l="tabItem_LNqP";var u=a(74848);function d({className:e}){const{selectedValue:n,selectValue:a,tabValues:r,block:s}=(0,i.uc)(),c=[],{blockElementScrollPositionUntilNextRender:d}=(0,o.a_)(),p=e=>{const t=e.currentTarget,i=c.indexOf(t),o=r[i].value;o!==n&&(d(t),a(o))},h=e=>{let n=null;switch(e.key){case"Enter":p(e);break;case"ArrowRight":{const a=c.indexOf(e.currentTarget)+1;n=c[a]??c[0];break}case"ArrowLeft":{const a=c.indexOf(e.currentTarget)-1;n=c[a]??c[c.length-1];break}}n?.focus()};return(0,u.jsx)("ul",{role:"tablist","aria-orientation":"horizontal",className:(0,t.A)("tabs",{"tabs--block":s},e),children:r.map((({value:e,label:a,attributes:r})=>(0,u.jsx)("li",{role:"tab",tabIndex:n===e?0:-1,"aria-selected":n===e,ref:e=>{c.push(e)},onKeyDown:h,onClick:p,...r,className:(0,t.A)("tabs__item",l,r?.className,{"tabs__item--active":n===e}),children:a??e},e)))})}function p({children:e}){return(0,u.jsx)("div",{className:"margin-top--md",children:e})}function h({className:e,children:n}){return(0,u.jsxs)("div",{className:(0,t.A)(r.G.tabs.container,"tabs-container",c),children:[(0,u.jsx)(d,{className:e}),(0,u.jsx)(p,{children:n})]})}function f(e){const n=(0,s.A)(),a=(0,i.OC)(e);return(0,u.jsx)(i.O_,{value:a,children:(0,u.jsx)(h,{className:e.className,children:(0,i.vT)(e.children)})},String(n))}},47751(e,n,a){a.d(n,{OC:()=>f,O_:()=>g,uc:()=>v,vT:()=>u});var t=a(96540),r=a(90612),i=a(205),o=a(57485),s=a(70679),c=a(31682),l=a(74848);function u(e){return t.Children.toArray(e).filter((e=>"\n"!==e))}function d(e){const{values:n,children:a}=e;return(0,t.useMemo)((()=>{const e=n??function(e){return t.Children.toArray(e).flatMap((e=>{if(!e)return[];if((0,t.isValidElement)(e)&&function(e){const{props:n}=e;return!!n&&"object"==typeof n&&"value"in n}(e))return[e];
1const 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:a,default:t}})=>({value:e,label:n,attributes:a,default:t})))}(a);return function(e){const n=(0,c.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,a])}function p({value:e,tabValues:n}){return n.some((n=>n.value===e))}function h({queryString:e=!1,groupId:n}){const a=(0,r.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,o.aZ)(i),(0,t.useCallback)((e=>{if(!i)return;const n=new URLSearchParams(a.location.search);n.set(i,e),a.replace({...a.location,search:n.toString()})}),[i,a])]}function f(e){const{defaultValue:n,queryString:a=!1,groupId:r}=e,o=d(e),[c,l]=(0,t.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(!p({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 a=n.find((e=>e.default))??n[0];if(!a)throw new Error("Unexpected error: 0 tabValues");return a.value}({defaultValue:n,tabValues:o}))),[u,f]=h({queryString:a,groupId:r}),[m,v]=function({groupId:e}){const n=function(e){return e?`docusaurus.tab.${e}`:null}(e),[a,r]=(0,s.Dv)(n);return[a,(0,t.useCallback)((e=>{n&&r.set(e)}),[n,r])]}({groupId:r}),g=(()=>{const e=u??m;return p({value:e,tabValues:o})?e:null})();(0,i.A)((()=>{g&&l(g)}),[g]);return{selectedValue:c,selectValue:(0,t.useCallback)((e=>{if(!p({value:e,tabValues:o}))throw new Error(`Can't select invalid tab value=${e}`);l(e),f(e),v(e)}),[f,v,o]),tabValues:o,lazy:e.lazy??!1,block:e.block??!1}}const m=(0,t.createContext)(null);function v(){const e=t.useContext(m);if(!e)throw new Error("useTabsContext() must be used within a Tabs component");return e}function g(e){return(0,l.jsx)(m.Provider,{value:e.value,children:e.children})}},28453(e,n,a){a.d(n,{R:()=>o,x:()=>s});var t=a(96540);const r={},i=t.createContext(r);function o(e){const n=t.useContext(i);return t.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function s(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(r):e.components||r:o(e.components),t.createElement(i.Provider,{value:n},e.children)}}}]);

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.