Converting MATLAB Web Apps
to HTML apps backed by MATLAB Production Server
A step-by-step guide for converting MATLAB Web Apps to HTML/CSS/JavaScript front ends backed by MATLAB Production Server APIs.
2. Documenting the
Original App with Screenshots
3. Example Prompts for
AI-Assisted Conversion
Prompt 1: Analyze the
Original App Layout
Prompt 2: Build the HTML
Skeleton
Prompt 3: Extract the
MATLAB Callback Logic
Prompt 4: Create the
JavaScript Chart
Prompt 5: Wire Up the API
Call
Prompt 6: Iterate on
Styling and Behavior
4. Separating UI Logic
from Algorithm Logic
Anatomy of a Typical
.mlapp Callback
Step 1: Identify the
Three Zones
Step 2: Extract the
Algorithm into a Standalone Function
Step 3: Test the Function
Independently
Example Prompts for
Extracting Callbacks
Prompt A: Identify the
Three Zones
Prompt B: Extract and
Write the Standalone Function
Prompt C: Handle a
Callback with Multiple Plots and Table Output
Prompt D: Refactor a
Callback That Uses Shared State
5. Replacing MATLAB Plots
with JavaScript Charts
6. Handling Input and
Output Data
Input: HTML Fields to
Function Arguments
Output: Function Returns
to Chart Data
7. Constructing the HTTP
POST to MATLAB Production Server and dealing with the result data
Prompt A: Capture Inputs,
Build the JSON Payload, and Call the API
Prompt B: Handle the API
Response and Render Charts
8. Compiling and
Deploying to MATLAB Production Server
Step 2: Open the
Production Server Compiler
Step 5: Deploy to MATLAB
Production Server
9. Calling the MATLAB
Production Server API from JavaScript
10. Handling the Response
and Debugging with Postman/cURL
Understanding the MATLAB
Production Server Response Format
Option A: Use MATLAB
Production Server Built-In Static File Serving
Step 1: Enable Static
File Serving in main_config
MATLAB Production Server
Configuration Reference
Option B: Use a Separate
Web Server
12. Summary and Best
Practices
A MATLAB Web App (.mlapp) bundles both the UI rendering logic and the computational logic into a single artifact that runs inside the MATLAB Desktop or MATLAB Web App Server. Converting to a HTML app requires us to decouple these two concerns:
• Front end: A standard HTML/CSS/JavaScript application that can be served from any http server, giving you full creative control over the user experience.
• Back end: The core MATLAB algorithm compiled into a .ctf (Component Technology File) archive and deployed to MATLAB Production Server, exposed as a RESTful API.
This separation brings several advantages: the front end is lighter and more customizable, the MATLAB computation scales independently, and the two layers can be developed and maintained by different teams.
The general workflow is:
1. Screenshot and document every interaction in the original .mlapp.
2. Extract the algorithmic callback logic into a standalone MATLAB function.
3. Build an HTML/JS front end that replicates the original UI.
4. Replace MATLAB plots with Chart.js or Plotly.js equivalents.
5. Compile the MATLAB function, deploy to MATLAB Production Server, and wire the front end to the API.
Before writing any code, create a comprehensive visual record of the original MATLAB Web App. This serves as your specification and ensures nothing is lost in translation.
Take one screenshot per distinct user interaction. At minimum, capture:
• Initial state: The app as it appears on launch, before any user input. This shows default values, placeholder text, and the overall layout.
• Each input step: If the user fills in fields, selects dropdowns, toggles checkboxes, or moves sliders, capture the state after each meaningful change.
• The computation trigger: The moment just before the user clicks “Calculate”, “Run”, “Plot”, or whatever button initiates the MATLAB computation.
• Each output state: The resulting plot, table, status message, or updated UI after the computation completes. If there are multiple outputs (e.g., a plot and a numeric summary), capture them all.
• Error states: Any validation messages, warning dialogs, or error conditions the app displays.
• Edge cases: Unusual input combinations, empty states, or boundary conditions.
Use a consistent naming scheme for your screenshot files:
01_initial_state.png
02_input_filled.png
03_dropdown_selected.png
04_calculate_clicked.png
05_plot_result.png
06_error_validation.png
When working with an AI assistant (such as Claude) to help build the new front end, attach the screenshot as context. The assistant can analyze the layout, identify UI components (text fields, dropdowns, buttons, axes), and suggest the corresponding HTML/CSS structure. One screenshot per prompt interaction keeps things focused and prevents the AI from missing details.
Using an AI assistant to accelerate the conversion is highly effective. Below are example prompts you can adapt for each stage of the process. Each prompt assumes you are attaching the relevant screenshot(s).
[Attach: 01_initial_state.png]
This is a screenshot of a MATLAB Web App (.mlapp). I want to recreate this UI as a standalone HTML/CSS/JavaScript page. Please identify all the UI components visible (text fields, labels, dropdowns, buttons, plot axes, tables) and suggest an HTML structure using Bootstrap 5 for layout. Keep the same general arrangement.
Based on the UI components you identified, create a complete single-page HTML file with Bootstrap 5. Include placeholder <div> elements for each chart area. Use <input>, <select>, and <button> elements matching the original app controls. Add id attributes to every interactive element so I can wire them up to JavaScript later.
[Attach: 04_calculate_clicked.png and/or the .mlapp source]
Here is the callback code from my MATLAB Web App’s ‘Calculate’ button. It reads values from UI components like app.FrequencyEditField.Value and app.AmplitudeDropDown.Value, performs a computation, and plots the result to app.UIAxes. Please refactor this into a standalone MATLAB function with explicit input arguments and output arguments. Remove all app.* references.
[Attach: 05_plot_result.png]
This is what the MATLAB plot looks like. Please create the equivalent chart using Chart.js (or Plotly.js). The x-axis represents frequency in Hz and the y-axis represents amplitude in dB. Use the same axis labels and a similar color scheme. The data will come from a JSON API response — just use placeholder arrays for now.
Now connect the front end to MATLAB Production Server. When the user clicks ‘Calculate’, gather the input values from the HTML form, construct a JSON payload matching the MATLAB Production Server schema, POST it to the Production Server endpoint, parse the response from lhs.mwdata, and pass the result arrays to the Chart.js chart to update the display.
[Attach: side-by-side of original vs. current HTML]
Here is the original MATLAB app and my current HTML version. Can you adjust the styling to more closely match the original? Specifically, the button should be blue, the chart area should have a light gray background, and the input fields should be arranged in two columns.
This is the most difficult step in the migration. In a MATLAB Web App, callback functions typically interleave UI reads, computation, and UI writes. You need to tease these apart.
% Inside the .mlapp (before refactoring)
function CalculateButtonPushed(app, event)
% UI READ — get values from widgets
freq = app.FrequencyEditField.Value;
amp = app.AmplitudeDropDown.Value;
duration = app.DurationSlider.Value;
method = app.MethodDropDown.Value;
% ALGORITHM — the core computation
t = 0:1/1000:duration;
switch method
case 'Sine'
signal = amp * sin(2 * pi * freq * t);
case 'Square'
signal = amp * square(2 * pi * freq * t);
end
spectrum = abs(fft(signal));
freqAxis = linspace(0, 1000, length(spectrum));
% UI WRITE — display results
plot(app.UIAxes, t, signal);
title(app.UIAxes, 'Generated Signal');
app.RMSLabel.Text = sprintf('RMS: %.4f', rms(signal));
end
Go through each callback and mark every line as belonging to one of three categories:
• UI Read: Any line that reads from app.SomeComponent.Value.
• Algorithm: Any computation that does not reference app.*.
• UI Write: Any line that writes to app.SomeComponent, calls plot(app.UIAxes, ...), or updates labels/tables.
Create a new .m file with a function signature that takes the UI-read values as inputs and returns the UI-write data as outputs.
function [t, signal, freqAxis, spectrum, rmsValue] = generateSignal(freq, amp, duration, method)
% generateSignal - Core algorithm extracted from MATLAB Web App
%
% Inputs:
% freq - Frequency in Hz (double)
% amp - Amplitude (double)
% duration - Duration in seconds (double)
% method - Waveform type: 'Sine' or 'Square' (string)
%
% Outputs:
% t - Time vector (1xN double)
% signal - Generated signal (1xN double)
% freqAxis - Frequency axis for spectrum (1xM double)
% spectrum - FFT magnitude spectrum (1xM double)
% rmsValue - RMS value of signal (scalar double)
t = 0:1/1000:duration;
switch method
case 'Sine'
signal = amp * sin(2 * pi * freq * t);
case 'Square'
signal = amp * square(2 * pi * freq * t);
otherwise
error('generateSignal:InvalidMethod', ...
'Method must be ''Sine'' or ''Square''.');
end
spectrum = abs(fft(signal));
freqAxis = linspace(0, 1000, length(spectrum));
rmsValue = rms(signal);
end
Before compiling, test the function from the MATLAB command window:
[t, sig, fAx, spec, rmsVal] = generateSignal(50, 1.0, 2, 'Sine');
plot(t, sig);
title(sprintf('RMS = %.4f', rmsVal));
• Multiple callbacks: If your app has several buttons that trigger different computations, extract each into its own function. You can deploy multiple functions within a single MPS archive.
• Shared state: If callbacks depend on shared state (e.g., a dataset loaded in one callback and processed in another), consider restructuring so each function call is self-contained. Pass the data as an input argument rather than relying on persistent state.
• Startup/initialization: If the app’s startupFcn pre-loads data or sets defaults, move that logic into the function or into a separate initialization function.
The following prompts demonstrate how to use an AI assistant to help tease apart a .mlapp callback into a clean, standalone MATLAB function. Use these as templates — paste your own callback code in place of the examples.
"Here is a callback function from my MATLAB Web App. Please go through it line by line and label each line as one of three zones:
1. UI Read — any line that reads a value from an app.* component
2. Algorithm — any line that performs computation without referencing app.*
3. UI Write — any line that writes to an app.* component, plots to app.UIAxes, or updates a display element
|
function RunButtonPushed(app, event) material = app.MaterialDropDown.Value; thickness = app.ThicknessEditField.Value; temperature = app.TempSlider.Value; includeDefects
= app.DefectsCheckBox.Value; props = getMaterialProperties(material); stress = props.E * thickness * temperature / 1000; if includeDefects stress = stress * 0.85; end strain = linspace(0, 0.1, 200); stressCurve =
stress * (1 - exp(-30 * strain)); yieldPoint =
interp1(stressCurve, strain, 0.9 * max(stressCurve)); plot(app.UIAxes, strain, stressCurve,
'b-', 'LineWidth', 1.5); hold(app.UIAxes, 'on'); xline(app.UIAxes, yieldPoint,
'r--', 'LineWidth', 1); hold(app.UIAxes, 'off'); title(app.UIAxes, sprintf('%s Stress-Strain Curve', material)); xlabel(app.UIAxes, 'Strain'); ylabel(app.UIAxes, 'Stress (MPa)'); app.YieldPointLabel.Text
= sprintf('Yield Point: %.4f', yieldPoint); app.MaxStressLabel.Text
= sprintf('Max Stress: %.1f MPa', max(stressCurve)); end |
After labeling, list the inputs (from the UI Read zone) and the outputs (the data needed by the UI Write zone) that the extracted function will need."
"Based on the three-zone analysis above, please extract the Algorithm zone into a standalone MATLAB function. Requirements:
1. The function must have no app.* references whatsoever.
2. Every value that was read from a UI component becomes an input argument.
3. Every piece of data that was written to a UI component or plotted becomes an output argument.
4. Add full documentation with a comment block listing each input and output, its type, and its size.
5. Add input validation with an otherwise clause or validateattributes where appropriate.
6. Name the function descriptively (e.g., computeStressStrain rather than RunButtonPushed).
Also provide a test snippet I can paste into the MATLAB Command Window to verify it works."
"This callback produces two plots and populates a UITable. Please extract it into a standalone function. For the table data, return it as a MATLAB table or cell array that I can later convert to JSON for the front end.
|
function AnalyzeButtonPushed(app,
event)
data = app.DataTable.Data;
colNames = app.DataTable.ColumnName;
filterIdx = app.FilterDropDown.Value;
smoothWindow = app.SmoothSpinner.Value;
filtered = data(data(:,1) > filterIdx, :);
smoothed = movmean(filtered(:,2), smoothWindow);
residuals = filtered(:,2) - smoothed;
stats = [mean(residuals), std(residuals), max(abs(residuals))];
summaryTable = array2table(stats,
...
'VariableNames', {'Mean',
'StdDev', 'MaxAbs'});
plot(app.UIAxes1, filtered(:,1),
filtered(:,2), 'b-', ...
filtered(:,1), smoothed, 'r-', 'LineWidth', 1.5);
legend(app.UIAxes1, 'Raw', 'Smoothed');
title(app.UIAxes1, 'Signal Analysis');
bar(app.UIAxes2, residuals);
title(app.UIAxes2, 'Residuals');
app.StatsTable.Data
= summaryTable;
app.RMSELabel.Text
= sprintf('RMSE: %.4f', std(residuals)); end |
The function should return all the data I need to recreate both plots and the table on the front end. Include the x and y data for each plot as separate output arguments, plus the summary statistics."
"This callback depends on data that was loaded by a separate LoadDataButtonPushed callback and stored in app.LoadedData. I need to make it self-contained so it can run on MATLAB Production Server with no persistent state between calls.
|
function ProcessButtonPushed(app,
event)
% Uses app.LoadedData
which was set by a different callback
rawData = app.LoadedData;
nBins = app.BinsSpinner.Value;
normalize = app.NormalizeCheckBox.Value;
if normalize
rawData = (rawData
- mean(rawData)) / std(rawData);
end
[counts, edges] = histcounts(rawData, nBins);
centers = edges(1:end-1) + diff(edges)/2;
peakVal = max(counts);
peakBin = centers(counts
== peakVal);
bar(app.UIAxes,
centers, counts);
title(app.UIAxes,
'Distribution');
app.PeakLabel.Text
= sprintf('Peak at %.2f (count: %d)', peakBin(1), peakVal); end |
Please refactor this so that rawData is passed as an input argument instead of coming from app.LoadedData. The front end will be responsible for sending the data with each API call. Also explain how I should structure this data in the MPS JSON payload — should it go as a flat mwdata array with the appropriate mwsize?"
MATLAB plots need to be replicated on the front end using a JavaScript charting library. The two best options are Chart.js and Plotly.js.
|
Feature |
Chart.js |
Plotly.js |
|
Line, bar, scatter, pie |
Excellent |
Excellent |
|
Interactive zoom/pan |
Plugin required |
Built-in |
|
3D plots, surface plots |
Not supported |
Built-in |
|
Heatmaps, contour plots |
Limited |
Built-in |
|
Bundle size |
~60 KB |
~3 MB |
|
Learning curve |
Simpler |
More complex |
Rule of thumb: Use Chart.js for standard 2D line/bar/scatter charts. Use Plotly.js if you need 3D surfaces, contours, heatmaps, or advanced interactivity.
Include Chart.js from CDN and add a canvas element:
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="signalChart" width="600" height="400"></canvas>
Initialize the chart in JavaScript:
const ctx = document.getElementById('signalChart').getContext('2d');
const signalChart = new Chart(ctx, {
type: 'line',
data: {
labels: [], // Will be populated from API response
datasets: [{
label: 'Signal',
data: [],
borderColor: 'rgb(0, 114, 189)', // MATLAB default blue
borderWidth: 1.5,
pointRadius: 0,
fill: false
}]
},
options: {
responsive: true,
scales: {
x: { title: { display: true, text: 'Time (s)' } },
y: { title: { display: true, text: 'Amplitude' } }
}
}
});
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
<div id="signalPlot" style="width:600px;height:400px;"></div>
const trace = {
x: [], // Time vector from API
y: [], // Signal data from API
type: 'scatter',
mode: 'lines',
name: 'Signal',
line: { color: 'rgb(0, 114, 189)', width: 1.5 }
};
Plotly.newPlot('signalPlot', [trace], {
title: 'Generated Signal',
xaxis: { title: 'Time (s)' },
yaxis: { title: 'Amplitude' }
});
Use these colors to give charts a familiar MATLAB feel:
|
Line |
RGB |
Hex |
|
1st |
(0, 0.4470, 0.7410) |
#0072BD |
|
2nd |
(0.8500, 0.3250, 0.0980) |
#D95319 |
|
3rd |
(0.9290, 0.6940, 0.1250) |
#EDB120 |
|
4th |
(0.4940, 0.1840, 0.5560) |
#7E2F8E |
|
5th |
(0.4660, 0.6740, 0.1880) |
#77AC30 |
|
6th |
(0.3010, 0.7450, 0.9330) |
#4DBEEE |
|
7th |
(0.6350, 0.0780, 0.1840) |
#A2142F |
The data flow needs to be clearly mapped: HTML form fields → JSON input → MATLAB function arguments → MATLAB return values → JSON output → chart data.
For each input argument of your extracted MATLAB function, there must be a corresponding HTML element:
MATLAB function signature:
generateSignal(freq, amp, duration, method)
Mapping:
freq ← <input type="number" id="freqInput" value="50">
amp ← <input type="number" id="ampInput" value="1.0">
duration ← <input type="range" id="durationSlider" min="0.1" max="5">
method ← <select id="methodSelect">
<option value="Sine">Sine</option>
<option value="Square">Square</option>
</select>
Gather these into a JavaScript object:
function getInputs() {
return {
freq: parseFloat(document.getElementById('freqInput').value),
amp: parseFloat(document.getElementById('ampInput').value),
duration: parseFloat(document.getElementById('durationSlider').value),
method: document.getElementById('methodSelect').value
};
}
Your MATLAB function returns multiple outputs. These arrive in the Production Server JSON response as lhs (left-hand side) outputs:
Lhs[0].mwdata[0] → t → Chart X-axis (time domain)
lhs[0].mwdata[1] → signal → Chart Y-axis (time domain)
lhs[0].mwdata[2] → freqAxis → Chart X-axis (frequency domain)
lhs[0].mwdata[3] → spectrum → Chart Y-axis (frequency domain)
lhs[0].mwdata[4] → rmsValue → Display in a label or badge
MATLAB Production Server exposes deployed functions as REST endpoints. The RESTful API specification can be found at https://www.mathworks.com/help/mps/restfuljson/restful-api.html and the JSON schema is documented at https://www.mathworks.com/help/mps/restfuljson/json-representation-of-matlab-data-types.html.
http://<server>:<port>/<archiveName>/<functionName>
Example:
POST http://localhost:9910/signalApp/generateSignal
Production Server expects a specific JSON structure. Each input argument is an element in the rhs (right-hand side) array. The nargout (number of output arguments) field tells MATLAB how many output arguments to return. In this example there are 4 input arguments:
{
"nargout": 5,
"rhs": [
{ "mwdata": [50], "mwsize": [1, 1], "mwtype": "double" },
{ "mwdata": [1.0], "mwsize": [1, 1], "mwtype": "double" },
{ "mwdata": [2], "mwsize": [1, 1], "mwtype": "double" },
{ "mwdata": ["Sine"], "mwsize": [1, 4], "mwtype": "char" }
]
}
• nargout: The number of outputs you expect from the MATLAB function. Must match the number of return values in your function signature (or be fewer).
• rhs: An ordered array of input arguments. The order must match the MATLAB function signature.
• mwdata: The actual value(s). For scalars: [50]. For vectors: [1, 2, 3, 4, 5]. For strings: ["Sine"].
• mwsize: The MATLAB array dimensions as [rows, columns]. A scalar is [1, 1]. A 1×N row vector is [1, N].
• mwtype: The MATLAB data type. Common values: "double", "single", "char", "logical", "int32", "cell", "struct".
Scalar double:
{ "mwdata": [3.14], "mwsize": [1, 1], "mwtype": "double" }
Row vector (1×5 double):
{ "mwdata": [1, 2, 3, 4, 5], "mwsize": [1, 5], "mwtype": "double" }
Matrix (2×3 double) — stored column-major:
{ "mwdata": [1, 4, 2, 5, 3, 6], "mwsize": [2, 3], "mwtype": "double" }
Note: MATLAB stores matrices column-major, so [1 2 3; 4 5 6]
flattens as [1, 4, 2, 5, 3, 6] (down columns first).
String / char array:
{ "mwdata": ["Sine"], "mwsize": [1, 4], "mwtype": "char" }
If you are just passing in a few arguments and don’t wish to specify the size or type, you can send a JSON payload to the HTTP POST like this:
{"nargout":1,
"rhs":[308.0,2.4,3.5,"Step"]}
In this example there is just 1 output result, and you are passing in 3 numeric values plus a string input value.
These prompts demonstrate how to ask an AI assistant to generate the JavaScript that bridges the HTML front end to the MATLAB Production Server API. They are split into two separate tasks: one for capturing inputs and calling the API, and one for handling the response and rendering charts.
"I have the following MATLAB function deployed to MATLAB Production Server as an archive called signalApp:
|
function [t,
signal, freqAxis, spectrum, rmsValue]
= generateSignal(freq, amp, duration, method) % Inputs: %
freq
- Frequency in Hz (double scalar) %
amp - Amplitude (double
scalar) %
duration - Duration in seconds (double scalar) %
method - Waveform type: 'Sine'
or 'Square' (char) % % Outputs: %
t - Time vector (1×N
double) %
signal - Generated signal
(1×N double) %
freqAxis - Frequency axis
for spectrum (1×M double) %
spectrum -
FFT magnitude spectrum (1×M double) %
rmsValue - RMS value of
signal (double scalar) My HTML form has these elements:
My MPS server runs at http://serverhostname.domain.com:9910. Please generate a JavaScript function
that: 1. Reads the value
from each HTML element. 2. Validates that
frequency and amplitude are positive numbers and duration is
within range. 3. Constructs the
MPS JSON payload with the correct rhs array, where
each element has mwdata, mwsize,
and mwtype. Pay special attention to the mwsize for the method string — it must be [1, numChars] where numChars is the
length of the selected string (e.g. [1, 4] for 'Sine', [1, 6] for 'Square'). 4. Sets nargout to 5 (matching the five outputs of the function). 5. POSTs the
payload to http://serverhostname.domain.com:9910/signalApp/generateSignal
with Content-Type: application/json. 6. Includes error
handling: disable the button and show 'Computing...' while the request is in
flight, catch HTTP errors, and display a user-friendly error message in a
Bootstrap alert div with id errorAlert. 7. Returns the
parsed lhs array from the response so a separate
function can handle the charting. Also show me the exact JSON payload that would be sent if the user enters frequency 50, amplitude 1.0, duration 2, and selects 'Sine' — so I can verify it with cURL or Postman before testing in the browser." |
|
"My JavaScript calculate()
function (from Prompt A above) calls generateSignal
on MATLAB Production Server and gets back a response with this structure: { "lhs":
[ { "mwdata": [0, 0.001, 0.002, ...], "mwsize": [1, 2001], "mwtype":
"double" }, { "mwdata": [0, 0.309, 0.588, ...], "mwsize": [1, 2001], "mwtype":
"double" }, { "mwdata": [0, 0.5, 1.0, ...], "mwsize":
[1, 2001], "mwtype": "double" }, { "mwdata": [42.1, 38.7, 12.3, ...], "mwsize": [1, 2001], "mwtype":
"double" }, { "mwdata": [0.7071], "mwsize":
[1, 1], "mwtype":
"double" } ] } Where:
I have the following elements on my
page:
Please generate JavaScript that: 1. Extracts each
output from the lhs array using result.lhs.mwdata[0..3]
for the vectors and result.lhs.mwdata[4]
for the RMS scalar. 2. Initializes two
Chart.js line charts: o Time domain: MATLAB default
blue (#0072BD), x-axis label 'Time (s)', y-axis
label 'Amplitude', title 'Generated Signal'. o Frequency domain: MATLAB default
orange (#D95319), x-axis label 'Frequency (Hz)',
y-axis label 'Magnitude', title 'FFT Spectrum'. o Both charts
should have no point markers (pointRadius: 0) and
animation disabled for fast updates. 3. Updates both
charts when new results arrive (update the data in place rather than
destroying and recreating the chart instances). 4. Updates the rmsDisplay span with the formatted RMS value (e.g.
"RMS: 0.7071"). 5. If the time or
frequency vectors have more than 5,000 points, downsamples
both the x and y arrays to 2,000 points before passing them to Chart.js. Make
sure the downsampling keeps both arrays in sync
(same indices). Also show me the equivalent Plotly.js version of the same two charts as a commented-out alternative, so I can switch to Plotly later if I need interactive zoom/pan on the spectrum." |
Ensure your standalone function works correctly from the MATLAB command window with no dependencies on app.* objects, UI components, or desktop features. All helper functions or data files it depends on must be on the MATLAB path.
In MATLAB, run:
productionServerCompiler
Or navigate to the Apps tab and select Production Server Compiler.
1. Click Add Exported Function and select your .m file (e.g., generateSignal.m).
2. If your function depends on other .m files, toolboxes, or data files, add them under Files required for your function to run.
3. Set the Archive Name (e.g., signalApp). This becomes part of the API URL.
Click Package. This produces a .ctf file (e.g., signalApp.ctf) in the output directory. Alternatively, compile from the command line:
compiler.build.productionServerArchive(“generateSignal.m”,”ARchiveName”,”signalApp”)
Copy the .ctf file into the Production Server auto_deploy directory:
<MPS_INSTALL>/auto_deploy/signalApp.ctf
Production Server monitors this folder and automatically loads new or updated archives.
Here is a complete JavaScript function that gathers inputs from the HTML form, constructs the Production Server JSON payload, calls the API, and updates the chart.
async function calculate() {
// 1. Gather inputs from the HTML form
const freq = parseFloat(document.getElementById('freqInput').value);
const amp = parseFloat(document.getElementById('ampInput').value);
const duration = parseFloat(document.getElementById('durationSlider').value);
const method = document.getElementById('methodSelect').value;
// 2. Construct the Production Server JSON payload
const payload = {
nargout: 5,
rhs: [
{ mwdata: [freq], mwsize: [1, 1], mwtype: "double" },
{ mwdata: [amp], mwsize: [1, 1], mwtype: "double" },
{ mwdata: [duration], mwsize: [1, 1], mwtype: "double" },
{ mwdata: [method], mwsize: [1, method.length], mwtype: "char" }
]
};
// 3. POST to MATLAB Production Server
const MPS_URL = 'http://serverhostname.domain.com:9910/signalApp/generateSignal';
try {
const response = await fetch(MPS_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!response.ok) throw new Error(`MPS returned HTTP ${response.status}`);
const result = await response.json();
// 4. Extract output data from the lhs array
const t = result.lhs[0].mwdata[0];
const signal = result.lhs[0].mwdata[1];
const freqAxis = result.lhs[0].mwdata[2];
const spectrum = result.lhs[0].mwdata[3];
const rmsValue = result.lhs[0].mwdata[4];
// 5. Update the Chart.js chart
signalChart.data.labels = t;
signalChart.data.datasets[0].data = signal;
signalChart.update();
// 6. Update the RMS display
document.getElementById('rmsDisplay').textContent =
`RMS: ${rmsValue.toFixed(4)}`;
} catch (error) {
console.error('API call failed:', error);
alert('Calculation failed. Check the console for details.');
}
}
document.getElementById('calculateBtn').addEventListener('click', calculate);
If the front end is served from a different origin than Production Server (different host or port), you will encounter CORS errors. You have three options:
1. Serve the front end from MATLAB Production Server itself (see Section 11) — avoids CORS entirely since everything is same-origin.
2. Configure a reverse proxy (e.g., NGINX or Apache) that serves both the front end and proxies /api/ to MATLAB Production Server.
3. Enable CORS on MATLAB Production Server by configuring the cors-allowed-origins property in main_config (available in newer MATLAB Production Server versions).
A successful MATLAB Production Server response has HTTP status 200 and a JSON body containing an lhs (left-hand side) array. Each element corresponds to one output argument in order:
{
"lhs": [
{ "mwdata": [0, 0.001, 0.002, 0.003], "mwsize": [1, 4], "mwtype": "double" },
{ "mwdata": [0, 0.309, 0.588, 0.809], "mwsize": [1, 4], "mwtype": "double" },
{ "mwdata": [42.5], "mwsize": [1, 1], "mwtype": "double" }
]
}
Key points:
• lhs[0] is the first output argument, lhs[1] is the second, and so on.
• The actual data values are always in lhs[n].mwdata — this is the array you pass to your chart.
• lhs[n].mwsize tells you the MATLAB dimensions. For a 1×1000 row vector, it will be [1, 1000].
• For scalar outputs, the value is lhs[n].mwdata[0].
Test your deployed function directly from the command line:
curl -X POST http://serverhostname.domain.com:9910/signalApp/generateSignal \
-H "Content-Type: application/json" \
-d '{
"nargout": 5,
"rhs": [
{"mwdata": [50], "mwsize": [1,1], "mwtype": "double"},
{"mwdata": [1.0], "mwsize": [1,1], "mwtype": "double"},
{"mwdata": [0.05], "mwsize": [1,1], "mwtype": "double"},
{"mwdata": ["Sine"], "mwsize": [1,4], "mwtype": "char"}
]
}'
Pipe through jq for formatted output:
curl -s -X POST ... | jq .
curl -s -X POST ... | jq '.lhs[] | {mwtype, mwsize}'
1. Create a new POST request.
2. Set the URL to http://serverhostname.domain.com:9910/signalApp/generateSignal.
3. Under the Headers tab, add Content-Type: application/json.
4. Under the Body tab, select raw and paste the JSON payload.
5. Click Send and inspect the response body.
|
HTTP Status |
Meaning |
What to Check |
|
404 |
Archive or function not found |
Is the .ctf deployed? Is the URL path correct? |
|
400 |
Bad request / invalid JSON |
Check JSON syntax, mwtype, mwsize consistency. |
|
500 |
MATLAB runtime error |
Check MPS logs in <MPS_INSTALL>/log/main.log. |
You have two primary options for serving the HTML/CSS/JS front end.
MATLAB Production Server can serve static files directly, which means your front end and API share the same origin — no CORS configuration needed. Use this approach for simplicity and protoyping/test. We do not recommend this for a production deployment.
Open the Production Server configuration file at <MPS_INSTALL>/config/main_config and add or modify these properties:
--enable-static-folders true
--static-folders-root /opt/mps/static
Place your front-end files in the static folder:
/opt/mps/static/
├── index.html
├── css/
│ └── styles.css
├── js/
│ └── app.js
└── images/
└── logo.png
After restarting Production Server, your app is available at:
http://localhost:9910/index.html (front end)
http://localhost:9910/signalApp/generateSignal (API)
Since both are on the same origin, there are no CORS issues.
Other useful main_config settings:
--http 9910 # Port MPS listens on (default 9910)
--https 9920 # HTTPS port
--ssl-cert /path/to/cert.pem # HTTPS certificate
--ssl-key /path/to/key.pem # HTTPS private key
--num-workers 4 # Number of MATLAB workers
--request-timeout 120 # Request timeout (seconds)
If you need more control (custom routing, authentication, SSR, integration with an existing site), serve the front end from a dedicated web server and proxy API calls to Production Server.
server {
listen 80;
server_name myapp.example.com;
root /var/www/myapp;
index index.html;
location /api/ {
proxy_pass http://localhost:9910/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 120s;
}
}
For local development and testing:
cd /path/to/your/web/files
python3 -m http.server 8080
1. Screenshot every state and interaction of the original .mlapp.
2. Extract the algorithmic logic into a standalone MATLAB function with clear inputs and outputs.
3. Test the function from the MATLAB command window.
4. Build the HTML/CSS/JS front end, matching the original layout.
5. Replace MATLAB plots with Chart.js or Plotly.js.
6. Compile the function into a .ctf archive using the Production Server Compiler.
7. Deploy the .ctf to the MATLAB Production Server auto_deploy folder.
8. Test the API with cURL or Postman before wiring up the front end.
9. Connect the front end to the API using fetch().
10. Deploy the front end via MATLAB Production Server static serving or an external web server.
• One function per computation: Keep MATLAB Production Server functions focused. If the original app has three separate calculations triggered by different buttons, create three functions. Multiple function can be added into a single .ctf.
• Always use Postman or cURL first: Never debug the API through the front end. Isolate the layers. If the cURL call works but the front end does not, the issue is in your JavaScript.
• Watch the mwsize field: A common mistake is getting the size wrong, especially for strings. "Sine" has mwsize: [1, 4] (4 characters). Getting this wrong can cause silent failures.
• Keep durations short during testing: If your function generates large arrays (e.g., 10 seconds of audio at 44.1 kHz), use short durations while debugging to keep response payloads manageable.
• Downsample before plotting: If the MATLAB function returns very large arrays, consider adding a downsampling step in the MATLAB function or JavaScript to keep the chart responsive.
• Log the raw response: During development, always console.log(result) so you can inspect the full lhs structure in browser DevTools.
• Version your archives: Include a version in the archive name (e.g., signalApp_v2) during development so you can roll back if needed.
• Handle errors gracefully: Check response.ok before parsing JSON. Display user-friendly error messages and log details to the console.
· Claude skill you can install to help you migrate MATLAB Apps to HTML apps powered by MATLAB Production Server