Plugins let you extend the labeling interface with custom JavaScript to add validation, dynamic UI, and custom annotator workflows. Plugins (previously called Custom Scripts) are a Label Studio Enterprise feature only they are not available in Starter Cloud or Community editions. They are defined per project under Settings > Labeling Interface and run arbitrary JavaScript in each annotator's browser. This guide covers the most common problems: the feature being unavailable, plugins that don't load or fire, code that runs too often or errors out, and the account restrictions tied to Plugins.
Because plugins execute custom JavaScript client-side, most issues fall into one of three buckets: availability/permissions, the script not running as expected, or errors in the plugin code itself. Work through the section that matches your symptom.
Before you start
- Confirm you are on Label Studio Enterprise. Plugins are not available in Starter Cloud or Community.
- Confirm Plugins is enabled for your organization. It is opt-in for security reasons and must be requested before it appears.
- Confirm your role. By default, only Admin, Owner, or Manager roles can open project settings to view, add, and edit plugins. (Your org may have an optional stricter setting that limits editing to Admin/Owner only.)
- Test on a test project first. Plugins run for every annotator, so a broken plugin on a live project can block real labeling. Reproduce and iterate on a throwaway project.
The plugin panel is missing or you can't add a plugin
Plugins appear in Project > Settings > Labeling Interface. If you don't see the option:
- Verify the feature is enabled. Plugins are not available unless enabled for your organization. To turn it on, go to Project > Settings > Labeling Interface and click Request Access, or contact your account manager. Because plugins run arbitrary JavaScript on annotators' machines, this is a deliberate opt-in with security considerations.
- Verify your role. If the project settings open but you can't edit plugins, you likely lack an Admin/Owner (or Manager) role. Ask your organization owner to confirm your role.
- Confirm you're on Enterprise, not Starter Cloud or Community the feature does not exist on those tiers.
Error: "Users who are members of any organization using Plugins cannot be added to other organizations"
This is expected behavior, not a bug. To enable Plugins, an organization cannot have members who belong to multiple organizations this is enforced in application logic for data security, because plugins execute arbitrary JavaScript. You will hit this when trying to flip a user from Pending to an active role (for example, Annotator) in a Plugins-enabled org while that user is still an active member of another org.
The most common cause is a leftover or accidental second organization often an expired free-trial org, a proof-of-concept/test org, or an org the user created by accident during their first login.
To resolve:
- Identify the user's other organization(s). If you can't see them, support can look them up.
- Deactivate the user in the other org(s) so they have only one active membership: open that Organization page, find the user, and set their role to Deactivated. (You do not need to delete the other org.)
- Return to your Plugins-enabled org and change the user from Pending to the intended role. It should now succeed.
If the user genuinely doesn't appear to belong to any other org, they may have accidentally created an empty personal org at signup. Submit a ticket with the affected email address and both orgs involved so support can locate and remove the stray membership.
A plugin doesn't load, or the Testing panel doesn't appear
When you add a plugin, a Testing panel appears below the script field. Use it to run the plugin against sample data, manually trigger events, and see which events fire. If nothing loads:
- Check the Code panel for labeling-config validation errors. The Testing panel does not appear if your labeling configuration has validation errors, so fix any errors flagged in the Code panel first.
- Check for JavaScript syntax errors. A single syntax error prevents the whole plugin from loading.
- Open your browser's developer tools → Console (F12) while a task is open. Use it to confirm the plugin is running and to read any errors it throws.
-
Check the Network tab. Plugin information is returned
with
the
/project/:idAPI call confirm the plugin is actually being delivered to the browser. -
Add a
debuggerstatement to your script to set a breakpoint and step through it in developer tools. -
Rule out browser extensions and network filtering. Script
blockers, ad blockers, and strict corporate proxies/CSP can prevent custom
scripts (and any external scripts you load via
LSI.import) from executing. Retry in an incognito window with extensions disabled.
A plugin loads but doesn't fire on the expected event
The script loads without errors, but your logic never triggers (for example, validation doesn't run on submit):
-
Use
LSI.on(eventName, handler)to subscribe to frontend events, rather than wiring handlers up ad hoc. For example, most validation plugins hookbeforeSaveAnnotation:
javascript
LSI.on("beforeSaveAnnotation", (store, annotation) => {
// return false to block the save ("hard" block); return true to allow it
return true;
});
- Event names are case-sensitive. A typo or wrong case means the handler attaches to nothing. See the Frontend reference for the list of available events.
-
Top-level events cannot be used in plugins. Events such
as
labelStudioLoadandstorageInitializedfire before the plugin is initialized, so handlers for them never run. Use annotation-scoped events instead. -
Match names in your handler to the labeling config exactly.
When filtering results (for example
r.from_name.name === "answer"orr.type === "textarea"), the referenced controlnamemust match your labeling configuration exactly. A mismatch means your condition never matches anything. -
Add temporary logging. Put
console.log(...)at the top of the handler to confirm it is reached at all. If it never prints, the subscription isn't wired up; if it prints but the action fails, the problem is inside the handler.
A plugin fires too often, loops, or freezes the page
Plugins are executed each time an annotation is displayed when you open a task, move between tasks, create or switch annotations, or view older annotation versions. If you don't account for this, you can end up with duplicated handlers, infinite loops, memory leaks, or crashes.
-
Prefer
LSI.on()for event subscriptions. Handlers attached withLSI.on()are automatically unsubscribed when the annotation is closed/switched, which prevents the most common "handler fires N times" problem. -
Manually clean up any handler you attach directly (for
example
window.addEventListener). These are not auto-removed. Store them in a global register so each run can check, stop, or replace the previous run's handlers, and guard against re-adding them. - Guard against re-entrancy. Have each handler confirm it is still operating on the current annotation/data before acting.
-
Remember plugins run inside an async function, so you
can
await(for example when usingLSI.import(...)to load an external library before your logic runs).
JavaScript errors in the plugin
When the console shows errors coming from your plugin:
- Read the first error, not the last. The earliest error is usually the root cause; later ones are often cascading effects.
-
Handle the real shape of results. For
<TextArea>results, the value is always an array for example find the result withr.type === "textarea"and then readresult.value.text[0]. Assuming a scalar or assuming a field always exists is a common source of null/undefined errors. - Add null checks and early returns so one unexpected task shape doesn't break labeling for everyone.
-
Show validation feedback with
Htx.showModal(message, "error")and returnfalsefrombeforeSaveAnnotationfor a hard block (returntrueto allow the save). This is more robust than throwing.
Changes to a plugin don't take effect
- Confirm the edit saved to the correct project with no validation errors.
- Hard refresh the labeling page (Cmd/Ctrl+Shift+R) so the browser drops the cached script.
- Have annotators refresh too. Anyone with the labeling page already open keeps running the previously loaded version until they reload.
- Check for multiple plugins on the same project. Another active plugin may conflict with or override your change. Disable others temporarily to isolate.
A custom field in the Review UI can't be edited
If your interface includes a custom comment/text field (not the native right-hand comments panel) and reviewers can't edit an entry without deleting and retyping it:
-
If the field is a
<TextArea>control tag, addeditable="true"so submitted entries get an inline edit affordance. This is a one-line labeling-config change, not a plugin change. (maxSubmissionsandskipDuplicates="true"are also useful here.) -
If the field is a custom component (built with the Enterprise
Custom Interface /
ReactCode), there is no automatic edit button the edit behavior must be implemented in the component code so it updates the existing region rather than appending a new one.
The native comments panel already supports editing and resolving; the limitation above is specific to custom-built fields.
A plugin's markup or state isn't shared with annotators
A client-side-only browser userscript (for example, a Tampermonkey tool) renders only in the reviewer's own browser and is not saved with the task, so annotators never see it. To make custom visual feedback or markup persist and be visible to others, it must be built as a native plugin that writes its output into the annotation results/regions so it is stored with the task. If you're evaluating whether a specific custom workflow is feasible as a plugin, open a ticket and we can advise.
Still stuck? Submit a ticket
If you've worked through the relevant section and the plugin still doesn't behave as expected, please submit a ticket. To speed up diagnosis, include:
- Whether you are on HumanSignal Cloud (SaaS) or self-hosted, and your Label Studio Enterprise version.
- The project ID, the plugin code (or a minimal version that reproduces the issue), and your labeling configuration.
- The exact browser console output (copy the full error text) and, if relevant, a screen recording.
- For the user-activation error, the affected email address and the organizations involved.
More resources: