Skip to main content

Using the Vault Messages Package

⚠️ Note

Sample codes are for demonstration purposes only. Before using in production, add proper error handling such as try-catch blocks, null checks, and input validation.

Overview​

This sample demonstrates how to use the @m-filescorporation/uix-vault-messages NPM package to work with M-Files vault gRPC messages using type-safe constructors and helpers instead of raw JSON.

The application creates a custom command that searches the vault for documents matching a keyword, then displays the results in a message box.

This sample does not show how to create a local development folder or to deploy the code to the M-Files server. It is assumed that a local development folder already exists, and that is the location in which the development is occurring.

Prerequisites​

Since this sample uses an NPM package, a bundler is needed to bundle the package into a single file that M-Files can load. This sample uses Vite.

Initialize your project, install the dependencies, and add a build script to your package.json:

npm init -y
npm install @m-filescorporation/uix-vault-messages
npm install --save-dev vite
npm pkg set scripts.build="vite build"

Create a vite.config.js to bundle the module as an IIFE (immediately-invoked function expression) that M-Files can load directly:

vite.config.js
import { defineConfig } from 'vite';
import { copyFileSync } from 'fs';
import { execSync } from 'child_process';

export default defineConfig({
build: {
lib: {
entry: 'main.js',
formats: ['iife'],
name: 'window',
fileName: () => 'main.js',
},
outDir: 'dist',
minify: false,
rollupOptions: {
output: {
// Extend window instead of creating a new variable.
// This places exported functions (e.g. OnNewShellUI) directly
// on the global scope so M-Files can call them.
extend: true,
},
},
},
plugins: [{
name: 'package-app',
closeBundle() {
copyFileSync('appdef.xml', 'dist/appdef.xml');
// Create a deployable zip from the dist folder.
execSync(
'powershell -Command "Compress-Archive -Path dist/appdef.xml,dist/main.js -DestinationPath dist/app.zip -Force"',
);
},
}],
});

After writing the application code, run npm run build to produce the bundled output in the dist folder. The build copies appdef.xml alongside the bundled main.js and creates a ready-to-deploy app.zip.

See the Application Structure guide for general details on UIX project structure.

Creating the application structure​

Creating the application definition file​

Into this folder we will create an application definition file. This file must be named appdef.xml. The application will use version 5 of the client schema (as we are only targeting newer M-Files versions). The application will declare a single Shell UI module (with its code in main.js), and no dashboards.

appdef.xml
<?xml version="1.0"?>
<application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.m-files.com/schemas/appdef-client-v5.xsd">
<guid>A7E3B2D1-9F4C-4E8A-B5D6-1C2E3F4A5B6C</guid>
<name>Vault Messages Package Sample</name>
<version>0.1</version>
<description>Demonstrates usage of @m-filescorporation/uix-vault-messages for type-safe vault operations.</description>
<publisher>M-Files Corporation</publisher>
<enabled-by-default>true</enabled-by-default>
<modules>
<module environment="shellui">
<file>main.js</file>
</module>
</modules>
</application>
Ensure that your application has a unique GUID by using a GUID generator.

Creating the module​

Next we will create a module file to contain our actual application logic. Initially:

  • We will import MFGrpc and the enums we need from the package.
  • We will declare and export the entry point for the ShellUI module. The export keyword ensures the bundler places OnNewShellUI on the global scope (via the Vite config above).
  • We will react to the NewShellFrame event and obtain a reference to the shell frame.
  • We will react to the shell frame's Started event (as using the shell frame before this point will result in an exception).
  • Create a command button, place it into the menu area, and set it as active.
  • React to the shell frame's CustomCommand event and add placeholder code to execute when the command is clicked.
main.js
import { MFGrpc, Datatype, ConditionType, StatusType } from '@m-filescorporation/uix-vault-messages';

export function OnNewShellUI(shellUI) {
/// <summary>The entry point of ShellUI module.</summary>
/// <param name="shellUI" type="MFiles.ShellUI">The new shell UI object.</param>

// Register to be notified when a new shell frame is created.
shellUI.Events.Register(MFiles.Event.NewShellFrame, handleNewShellFrame);
}

function handleNewShellFrame(shellFrame) {
/// <summary>Handles the OnNewShellFrame event for an IShellUI.</summary>
/// <param name="shellFrame" type="MFiles.ShellFrame">The shell frame object which was created.</param>

// Register to listen to the started event.
shellFrame.Events.Register(
MFiles.Event.Started,
getShellFrameStartedHandler(shellFrame),
);
}

function getShellFrameStartedHandler(shellFrame) {
/// <summary>Gets a function to handle the Started event for shell frame.</summary>
/// <param name="shellFrame" type="MFiles.ShellFrame">The current shell frame object.</param>
/// <returns type="MFiles.Events.OnStarted">The event handler.</returns>

// Return the handler function for ShellFrame's Started event.
return async () => {
// Create a command button in the top menu.
const searchCommandId =
await shellFrame.Commands.CreateCustomCommand('Search Documents');

// Add the command to the main menu.
await shellFrame.Commands.AddCustomCommandToMenu(
searchCommandId,
MFiles.MenuLocation.MenuLocation_TopPaneMenu,
1,
);

// Set the command as active (visible and enabled).
await shellFrame.Commands.SetCommandState(
searchCommandId,
MFiles.CommandLocation.All,
MFiles.CommandState.CommandState_Active,
);

// Register to respond to commands being clicked.
shellFrame.Commands.Events.Register(
MFiles.Event.CustomCommand,
async (command) => {
// We only care about our command.
// If the command being clicked is something else then return.
if (command !== searchCommandId) {
return;
}

// TODO: Build search conditions and execute the search.
},
);
};
}

Building search conditions with helpers​

Next we will add a function that builds search conditions using the package's SearchConditionArray helpers. We will:

  • Create a new SearchConditionArray using MFGrpc.SearchConditionArray.Create().
  • Use Status() with STATUS_TYPE_OBJECT_TYPE to filter by object type (0 = Document).
  • Use Status() with STATUS_TYPE_IS_DELETED to exclude deleted objects.
  • Use the Property() method to add a condition that searches the "Name or Title" property (ID 0) for the keyword.
  • All three methods return the array, so calls can be chained.
main.js
import { MFGrpc, Datatype, ConditionType, StatusType } from '@m-filescorporation/uix-vault-messages';

export function OnNewShellUI(shellUI) {
/// <summary>The entry point of ShellUI module.</summary>
/// <param name="shellUI" type="MFiles.ShellUI">The new shell UI object.</param>

// Register to be notified when a new shell frame is created.
shellUI.Events.Register(MFiles.Event.NewShellFrame, handleNewShellFrame);
}

function handleNewShellFrame(shellFrame) {
/// <summary>Handles the OnNewShellFrame event for an IShellUI.</summary>
/// <param name="shellFrame" type="MFiles.ShellFrame">The shell frame object which was created.</param>

// Register to listen to the started event.
shellFrame.Events.Register(
MFiles.Event.Started,
getShellFrameStartedHandler(shellFrame),
);
}

function getShellFrameStartedHandler(shellFrame) {
/// <summary>Gets a function to handle the Started event for shell frame.</summary>
/// <param name="shellFrame" type="MFiles.ShellFrame">The current shell frame object.</param>
/// <returns type="MFiles.Events.OnStarted">The event handler.</returns>

// Return the handler function for ShellFrame's Started event.
return async () => {
// Create a command button in the top menu.
const searchCommandId =
await shellFrame.Commands.CreateCustomCommand('Search Documents');

// Add the command to the main menu.
await shellFrame.Commands.AddCustomCommandToMenu(
searchCommandId,
MFiles.MenuLocation.MenuLocation_TopPaneMenu,
1,
);

// Set the command as active (visible and enabled).
await shellFrame.Commands.SetCommandState(
searchCommandId,
MFiles.CommandLocation.All,
MFiles.CommandState.CommandState_Active,
);

// Register to respond to commands being clicked.
shellFrame.Commands.Events.Register(
MFiles.Event.CustomCommand,
async (command) => {
// We only care about our command.
// If the command being clicked is something else then return.
if (command !== searchCommandId) {
return;
}

// TODO: Execute the search and display results.
const conditions = buildDocumentSearchConditions('Report');
},
);
};
}

function buildDocumentSearchConditions(keyword) {
/// <summary>Builds search conditions for finding documents by keyword.</summary>
/// <param name="keyword" type="string">The keyword to search for in document titles.</param>
/// <returns type="SearchConditionArray">The search conditions.</returns>

// Create a new SearchConditionArray and build conditions using chaining.
// Status() and Property() return the array, so calls can be chained.
const conditions = MFGrpc.SearchConditionArray.Create()

// Filter by object type: Documents (type 0).
.Status(
StatusType.STATUS_TYPE_OBJECT_TYPE,
ConditionType.CONDITION_TYPE_EQUAL,
Datatype.DATATYPE_LOOKUP,
MFGrpc.Lookup.Create(0),
)

// Exclude deleted objects.
.Status(
StatusType.STATUS_TYPE_IS_DELETED,
ConditionType.CONDITION_TYPE_EQUAL,
Datatype.DATATYPE_BOOLEAN,
false,
)

// Search "Name or Title" property (ID 0) for the keyword.
.Property(
0,
ConditionType.CONDITION_TYPE_CONTAINS,
Datatype.DATATYPE_TEXT,
keyword,
);

return conditions;
}

Executing the search and reading results​

Finally, we will add a function that executes the search and displays the results. We will:

  • Call SearchOperations.SearchObjects on the vault, passing our search conditions. Use toJSON() to convert proxy objects to plain data before sending over PostMessage.
  • Wrap each result in MFGrpc.ObjectVersionEx to access helper properties like Title, ID, and Version.
  • Display the results in a message box.
main.js
import { MFGrpc, Datatype, ConditionType, StatusType } from '@m-filescorporation/uix-vault-messages';

export function OnNewShellUI(shellUI) {
/// <summary>The entry point of ShellUI module.</summary>
/// <param name="shellUI" type="MFiles.ShellUI">The new shell UI object.</param>

// Register to be notified when a new shell frame is created.
shellUI.Events.Register(MFiles.Event.NewShellFrame, handleNewShellFrame);
}

function handleNewShellFrame(shellFrame) {
/// <summary>Handles the OnNewShellFrame event for an IShellUI.</summary>
/// <param name="shellFrame" type="MFiles.ShellFrame">The shell frame object which was created.</param>

// Register to listen to the started event.
shellFrame.Events.Register(
MFiles.Event.Started,
getShellFrameStartedHandler(shellFrame),
);
}

function getShellFrameStartedHandler(shellFrame) {
/// <summary>Gets a function to handle the Started event for shell frame.</summary>
/// <param name="shellFrame" type="MFiles.ShellFrame">The current shell frame object.</param>
/// <returns type="MFiles.Events.OnStarted">The event handler.</returns>

// Return the handler function for ShellFrame's Started event.
return async () => {
// Create a command button in the top menu.
const searchCommandId =
await shellFrame.Commands.CreateCustomCommand('Search Documents');

// Add the command to the main menu.
await shellFrame.Commands.AddCustomCommandToMenu(
searchCommandId,
MFiles.MenuLocation.MenuLocation_TopPaneMenu,
1,
);

// Set the command as active (visible and enabled).
await shellFrame.Commands.SetCommandState(
searchCommandId,
MFiles.CommandLocation.All,
MFiles.CommandState.CommandState_Active,
);

// Register to respond to commands being clicked.
shellFrame.Commands.Events.Register(
MFiles.Event.CustomCommand,
async (command) => {
// We only care about our command.
// If the command being clicked is something else then return.
if (command !== searchCommandId) {
return;
}

// Search for documents and display results.
await searchAndDisplayResults(shellFrame);
},
);
};
}

function buildDocumentSearchConditions(keyword) {
/// <summary>Builds search conditions for finding documents by keyword.</summary>
/// <param name="keyword" type="string">The keyword to search for in document titles.</param>
/// <returns type="SearchConditionArray">The search conditions.</returns>

// Create a new SearchConditionArray and build conditions using chaining.
// Status() and Property() return the array, so calls can be chained.
const conditions = MFGrpc.SearchConditionArray.Create()

// Filter by object type: Documents (type 0).
.Status(
StatusType.STATUS_TYPE_OBJECT_TYPE,
ConditionType.CONDITION_TYPE_EQUAL,
Datatype.DATATYPE_LOOKUP,
MFGrpc.Lookup.Create(0),
)

// Exclude deleted objects.
.Status(
StatusType.STATUS_TYPE_IS_DELETED,
ConditionType.CONDITION_TYPE_EQUAL,
Datatype.DATATYPE_BOOLEAN,
false,
)

// Search "Name or Title" property (ID 0) for the keyword.
.Property(
0,
ConditionType.CONDITION_TYPE_CONTAINS,
Datatype.DATATYPE_TEXT,
keyword,
);

return conditions;
}

async function searchAndDisplayResults(shellFrame) {
/// <summary>Searches for documents and displays results in a message box.</summary>
/// <param name="shellFrame" type="MFiles.ShellFrame">The current shell frame object.</param>

try {
// Get the vault reference.
const iVault = shellFrame.ShellUI.Vault;

// Build search conditions using package helpers.
const conditions = buildDocumentSearchConditions('Report');

// Execute the search.
// Use toJSON() to convert proxy objects to plain data — proxy
// objects cannot be sent over PostMessage to the vault.
const searchResponse = await iVault.SearchOperations.SearchObjects({
call_importance: 1, // CallImportance.CALL_IMPORTANCE_NORMAL
target: {
mode: 0, // SearchMode.SEARCH_MODE_DEFAULT
},
conditions: [conditions.toJSON()],
options: {},
limit: 10,
timeout_in_seconds: 30,
});

// Process the results.
const results = searchResponse.results || [];

if (results.length === 0) {
await shellFrame.ShowMessage('No documents found matching "Report".');
return;
}

// Read object metadata from each search result.
const summaryLines = results.map((result) => {
// Wrap the server response to get access to helper properties
// like Title and ID, instead of navigating deeply nested JSON.
const objVerEx = new MFGrpc.ObjectVersionEx(result.object);

const title = objVerEx.Title;
const id = objVerEx.ID;
const version = objVerEx.Version;

return `- ${title} (ID: ${id}, Version: ${version})`;
});

const message = `Found ${results.length} document(s):\n\n${summaryLines.join('\n')}`;
await shellFrame.ShowMessage(message);

} catch (exception) {
console.error('Search failed:', exception);
MFiles.ReportException(exception);
}
}

Building and testing the application​

  1. Run npm run build to bundle the application. The output is in the dist folder.
  2. Deploy dist/app.zip to your M-Files vault.
  3. Open M-Files web and navigate to the vault.
  4. Click the Search Documents button in the top menu.
  5. A message box will appear listing documents whose title contains "Report".
For the full reference of all available helper methods, see the Vault API Helpers overview page.

Download​

Download the complete sample project — contains appdef.xml, main.js, vite.config.js, and package.json. After extracting, run npm install and npm run build to produce the deployable dist/app.zip.