Skip to content
| Marketplace
Sign in
Visual Studio Code>Programming Languages>JSConsole for AlfrescoNew to Visual Studio Code? Get it now.
JSConsole for Alfresco

JSConsole for Alfresco

Preview

Pleo Soft

|
2 installs
| (0) | Free
Write, run and get IntelliSense for Alfresco administrative JavaScript in VS Code: migrations, bulk fixes, reports and investigations. Runs your scripts through the JavaScript Console web scripts of Order of the Bee Support Tools, which must be installed on the repository. Not a web script developme
Installation
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press enter.
Copied to clipboard
More Info
JSConsole for Alfresco icon: code braces around a play mark

JSConsole for Alfresco

Repository · Report an issue · Licence: Apache-2.0 · PleoSoft

Write, run and get IntelliSense for Alfresco repository JavaScript without leaving VS Code: write the script in the editor, run it against a live repository, and get the output, the errors and the completions back where you wrote it.

Execution goes through the JavaScript Console web scripts of OOTBee Support Tools on the repository tier, so there is nothing of ours running inside the repository and nothing to deploy beyond a module most administrators already have. No Share, no custom repository module. Output streams while the script runs, and a Rhino error comes back mapped to the line you wrote rather than the line the console executed.

A script in the editor with its output streamed into the panel below, the five views in the sidebar (scripts, recipes, run history, script API, content model) and the active profile in the status bar

What the editor adds over that console:

  • Completion and hover for the script API, generated per profile from the server the profile points at, so an unfamiliar method is documented by the machine that is going to run it. Only what that server's engine runs is offered.
  • Diagnostics: syntax the engine will reject, root objects that do not exist, members an object does not have, and prefixed names the content model does not know.
  • Recipes: measured snippets for the common tasks, and your own, kept as ordinary scripts in folders you choose.
  • Five views: the scripts you declared, the recipes, the runs you have made, and the connected server's script API and content model.
  • Run parameters — transaction mode, run-as, space, document, arguments — set per run or pinned in the file with a directive.
  • A notebook kernel, for answering one question across several cells.
  • A language server and an MCP server, so the same knowledge reaches another editor or an agent working in the folder.

Completion inside a query string offering the content model's types, each with its description

Read-write runs need explicit intent. The default is read-only.

Scope: administrative scripts. Migrations, bulk fixes, reports, repairs and investigations — the scripts you run against a repository under an admin account and keep in your own Git repository. It is not a web script development or testing tool: no descriptors, no FreeMarker, no HTTP request or response, no deploy or refresh. For that keep using the browser, with the Support Tools pages in Share and Alfresco's Web Scripts Home at /alfresco/s/index.

New here? If you want to use it, start with docs/USER-GUIDE.md: installing it, connecting to a server, and what the editor does once both are in place. If you want to change it, start with CONTRIBUTING.md, which covers the toolchain, the test setup and the conventions. This README is the reference behind them.

  • docs/USER-GUIDE.md — what the Alfresco script API is, connecting and authenticating, TLS, syncing with your server, and moving your existing scripts into git.
  • docs/HOWTO.md — short recipes with screenshots, one per task.
  • docs/COMMANDS.md — every command, grouped by what you are trying to do.
  • docs/RECIPES.md — every built-in recipe with its code and the traps it avoids, and how to write your own.
  • docs/SERVERS.md — the language server and the MCP server: how to start each, what they read, what they answer, and how to wire them into something that is not VS Code.

Who makes it. PleoSoft, an Alfresco consultancy. The source, the issue tracker and the releases are on GitHub.

Licence and requirements. Apache-2.0. Requires Order of the Bee Support Tools on the repository, which is a separate LGPL v3 module installed by your administrators. This extension calls its JavaScript Console web scripts over HTTP and ships none of its code. Not affiliated with or endorsed by Hyland or the Order of the Bee.

This is a pre-release. Please report anything that surprises you at github.com/PleoSoft/jsconsole-for-alfresco/issues. The JSConsole Log output channel carries the request trace, and Developer: Set Log Level... turns it up; that log plus your Alfresco and OOTBee versions is usually the whole story. Behaviour of the console itself that we have already measured is under Known upstream issues — worth a glance before reporting one of those as ours.

Features

  • Run in Alfresco (Ctrl+Alt+R, the play button in the editor title, or the JSConsole for Alfresco submenu on the editor's right-click, both shown only on Alfresco scripts). Runs the current file, or just what you have selected. print() output streams into the JSConsole Output output channel while the script runs.
  • Notebooks. An .alfnb file interleaves markdown with code cells that run on the active profile and show their output inline, so an investigation becomes something you can save and hand over.
  • Run history. Every run is kept locally with its script, parameters and output. Reopen the script, rerun it, compare two runs side by side, or throw them away.
  • Environment guardrails. A profile declares whether it points at development, test or production. Production shows red in the status bar and makes you type the profile name before anything that can write, or before running as another user. A production profile can also refuse writes outright.
  • Script parameters like the Share console. Transaction mode (read-only, read-write, none) and run-as user sit in the status bar; JSConsole: Script Parameters... also covers the space and document root objects (by nodeRef or by path below Company Home), URL arguments for args, and the dump limit. A file can pin its own parameters with a header line such as // @jsconsole transaction=readwrite runas=alice. Read-write runs ask for confirmation.
  • Errors mapped back to your file. Rhino error lines are translated to the line in your script and shown as a diagnostic in the editor, with the server stack trace in the output channel.
  • Server profiles. Several servers, switch from the status bar. Passwords live in VS Code's secret storage.
  • Typings generated from the live server. Refresh API Typings from Server — the download button on the Script API and Content Model views, or the first entry when you click the profile in the status bar — asks the repository for its script API, its content model and what its engine accepts. VS Code's TypeScript service then gives you completion, hover and signature help for search, companyhome and everything else, through a bundled plugin, so nothing is written into your project. Definitions are cached per profile, so switching servers switches the model too, and the views show which server they describe and how long ago it was read.
  • A completion list that tells the truth about Rhino. It offers only the standard library the server's engine has — no Promise, so no .then() — only the members an object really has rather than every word in the file, and getters such as getProperties() measured on your server. After a dot on a list or a map that starts its line, it offers the loop that works there: an indexed for for an array, for…in for a map, where forEach fails.
  • Members an object does not have, underlined. task.then(...) or properties.forEach(...) is an error before the run rather than a failure during it, for every object whose whole surface is known. Calling or assigning such a member fails on the server and is an error; reading one only gives undefined, or null on a property map, so that is a warning.
  • The content model, browsable and checked. A view lists every type and aspect with its properties and associations, grouped by namespace. A language server completes QNames inside string literals, knows that addAspect( wants an aspect and TYPE: wants a type, and warns when a name's namespace exists but the name does not, which catches typos before a run.
  • Syntax checked against the actual engine. The extension asks each server what its JavaScript engine accepts, then underlines what it will reject. Writing let against a repository whose Rhino only has var is an error in the editor rather than a failed round trip.
  • Search queries checked too, including the Lucene escaping rule that catches everyone. Typos in QNames, root objects and query languages come with a "did you mean" quick fix.
  • Recipes for the tasks that go wrong quietly. Nineteen of them — searching, versions, permissions, sites, workflows, creating and deleting. Type search, version or delete and press Ctrl+Space for a worked script, with the reason it is written that way: a query without paging returns the server default and looks complete, a property set before addAspect() is silently discarded, a caught repository error discards the whole run. Every trap was measured on a real server. Add your own as ordinary .js files in a folder shared with the team, and they appear beside the built-in ones in completion, in the Recipes view and for AI assistants. They appear only in files recognised as Alfresco scripts, never in ordinary JavaScript. See docs/RECIPES.md.
  • An MCP server for AI assistants, bundled and registered automatically with VS Code, with one command to wire it into Claude Code. Eight tools let an assistant read your repository's root objects, content model and engine capabilities instead of recalling what Alfresco usually has, and check a script before showing it to you. It runs locally, holds no credentials and can change nothing. See docs/SERVERS.md.

Requirements

  • VS Code 1.101 or later.
  • Alfresco Content Services with OOTBee Support Tools installed on the repository. Developed and tested against Community 25.2.0 with OOTBee Support Tools 1.2.3.0; older versions of either are untested.
  • A user that is a member of ALFRESCO_ADMINISTRATORS (the console web scripts are admin-only).
  • Authentication: Basic (alfrescoNtlm or LDAP chains) or the external subsystem (trusted header). Ticket and OIDC/Keycloak modes are planned.

Running part of a file

Ctrl+Alt+R runs the whole file, or only the selection when there is one. JSConsole: Run Selection in Alfresco does the same thing explicitly, and the JSConsole for Alfresco submenu on the right-click menu offers whichever applies.

Two details make a selection behave the way you would want:

  • Error lines still point at the right place. A failure on the second line of a selection that starts at line 6 is reported against line 7 of the file, not line 2.
  • @jsconsole header directives still apply, because they are read from the whole file. A script pinned to readwrite stays read-write when you run three lines of it.

A selection is sent as a complete script, so it has to stand on its own. Selecting the body of a function that reads variables declared above it will fail, exactly as it would in the Share console.

Which files count as Alfresco scripts

Everything this extension knows is true of repository scripts and false of ordinary JavaScript, so the rules have to be scoped. Told to check a Node build script, the engine check would report let and the root object check would report require, module and process.

Declare your folders. Right-click a folder in the Explorer and choose JSConsole for Alfresco › Declare Folder as Alfresco Scripts, or use the gear in the Scripts view. That writes a glob into jsconsole.scripts.include in that workspace folder's settings, so the whole team inherits it. Remove one from the folder's own context menu in the Scripts view, or through the gear.

The Scripts view lists declared folders only. Until you declare one it stays empty and offers to help, rather than listing whatever JavaScript happens to be lying around and calling it yours. A declaration also keeps the run commands in the editor menus for every file in the folder, however little it holds yet, and decides which folders get definitions written for them.

Diagnostics judge each file on its own, declared or not, because silence would just look broken. A file counts as an Alfresco script when it uses a distinctive root object such as search or companyhome, reaches for the Rhino Java bridge through Packages or java.lang, calls a bare print() or dump(), or carries an @jsconsole line. A file using require, module.exports, import, window or the DOM is never checked, whatever else it contains.

Adding // @jsconsole to a file always marks it as yours, which is the way to be certain.

Setting Default Description
jsconsole.scripts.include [] Folders that hold Alfresco scripts. Empty means an empty Scripts view. Diagnostics judge each file either way.
jsconsole.diagnostics.scope auto auto, marked for only @jsconsole files, or all.

Declaring script folders

The Scripts view shows nothing until you say which folders hold Alfresco scripts. Right-click a folder in the Explorer and choose JSConsole for Alfresco › Declare Folder as Alfresco Scripts, or run JSConsole: Configure Script Folders. A declared folder can be undeclared from its own row.

A declaration is stored in jsconsole.scripts.include as a path relative to the workspace folder that contains it, and written to that folder's settings. That matters in a multi-root workspace: written at workspace level, samples/** would mean "samples under every root", and every root would look declared.

A declared folder is listed as soon as it exists, whether or not it holds a script yet, so you can see the declaration was made and take it back. A declaration naming a folder that is not there is left out rather than invented, which is what a stale entry or one meant for another root looks like.

Declaring a whole workspace folder gives it no folder row of its own, so when it matches nothing the row that says so carries the remove command instead. Every declaration is removable from the view; none of them needs the settings file edited by hand.

The Alfresco sidebar

The extension has its own icon in the activity bar, holding five views.

  • Scripts. The .js and .alfnb files in the folders you have declared, as a folder tree that mirrors the one on disk. Nothing is listed until a folder is declared. A chain of single subfolders collapses into one row, following your own explorer.compactFolders setting. Each script has a run button on the row, so you can run one without opening it. Dependencies and build output are skipped, including target, which matters in an Alfresco SDK project where Maven copies every script into it.
  • Recipes. Your own recipe folders, then the built-in recipes. Open, insert or copy one, and add, create or remove a folder. See Your own recipes.
  • Run History. Described below.
  • Script API. Described below.
  • Content Model. Described below.

A view lives in one container, so these five are not also in the Explorer. Drag any of them back there if you prefer, and VS Code will remember.

Running a script you already have open is unchanged: the play button in the editor title bar, the JSConsole for Alfresco submenu on the right-click menu, or Ctrl+Alt+R. Notebook cells keep their own run buttons.

Setting Default Description
jsconsole.scripts.include [] Folders and globs for the Scripts view, relative to each workspace folder. Empty means the view lists nothing.
jsconsole.scripts.maxFiles 500 Most files listed per workspace folder before the view asks you to narrow the search.

Notebooks

JSConsole: New Notebook creates an .alfnb file: markdown cells for the reasoning, code cells that run on the active profile with their output underneath. samples/investigation.alfnb is a worked example.

Each cell is sent to the server as its own script, so cells do not share variables. Anything a later cell needs, it looks up again. That is a property of the console, not a limitation of the notebook.

The status bar under a code cell shows its transaction mode. Click it to pin readonly, readwrite or none for that cell alone, which is how a notebook can stay read-only except for the one step that writes. A // @jsconsole line inside the cell wins over that, and the environment guardrails apply to every cell exactly as they do to a file.

Running a notebook runs its cells in order and stops at the first one that fails. Stored output is not saved with the file, so committing an .alfnb shares the questions and the code, not the answers. JSConsole: Export Notebook as Script flattens the whole notebook into one .js file, markdown included as comments.

Running as another user

Set a run-as user from the status bar, from Run in Alfresco with Options..., or with an // @jsconsole runas=alice header line. The connected user must be an administrator.

Two things about run-as are worth knowing, because neither is obvious and neither is our doing. Both are listed under Known upstream issues below.

  • Permissions follow the run-as user, but person and userhome do not. Those root objects always describe the user you connected as. A script that reports its own identity through person will report the wrong one. AuthenticationUtil.getRunAsUser() returns the effective user.
  • Alfresco accepts any name. A typo is not rejected; the script runs as a stranger holding only GROUP_EVERYONE, which on a typical repository can still read most content, so the run looks fine and tells you nothing. The extension therefore checks the name against the server the first time you use it on a profile and asks before running if there is no such person. The check costs one read-only round trip per name per profile, and if it cannot answer it never blocks the run.

samples/06-run-as-and-people.js demonstrates both.

Script API

The Script API view lists every root object the connected repository exposes, read from the same download the typings come from, so it describes that server rather than a list that drifts. On ACS 25.2 with OOTBee 1.2.3.0 the download declares 56 root objects over 209 types: 39 from the repository payload and 17 from the web script payload. The view lists 41 of them by default, for the reason below.

Three things the raw payload gets wrong for a reader are undone first:

  • Overloads. The server sends luceneSearch, luceneSearch2 ... luceneSearch6 as separate members, each naming the real one. Listing them as written would invent five methods that do not exist, so they become one entry with six signatures, fewest arguments first.
  • Inherited members. These sit behind a prototype and are not repeated on the subtype. utils has 21 members, 14 of them inherited, and would look nearly empty without following it. Inherited ones are marked with the type they came from.
  • Boxed Java types. JavaString is shown as string.

A member whose value is a type the payload knows can be opened, so the tree walks from search to ScriptNode to NodeProperties without anyone needing those names. Depth is capped, because the API is full of cycles: a node has a parent, which is a node.

The view lists what a console script can use, in two groups: Root objects and Console functions. It is not grouped by which payload file a global came from, because that describes the download rather than anything you can act on.

The server also sends a web script payload. Those root objects do resolve in the console, since it runs your script from inside a web script of its own, but they describe an HTTP request you are not serving: url is the console's own endpoint, webscript its own descriptor, headers the headers of the request that carried your script. This extension runs administrative scripts, not web scripts, so they are left out.

Two exceptions, and one caveat:

  • args and argsM are always listed. The console supplies them from its URL arguments field, which is what this extension's own arguments parameter feeds.
  • jsonUtils, stringUtils, roothome and server are in that payload too, and all four work. Set jsconsole.scriptApi.includeWebScriptGlobals to true to list the whole payload.

Either way they stay in the generated typings and in the unknown-global check, so using one is completed and never reported as a name the repository does not have.

Action What it does
Search the Script API One list of every member of every object, for when you remember the method but not the object it hangs off
Filter Narrows the tree to matching object and member names
Insert at Cursor Writes the call with its parameter names as placeholders
Copy Signature Puts the full signature on the clipboard

print and dump are listed under Console functions. They are injected by the console around your script rather than registered by the repository, so they are not in the payload at all and come from a list the typings generator shares, which is what keeps the two from disagreeing.

Custom root objects registered by your own modules appear here too, once the definitions have been refreshed against that server. If the editor offers a name this view does not list, the likely cause is that the definitions were last refreshed against a different profile than the one now active.

Content model

The Content Model view shows what the active repository actually defines: every type and every aspect, grouped by namespace, each expanding to its properties and associations. Inherited members are listed too and say which class they come from. Click any entry to insert its QName at the cursor, quoted unless you are already inside a string. The filter in the view title narrows it to one namespace, which is how you look at a custom model on its own.

The same data drives the language server:

  • Completion knows the context. A string after addAspect( or ASPECT: offers aspects; one after TYPE: or in a createFile type argument offers types; anywhere else you get properties and associations.
  • Typos are reported. A name such as cm:naem, whose namespace the repository uses but whose name it does not define, is flagged as a warning. A name in a namespace the repository does not have is left alone, because that is usually a string that merely looks like a QName rather than a mistake.

It comes from GET /alfresco/s/api/classes, a stock Alfresco web script rather than an OOTBee one, so it needs only an ordinary user account. On a stock repository that is about 1 MB, fetched in around 100 ms.

If the repository will not serve it, everything else still works and only these features stay off.

What the engine will actually accept

Alfresco repositories run Rhino, and which JavaScript it accepts is not something you can predict. The local test server, ACS 25.2 on Rhino 1.7.14, gives this:

Works Does not work
const, arrow functions, template literals let, class, for...of
destructuring, getters and setters spread and rest, Promise, Map, Set, Symbol

const works but let does not. Arrow functions work but class does not. Destructuring works but spread does not. Nobody would guess that, and a version number will not tell you either, since Alfresco sets the language version and two servers on the same Rhino can differ.

So the extension asks. A refresh runs a small read-only probe that tries each feature and reports what survived, caches the answer with the profile's other definitions, and the language server then underlines exactly what that server will reject, with the alternative to use. The same pass reports a reference to a root object the profile does not register, which is how a script that works on development fails on a production server that is missing an addon.

Both checks stay silent until a profile has been refreshed, because guessing would be worse than saying nothing.

Search queries

search.query and search.luceneSearch calls are read statically, which catches two things that otherwise only show up at run time.

The Lucene escaping rule. In a lucene query a property field is written @cm\:name:value: the first colon belongs to the field name and has to be escaped. Unescaped, the query throws rather than quietly returning nothing. The check reads the string the repository would receive rather than the source text, so it also catches "@cm\:name:x" written with one backslash, which JavaScript silently eats before the server ever sees it. The quick fix rewrites the whole literal.

Only lucene queries are checked. In fts-alfresco, cm:name:value needs no escaping, and a sort column passed to luceneSearch is written @cm:name with no escape either, so neither is flagged.

The language name. search.query({ language: "fts" }) fails at run time; the repository accepts fts-alfresco, lucene, cmis-strict, cmis-alfresco, xpath and db-afts. A near miss is reported with the correction as a quick fix.

Where completion comes from

Three different things suggest code in a repository script, and only two of them are this extension.

Source What it offers
VS Code's JavaScript service, given the generated declarations by the bundled plugin members after a dot, with real signatures and overloads, and the loops that work on a list or a map
This extension's language server prefixed names inside strings, and the diagnostics below
An AI assistant, if one is installed whole lines of grey ghost text, guessed rather than read

The third knows nothing about your repository. It writes what an Alfresco call tends to look like, which is often right and sometimes invents a parameter that does not exist. Accepting one of those is how a call like this gets written:

search.selectNodes("/app:company_home//*", "cm:name, cm:created");

There is no property-list parameter. The two-argument form of selectNodes is (store, search), so the second argument is run as the query and the server answers Failed to execute search: cm:name, cm:created. That call is now reported before it runs. Press Ctrl+Space to see the real signatures, which come from the server and list every overload it actually has.

Assign to a variable

A call whose result goes nowhere is how a script usually starts: run search.query(...), see what it does, then want to keep the answer. Put the cursor on the call and the lightbulb offers Assign to "results", which is Eclipse's Ctrl+2 L. Nothing in VS Code's JavaScript support does this.

The name comes from the call rather than a counter, so getPerson gives person, createFolder gives folder, listSites gives sites and childByNamePath gives child. A search names its query and never its result, so those give results. A name already in scope is not shadowed; the new one gets a number instead.

It is offered only where it would be correct: the call has to be the whole statement. In print(search.query({}).length) the cursor may sit on the search, but a declaration in front of that line would name print, so nothing is offered.

Filling in arguments

Accepting a completion inserts the name alone, because js/ts.suggest.completeFunctionCalls is off by default in VS Code. Run JSConsole: Fill Arguments When Completing a Function to turn it on, and accepting selectNodes writes selectNodes(search) with the parameter selected. It is a command rather than something the extension sets for you, because it applies to all JavaScript in the workspace: it writes the setting under "[javascript]" in workspace settings, or in user settings when no folder is open. On a VS Code from before the js/ts.* rename it writes javascript.suggest.completeFunctionCalls instead.

Your own recipes

The Recipes view in the JSConsole sidebar lists the built-in recipes and any folders of your own. A recipe of yours is an ordinary .js script with a /** ... */ header — @recipe for its id, then @title, @task, @keywords and any number of @trap lines — so you write and run it like any other script, and it is offered only once it parses. A recipe of yours with a built-in id replaces the built-in one. Create Recipe Folder... sets one up in the project with an example, Save Selection as Recipe... turns code you have into one, and Copy to My Recipes... gives you a built-in one to change. They are offered in completion, labelled with their folder, and served to agents through the MCP server. The folders are listed in jsconsole.recipes.folders. Share one with the team through workspace settings, or keep one for yourself in user settings; both apply at once. jsconsole.recipes.enabled turns recipe completion off.

Inline suggestions

The grey text proposed as you type comes from GitHub Copilot or another provider, not from this extension, and it is written from general JavaScript habit without reading the typings. In a repository script that means calls like task.then(...) or properties.forEach(...), which the server rejects. The editor underlines them once they are written. To stop them being proposed at all, run JSConsole: Toggle Inline Suggestions in Script Folders. It writes "[javascript]": {"editor.inlineSuggest.enabled": false} into the settings of each workspace folder that declares script folders (workspace settings in a single-folder window), so it covers all JavaScript in that workspace folder, not just the declared subfolders. Running it again turns them back on. Declaring a folder asks the same question once, with the reasons, when Copilot is installed; nothing is changed without an answer.

What the editor cannot do is pick the argument for you. Nearly every parameter in the script API is declared string, so every string in scope fits and knowing the type tells you nothing. Where the repository accepts only a handful of values, this extension suggests those instead:

Where the cursor is What is offered
language: in a search the six query languages
transaction: readonly, readwrite, none
the first argument of selectNodes, luceneSearch, xpathSearch or tagSearch the repository's stores, live content first
a string holding a prefixed name types, aspects, properties and associations from the model

The store list steps aside as soon as what you are typing stops being the start of one, so a path or a query in that position gets name completion instead.

Names inside a query

A query is a string, so nothing in JavaScript objects to a misspelled namespace. The repository does not object either: an unresolvable name matches nothing, the search returns an empty list, and the script carries on as though the repository were empty. That silence is what these checks are for.

search.luceneSearch('PATH:"/acmeMial:attachments//*"');
// No namespace is registered with the prefix "acmeMial", so it cannot match anything.

search.query({ query: 'TYPE:"cm:contnet"', language: 'fts-alfresco' });
// "cm:contnet" is not a type in this repository's model. Did you mean cm:content?

Two things are reported, both of which can be stated without knowing your data. A prefix no namespace registers, anywhere in the query, in any language. And TYPE: or ASPECT: naming a class the model does not define. A prefixed name anywhere else is left alone: in a path the local part is a node's name rather than a class, so flagging it would turn a check that is always right into one that cries wolf.

Completion works inside the string too. Typing a colon is the signal, so TYPE:"cm: offers types, ASPECT:"cm: offers aspects, and only the half-written name is replaced rather than the whole query. An ordinary string is left alone, since otherwise every one of them would open a list of a thousand names.

Both need the content model, so they stay quiet until definitions have been refreshed from the server.

Quick fixes

A misspelled QName, root object or query language is reported with "did you mean", and Ctrl+. applies the correction. Suggestions rank an abbreviation ahead of an edit-distance match, so fts offers fts-alfresco, and a transposition such as cm:naem counts as one mistake rather than two.

Setting Default Description
jsconsole.diagnostics.engineSyntax true Underline syntax the engine cannot parse.
jsconsole.diagnostics.unknownGlobals true Report a root object the active profile does not have.
jsconsole.diagnostics.searchQueries true Check query languages and Lucene field escaping.

One scope per script

Each script the repository runs gets its own scope. Nothing declared in one is visible to another, and a name borrowed from a sibling file fails at run time with "node" is not defined.

The bundled TypeScript plugin says so, with moduleDetection: force, and so does a generated jsconfig.json where one is written. Without it TypeScript treats every .js file in the folder as one shared script, so a var node at the top of one file is offered as a completion in all the others, and the editor quietly suggests names that cannot work. Ambient declarations in the generated .d.ts stay global either way, which is what the root objects need.

If a jsconfig.json of your own is there it is left alone, but a refresh says so when it has the shared scope, because the only symptom is completions that look right and are not.

Where definitions are kept

Two profiles can point at repositories with completely different content models, so the raw payloads are cached per profile in the extension's own storage, each stamped with the profile, its URL, the server version and the fetch time. Each project gets a generated copy for the profile that is currently active: the .d.ts files and the files listed below.

By default (jsconsole.typings.location set to storage) that copy lives outside the project, under ~/.jsconsole-for-alfresco/projects/, in a folder named after the project's absolute path. A TypeScript server plugin bundled with the extension hands the declarations to VS Code's JavaScript service, pins the standard library to what the server's engine has, and keeps each script in its own scope, so nothing is written into the project. Set jsconsole.typings.location to workspace to keep the copy in the project instead, in the folder named by jsconsole.typings.directory (.jsconsole). An in-project folder that already exists is used whatever the setting says. Add it to your .gitignore: the files are generated, describe one server, and can expose the shape of a custom model.

A jsconfig.json is the one file that cannot live outside the project, because the JavaScript service finds it by walking up from an open file. With jsconsole.typings.jsConfig at auto it is written only when the plugin could not be configured, and only when the folder has neither a jsconfig.json nor a tsconfig.json. always writes it even with the plugin running, and never never does. A generated one is removed the next time definitions are written with the plugin running; one you wrote is never touched.

That has three consequences worth knowing:

  • Switching profiles regenerates the project's copy from the cache, with no download.
  • If a cached set was taken from a different URL than the profile now points at, it is never used. The extension refetches instead of quietly completing against the wrong server.
  • Deleting or editing a profile discards its cache.

What the definitions folder contains

Besides the .d.ts files, the folder carries what the server said in a form anything can read, including the language server and the MCP server:

File What it is
alfresco-*.d.ts The script API of this repository, for the TypeScript service.
tern.json The API payload exactly as the server sent it, for the servers to answer API questions from.
qnames.json Every type, aspect, property and association name, with its kind and owning class.
classes.json The same model by class: what each type and aspect declares and inherits.
globals.json The root objects this repository registers.
engine.json What this server's JavaScript engine accepts, measured by the probe.
meta.json Which profile, URL and server version the folder describes, when it was read, and which build wrote it.
recipes.json Your own recipes, when jsconsole.recipes.folders names any, for the MCP server.
README.md Which profile and server the folder describes, and how to write scripts for it.

qnames.json and classes.json need the content model, and engine.json the probe; each is left out when that part of the refresh could not be read.

That README is deliberately specific. Telling a tool "Alfresco probably rejects let" ages badly and is wrong as often as not, while naming the measured result for the server in play does not.

The language server reads the definitions itself at startup, so it needs nothing from this extension. It looks in the project first and then in storage, working the storage folder out from the project's path, so either location works. Point any LSP client at node dist/server.cjs --stdio in a project that has been refreshed and you get the same completions, hovers, quick fixes and diagnostics. The MCP server finds them the same way.

Run history

A rerun goes back to the profile the run was recorded on, not whichever is active. Rerunning a script recorded against a development server while a production profile is selected would otherwise run it against production, and the only sign would be the profile name in the output header, after the fact.

When the recorded profile is not the active one, the rerun is confirmed first and names both. When it has been deleted, the rerun is refused rather than quietly sent somewhere else. The guardrails and the run-as check both use the recorded profile too, so a production tier is treated as production.

The parameters come from the record as well, not from the status bar: the transaction mode, the run-as user, the space and document nodes, the URL arguments and the dump limit. The run header names the ones in force, so a rerun that used something other than the current selection says so on its first line.

Every run is written to the Run History view, grouped by profile and newest first. Each entry keeps the script exactly as it was sent, the parameters it ran with, the output, and how it ended. Click an entry to read its output; right-click for the rest:

  • Rerun sends the stored script again, with its stored parameters, through the same guardrails. It goes to the profile the run was recorded on, confirming first when that is not the active one, as described above.
  • Open script puts the stored source in a new editor, with a comment naming where it came from.
  • Diff output with… picks a second entry and opens both outputs in a diff editor. This is the quickest way to see what changed between two runs of the same script, or the same script on two servers.
  • Delete removes one entry; Clear empties a profile's history after confirming.
Setting Default Description
jsconsole.history.maxEntries 200 Runs kept per profile. Older ones are deleted. 0 turns the history off.
jsconsole.history.maxOutputKb 512 Size ceiling per run. Dumps, rendered templates, then the middle of the output are dropped to fit.

History lives in the extension's own storage folder, not in your workspace, so it never reaches a repository. It is stored as plain JSON and is not encrypted, so treat anything a script prints the same way you treat the script itself. The script is never truncated, because rerunning it is the point.

Environment guardrails

Every profile has an environment: development, test or production. You pick it when adding the profile, and it changes how hard it is to run something destructive. Profiles created before this existed count as development.

Run Development Test Production
Read-only runs runs runs
Read-write or no transaction confirmation dialog confirmation dialog type the profile name
Run as another user runs runs type the profile name, once per session

Both read-write and "no transaction" count as writes: without a surrounding transaction the script is free to open its own and commit. jsconsole.execution.confirmReadWrite turns the dialog off for development profiles only; test and production always ask.

A production profile can additionally be set to never allow writes. It then refuses read-write and no-transaction runs outright, whatever the settings, the status bar or an @jsconsole header line ask for. Use it for the servers nobody should be writing to from an editor.

The active profile sits in the status bar: red for production, orange for test. The run header in the JSConsole Output output names the environment too, for example ▶ prod-eu · [PROD] · readonly.

Authentication modes

Basic. User name and password. The password is stored in VS Code secret storage.

External (trusted header). For repositories whose authentication.chain contains external. The extension sends the remote user name in the header configured as external.authentication.proxyHeader (X-Alfresco-Remote-User by default), exactly as a reverse proxy would after SSO. Use it when:

  • the repository is reachable directly, typically development and test environments, or
  • external.authentication.proxyUserName is set and the profile presents the proxy's TLS client certificate (PKCS#12 or PEM certificate plus key; the passphrase goes to secret storage).

Without one of those, the repository would trust any caller that sets the header, which is a server misconfiguration rather than something this extension can fix.

HTTPS and certificates

A repository behind a company certificate authority, or on a development box with a self-signed certificate, will not verify against the roots the machine already trusts. When that happens the error names the cause and offers the fix, and JSConsole: Certificate Trust for the Active Profile... reaches the same choices at any time.

Choice What it does When
Import from server Reads the chain the server presents, shows it with its SHA-256 fingerprint, and saves the authority above the server certificate as the profile's CA A company or internal CA. The preferred answer
Choose CA file... Validates against a PEM you already have Your administrators published the CA
Trust this server Skips verification: accepts any certificate for this profile Throwaway development servers only

The error offers the choices that fit the failure: all three for an unknown authority, Edit profile and Trust this server for a host name mismatch, and only Trust this server for a certificate that has expired or is not valid yet. The certificate trust command lists the same three, with the last labelled Skip verification, plus Validate normally to forget a CA or stop skipping.

The first two keep validation on: the host name, the expiry and the signature are all still checked, and the CA is added to the ones already trusted rather than replacing them. An imported CA is written to the extension's global storage as certificates/<profile id>.pem and removed when the profile is deleted.

Importing reads the chain over the very connection that could not be verified, so it proves nothing by itself. That is why the subject names and the fingerprint are shown before anything is saved: check them against what your administrators published.

Skipping verification is different in kind, not degree. It accepts any certificate for that profile, so anything able to answer on the host is trusted and the traffic can be read or altered in transit without showing. It is confirmed with a modal that says so, and a profile on a test or prod tier is named as such in the confirmation.

Getting started

  1. JSConsole: Add Server Profile. Enter a name, the repository URL (for example http://localhost:8080/alfresco), the user name and the password.
  2. JSConsole: Test Connection to confirm the console is reachable.
  3. Declare the folder that holds your scripts: right-click it in the Explorer and choose JSConsole for Alfresco › Declare Folder as Alfresco Scripts.
  4. JSConsole: Refresh API Typings from Server. This reads the server's script API, content model and engine, caches them with the profile, and gives the editor completion and checks for them. By default nothing is written into the project; see Where definitions are kept.
  5. Open a .js file, write a script, press Ctrl+Alt+R.
var results = search.query({ query: 'TYPE:"cm:content"', language: 'fts-alfresco' });
print(results.length + " documents");
for (var i = 0; i < results.length && i < 5; i++) {
  print(results[i].properties["cm:name"]);
}

Settings

Setting Default Description
jsconsole.execution.transaction readonly readonly, readwrite or none.
jsconsole.execution.runAs Run scripts as this user.
jsconsole.execution.confirmReadWrite true Ask before running in a read-write transaction.
jsconsole.execution.pollIntervalMs 500 How often print output is fetched while a script runs.
jsconsole.typings.location storage storage keeps a project's definitions under ~/.jsconsole-for-alfresco/projects/; workspace writes them into the project. An existing in-project folder is used either way.
jsconsole.typings.directory .jsconsole The in-project folder name used when jsconsole.typings.location is workspace.
jsconsole.typings.jsConfig auto Write a jsconfig.json into the project: auto only when the TypeScript plugin could not be configured, always, or never.
jsconsole.typings.includeWebScriptApi true Also generate typings for web script root objects (model, args, url, ...).
jsconsole.typings.refreshOnProfileSwitch true Read a profile's server the first time you switch to it. A profile read before switches instantly from its cache.
jsconsole.typings.maxAgeDays 7 Definitions older than this count as stale. 0 disables the check.
jsconsole.typings.staleAction prompt What happens when they are stale, at startup or on a switch: prompt asks, offering Refresh, Always Refresh, Not Now and Never Ask; refresh reads them again without asking; ignore leaves them. Always and Never set this in your user settings.
jsconsole.recipes.enabled true Offer recipes in completion, in files recognised as Alfresco scripts.
jsconsole.recipes.folders [] Folders of your own recipes. Read from user, workspace and folder settings together.
jsconsole.scriptApi.includeWebScriptGlobals false List the web script root objects in the Script API view.

The settings for the Scripts view, diagnostics and run history are in their own sections above.

Typings are a snapshot of the server. Refresh them after deploying a content model or an addon that adds script root objects; the stale check reminds you when the active profile has not been read for a while.

How it works

Action Endpoint on the repository
Run script POST /alfresco/s/ootbee/jsconsole/execute
Stream output / recover result GET /alfresco/s/ootbee/jsconsole/{channel}/executionResult
Test connection GET /alfresco/s/ootbee/jsconsole/serverInfo
API definitions GET /alfresco/s/ootbee/jsconsole/tern-definitions/alfresco-script-api
Content model GET /alfresco/s/api/classes (stock Alfresco, not OOTBee)
Engine probe, run-as check POST /alfresco/s/ootbee/jsconsole/execute, read-only

The execute request stays open for the whole run. If a proxy cuts it, the extension keeps polling the result endpoint, where the server caches the outcome under the channel id.

OOTBee publishes print() output in chunks of five lines, so short scripts show all output at the end while long-running scripts stream as they go.

The extension activates when a JavaScript file is opened (onLanguage:javascript), when a workspace contains an in-project .jsconsole/ folder with generated typings, when a notebook is opened, or when one of its commands or views is used. Because any JavaScript project activates it, definitions are written automatically only for a folder that has been declared or already has them from a refresh.

Generated typings are a translation of the Tern definitions OOTBee produces by reflecting over the running repository. Everything is placed in a declare namespace Alfresco; root objects are declared as globals.

Troubleshooting

  • Nothing happens after Run. Open Output and pick the JSConsole Log channel. Every request and response is logged there with status and timing (Developer: Set Log Level to Debug also shows the result polling). The JSConsole Output channel holds the script output itself, and JSConsole Language Server the language server's own log.
  • "A script is already running". The previous run has not finished. The message offers to cancel it; the progress notification for a run also has a Cancel button.
  • Password prompt appears and the run stops. The stored password is missing or was rejected. Use JSConsole: Set Profile Password and run again. Background polling never prompts.
  • 404 on every call. The profile URL must point at the repository web application, for example http://localhost:8080/alfresco, and OOTBee Support Tools must be installed there.
  • "Cannot verify ". The server certificate did not validate. The message says why, and offers to import the authority, point at a CA file, or trust the server without verifying it. See HTTPS and certificates.
  • A profile you no longer want. JSConsole: Delete Server Profile removes it together with its stored password, client certificate passphrase, imported CA and cached definitions.

Known upstream issues

Behaviour of the OOTBee JavaScript Console that surprised us, verified against Alfresco Content Services Community 25.2.0 with OOTBee Support Tools 1.2.3.0. None of these is caused by this extension, and none is fixable from the client. They are recorded here so they can be rechecked against a newer OOTBee release, and ideally fixed upstream.

1. dump() inside a script produces no output. DumpService.addDump builds and returns a fresh list on every call. The console's pre-roll dump() wrapper throws that return value away, and ExecuteWebscript only stores the list from its own final addDump(documentNode). So the dump section contains the node set as the document parameter and nothing the script dumped. Verified by dumping one node while pointing the document parameter at another: the response described the document. Our workaround: samples/03-dump.js shows the document-parameter path and prints properties instead. Likely fix upstream: have the pre-roll accumulate what it dumps and have the webscript store that.

2. An unknown run-as user is accepted silently. AuthenticationUtil.runAs pushes whatever string it is given; nothing checks that the person exists. The run then proceeds with only GROUP_EVERYONE. On a default repository that still reads most content, so nothing appears to be wrong. Our workaround: the extension checks the name and warns. Arguably not a bug in Alfresco itself, since runAs is a low-level primitive, but the console exposes it to people who reasonably expect a typo to be caught.

3. person and userhome ignore run-as. The script model is built from the authenticated user, so under runas=alice the script still sees person.properties["cm:userName"] === "admin" while permission checks correctly use alice. Half the model honours run-as and half does not. Our workaround: documentation only, plus samples/06-run-as-and-people.js, which prints both identities side by side. Likely fix upstream: resolve person and userhome from AuthenticationUtil.getRunAsUser() when a run-as user is set.

4. transaction=none drops half the script model, and fails outright without a Space. Company Home, the user's home and the person node are resolved through calls that need a transaction, and in this mode the console does not open one. So companyhome, userhome, person and document are never put into the script scope at all: using one raises ReferenceError: "companyhome" is not defined, which reads as though the repository were missing it.

Worse, space defaults to Company Home, so with no Space parameter it is null too, and the webscript then fails while building its response: Cannot invoke "org.alfresco.repo.jscript.ScriptNode.getNodeRef()" because "newSpace" is null. That happens after the script has run and regardless of what the script contains, so in this mode every run fails, including var x = 1 + 1;.

Set a Space parameter and a run succeeds, with space present and the four node globals still absent. Everything else is there — search, people, siteService, actions, utils, logger, session, args, model, workflow, paging, classification, preferenceService, activities — and ordinary repository reads still work, because Alfresco opens its own transaction per service call. All measured by listing typeof for each global in both modes. Our workaround: the extension warns before a run in this mode. Likely fix upstream: resolve the node model inside a short read-only transaction whatever the requested mode, or reject none with an explanation.

5. A paged folder listing never says it is finished. childFileFolders(files, folders, ignoreTypes, skip, max, …) returns a page whose hasMoreItems() is a Java Boolean object rather than a JavaScript boolean, and an object is truthy even when it holds false. So if (!result.hasMoreItems()) break; never breaks: the script keeps requesting empty pages on the server until the repository is restarted, since aborting the request does not stop it. String(result.hasMoreItems()) and result.hasMoreItems() == false read it correctly. This paged form is also missing from the API payload, which lists only the zero- and two-argument forms. Measured on ACS 25.2, the hard way. Our workaround: the children recipe reads it through String and also stops on an empty page. Likely fix upstream: return a primitive boolean.

6. A caught repository error throws away the whole read-write run, and the run reports success. Catch an error the repository threw — a name that already exists, a protected node, a bad workflow transition, even a failed lookup such as workflow.getTask("147") — and carry on, and everything the run did is rolled back when it ends. The console answers exactly as it does for a run that committed: HTTP 200, the same fields, the script's own output, nothing to say it was discarded. An error from the script's own code (a TypeError, a throw) can be caught without this. Measured on ACS 25.2 with six kinds of error, each after a create in the same run. Our workaround: the errors recipe rethrows any JavaException, so the run fails where it can be seen, and the create recipe looks before it writes. Likely fix upstream: report the rollback in the response, or fail the run when the transaction it commits is already marked rollback-only.

Development

npm install
npm run build        # bundles dist/extension.cjs and dist/server.cjs
npm run watch
npm test             # unit and mock-server tests (node --test)
npm run typecheck
npm run docs:recipes # regenerates docs/RECIPES.md from the catalogue; a test fails when it is stale
npm run package      # builds the .vsix

The repository carries no launch configuration; CONTRIBUTING.md has one to start from, so F5 opens an Extension Development Host. Node 22.18 or newer is required to run the tests, which execute the TypeScript sources directly.

CONTRIBUTING.md has the rest: how the tests are put together, how to debug either server, the conventions the code follows, and why measuring against a live repository matters more here than it usually does.

Roadmap

  • Ticket and OIDC (Keycloak / Identity Service) authentication via a VS Code AuthenticationProvider.
  • Saved scripts (the console's list/save endpoints).
  • Contact us
  • Jobs
  • Privacy
  • Manage cookies
  • Terms of use
  • Trademarks
  • Your Privacy Choices
  • Consumer Health Privacy
© 2026 Microsoft