npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@instructure/platform-cohorts

v1.0.1

Published

Cohort management UI for Canvas.

Readme

@instructure/platform-cohorts

Cohort management UI for Canvas.

Cohorts is a new domain model for grouping users into hierarchical groups agnostic of the account structure that powers exactly six things:

  • enrollment into courses
  • enrollment into programs
  • enrollment into collections
  • targeted global announcements
  • targeted learner dashboards
  • filtered reporting

This package is the foundational slice of that work. Per-product extensions belong in the consuming app, not here.

Read more: the tech spec. For Claude Code, the canvas-cohorts plugin carries a mirror of it plus a cross-repo code map.

The REST contract lives in openapi.yaml and is the source of truth for every API operation this package performs.

Development

Everything runs from the monorepo root unless noted.

pnpm install                                        # link the package into the workspace
pnpm storybook                                      # http://localhost:6006 — the dev loop
pnpm --filter @instructure/platform-cohorts test     # vitest + coverage
pnpm --filter @instructure/platform-cohorts build    # vite lib build + .d.ts
pnpm --filter @instructure/platform-cohorts type-check
pnpm --filter @instructure/platform-cohorts api:docs # browse openapi.yaml in Scalar, http://localhost:4727
pnpm lint                                            # biome + oxlint + ast-grep

Mounting CohortsApp

CohortsApp is the root export. It mounts no providers and sets no InstUI theme, so the host supplies all three, outermost first:

<InstUISettingsProvider theme={theme}>
  <PlatformUiProvider {...platformProps} queryClient={queryClient}>
    <CohortsI18nProvider locale={locale} translations={translations}>
      <CohortsApp
        accountId={accountId}
        onSelectCohort={id => navigate(`/cohorts/${id}`)}
        getCohortHref={id => `/cohorts/${id}`}
      />
    </CohortsI18nProvider>
  </PlatformUiProvider>
</InstUISettingsProvider>

The host owns the URL: it reads IDs from its router and passes them in as props. The package reports intent through onSelectCohort(id) and onBack(), and getCohortHref(id) lets rows be real links. Stories get the light theme from CohortsStoryHost.

The contract

openapi.yaml is OpenAPI 3.1, 27 operations across 18 paths, with no server implementing it yet. src/__tests__/openapi.test.ts proves it's structurally complete: the document validates against the OpenAPI 3.1 schema and every $ref resolves, every operation has a unique operationId, a 403, and a 404, every POST/PUT/DELETE also has a 400, the path+method matrix matches exactly, the Cohort, WorkflowState, and ErrorCode schemas carry the fields the rest of the contract depends on, no path reaches into pseudonym, generator, or sync territory, and no request body accepts a full user object where an id would do. If a check fails, the fix belongs in the YAML, not the test.

The contract uses no custom (x-*) vendor extensions. Service limits live in the schema and response descriptions that enforce them, and which operations extend an existing Canvas API is documented in each operation's own description.

Which permission an operation requires and which feature flag gates it are determined by Canvas's real permission registry and feature-flag system, not documented per-operation in this contract.

Conventions consumers rely on

  • Pagination: list operations return a Link header in RFC 5988 format, matching the rest of the Canvas API.
  • Ids: every id is a 64-bit integer, modelled as the shared Id schema. Canvas serialises ids as JSON strings instead when the request carries Accept: application/json+canvas-string-ids, so clients should accept both forms.
  • Error payloads: Error (message + optional machine-readable error_code), ValidationError (per-attribute field errors), and UnauthorizedError (the 403 shape) cover every failure case. The one exception is bulkEnroll, whose pre-existing Canvas validation keeps its string-bodied LegacyStringError.
  • Status codes follow Canvas: creates return 200, not 201; an authenticated caller without permission gets 403 with status: unauthorized. No operation in this contract returns 409.
  • List filters follow Canvas: lifecycle filtering is the repeatable workflow_state[] (active, deleted, all), per_page caps at 100, and search_term needs at least two characters.
  • 404 means three things on purpose: a missing/deleted resource, the institutional_cohorts flag being off for the root account, or the resource belonging to a different root account than the caller's. The API never distinguishes these, so it never reveals what it won't show you.
  • 400 feature_disabled: the three extended Canvas operations (bulkEnroll, createAccountNotification, updateAccountNotification) keep working when the institutional_cohorts flag is off, but reject their cohort-specific parameters with 400 and error code feature_disabled, rather than 404ing the whole operation.
  • Form encodings: the three extended Canvas operations accept application/x-www-form-urlencoded and multipart/form-data with the same schema. canvas-fetch emits FormData natively, so multipart is the path of least resistance for clients in this repo.

Key contract decisions

  • createCohort creates both kinds of cohort: pass parent_cohort_id for a sub-cohort, or null for a root cohort at depth 1 with no parent. An account may anchor several independent root cohorts — Canvas has no /root route and neither does this contract. listCohorts finds them with roots=true.
  • Depth is 1-based (the root cohort is depth 1) and capped at 10.
  • Deletion is soft: cohorts, memberships, and leader assignments move to workflow_state: deleted rather than being removed.
  • A Leader's rights always cascade to every cohort beneath the one the assignment lives on — there's no way to scope a Leader to a single level.
  • Adding a leader is idempotent: a duplicate addCohortLeader returns the existing assignment with 200, the way the Canvas admins API does.
  • Leader grants carry no ceiling on which roles a leader may hand out. An earlier revision refused, with 403, any role other than the one that authorised the caller. That rule appeared nowhere in the tech spec, so it was dropped; Canvas instead enforces a subset rule on AccountUser (is_subset_of?: the granted role's permissions must be a subset of the caller's). If a ceiling is wanted, amend the spec first, and prefer the Canvas subset rule over strict equality.
  • updateCohortLeader changes a leader's role in place. The tech spec does not describe it and the Canvas admins API has no equivalent (role change is delete plus create there), so it stays pending a spec amendment.
  • getCohortTree defaults to depth=2 (the cohort and its direct children) so a client expands one level at a time unless it asks for more; depth=10 returns the full hierarchy.
  • Bulk course enrolment, program enrolment, and collection enrolment are all one-shot actions: they resolve cohort membership once, when the job runs, and nothing keeps the target roster in sync with the cohort afterwards.