{"id":39605743,"url":"https://github.com/marszall87/retention","last_synced_at":"2026-01-18T08:00:16.234Z","repository":{"id":57355209,"uuid":"145137028","full_name":"marszall87/retention","owner":"marszall87","description":"Calculate user retention matrix and render table or chart with data","archived":false,"fork":false,"pushed_at":"2018-08-19T20:54:45.000Z","size":832,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-04T07:49:20.920Z","etag":null,"topics":["analytics","chart","data-visualization","metrics","retention"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marszall87.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-17T15:26:57.000Z","updated_at":"2019-10-06T09:09:44.000Z","dependencies_parsed_at":"2022-09-09T09:51:09.364Z","dependency_job_id":null,"html_url":"https://github.com/marszall87/retention","commit_stats":null,"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/marszall87/retention","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marszall87%2Fretention","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marszall87%2Fretention/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marszall87%2Fretention/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marszall87%2Fretention/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marszall87","download_url":"https://codeload.github.com/marszall87/retention/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marszall87%2Fretention/sbom","scorecard":{"id":621284,"data":{"date":"2025-08-11","repo":{"name":"github.com/marszall87/retention","commit":"479ec4436932bb012a757b086c01b0a538adb1bc"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/20 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"40 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-cwfw-4gq5-mrqx","Warn: Project is vulnerable to: GHSA-g95f-p29q-9xw4","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-257v-vj4p-3w2h","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-ff7x-qrg7-qggm","Warn: Project is vulnerable to: GHSA-qqgx-2p2h-9c37","Warn: Project is vulnerable to: GHSA-7r28-3m3f-r2pr","Warn: Project is vulnerable to: GHSA-r8j5-h5cx-65gg","Warn: Project is vulnerable to: GHSA-2pr6-76vf-7546","Warn: Project is vulnerable to: GHSA-8j8c-7jfh-h6hx","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-6c8f-qphg-qjgp","Warn: Project is vulnerable to: GHSA-76p3-8jx3-jpfq","Warn: Project is vulnerable to: GHSA-4xc9-xhrj-v574","Warn: Project is vulnerable to: GHSA-x5rq-j2xg-h7qm","Warn: Project is vulnerable to: GHSA-jf85-cpcp-j695","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-9wv6-86v2-598j","Warn: Project is vulnerable to: GHSA-566m-qj78-rww5","Warn: Project is vulnerable to: GHSA-7fh5-64p2-3v2j","Warn: Project is vulnerable to: GHSA-hwj9-h5mp-3pm3","Warn: Project is vulnerable to: GHSA-gcx4-mw62-g8wm","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-cpgr-wmr9-qxv4","Warn: Project is vulnerable to: GHSA-xw79-hhv6-578c","Warn: Project is vulnerable to: GHSA-48gc-5j93-5cfq","Warn: Project is vulnerable to: GHSA-4wf5-vphf-c2xc"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T05:17:34.872Z","repository_id":57355209,"created_at":"2025-08-21T05:17:34.873Z","updated_at":"2025-08-21T05:17:34.873Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28533735,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-18T00:39:45.795Z","status":"online","status_checked_at":"2026-01-18T02:00:07.578Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["analytics","chart","data-visualization","metrics","retention"],"created_at":"2026-01-18T08:00:14.128Z","updated_at":"2026-01-18T08:00:16.031Z","avatar_url":"https://github.com/marszall87.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 📉 Retention\n\n\u003e Calculate user retention matrix and render table or chart with data\n\n[![NpmVersion](https://img.shields.io/npm/v/retention.svg?style=flat-square)](https://www.npmjs.com/package/retention)\n[![npm](https://img.shields.io/npm/dm/retention.svg?style=flat-square)](https://www.npmjs.com/package/retention)\n[![](https://data.jsdelivr.com/v1/package/npm/retention/badge)](https://www.jsdelivr.com/package/npm/retention)\n[![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/retention.svg?style=flat-square)](https://www.npmjs.com/package/retention)\n\n\u003cp align=\"center\"\u003e\u003cimg width=\"600\" alt=\"Screenshot\" src=\"https://raw.githubusercontent.com/marszall87/retention/master/screenshot.png\"\u003e\u003c/p\u003e\n\n### What?\n\nThis library calculates retention metric, which answers the question: how many users\u003csup\u003e1\u003c/sup\u003e that registered\u003csup\u003e2\u003c/sup\u003e in a given week\u003csup\u003e3\u003c/sup\u003e were active\u003csup\u003e4\u003c/sup\u003e in the following weeks. In other words, how soon and how many users are abandoning you product/app or some specific feature.\n\n\u003csub\u003e1. or accounts, or instances; 2. or started trial, or enabled some feature; 3. or in any other time range; 4. or did some specific action\u003c/sub\u003e\n\n### Why?\n\nSome services (e.g. keen.io) don't have built in tool or API to calculate retention metric, so you have to do it on your own. Existing tools, like keen.io's cohort builder, are usually locked into specific provider and are not very flexible - for example I needed something where I can use multiple events to determine the \"active\" user.\n\n### How?\n\nYou have to provide two kinds of data sets. First, an `Array` of events that define your \"registered\" actions, i.e. initial events. Every event is an `Object` with `date` and `id` props:\n\n```js\n[\n  {\n    id: 'b7f1181a',\n    date: '2018-02-19T16:57:12.188Z'\n  }\n  // ...more events\n];\n```\n\nThe second data set is an `Array` of user IDs that were active in a given time range. You have to provide this for every time range you want to calulate, i.e. if you want retentnion for the last four weeks, you should provide four `Array`s like this:\n\n```js\n[\n  '855fc7a1',\n  '5c8325a9',\n  '2a26adea',\n  '6623141d',\n  '269f7d39',\n  '832c4038',\n  'b7f1181a'\n  // ...more IDs\n];\n```\n\nAnd that's it. The `buildMatrix` function will return a retention matrix that looks like this:\n\n```js\n[\n  {\n    range: {\n      from: '2018-02-01T00:00:00.000Z',\n      to: '2018-02-07T23:59:59.999Z'\n    },\n    cohort: [\n      { id: 'b7f1181a', date: '2018-02-19T16:57:12.188Z' }\n      // ...more IDs\n    ],\n    activity: [\n      {\n        activeCount: 757,\n        percentage: 74,\n        active: [\n          {\n            id: 'b7f1181a'\n            // events: [ 'action-a', 'action-b ]\n          }\n          // ...more IDs\n        ]\n      }\n      // ...more time ranges\n    ]\n  }\n  // ...more time ranges\n];\n```\n\nYou can specify more than one `Array` of active users for every time range (e.g. for different actions they're performing, see below) and it will be included in every object in the `active` array in the `events` property.\n\nBoth `renderTable` and `renderChart` functions take this matrix plus some configuration options as input.\n\nRegarding performance, it calculates and renders both table and chart in a reasonable time (\u003c1s) on a Macbook Pro for up to ~20k unique user IDs (4 week retention). However, in such case I recommend to move the matrix calculation to some back-end service or pre-compute it offline. But let's be honest, if you have more than 20k unique users per month you wouldn't be using this simple library that I wrote over the weekend, would you? 🤔\n\n## Installation\n\n```sh\n$ npm i retention\n```\n\nD3 and C3 libraries are not included in the bundle, you have to install them separately.\n\n#### ES6 modules\n\n```js\nimport { buildMatrix, renderTable, renderChart } from 'retention';\n```\n\n#### CommonJS\n\n```js\nconst { buildMatrix } = require('retention');\n```\n\n_Notice:_ Because CJS modules are almost always used in NodeJS backend code (Webpack or Rollup should automatically use UMD or ESM version), this version includes only `buildMatrix` function, no rendering functions. If you want those you should probably explicitly include the UMD version:\n\n```js\nconst { buildMatrix, renderTable, renderChart } = require('retention/dist/retention.umd.js');\n```\n\n#### Browser\n\n```html\n\u003cscript src=\"https://cdn.jsdelivr.net/npm/retention@latest\"\u003e\u003c/script\u003e\n\u003cscript\u003e\n    const { buildMatrix, renderTable, renderChart } = Retention;\n\u003c/script\u003e\n```\n\n#### CSS\n\nThere's also a simple CSS file which adds some styling to the table:\n\n```html\n\u003clink href=\"https://cdn.jsdelivr.net/npm/retention@latest/dist/retention.min.css\" rel=\"stylesheet\" /\u003e\n```\n\n## Sample\n\nClone the repo, install dependencies, build the bundle, and start a simple web server with these commands:\n\n```sh\ngit clone https://github.com/marszall87/retention.git\ncd retention\nnpm install\nnpm run build\nnpm run sample\n```\n\nNavigate to `http://localhost:5000/sample` and you should see a sample retention chart and table.\n\n## API\n\n### buildMatrix(opts)\n\nReturns promise with retention matrix object.\n\n`opts.dateRanges` - `Array` of `{ from, to }` objects that defines date ranges for cohorts, if not specified it's 4 last weeks\n\n`opts.getInitialEvents` - function that should return a promise with `Array` of objects with two props: `date` and `id`, date represents\n\n`opts.getActivity` - function or object in form of `{ eventsA: () =\u003e {...}, eventsB: () =\u003e {...} }`, functions take three args: start date, end date and index of a given cohort date range, should return an `Array` of ids that were active in that time\n\n### generateRanges(opts)\n\nHelper function that generates cohor date ranges.\n\n`opts.endingDate` - ending date of the last (most recent) cohort, end of the current day by default\n\n`opts.daysInRange` - number of days in every date range, default is 7\n\n`opts.rangeCount` - number of date ranges to generate, 4 by default\n\n### renderTable(opts)\n\n`opts.container` - container in which the table will be rendered\n\n`opts.matrix` - retention matrix as returned by `buildMatrix()`\n\n### renderChart(opts)\n\n`opts.container` - container in which the chart will be rendered\n\n`opts.matrix` - retention matrix as returned by `buildMatrix()`\n\n`opts.options` - options passed directly to `c3.generate` function, can be used to modify the chart, full list of options available in [C3 docs](https://c3js.org/reference.html).\n\n## License\n\nMIT © [Michał Nykiel](https://github.com/marszall87)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarszall87%2Fretention","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarszall87%2Fretention","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarszall87%2Fretention/lists"}