@morscherlab/mint-sdk
@morscherlab/mint-sdk (Frontend)
Vue 3 SDK for MINT Platform plugin development. Provides reusable components, composables, stores, and types for building consistent, well-integrated plugins.
Full Documentation: See the MINT SDK docs for detailed API reference and guides.
- API Reference
- Component Catalog - All public components with props/events
- Composables Reference - All public composables with examples
- Theming Guide - CSS variables and customization
Installation
bun add @morscherlab/mint-sdk
Requirements
- Vue 3.4+
- Pinia 2.1+
- Tailwind CSS 4.0+ (optional - SDK includes pre-built utility classes)
Quick Start
1. Import Styles
Import the SDK styles in your main entry point:
// main.ts
import '@morscherlab/mint-sdk/styles'
The SDK includes all necessary CSS utility classes pre-built, so Tailwind CSS is optional. The styles work standalone or alongside Tailwind.
Required.
dist/styles.cssis the SDK's single stylesheet: components no longer@importtheir own CSS from their<style>block, so a plugin that skips this import renders unstyled components.src/styles/index.cssis the sole aggregator — every component stylesheet is imported there exactly once.
2. (Optional) Configure Tailwind
If you're using Tailwind CSS and want to extend the MINT theme:
import mintPreset from '@morscherlab/mint-sdk/tailwind.preset'
export default {
presets: [mintPreset],
content: [
'./src/**/*.vue',
'./node_modules/@morscherlab/mint-sdk/**/*.vue',
],
}
3. Use Components
<script setup lang="ts">
import { BaseButton, BaseInput, FormField } from '@morscherlab/mint-sdk'
import { useToast } from '@morscherlab/mint-sdk'
import type { ButtonVariant } from '@morscherlab/mint-sdk'
const { success } = useToast()
function handleSubmit() {
success('Saved!')
}
</script>
<template>
<FormField label="Username" required>
<BaseInput v-model="username" placeholder="Enter username" />
</FormField>
<BaseButton variant="primary" @click="handleSubmit">
Save
</BaseButton>
</template>
Native Plotly charts
PlotlyChart accepts native Plotly data, layout, and config props and
loads Plotly lazily. It shares MINT's chart header, loading/empty states, and
toolbar/legend slots. Explicit layout colors override the MINT defaults.
<PlotlyChart
title="QC overview"
:data="[{ type: 'scatter', x: [1, 2, 3], y: [3, 4, 2] }]"
:layout="{ xaxis: { title: { text: 'Sample' } } }"
:config="{ toImageButtonOptions: { filename: 'qc-overview', format: 'png' } }"
/>
Replace data/layout/config references to redraw; nested in-place mutations do
not schedule a render. The component isolates Plotly's mutations of data and
layout, observes container size and the root theme, and cleans up on unmount.
Use loading, empty, and emptyMessage for explicit states, and the error
slot for a custom failure presentation. Generated Plotly results reuse this
renderer and retain their static-image fallback.
4. Use Generated Plugin Clients
For plugin frontends scaffolded by mint init, run this from the plugin root:
mint sdk generate
Use mint sdk generate --check --json when CI or editor tasks need machine-readable contract drift status.
Then call backend routes through the generated client:
import { useGeneratedPluginClient } from './generated/mint-plugin'
const pluginClient = useGeneratedPluginClient()
const health = await pluginClient.health()
// Cancel a generated request without bypassing SDK authentication.
const controller = new AbortController()
const result = await pluginClient.analyze(
{ body: { parameters: { threshold: '0.05' } } },
{ signal: controller.signal },
)
Generated requests (including uploads and downloads) throw MintApiError from
@morscherlab/mint-sdk for HTTP failures. It exposes code, message, status,
requestId, details, and the original response body; the status comes from
HTTP, and the request ID falls back to X-Request-ID. Legacy FastAPI detail
and SDK plugin error responses are supported. Direct useApi() calls retain
Axios errors unless configured with { typedErrors: true }; cancellation and
authentication retry behavior are unchanged.
Generated files also expose typed plugin settings when the backend declares
@mint_plugin(config=PluginConfig):
import {
pluginSettingsFormSchema,
pluginSettingsValueSchema,
useGeneratedPluginSettings,
} from './generated/mint-plugin'
const pluginSettings = useGeneratedPluginSettings()
const threshold = pluginSettings.settings.value?.threshold
const settingsConfig = pluginSettings.settingsConfig
Pass settingsConfig to PluginWorkspaceView or AppTopBar.
pluginSettingsValueSchema is the Pydantic JSON Schema used for generated
TypeScript values. pluginSettingsFormSchema is the SDK
SettingsModalSchema used to render controls; they are deliberately separate.
When .env, process-environment, or startup values override a field, saving
still updates its backing value. The effective override remains active, while
the saved value is returned in stored_settings and marked inactive until
that higher-priority override is removed.
The generated client is built from backend plugin metadata, FastAPI routes, and Pydantic schemas, and uses the frontend SDK to resolve the API prefix automatically.
FastAPI request or response content declared as standard
application/octet-stream is generated as a raw binary transport: request
bodies accept Blob | ArrayBuffer | Uint8Array, responses resolve to Blob,
and the SDK does not JSON-encode the payload. Multipart
type: string, format: binary fields remain typed as File | Blob.
Routes with an experimentId path parameter can omit it inside an experiment page; the client reads the current platform experiment from injected context or the URL:
await pluginClient.analyze({ body: { parameters: { threshold: '0.05' } } })
Use useCurrentExperiment() when a plugin view needs the platform experiment selected in the URL or injected context:
import { useCurrentExperiment } from '@morscherlab/mint-sdk'
const {
experimentId,
hasExperiment,
requireExperimentId,
experiment,
isLoading,
refresh,
} = useCurrentExperiment()
async function saveCurrentExperiment() {
if (!hasExperiment.value) return
const id = requireExperimentId()
// Use id for platform APIs or explicit cross-experiment overrides.
}
5. Biology Data Templates (deprecated)
Deprecated in MINT 1.3 and removed in 1.4, with no SDK replacement: the biology data templates (
@morscherlab/mint-sdk/templates,mint_sdk.templates,mint add data-template*), theBioTemplate*views, theuseBioTemplate*composables,useTemplateCollection(),useExperimentSave(), and the template-only componentsReagentList,ReagentEditor,ExperimentTimeline,SampleLegendandFitPanel.
6. Build Control Workspaces
For custom plugin tools, pass one workspace model to ControlWorkspaceView to
generate AppTopBar, AppSidebar, FormBuilder, component prop bindings, and
their shared values from the same source:
<script setup lang="ts">
import { ref } from 'vue'
import { ControlWorkspaceView, defineControlModel } from '@morscherlab/mint-sdk'
const workspaceModel = defineControlModel({
views: {
analysis: {
label: 'Analysis',
sections: {
parameters: {
label: 'Parameters',
controls: {
threshold: { type: 'number', default: 0.05, min: 0, max: 1 },
method: ['linear', 'logistic'],
saveResults: true,
},
},
},
},
},
})
const values = ref({})
</script>
<template>
<ControlWorkspaceView v-model="values" :model="workspaceModel" title="Analysis" />
</template>
Use defineControls() for flat forms, and use useControlWorkspace()
directly only when a plugin needs full manual control over the generated
AppTopBar, AppSidebar, or FormBuilder bindings.
For WellPlate + DoseCalculator pages, start from the dose-design preset instead of writing the sidebar controls and component prop mappings yourself:
<script setup lang="ts">
import { ControlWorkspaceView, DoseCalculator, WellPlate, defineDoseDesignControlModel } from '@morscherlab/mint-sdk'
const workspaceModel = defineDoseDesignControlModel({
selectedWells: ['A1', 'A2'],
includeMolecularWeight: true,
})
</script>
<template>
<ControlWorkspaceView :model="workspaceModel" title="Dose Design" v-slot="{ componentPropsById }">
<WellPlate v-bind="componentPropsById.plate" />
<DoseCalculator v-bind="componentPropsById.dose" />
</ControlWorkspaceView>
</template>
Components
Base Components
BaseButton- Button with variants (primary, secondary, cta, danger, success, ghost)BaseInput- Text input with validation statesBaseSelect- Dropdown select with generic typingBaseTabs- Tab navigation with underline and pills variantsBaseModal- Modal dialog with Teleport
Form Components
FormField- Form field wrapper with label, error, and hintFormBuilder- Schema-driven forms from fullschemaobjects or compactcontrols
Contributor rule: SDK components compose SDK fields (FormField + BaseInput,
BaseSelect, BaseTextarea, UnitInput, SearchableSelect, BaseButton) and
never restyle them. A component stylesheet may size and place a field; it does
not define its own input, select, textarea, unit or button looks.
Feedback Components
AlertBox- Alert messages (success, error, warning, info)AppToastContainer- Toast notification container for the globaluseToast()queue
Action Components
IconButton- Icon-only button with accessibilityThemeToggle- Light/dark mode toggle
Layout Components
CollapsibleCard- Expandable card sections with optional icon badge and toggle switchAppTopBar- Application header with slotsAppSidebar- Context sidebar that accepts panels/forms or a compactcontrolsschema
File & Data Components
FileUploader- Drag-and-drop file upload with file/folder mode support
Sample Management Components
Deprecated in MINT 1.3 and removed in 1.4, with no SDK replacement: sample
grouping as a whole (SampleSelector, SmartGroupModal,
SmartGroupFieldRecipe, SmartGroupManual, AutoGroupModal,
useAutoGroup, GroupAssigner, useGroupAssignment).
Removed Compatibility APIs
The public SDK no longer exports ToastNotification, GroupingModal,
SettingsButton, FormSection, FormFieldRenderer, usePluginApi(), or
usePluginConfig(). The identity-only usePluginClient() and the legacy
useJobsStatusTray() job adapter API remain deprecated until MINT 1.4.
Use AppToastContainer, AppTopBar settings or
SettingsModal, FormBuilder, usePluginSettings(), generated
useGeneratedPluginClient() helpers, and source-driven usePluginJobCenter()
instead.
MINT 1.3 also removed ColorSlider (pass colorStops, thresholds,
showLabels, minLabel and maxLabel to BaseSlider), DropdownButton
(BaseSelect or SearchableSelect for values, ActionMenu for actions),
InstrumentAlertLog, InstrumentStatusCard, LcmsSequenceTable, useAsync(),
useOptimisticMutation() (use useRequestSyncState()), useResourceCrud()
(use useGeneratedPluginClient()), useCommandHistory(),
useWellPlateAdapter(), useWellPlateValidation() (no SDK replacement; keep
them in the plugin), useWellPainting() and useWellPlateEditor() (use
useRackEditor() with PlateEditor). mint doctor still detects old imports
and reports the replacement path.
MINT 1.3 removed AppPluginSwitcher and its PluginSwitcherPlugin /
PluginSwitcherInfo types, with the pluginSwitcher prop and
plugin-switcher-* events on AppTopBar and PluginWorkspaceView: integrated
plugins now show their icon, name and version as a static identity in
AppTopBar. ProgressBar lost its segmented variant (variant, steps,
currentStep); use StepWizard for step progress and the rail for percentages.
Composables
usePluginJobs(options)
Generated plugin contracts wrap this composable with definition-specific
input, job, and output types. For a component dedicated to one generated job,
bind the definition once with forDefinition():
import { usePluginJobs } from './generated/mint-plugin'
const jobs = usePluginJobs()
await jobs.ready()
const analyzeJobs = jobs.forDefinition('analyze')
const job = await analyzeJobs.submit({
source: new File(['peak\n1'], 'peaks.csv'),
threshold: 0.1,
})
const result = await analyzeJobs.loadResult(job)
const canonicalJob = await analyzeJobs.getJob(job.job_id)
const completedJob = await analyzeJobs.waitForJob(canonicalJob, {
signal: abortController.signal,
rejectOnFailure: true,
})
await analyzeJobs.cancelJob(canonicalJob)
// Releases only finished `analyze` jobs and their retained results.
await analyzeJobs.clearFinishedJobs()
The returned scope exposes a typed jobs getter, isJob() type guard,
submit(), getJob(), waitForJob(), loadResult(), cancelJob(), deleteJob(), and
definition-scoped clearFinishedJobs(). It also exposes the generated
lifecycle and profile literals for that definition without adding runtime
requests. Passing a job from another definition
fails instead of crossing the scope. Capability fields are presentation hints;
the server remains authoritative when scoped cancel/delete methods are called.
The root wrapper still supports dynamic definition IDs through submit() and
jobsFor(). Its no-argument clearFinishedJobs() intentionally clears
terminal jobs across every definition.
The underlying frontend composable accepts an optional definition ID in
clearFinishedJobs(definitionId); generated clients expose the scoped method
so static definition IDs retain their generated input, job, and output types.
getJob() fetches one fresh canonical job without refreshing the list.
waitForJob() settles from the same SSE/poll-backed canonical source and
cleans up on abort, deletion, or disposal, so callers do not need a second
event stream or Promise registry. A call made while the runtime is paused
rejects immediately. Waiting on a known active job tolerates a transient direct
lookup failure and continues through SSE/poll updates; pausing an existing wait
suspends those updates until resume().
cancelJob() updates active work, while deleteJob() releases one terminal
job and its retained result. Background work can be pause()d and resume()d,
while dispose() is idempotent and also runs automatically with the owning Vue
effect scope.
Both ordinary uploadPath({ ..., signal }) and a staged scope's
uploadPart(job, { ..., signal }) forward the supplied AbortSignal to the
authenticated multipart request. Abort stops the client-side transfer, but the
caller should refresh job/staging state before retrying because the server may
already have committed the upload.
Automatic submission binds ordinary Path uploads to its job definition. When
uploading manually for an administrator-protected definition, call
uploadPath(input, definitionId) so the server can authorize and bind the
upload before reading its body.
cancelJob() and deleteJob() accept either a canonical job object or its ID.
Pass the runtime's canonical source directly to standard job UI. The job center derives its groups from the same jobs ref and does not mirror it with a watcher:
<script setup lang="ts">
import { JobsStatusTray } from '@morscherlab/mint-sdk'
import { usePluginJobs } from './generated/mint-plugin'
const jobs = usePluginJobs()
</script>
<template>
<JobsStatusTray :source="jobs" />
</template>
JobsStatusTray accepts only the runtime source. Polling, SSE updates, and job
actions therefore have exactly one state owner.
When an incremental migration temporarily has more than one owner, use
combinePluginJobSources([sdkJobs, legacyJobs]). It preserves each source's
live jobs getter, fans out refresh/clear operations, routes per-job actions to
the owning source, and rejects duplicate IDs instead of guessing.
Job state exposes definition_id, result_status, and result_expires_at.
Managed file results carry a short-lived, result-scoped download_url instead
of embedded base64 data or the user's bearer token, so regular browser
downloads work through an integrated plugin proxy.
useApi(options?)
Generic API client with auth interceptor.
const api = useApi()
// GET request
const data = await api.get<User[]>('/users')
// POST with body
const created = await api.post<User>('/users', { name: 'John' })
// File upload
const result = await api.upload<UploadResponse>('/upload', file)
// Build URLs for external use
const downloadUrl = api.buildUrl('/files/123/download')
Authenticated requests that receive HTTP 401 clear the rejected SDK token and
throw the public AuthenticationRequiredError. Requests are never replayed;
callers can present a single sign-in prompt without risking duplicate writes.
Clients created with withAuth: false retain their existing auth state and
receive the original HTTP error.
useTheme()
Theme switching.
const { isDark, toggleTheme, setTheme } = useTheme()
toggleTheme()
setTheme(true) // dark mode
useToast()
Toast notification management.
const { success, error, warning, info } = useToast()
success('Operation completed!')
error('Something went wrong')
warning('Please review your input')
info('New update available')
usePlatformContext()
Detect and interact with MINT Platform when running as a plugin.
const { isIntegrated, plugin, user, navigate, notify } = usePlatformContext()
if (isIntegrated.value) {
// Running under MINT Platform
console.log('Plugin:', plugin.value?.name)
notify('Hello from plugin!', 'info')
}
useExperimentSamples(options?)
Load experiment design_data and derive SampleSelector-compatible sample values.
const { samples, sampleOptions, fetch } = useExperimentSamples({ experimentId: 42 })
await fetch()
Stores
useAuthStore
Pinia store for authentication state.
const authStore = useAuthStore()
// State
authStore.token
authStore.userInfo
authStore.isAuthenticated
authStore.isAdmin
// Actions
authStore.initialize()
authStore.setToken(token, expiresIn)
authStore.logout()
useSettingsStore
Pinia store for app settings.
const settingsStore = useSettingsStore()
// State
settingsStore.theme // 'light' | 'dark' | 'system'
settingsStore.colorPalette
// Methods
settingsStore.getApiBaseUrl()
settingsStore.getWsBaseUrl()
settingsStore.isDark()
TypeScript Types
All types are exported from @morscherlab/mint-sdk:
import type {
// Components
ButtonVariant,
ButtonSize,
ModalSize,
AlertType,
Toast,
TabItem,
SelectOption,
// Sample Management
SampleGroup, // { name, color, samples[] }
GroupItem, // { name, color, count }
FileUploaderMode, // 'file' | 'folder'
// Auth
AuthConfig,
UserInfo,
// Platform
PluginInfo,
PlatformContext,
ThemeMode,
} from '@morscherlab/mint-sdk'
CSS Utility Classes
The SDK provides ready-to-use CSS classes:
Buttons
.btn-primary- Blue primary button.btn-secondary- Outlined/bordered button.btn-cta- Orange call-to-action button.btn-success- Green success button.btn-danger- Red danger button
Inputs
.input-modern- Styled text input.select-modern- Styled dropdown select.label-modern- Uppercase label
Cards & Containers
.card- Card with border and shadow.card-hover- Card with hover effect.floating-card- Floating container pattern
Alerts
.alert-success- Green success alert.alert-error- Red error alert.alert-warning- Yellow warning alert.alert-info- Blue info alert
Form Controls
.toggle-switch- Toggle switch.checkbox-modern- Styled checkbox
Icons
lucide-vue-next is the icon standard for this SDK. New components should import the icon component they need rather than inlining an SVG:
<script setup lang="ts">
import { RefreshCw } from 'lucide-vue-next'
</script>
<template>
<RefreshCw :size="16" />
</template>
lucide-vue-next is a declared dependency and is external to the built
bundle (like axios and @floating-ui/dom), so the consumer resolves one
shared copy instead of the SDK shipping its own.
A number of older components still carry hand-written inline <svg> markup —
usually annotated with the Lucide icon name they were traced from. Those are
converted opportunistically, not in bulk; leave one alone unless you are already
editing the component.
Theming
The SDK uses CSS variables for theming. Override them to customize:
:root {
--bg-primary: #F8FAFC;
--bg-secondary: #FFFFFF;
--bg-tertiary: #F1F5F9;
--text-primary: #1E293B;
--text-secondary: #64748B;
--border-color: #E5E7EB;
--color-primary: #3B82F6;
}
html.dark {
--bg-primary: #0F172A;
--bg-secondary: #1E293B;
--bg-tertiary: #334155;
--text-primary: #F8FAFC;
--text-secondary: #94A3B8;
--border-color: #334155;
}
License
MIT
Related Packages
MLD Platform SDK - Vue 3 components, composables, and types for plugin developme...
Build plugins for Briefing, the secure video chat: plugin API, Vite preset and t...
webpack plugin to find duplicated dependencies from your bundle
Version History
| Version | Released | License |
|---|---|---|
| 1.3.8 | MIT | |
| 1.3.7 | MIT | |
| 1.3.6 | MIT | |
| 1.3.5 | MIT | |
| 1.3.4 | MIT | |
| 1.3.3 | MIT | |
| 1.3.2 | MIT | |
| 1.3.1 | MIT | |
| 1.3.0 | MIT | |
| 1.2.10 | MIT | |
| 1.2.9 | MIT | |
| 1.2.8 | MIT | |
| 1.2.7 | MIT | |
| 1.2.6 | MIT | |
| 1.2.5 | MIT | |
| 1.2.4 | MIT | |
| 1.2.3 | MIT | |
| 1.2.2 | MIT | |
| 1.2.1 | MIT | |
| 1.2.0 | MIT | |
| 1.1.25 | MIT | |
| 1.1.24 | MIT | |
| 1.1.23 | MIT | |
| 1.1.22 | MIT | |
| 1.1.21 | MIT | |
| 1.1.20 | MIT | |
| 1.1.19 | MIT | |
| 1.1.18 | MIT | |
| 1.1.17 | MIT | |
| 1.1.16 | MIT |
Showing the latest 30 of 108. See every release on npm
Pre-releases (76)
| Version | Released | License |
|---|---|---|
| 1.3.0-beta.11 | MIT | |
| 1.3.0-beta.10 | MIT | |
| 1.3.0-beta.9 | MIT | |
| 1.3.0-beta.8 | MIT | |
| 1.3.0-beta.7 | MIT | |
| 1.3.0-beta.6 | MIT | |
| 1.3.0-beta.5 | MIT | |
| 1.3.0-beta.4 | MIT | |
| 1.3.0-beta.3 | MIT | |
| 1.3.0-beta.2 | MIT | |
| 1.3.0-beta.1 | MIT | |
| 1.2.0-beta.20 | MIT | |
| 1.2.0-beta.19 | MIT | |
| 1.2.0-beta.18 | MIT | |
| 1.2.0-beta.17 | MIT | |
| 1.2.0-beta.16 | MIT | |
| 1.2.0-beta.15 | MIT | |
| 1.2.0-beta.14 | MIT | |
| 1.2.0-beta.13 | MIT | |
| 1.2.0-beta.12 | MIT | |
| 1.2.0-beta.11 | MIT | |
| 1.2.0-beta.10 | MIT | |
| 1.2.0-beta.9 | MIT | |
| 1.2.0-beta.8 | MIT | |
| 1.2.0-beta.7 | MIT | |
| 1.2.0-beta.5 | MIT | |
| 1.2.0-beta.4 | MIT | |
| 1.2.0-beta.3 | MIT | |
| 1.2.0-beta.2 | MIT | |
| 1.2.0-beta.1 | MIT |
Showing the latest 30 of 76. See every release on npm