Skip to content

Composables

The frontend SDK ships ~29 typed composables. This page lists every one with a one-line summary, then deep-dives on the 7 you'll use most often: useApi, useAuth, useToast, usePlatformContext, useExperimentSelector, useExperimentData, useFormBuilder.

Full list

All 29 composables (click to expand)
ComposableWhat it returnsWhen to reach for it
useApiTyped fetch wrapperAny API call from the frontend
useAuthCurrent user, login/logout actionsReading user identity, role checks
usePasskeyWebAuthn registration / login flowsBuilding passkey UX
useThemeTheme state + toggleLight/dark switcher
useToastToast dispatcherUser feedback
usePlatformContextActive project / experiment contextPlugins mounted inside the platform shell
useFormReactive form state with validation rulesManual form management
useFormBuilderSchema-driven form runtimeThe FormBuilder component (rare to use directly)
useAsync, useAsyncBatchAsync-state helpers (loading/data/error)Wrap any async operation
useWellPlateEditorWell-plate state + helpersPlate-design UIs
useRackEditorRack-layout stateSample-rack UIs
useConcentrationUnitsConcentration parsing / conversionAnything dealing with µM / mg/mL / %
useDoseCalculatorDilution + serial-dilution mathDrug-screening tools
useReagentSeriesDilution series generatorsBuilding dose-response panels
useChemicalFormulaFormula parsing + MWShowing elemental composition
useSequenceUtilsDNA / protein sequence helpersSequence inputs and stats
useTimeUtilsTime math + slot generationSchedule UIs
useScheduleDragDrag-to-reschedule handlersCalendar / timeline UIs
useProtocolTemplatesLab-protocol template engineStep-by-step protocol UIs
useAutoGroupAuto-group samples by name prefixSample grouping helpers
usePluginConfigPlugin settings reactive objectReading plugin config from the frontend
usePluginApiPlugin-scoped API clientCalls scoped to the plugin's prefix
useExperimentSelectorPicker UI + reactive selected experimentExperiment dropdowns
useExperimentDataReactive experiment design + analysisLive experiment view
useExperimentSaveSave flow with conflict detectionForms that save back to an experiment
useAppExperimentApp-level experiment provide/injectPlugin pages that need the active experiment

Deep dives

useApi

A typed fetch wrapper that reads the platform's auth state and prepends /api to relative paths.

ts
import { useApi } from '@morscherlab/mint-sdk'

const api = useApi()

// Plain GET — auto-typed by the type parameter
const summary = await api.get<ExperimentSummary>('/api/my-plugin/experiments/1')

// POST with a body
const created = await api.post<Panel>('/api/my-plugin/panels', {
  experiment_id: 1, name: 'Cisplatin', drugs: [...]
})

// Other methods on the returned object:
await api.put<Panel>('/api/my-plugin/panels/1', { ... })
await api.patch('/api/my-plugin/panels/1', { name: 'Renamed' })
await api.delete(`/api/my-plugin/panels/${id}`)

// File operations
const result = await api.upload('/api/my-plugin/files', file)
const blob = await api.download(`/api/my-plugin/files/${id}`)

// URL builders for WebSocket / SSE endpoints
const wsUrl = api.buildWsUrl('/api/my-plugin/stream')

The full return shape is { client, get, post, put, patch, delete, upload, download, buildUrl, buildWsUrl }. client is the underlying typed-fetch instance — use it for advanced cases (custom headers, response streaming).

api automatically:

  • Sends the platform's auth cookie (or bearer token) — no manual header
  • Adds Content-Type: application/json for JSON bodies, multipart/form-data for FormData
  • Throws on non-2xx — the rejection is an Error with status, code, and details fields populated from the platform's structured error response
  • Propagates the OpenTelemetry trace context

For plugin-scoped calls (auto-prepending /api/<plugin-prefix>), use usePluginApi instead.

useAuth

Reactive access to the current user and authentication actions.

ts
import { useAuth } from '@morscherlab/mint-sdk'

const { user, isAuthenticated, login, logout } = useAuth()

// Reactively gate UI
const canEdit = computed(() => user.value?.role === 'Admin' || user.value?.role === 'Member')

// Programmatic logout
async function signOut() {
  await logout()
}

user is a Ref<User | null>. The user object includes id, username, email, role, and the platform-managed audit fields. Plugin roles are separate — fetch via your plugin's own /me/role endpoint as needed.

useToast

Programmatic toast notifications using the platform's toast stack.

ts
import { useToast } from '@morscherlab/mint-sdk'

const toast = useToast()

toast.success('Panel saved')
toast.warning('Detected 3 duplicates — review before saving')
toast.error('Failed to save: network error')
toast.info('Tip: use Cmd+K to open the command palette')

// With more options
toast.success({
  title: 'Panel saved',
  description: 'View in the panels list',
  duration: 5000,
  action: {
    label: 'View',
    onClick: () => router.push('/panels'),
  },
})

usePlatformContext

When your plugin is mounted inside the platform shell (under /<plugin-prefix>), the platform provides context about the active project and experiment. usePlatformContext reads it reactively.

ts
import { usePlatformContext } from '@morscherlab/mint-sdk'

const { project, experiment } = usePlatformContext()

watch(experiment, (e) => {
  if (e) loadDataFor(e.id)
})

Both project and experiment are Ref<… | null>. They're null when the plugin is rendered outside an experiment (e.g., at the top-level plugin home).

useExperimentSelector

Inline picker — fetches the user's accessible experiments and surfaces a reactive selected experiment.

ts
import { useExperimentSelector } from '@morscherlab/mint-sdk'

const {
  experiments,        // Ref<Experiment[]>
  total,              // Ref<number>
  selectedExperiment, // Ref<Experiment | null>
  filters,            // Ref<{ search, status, type, project, ... }>
  isLoading,
  error,
  page,
  hasMore,
  sortKey,
  experimentTypes,    // available faceted values
  projects,
  groupedByProject,
  fetch,              // re-fetch with current filters
  loadMore,
  reset,
  select,             // (experiment: Experiment) => void
  clear,
  fetchFilterOptions,
} = useExperimentSelector({ /* options */ })

// To search, mutate filters.value.search and call fetch():
filters.value.search = 'TCA'
await fetch()

The selected experiment is selectedExperiment (not selected); the search input is filters.search; re-fetch is fetch() (not refresh). Pair with the ExperimentSelectorModal component for a picker UI, or render experiments yourself.

For plugins that expect an experiment to always be active (because they're mounted on the experiment Analyze tab), use usePlatformContext instead — it reads the active experiment from the URL.

useExperimentData

Reactive view of a single experiment's design data and analysis results. Keeps the values in sync as other plugins write.

ts
import { useExperimentData } from '@morscherlab/mint-sdk'

const { design, analysis, isLoading, refresh } = useExperimentData({
  experimentId: 1,
  pluginId: 'my-plugin',   // which analysis plugin's results to read
})

// design = Ref<DesignData | null>
// analysis = Ref<PluginAnalysisResult | null>

Pair with useExperimentSave for the save side.

useFormBuilder

Powers the FormBuilder component — schema in, model out. You rarely call it directly; you pass a schema to <FormBuilder> and it handles the wiring. If you need programmatic control (e.g., custom validation hooks), reach for useFormBuilder directly.

ts
import { useFormBuilder, evaluateCondition } from '@morscherlab/mint-sdk'

const { fields, model, errors, validate, reset } = useFormBuilder({
  schema: panelSchema,
  initial: { name: '', drugs: [] },
})

// Conditional fields driven by the schema
const showAdvanced = evaluateCondition(model.value, panelSchema.advancedCondition)

See FormBuilder deep dive.

Other notable composables (one-line each)

ComposableUse it when
useAsyncWrap any async function so the template can show loading / error / data states
useDoseCalculatorBuilding dose-response calculators or serial dilution helpers
useConcentrationUnitsParsing user input like "5 mM" and converting between unit families
useChemicalFormulaShow elemental composition of a formula string
useThemeCustom theme switcher (the standard <ThemeToggle> already uses this)
useTimeUtilsPlate-reader scheduling, anything with time slots

Notes

  • All composables use Vue 3 Composition API. Call them inside <script setup> or setup() only.
  • Most composables return Ref or ComputedRef — destructure but keep the references reactive.
  • The composables that hit the network (useApi, useExperimentData, useExperimentSelector) handle auth automatically; you don't construct your own fetch calls.

MINT is open source. Made by the Morscher Lab.