Monaco Editor is the browser-based code editor that powers Visual Studio Code. The monaco-editor package is the distributable build of that editor: it bundles the editor core, syntax highlighting for ~90 languages, rich language services for TypeScript/JavaScript, CSS/SCSS/LESS, HTML, and JSON, plus the tooling you need to embed a full-featured code editor in any web page.
This guide is for web developers who want to embed Monaco in an application — a playground, an online IDE, a configuration editor, a documentation site, or anything that needs real code editing in the browser. It covers installation, the all-important web worker setup, creating and configuring editors, language support, themes, commands, and integration with every major bundler.
Note
The actual editor engine ships as a sibling package called
monaco-editor-core, which Microsoft builds from the VS Code source tree at a pinned commit. Themonaco-editorpackage wraps that core, adds the language definitions and language services, and is the package you install. Everywhere this guide says "Monaco", it means the editor you get fromnpm install monaco-editor.
1. Quick Start
The fastest way to see Monaco running is the prebuilt AMD bundle served from a CDN. Paste this into an HTML file and open it in a browser — no build step required.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<style>
html, body, #container { height: 100%; margin: 0; }
</style>
</head>
<body>
<div id="container"></div>
<script src="https://cdn.jsdelivr.net/npm/monaco-editor/min/vs/loader.js"></script>
<script>
require.config({ paths: { vs: 'https://cdn.jsdelivr.net/npm/monaco-editor/min/vs' } });
require(['vs/editor/editor.main'], function () {
monaco.editor.create(document.getElementById('container'), {
value: ['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'),
language: 'javascript'
});
});
</script>
</body>
</html>
A code editor with JavaScript syntax highlighting, a minimap, line numbers,
IntelliSense, and the full VS Code editing experience appears in #container.
Tip
The CDN/AMD path is ideal for prototypes, demos, and plain HTML pages because the loader resolves the editor and its web workers for you. For production applications built with a bundler (webpack, Vite, esbuild, Parcel), use the ESM installation in the next section so the editor is part of your dependency graph and your assets are versioned.
The same hello-world with an ESM bundler:
import * as monaco from 'monaco-editor';
monaco.editor.create(document.getElementById('container'), {
value: ['function x() {', '\tconsole.log("Hello world!");', '}'].join('\n'),
language: 'javascript'
});
Important
With a bundler you must tell Monaco how to load its web workers, or language features will fail at runtime. See Section 3: Web Workers before shipping. The CDN/AMD path handles this automatically; the ESM path does not.
2. Installation
2.1 Install from npm
npm install monaco-editor
added 1 package
This installs the package whose package.json declares:
| Field | Value | Meaning |
|---|---|---|
version |
0.55.1 |
The release you are using. |
main |
./min/vs/editor/editor.main.js |
Minified AMD build (CommonJS require). |
module |
./esm/vs/editor/editor.main.js |
ES module build (bundlers/import). |
typings |
./esm/vs/editor/editor.main.d.ts |
TypeScript declarations. |
The exports map also exposes the ESM tree under subpaths, so deep imports such as monaco-editor/esm/vs/editor/editor.api and the per-language worker files resolve correctly.
2.2 What's in the package
After install, the relevant directories are:
| Path | Contents |
|---|---|
monaco-editor/esm/ |
ES module build — use this with bundlers. |
monaco-editor/min/ |
Minified AMD build — use this with the CDN/AMD loader. |
monaco-editor/min/vs/loader.js |
The AMD loader for the CDN/AMD path. |
monaco-editor/esm/vs/editor/editor.main.js |
Full editor with all languages and features. |
monaco-editor/esm/vs/editor/editor.api.js |
Editor API only — you opt into languages/features yourself. |
monaco-editor/esm/vs/editor/editor.worker.js |
The core editor web worker. |
monaco-editor/esm/vs/language/{json,css,html,typescript}/*.worker.js |
Language-service web workers. |
Note
editor.mainpulls in everything (all languages + all editor features) and is the simplest starting point.editor.apipulls in only the editor and lets you import exactly the languages and features you want for a smaller bundle. Most applications start witheditor.mainand optimize later (see Section 11).
2.3 Three ways to import
Depending on your setup, you will import Monaco one of these ways:
Bare specifier (bundler resolves it)
import * as monaco from 'monaco-editor';
Explicit ESM entry
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';
API-only (opt into languages/features)
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api';
3. Web Workers — the most important setup step
Monaco runs its expensive language work (TypeScript type-checking, JSON schema validation, CSS/HTML analysis, and the core editor's text-diffing/tokenization) inside web workers so the UI thread stays responsive. With a bundler, Monaco cannot guess the URLs of those worker scripts — you must provide them via the global MonacoEnvironment object.
Caution
If you skip this step, you will see runtime errors such as
You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker, or language features (autocomplete, validation, formatting) will silently do nothing. This is the single most common Monaco integration problem.
3.1 The two hooks
MonacoEnvironment accepts either of two functions. Define one before you create any editor:
| Hook | Returns | Use when |
|---|---|---|
getWorker(moduleId, label) |
a Worker (or Promise<Worker>) |
Your bundler can produce Worker instances directly (Vite, modern webpack 5, esbuild with a worker plugin). Preferred. |
getWorkerUrl(moduleId, label) |
a URL string to the worker script | Your bundler emits worker files to known URLs and you load them by path. |
The label argument tells you which worker is needed. Map each label to the matching worker bundle:
label value |
Worker to return |
|---|---|
json |
json.worker |
css, scss, less |
css.worker |
html, handlebars, razor |
html.worker |
typescript, javascript |
ts.worker |
| anything else (default) | editor.worker |
3.2 getWorker with Vite
Vite resolves the ?worker suffix to a worker constructor, which is the cleanest approach:
import * as monaco from 'monaco-editor';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
self.MonacoEnvironment = {
getWorker(_, label) {
if (label === 'json') return new jsonWorker();
if (label === 'css' || label === 'scss' || label === 'less') return new cssWorker();
if (label === 'html' || label === 'handlebars' || label === 'razor') return new htmlWorker();
if (label === 'typescript' || label === 'javascript') return new tsWorker();
return new editorWorker();
}
};
3.3 getWorkerUrl with webpack (manual)
If you wire workers by URL, return the path your bundler emits the worker to:
import * as monaco from 'monaco-editor';
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === 'json') return './json.worker.bundle.js';
if (label === 'css' || label === 'scss' || label === 'less') return './css.worker.bundle.js';
if (label === 'html' || label === 'handlebars' || label === 'razor') return './html.worker.bundle.js';
if (label === 'typescript' || label === 'javascript') return './ts.worker.bundle.js';
return './editor.worker.bundle.js';
}
};
Tip
For webpack, you usually do not write this by hand — the official
monaco-editor-webpack-plugingenerates theMonacoEnvironmentand emits the worker bundles for you. See Section 10.1.
3.4 getWorkerUrl with esbuild
esbuild emits the worker files to a known output folder, so you return their relative URLs:
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === 'json') return './vs/language/json/json.worker.js';
if (label === 'css' || label === 'scss' || label === 'less') return './vs/language/css/css.worker.js';
if (label === 'html' || label === 'handlebars' || label === 'razor') return './vs/language/html/html.worker.js';
if (label === 'typescript' || label === 'javascript') return './vs/language/typescript/ts.worker.js';
return './vs/editor/editor.worker.js';
}
};
3.5 getWorkerUrl with Parcel
Parcel's url: import scheme gives you the emitted URL of each worker:
import JSONWorker from 'url:monaco-editor/esm/vs/language/json/json.worker.js';
import CSSWorker from 'url:monaco-editor/esm/vs/language/css/css.worker.js';
import HTMLWorker from 'url:monaco-editor/esm/vs/language/html/html.worker.js';
import TSWorker from 'url:monaco-editor/esm/vs/language/typescript/ts.worker.js';
import EditorWorker from 'url:monaco-editor/esm/vs/editor/editor.worker.js';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.main.js';
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
if (label === 'json') return JSONWorker;
if (label === 'css' || label === 'scss' || label === 'less') return CSSWorker;
if (label === 'html' || label === 'handlebars' || label === 'razor') return HTMLWorker;
if (label === 'typescript' || label === 'javascript') return TSWorker;
return EditorWorker;
}
};
Note
Returning only the editor worker (the
defaultbranch) is valid if you don't need any rich language services. A minimal setup that returnseditor.workerfor every label still gives you syntax highlighting via the basic-language tokenizers — you just lose TypeScript/JSON/CSS/HTML IntelliSense and validation.
3.6 Cross-origin workers
If your worker files are served from a different origin than the page (for example a CDN), browsers block constructing a Worker directly from the cross-origin URL. The standard workaround is to wrap the real worker in a same-origin proxy script created from a Blob:
self.MonacoEnvironment = {
getWorkerUrl: function (moduleId, label) {
const workerUrl = '/* …resolve to the cross-origin URL… */';
const proxy = URL.createObjectURL(
new Blob([`importScripts('${workerUrl}');`], { type: 'application/javascript' })
);
return proxy;
}
};
Tip
The
monaco-editor-webpack-pluginalready generates this Blob-proxy logic for cross-origin worker URLs automatically. If you use the plugin, set itspublicPathoption and let it handle the cross-origin case.
4. Creating and disposing an editor
4.1 monaco.editor.create
monaco.editor.create(domElement, options) mounts an editor into a DOM element and returns an editor instance:
const editor = monaco.editor.create(document.getElementById('container'), {
value: 'const foo = () => 0;',
language: 'javascript',
theme: 'vs-dark'
});
The editor fills #container and renders the code with dark-theme highlighting.
Important
The container element must have a height. Monaco sizes itself to its container; if the container collapses to
0px, the editor renders but is invisible. Give the container an explicit height (CSSheight, flex, or grid) or enableautomaticLayout(Section 5) so the editor re-measures on resize.
4.2 Disposing
Editors hold DOM nodes, listeners, and worker connections. When you remove an editor, dispose it to release them:
editor.dispose();
If you created models explicitly (Section 6), dispose those too:
const model = editor.getModel();
editor.setModel(null);
if (model) model.dispose();
Warning
Failing to dispose editors and models in single-page apps that mount/unmount editors repeatedly leads to leaked workers and detached DOM nodes. Always dispose on component teardown (e.g. React
useEffectcleanup, VueonUnmounted).
5. Editor options
You pass options when creating the editor and change them later with editor.updateOptions(...). These are the most commonly used options, all confirmed against the editor's own option surface:
| Option | Type | Example | Effect |
|---|---|---|---|
value |
string |
'hello' |
Initial text (when not using an explicit model). |
language |
string |
'typescript' |
Language id for syntax highlighting + services. |
theme |
string |
'vs-dark' |
Color theme (see Section 7). |
model |
ITextModel | null |
a created model | Use a specific model instead of value/language. |
readOnly |
boolean |
true |
Disable editing. |
lineNumbers |
'on' | 'off' | 'relative' | fn |
'on' |
Line-number gutter mode. |
glyphMargin |
boolean |
true |
Show the glyph margin (for decorations/breakpoints). |
minimap |
{ enabled: boolean } |
{ enabled: false } |
Toggle the minimap. |
roundedSelection |
boolean |
false |
Rounded vs. square selection corners. |
scrollBeyondLastLine |
boolean |
false |
Allow scrolling past the last line. |
renderWhitespace |
'none' | 'boundary' | 'all' |
'all' |
Render whitespace characters. |
wordWrap |
'off' | 'on' | 'wordWrapColumn' | 'bounded' |
'on' |
Soft wrapping. |
folding |
boolean |
true |
Enable code folding. |
automaticLayout |
boolean |
true |
Re-layout automatically when the container resizes. |
Note
The full option set is large (hundreds of options covering cursor, scrollbar, suggestions, bracket pair colorization, sticky scroll, accessibility, and more). The above are the everyday options. For the exhaustive, version-accurate list, consult the API reference — see Section 14: Reference.
5.1 Changing options at runtime
editor.updateOptions({ lineNumbers: 'off' });
editor.updateOptions({ minimap: { enabled: true } });
editor.updateOptions({ wordWrap: 'on' });
editor.updateOptions({ readOnly: true });
Each call applies immediately without recreating the editor.
5.2 Reading and writing content
const text = editor.getValue(); // current text
editor.setValue('new content'); // replace all text
const position = editor.getPosition(); // { lineNumber, column }
editor.setScrollTop(0); // scroll to top
6. Models — the text behind the editor
A model holds the text, language, and edit history for a document. The editor is a view onto a model. Working with models explicitly lets you share one document across editors, attach a file URI (which TypeScript and JSON services use to resolve modules and schemas), and manage multiple open documents.
6.1 Creating a model
const model = monaco.editor.createModel(
'class Test {}',
'typescript',
monaco.Uri.parse('file:///main.ts')
);
const editor = monaco.editor.create(document.getElementById('root'), {
model: model,
theme: 'vs-dark'
});
Tip
The third argument — the model URI — matters for the TypeScript/JavaScript language service. Giving models real
file:///URIs lets the service treat them as a project, resolve imports between them, and report cross-file diagnostics.
6.2 Switching models
const oldModel = editor.getModel();
const newModel = monaco.editor.createModel(source, 'python');
editor.setModel(newModel);
if (oldModel) oldModel.dispose();
6.3 Changing a model's language
monaco.editor.setModelLanguage(model, 'json');
6.4 Reacting to edits
model.onDidChangeContent((event) => {
console.log('document changed', model.getValue());
});
7. Themes
Monaco ships four built-in themes:
| Theme id | Description |
|---|---|
vs |
Light (default). |
vs-dark |
Dark. |
hc-black |
High-contrast dark. |
hc-light |
High-contrast light. |
Set the theme at creation time (theme: option) or switch it globally at any time:
monaco.editor.setTheme('vs');
monaco.editor.setTheme('vs-dark');
monaco.editor.setTheme('hc-black');
Important
monaco.editor.setThemeis global — it changes the theme for every editor on the page, not just one instance.
7.1 Custom themes
Define your own theme with token color rules and editor colors, then apply it by name:
monaco.editor.defineTheme('myTheme', {
base: 'vs-dark',
inherit: true,
rules: [
{ token: 'comment', foreground: '6A9955', fontStyle: 'italic' },
{ token: 'keyword', foreground: 'C586C0' }
],
colors: {
'editor.background': '#1E1E1E'
}
});
monaco.editor.setTheme('myTheme');
8. Language support
Monaco's language support comes in two tiers.
8.1 Basic languages (syntax highlighting)
The monaco-editor package bundles Monarch tokenizer definitions that provide syntax highlighting, bracket matching, comment toggling, and auto-indentation for a large catalog of languages. When you import editor.main, every basic language is registered automatically. The full set bundled in this release:
abap, apex, azcli, bat, bicep, cameligo, clojure, coffee, cpp, csharp, csp,
css, cypher, dart, dockerfile, ecl, elixir, flow9, freemarker2, fsharp, go,
graphql, handlebars, hcl, html, ini, java, javascript, julia, kotlin, less,
lexon, liquid, lua, m3, markdown, mdx, mips, msdax, mysql, objective-c,
pascal, pascaligo, perl, pgsql, php, pla, postiats, powerquery, powershell,
protobuf, pug, python, qsharp, r, razor, redis, redshift, restructuredtext,
ruby, rust, sb, scala, scheme, scss, shell, solidity, sophia, sparql, sql,
st, swift, systemverilog, tcl, twig, typescript, typespec, vb, wgsl, xml, yaml
Note
Basic-language support is highlighting only — it does not require a web worker and does not provide IntelliSense, validation, or formatting. For those, you need a rich language service (next section).
List the registered languages at runtime:
monaco.languages.getLanguages().forEach((language) => {
console.log(language.id);
});
Register a language id (for example to attach your own tokenizer):
monaco.languages.register({ id: 'mylang' });
8.2 Rich language services
Four languages get full language services running in web workers: TypeScript/JavaScript, JSON, CSS/SCSS/LESS, and HTML/Handlebars/Razor. These provide completions, hover, diagnostics, formatting, and more. They are exposed under monaco.languages.<name> and are configured through a *Defaults object.
Caution
Rich language services require the matching web worker (Section 3). If you only wired up
editor.worker, TypeScript completions and JSON validation will not appear.
TypeScript / JavaScript
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
target: monaco.languages.typescript.ScriptTarget.ES2020,
module: monaco.languages.typescript.ModuleKind.ESNext,
strict: true
});
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: false,
noSyntaxValidation: false
});
monaco.languages.typescript.typescriptDefaults.setInlayHintsOptions({
includeInlayParameterNameHints: 'all',
includeInlayFunctionParameterTypeHints: true,
includeInlayVariableTypeHints: true,
includeInlayFunctionLikeReturnTypeHints: true,
includeInlayEnumMemberValueHints: true
});
Add extra type declarations (e.g. library .d.ts) so completions know about your APIs:
monaco.languages.typescript.typescriptDefaults.addExtraLib(
'declare function greet(name: string): void;',
'file:///globals.d.ts'
);
Tip
Use
javascriptDefaults(instead oftypescriptDefaults) to configure the service for plain JavaScript models. Both share the same option shapes.
JSON
monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
schemas: [
{
uri: 'http://myserver/schema.json',
fileMatch: ['*'],
schema: {
type: 'object',
properties: { name: { type: 'string' } },
required: ['name']
}
}
]
});
Editing a JSON model now shows live validation errors and schema-driven completions.
CSS / SCSS / LESS and HTML
monaco.languages.css.cssDefaults.setOptions({
validate: true,
lint: { emptyRules: 'warning' }
});
monaco.languages.html.htmlDefaults.setOptions({
format: { wrapLineLength: 120 }
});
Note
CSS support covers three languages handled by one worker (
css,scss,less); HTML support covershtml,handlebars, andrazor. The defaults objects arecssDefaults/scssDefaults/lessDefaultsandhtmlDefaults/handlebarDefaults/razorDefaultsrespectively.
8.3 Extending a language yourself
For languages without a rich service, you can register your own providers — completions, hovers, definitions, formatting, and so on:
monaco.languages.registerCompletionItemProvider('mylang', {
provideCompletionItems: () => ({
suggestions: [
{
label: 'hello',
kind: monaco.languages.CompletionItemKind.Keyword,
insertText: 'hello'
}
]
})
});
Tip
The official Monaco Playground (linked in Section 14) has runnable, copy-paste examples for every provider type — completion, hover, signature help, code lens, color, folding, semantic tokens, inlay hints, symbols, and custom Monarch languages. It is the best place to learn the provider APIs.
9. Commands, actions, and keybindings
9.1 Add a command
A command binds a keyboard shortcut to a handler:
editor.addCommand(monaco.KeyMod.CtrlCmd | monaco.KeyCode.F9, function () {
alert('Command Running!!');
});
9.2 Add an action
An action is a richer command that also appears in the command palette and (optionally) the right-click context menu:
editor.addAction({
id: 'my-unique-id',
label: 'My Label!!!',
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.F10],
contextMenuGroupId: 'navigation',
contextMenuOrder: 2.5,
run: function (ed) {
console.log("i'm running => " + ed.getPosition());
}
});
The action is now reachable via Ctrl/Cmd+F10, the F1 command palette
("My Label!!!"), and the editor's right-click context menu.
9.3 Building keybindings
Combine modifiers and keys with bitwise OR using monaco.KeyMod and monaco.KeyCode:
| Constant | Example |
|---|---|
monaco.KeyMod.CtrlCmd |
Ctrl on Windows/Linux, Cmd on macOS |
monaco.KeyMod.Shift |
Shift |
monaco.KeyMod.Alt |
Alt/Option |
monaco.KeyMod.WinCtrl |
Ctrl on macOS, Win on Windows |
monaco.KeyCode.KeyS, monaco.KeyCode.F9, … |
Letter and function keys |
// Ctrl/Cmd + Shift + S
const chord = monaco.KeyMod.CtrlCmd | monaco.KeyMod.Shift | monaco.KeyCode.KeyS;
10. Bundler integration recipes
Monaco ships sample projects for every major bundler. The patterns below are distilled from those samples.
10.1 Webpack — monaco-editor-webpack-plugin
The official plugin generates the MonacoEnvironment worker wiring and emits the worker bundles, so you don't hand-write Section 3:
npm install monaco-editor monaco-editor-webpack-plugin
// webpack.config.js
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: ['style-loader', 'css-loader'] },
{ test: /\.ttf$/, type: 'asset/resource' }
]
},
plugins: [new MonacoWebpackPlugin()]
};
// app.js
import * as monaco from 'monaco-editor';
monaco.editor.create(document.getElementById('container'), {
value: 'hello world',
language: 'plaintext'
});
Restrict the bundle to only the languages and features you use:
new MonacoWebpackPlugin({
languages: ['javascript', 'typescript', 'json'],
features: ['!gotoSymbol'] // include all features except gotoSymbol
});
Tip
In the plugin's
featureslist, prefix a feature with!to exclude it while keeping all others. The plugin's full option set (languages,customLanguages,features,filename,monacoEditorPath,publicPath,globalAPI) is documented in the Admin Guide.
10.2 Vite
Use the getWorker setup from Section 3.2. No plugin is required. A typical React component:
import { useEffect, useRef } from 'react';
import * as monaco from 'monaco-editor';
import './userWorker'; // file that sets self.MonacoEnvironment (Section 3.2)
export function Editor() {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const editor = monaco.editor.create(ref.current!, {
value: 'console.log("hello")',
language: 'javascript',
theme: 'vs-dark'
});
return () => editor.dispose();
}, []);
return <div ref={ref} style={{ height: '100%' }} />;
}
10.3 esbuild
Use the getWorkerUrl setup from Section 3.4 and copy the worker files into your output directory as part of the build. The bundled sample uses a small build.js that runs esbuild and stages the vs/ worker tree alongside the app.
10.4 Parcel
Use the url: worker imports from Section 3.5. Parcel resolves and emits each worker automatically.
10.5 Electron
Electron renderer processes are browser contexts, so the standard ESM + worker setup applies. Load the bundled page into a BrowserWindow:
const { app, BrowserWindow } = require('electron');
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: { worldSafeExecuteJavaScript: true }
});
win.loadURL(`file://${__dirname}/dist/index.html`);
}
app.on('ready', createWindow);
Note
The repository also contains legacy AMD samples for Electron and NW.js under
samples/legacy/. Those are no longer supported and are slated for removal — prefer the ESM samples for any new project.
11. Shrinking your bundle
The full editor.main import includes every language and editor feature. To trim it:
- Use the webpack plugin's
languages/featuresoptions (Section 10.1) — the easiest path for webpack users. - Import
editor.apiand opt in manually. Start frommonaco-editor/esm/vs/editor/editor.api.js, then import only the feature and language contributions you need. The bundledbrowser-esm-webpack-smallsample shows the pattern: a commented list of every available feature import (e.g..../contrib/find/browser/findController.js) and language contribution (e.g..../basic-languages/python/python.contribution.js) that you uncomment selectively.
// Opt-in example: editor + find + Python highlighting only
import 'monaco-editor/esm/vs/editor/browser/controller/coreCommands.js';
import 'monaco-editor/esm/vs/editor/contrib/find/browser/findController.js';
import * as monaco from 'monaco-editor/esm/vs/editor/editor.api.js';
import 'monaco-editor/esm/vs/basic-languages/python/python.contribution.js';
self.MonacoEnvironment = {
getWorkerUrl: () => './editor.worker.bundle.js'
};
monaco.editor.create(document.getElementById('container'), {
value: 'print("hello")',
language: 'python'
});
Tip
Opting in manually is the most effective way to reduce bundle size, but it is also the most error-prone — miss a feature import and that feature silently disappears. Start with
editor.main, ship, then optimize with measurements.
12. Diff editor
To compare two documents side by side, create a diff editor with an original and a modified model:
const original = monaco.editor.createModel('line one\nline two', 'text/plain');
const modified = monaco.editor.createModel('line one\nline 2', 'text/plain');
const diffEditor = monaco.editor.createDiffEditor(document.getElementById('container'), {
theme: 'vs-dark',
renderSideBySide: true
});
diffEditor.setModel({ original, modified });
A two-pane diff highlighting the change from "line two" to "line 2".
Note
Set
renderSideBySide: falsefor an inline (unified) diff. Dispose the diff editor and both models when you're done.
13. Troubleshooting
Error: You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker
- Cause: No web worker provider was configured, and a feature tried to start a worker.
- Solution: Set
self.MonacoEnvironmentwith eithergetWorkerorgetWorkerUrlbefore creating any editor (Section 3). - Prevention: Put the worker setup in a module that is imported first, or use
monaco-editor-webpack-pluginso it's generated for you.
Language features (autocomplete/validation) do nothing
- Cause: Only the core
editor.workeris wired; the language-specific worker (e.g.ts.worker,json.worker) is missing. - Solution: Map each
label(json,css,html,typescript/javascript) to its worker ingetWorker/getWorkerUrl(Section 3.1). - Prevention: Use the full mapping from the samples, even if you think you only need one language.
The editor is invisible / zero height
- Cause: The container element has no height.
- Solution: Give the container an explicit
height, or setautomaticLayout: trueso the editor re-measures. - Prevention: Style the container (
height: 100%within a sized parent) before creating the editor.
Editor doesn't resize when its container changes
- Cause: Monaco measures once at creation.
- Solution: Enable
automaticLayout: true, or calleditor.layout()after the container resizes.
Language namespaces are missing after importing editor.api.js (0.55.0+)
- Cause: As of
0.55.0the language namespaces (e.g.monaco.languages.typescript) are no longer attached when importing the API-only entryeditor.api.js; they were moved. (See issue #5346.) - Solution: Import
editor.main(which registers languages), or explicitly import the language contributions you need (Section 11). - Prevention: Use
editor.mainunless you are deliberately doing a minimal opt-in build.
Error: monaco is not loaded yet
- Cause: Code referenced the global
monacobefore the AMD loader finished resolvingvs/editor/editor.main. - Solution: Put all editor code inside the
require(['vs/editor/editor.main'], …)callback (Section 1).
Failed to load source map: simpleWorker.nls.js.map
- Cause: A worker source map referenced in the shipped files isn't served. (See issue #3336.)
- Solution: This is a dev-tools console warning, not a functional error — it does not affect the editor. Disable source-map fetching in dev tools or ignore it.
Worker NLS file has a doubled extension (nls.messages.<lang>.js.js)
- Cause: A packaging bug in some
0.55.xbuilds produces a.js.jssuffix for localized NLS message files. (See issue #5345.) - Solution: If you load localized messages, verify the emitted filename and adjust your copy step; track the linked issue for a fixed release.
Right-click "Paste" does nothing
- Cause: Browsers restrict programmatic clipboard reads; the context-menu paste cannot access the system clipboard without a user gesture/permission. (See issue #5158.)
- Solution: Use the keyboard shortcut (Ctrl/Cmd+V), which the browser permits.
Content Security Policy blocks the editor or workers
- Cause: Monaco creates workers (and, for cross-origin workers, Blob URLs) and injects styles. A strict CSP can block these.
- Solution: Allow
worker-src blob:(andscript-src blob:if you use the cross-origin Blob proxy) and the worker origins. If you use Trusted Types, provide aMonacoEnvironment.createTrustedTypesPolicy(see the Admin Guide's security section).
14. FAQ
Q: What's the difference between monaco-editor and monaco-editor-core?
monaco-editor-core is the editor engine, built from the VS Code source. monaco-editor is what you install — it bundles the core plus all the language definitions and language services. Use monaco-editor.
Q: Do I need a server? No. Monaco is entirely client-side. The "workers" are browser web workers, not server processes.
Q: Why are there so many .worker.js files?
Each rich language service (TypeScript, JSON, CSS, HTML) runs in its own web worker so heavy analysis doesn't block the UI. The core editor has one worker too. You wire them up in MonacoEnvironment (Section 3).
Q: Can I use Monaco without any workers?
Yes, with reduced functionality. Return the core editor.worker for every label and you get syntax highlighting and editing, but no TypeScript/JSON/CSS/HTML IntelliSense or validation.
Q: How do I get TypeScript autocompletion for my own libraries?
Call monaco.languages.typescript.typescriptDefaults.addExtraLib(declarationSource, uri) with your .d.ts content (Section 8.2).
Q: How do I make the editor read-only?
Pass readOnly: true at creation or call editor.updateOptions({ readOnly: true }).
Q: How do I change the theme for just one editor?
You can't — monaco.editor.setTheme is global. To show different color schemes, render editors on separate pages/iframes, or switch the global theme when focus changes.
Q: Which languages have full IntelliSense vs. just highlighting? Full language services: TypeScript/JavaScript, JSON, CSS/SCSS/LESS, HTML/Handlebars/Razor. All other bundled languages get highlighting (Monarch) only.
Q: How do I support a language that isn't bundled?
Register it with monaco.languages.register({ id }) and provide a Monarch tokenizer via monaco.languages.setMonarchTokensProvider, plus any providers you need. See the Playground's "custom languages" example.
Q: Is Monaco mobile/touch friendly?
Monaco targets desktop browsers. There is an iPadShowKeyboard helper and basic touch support, but the editor is optimized for keyboard-and-mouse use.
Q: How do I update to a new version?
Bump monaco-editor in your package.json and re-bundle. Watch the changelog for breaking changes — the 0.55.0 language-namespace move (issue #5346) is an example of a change that affected API-only imports.
Q: Where is the official live editor to experiment in? The Monaco Playground (see Reference below) lets you run and tweak every example in the browser.
15. Reference
Import-path cheat sheet
| What | ESM path |
|---|---|
| Full editor (all languages/features) | monaco-editor or monaco-editor/esm/vs/editor/editor.main.js |
| Editor API only | monaco-editor/esm/vs/editor/editor.api |
| Core editor worker | monaco-editor/esm/vs/editor/editor.worker.js |
| JSON worker | monaco-editor/esm/vs/language/json/json.worker.js |
| CSS worker | monaco-editor/esm/vs/language/css/css.worker.js |
| HTML worker | monaco-editor/esm/vs/language/html/html.worker.js |
| TypeScript/JS worker | monaco-editor/esm/vs/language/typescript/ts.worker.js |
Worker label map
label |
Worker |
|---|---|
json |
json worker |
css / scss / less |
css worker |
html / handlebars / razor |
html worker |
typescript / javascript |
ts worker |
| (default) | editor worker |
Built-in themes
vs · vs-dark · hc-black · hc-light
Key API entry points
| Call | Purpose |
|---|---|
monaco.editor.create(el, opts) |
Create an editor. |
monaco.editor.createDiffEditor(el, opts) |
Create a diff editor. |
monaco.editor.createModel(value, lang, uri?) |
Create a text model. |
monaco.editor.setTheme(name) |
Set the global theme. |
monaco.editor.defineTheme(name, data) |
Define a custom theme. |
monaco.editor.setModelLanguage(model, lang) |
Change a model's language. |
monaco.languages.register({ id }) |
Register a language id. |
monaco.languages.getLanguages() |
List registered languages. |
monaco.languages.typescript.typescriptDefaults |
Configure the TS service. |
monaco.languages.json.jsonDefaults |
Configure the JSON service. |
monaco.languages.css.cssDefaults |
Configure the CSS service. |
monaco.languages.html.htmlDefaults |
Configure the HTML service. |
editor.addCommand(keybinding, handler) |
Bind a keyboard command. |
editor.addAction(descriptor) |
Add a palette/context-menu action. |
editor.updateOptions(opts) |
Change editor options. |
editor.dispose() |
Release the editor. |
Official resources
- Playground (runnable examples): https://microsoft.github.io/monaco-editor/playground.html
- API reference (typedoc): https://microsoft.github.io/monaco-editor/typedoc/index.html
- Website & docs: https://microsoft.github.io/monaco-editor/
- Source & issues: https://github.com/microsoft/monaco-editor
Note
The exhaustive list of editor options, model APIs, and provider interfaces is generated from the TypeScript declarations and published in the typedoc API reference. When you need an option or method this guide doesn't cover, that reference is the authoritative source for your installed version.