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.
1. Overview and Approach
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 an 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:
- Screenshot and document every interaction in the original
.mlapp. - Extract the algorithmic callback logic into a standalone MATLAB function.
- Build an HTML/JS front end that replicates the original UI.
- Replace MATLAB plots with Chart.js or Plotly.js equivalents.
- Compile the MATLAB function, deploy to MATLAB Production Server, and wire the front end to the API.
2. Documenting the Original App with Screenshots
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.
What to Capture
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.
Naming Convention
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
How to Use the Screenshots
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.
3. Example Prompts for AI-Assisted Conversion
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).
Prompt 1: Analyze the Original App Layout
[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.
Prompt 2: Build the HTML Skeleton
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.
Prompt 3: Extract the MATLAB Callback Logic
[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.
Prompt 4: Create the JavaScript Chart
[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.
Prompt 5: Wire Up the API Call
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.
Prompt 6: Iterate on Styling and Behavior
[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.
4. Separating UI Logic from Algorithm Logic
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.
Anatomy of a Typical .mlapp Callback
% 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
Step 1: Identify the Three Zones
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, callsplot(app.UIAxes, ...), or updates labels/tables.
Step 2: Extract the Algorithm into a Standalone Function
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
Step 3: Test the Function Independently
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));
Guidelines for Complex Apps
- 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
startupFcnpre-loads data or sets defaults, move that logic into the function or into a separate initialization function.
Example Prompts for Extracting Callbacks
The following prompts demonstrate how to use an AI assistant to help tease apart a .mlapp callback into a clean, standalone MATLAB function.
Prompt A: Identify the Three Zones
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, (2) Algorithm, (3) UI Write. After labeling, list the inputs and outputs that the extracted function will need.
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
Prompt B: Extract and Write the Standalone Function
Based on the three-zone analysis above, please extract the Algorithm zone into a standalone MATLAB function. Requirements: (1) No app.* references. (2) Every UI-read value becomes an input argument. (3) Every UI-written value becomes an output argument. (4) Add full documentation. (5) Add input validation. (6) Name it descriptively. Also provide a test snippet.
Prompt C: Handle a Callback with Multiple Plots and Table Output
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
Prompt D: Refactor a Callback That Uses Shared State
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. Please refactor so rawData is passed as an input argument. Also explain how I should structure this data in the MPS JSON payload.
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
5. Replacing MATLAB Plots with JavaScript Charts
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.
When to Use Which
| 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.
Chart.js Example
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' } }
}
}
});
Plotly.js Example
<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' }
});
MATLAB Default Color Order
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 |
6. Handling Input and Output Data
The data flow needs to be clearly mapped: HTML form fields → JSON input → MATLAB function arguments → MATLAB return values → JSON output → chart data.
Input: HTML Fields to Function Arguments
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
};
}
Output: Function Returns to Chart Data
Your MATLAB function returns multiple outputs. These arrive in the Production Server JSON response as lhs (left-hand side) outputs:
lhs[0].mwdata -> t -> Chart X-axis (time domain) lhs[1].mwdata -> signal -> Chart Y-axis (time domain) lhs[2].mwdata -> freqAxis -> Chart X-axis (frequency domain) lhs[3].mwdata -> spectrum -> Chart Y-axis (frequency domain) lhs[4].mwdata -> rmsValue -> Display in a label or badge
7. Constructing the HTTP POST to MATLAB Production Server
MATLAB Production Server exposes deployed functions as REST endpoints. The RESTful API specification can be found at the MPS REST API documentation and the JSON schema is documented at JSON representation of MATLAB data types.
Endpoint URL Pattern
http://<server>:<port>/<archiveName>/<functionName> Example: POST http://localhost:9910/signalApp/generateSignal
JSON Request Schema
Production Server expects a specific JSON structure. Each input argument is an element in the rhs (right-hand side) array. The nargout field tells MATLAB how many output arguments to return:
{
"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" }
]
}
Field Descriptions
- 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 1xN row vector is[1, N]. - mwtype: The MATLAB data type. Common values:
"double","single","char","logical","int32","cell","struct".
Handling Different Data Types
Scalar double:
{ "mwdata": [3.14], "mwsize": [1, 1], "mwtype": "double" }
Row vector (1x5 double):
{ "mwdata": [1, 2, 3, 4, 5], "mwsize": [1, 5], "mwtype": "double" }
Matrix (2x3 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 use a simplified payload:
{"nargout": 1, "rhs": [308.0, 2.4, 3.5, "Step"]}
Example Prompts
Prompt A: Capture Inputs, Build the JSON Payload, and Call the API
I have the following MATLAB function deployed to MATLAB Production Server as an archive called signalApp. [Include function signature with inputs/outputs documented.] My HTML form has these elements: [list elements with IDs]. Please generate JavaScript that reads values, validates them, constructs the MPS JSON payload with correct rhs array (mwdata, mwsize, mwtype), sets nargout, POSTs to the endpoint, includes error handling, and returns the parsed lhs array. Also show me the exact JSON payload for sample inputs so I can verify with cURL.
Prompt B: Handle the API Response and Render Charts
My JavaScript calculate() function calls generateSignal on MATLAB Production Server and gets back a response with lhs array. [Describe structure.] Please generate JavaScript that extracts each output from lhs, initializes Chart.js charts with MATLAB default colors, updates them when new results arrive, displays scalar values, and downsamples large arrays. Also show the Plotly.js equivalent as a commented-out alternative.
8. Compiling and Deploying to MATLAB Production Server
Step 1: Prepare the Function
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.
Step 2: Open the Production Server Compiler
In MATLAB, run:
productionServerCompiler
Or navigate to the Apps tab and select Production Server Compiler.
Step 3: Configure the Archive
- Click Add Exported Function and select your
.mfile (e.g.,generateSignal.m). - If your function depends on other
.mfiles, toolboxes, or data files, add them under Files required for your function to run. - Set the Archive Name (e.g.,
signalApp). This becomes part of the API URL.
Step 4: Package the Archive
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')
Step 5: Deploy to MATLAB Production Server
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.
9. Calling the MATLAB Production Server API from JavaScript
Here is a complete JavaScript function that gathers inputs, constructs the 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 = '/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;
const signal = result.lhs[1].mwdata;
const freqAxis = result.lhs[2].mwdata;
const spectrum = result.lhs[3].mwdata;
const rmsValue = result.lhs[4].mwdata[0];
// 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);
Handling CORS
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:
- Serve the front end from MATLAB Production Server itself (see Section 11) -- avoids CORS entirely since everything is same-origin.
- Configure a reverse proxy (e.g., NGINX or Apache) that serves both the front end and proxies API calls to MATLAB Production Server.
- Enable CORS on MATLAB Production Server by configuring the
cors-allowed-originsproperty inmain_config(available in newer versions).
10. Handling the Response and Debugging with Postman/cURL
Understanding the MATLAB Production Server Response Format
A successful response has HTTP status 200 and a JSON body containing an lhs (left-hand side) array. Each element corresponds to one output argument:
{
"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. lhs[n].mwsizetells you the MATLAB dimensions.- For scalar outputs, the value is
lhs[n].mwdata[0].
Debugging with cURL
Test your deployed function directly from the command line:
curl -X POST http://localhost: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}'
Debugging with Postman
- Create a new POST request.
- Set the URL to your endpoint.
- Under the Headers tab, add
Content-Type: application/json. - Under the Body tab, select raw and paste the JSON payload.
- Click Send and inspect the response body.
Common Error Responses
| 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. |
11. Deploying the Front End
You have two primary options for serving the HTML/CSS/JS front end.
Option A: Use MATLAB Production Server Built-In Static File Serving
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 prototyping/test. We do not recommend this for a production deployment.
Step 1: Enable Static File Serving in main_config
Open the Production Server configuration file at <MPS_INSTALL>/config/main_config and add or modify:
--enable-static-folders true --static-folders-root /opt/mps/static
Step 2: Organize Your Files
/opt/mps/static/
+-- index.html
+-- css/
| +-- styles.css
+-- js/
| +-- app.js
+-- images/
+-- logo.png
Step 3: Access the App
After restarting Production Server:
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.
MATLAB Production Server Configuration Reference
--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)
Option B: Use a Separate Web Server
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.
Nginx Example Configuration
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;
}
}
Simple Development Server
For local development and testing:
cd /path/to/your/web/files python3 -m http.server 8080
12. Summary and Best Practices
Workflow Checklist
- Screenshot every state and interaction of the original
.mlapp. - Extract the algorithmic logic into a standalone MATLAB function with clear inputs and outputs.
- Test the function from the MATLAB command window.
- Build the HTML/CSS/JS front end, matching the original layout.
- Replace MATLAB plots with Chart.js or Plotly.js.
- Compile the function into a
.ctfarchive using the Production Server Compiler. - Deploy the
.ctfto the MATLAB Production Serverauto_deployfolder. - Test the API with cURL or Postman before wiring up the front end.
- Connect the front end to the API using
fetch(). - Deploy the front end via MATLAB Production Server static serving or an external web server.
Best Practices
- One function per computation: Keep MPS functions focused. If the original app has three separate calculations, create three functions. Multiple functions can be added into a single
.ctf. - Always use Postman or cURL first: Never debug the API through the front end. Isolate the layers.
- Watch the mwsize field: A common mistake is getting the size wrong, especially for strings.
"Sine"hasmwsize: [1, 4]. - Keep durations short during testing: 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.
- Log the raw response: Always
console.log(result)during development so you can inspect the fulllhsstructure. - Version your archives: Include a version in the archive name (e.g.,
signalApp_v2) during development. - Handle errors gracefully: Check
response.okbefore parsing JSON. Display user-friendly error messages.
13. Useful Tools
- Claude skill you can install to help you migrate MATLAB Apps to HTML apps powered by MATLAB Production Server.