Skip to main content
A user hits a bug mid-match and all you get is “the overlay stopped working” - with no logs, that report is undebuggable. This guide builds a bug report flow end to end: the user fills out a short form in your overlay, and your code submits their description together with their log files to your Overlayed dashboard, where you can download and read them.
This guide covers the bug report workflow specifically. For the full API reference, see User Feedback and How Logging Works.

How Bug Report Logs Work

Before writing any code, it helps to know four things about how Overlayed handles feedback and logs:
  1. File logging is automatic. Every message sent through overlay.log is written to a log file in your application’s log directory. One file is created per day, and files are deleted after 7 days. You don’t manage these files - they’re what gets shipped with a bug report.
  2. submitFeedback does the collection for you. One call zips your app’s log files, Overlayed’s own recent log files, and any extra files you attach, then uploads the bundle with the user’s message. There is no server for you to build - submissions land in the Overlayed Dashboard.
  3. It’s a main-process API. The report form lives in a renderer window, but overlay.log.submitFeedback runs in the Electron main process. The form hands its data to the main process over standard Electron IPC.
  4. The user reports; your code submits. End users only type a message and click a button - calling the API, and deciding what context rides along with it, is your code’s job.

Building the Bug Report Flow

1

Log things worth reading

A bug report is only as useful as the logs inside it. Log state transitions and failures as they happen, using scoped loggers so every line is prefixed with where it came from:
main.ts
const matchLog = overlay.log.scope("match");

matchLog.info("Match started");
matchLog.error("Failed to parse round event", { raw: event });
The top-level overlay.log.info / warn / error methods take a single message string; scoped loggers accept any number of arguments, so use a scope when you want to attach data to a line.
debug calls are dropped entirely unless debug mode is enabled - they won’t appear in log files from production users. Log anything you’ll need for diagnosing real-world reports at info or above.
2

Create the report window

Create a window to host your report form, with a preload script for the IPC bridge:
main.ts
import path from "node:path";

const reportWindow = overlay.windows.createWindow({
	width: 420,
	height: 360,
	show: false,
	webPreferences: {
		preload: path.join(__dirname, "preload.js"),
	},
});
See Creating Windows for out-of-game vs. in-game windows - a report form works as either.
3

Bridge the form to the main process

In the window’s preload script, expose a function the form can call:
preload.ts
import { contextBridge, ipcRenderer } from "electron";

contextBridge.exposeInMainWorld("bugReports", {
	submit: (report: { email?: string; message: string }) => ipcRenderer.invoke("bug-report:submit", report),
});
Your form UI then calls window.bugReports.submit({ email, message }) when the user clicks send.
4

Submit the feedback

In the main process, handle the IPC call by passing the user’s input to submitFeedback:
main.ts
import { ipcMain } from "electron";

ipcMain.handle("bug-report:submit", async (_event, report: { email?: string; message: string }) => {
	const feedbackId = await overlay.log.submitFeedback("bug_report", {
		email: report.email,
		message: report.message,
	});

	return feedbackId !== undefined;
});
The call resolves to a feedback ID on success, or undefined if the upload failed. There is no automatic retry, so use the return value to show a success or error state in your form.
5

Read the report in your dashboard

Open overlay.dev/feedback/bug-reports. Each submission includes the email, username, and message the user provided, plus a zip containing your app’s log files and Overlayed’s internal logs from the last 7 days.

User-Driven vs. Event-Triggered Reports

submitFeedback doesn’t have to wait for a user to fill out a form - you can also fire it from code when something goes badly wrong.
The flow above. The user describes the problem in their own words, and your code forwards it. The same form can double as a feature-request box - submit with type "suggestion" instead of "bug_report":
await overlay.log.submitFeedback("suggestion", {
	email: report.email,
	message: report.message,
});
Both types travel through the same pipeline and include the same log zip; the type controls how the submission is categorized in your dashboard.

Attaching Your Own Context

Logs tell you what happened over time; sometimes you also want a snapshot of right now. submitFeedback gives you two places to put it:
  • extra - a record of structured data stored on the feedback entry itself. Good for small values you want to see at a glance.
  • additionalFiles - named files added inside the log zip. Good for larger payloads like a serialized match state.
main.ts
await overlay.log.submitFeedback(
	"bug_report",
	{
		message: report.message,
		extra: {
			inGame: overlay.hasAnyActiveGames(),
		},
	},
	{
		additionalFiles: {
			"matchState.json": JSON.stringify(currentMatchState),
		},
	},
);
Everything is uploaded as-is - log lines, extra values, and additional files are not sanitized. Don’t log secrets, tokens, or personal data you wouldn’t want attached to a bug report.

Sending Logs Somewhere Else

If you want the same log bundle in your own pipeline - your support system, your own storage - getLogsZip returns the identical zip (a JSZip instance) without submitting anything:
const zip = overlay.log.getLogsZip({
	additionalFiles: {
		"matchState.json": JSON.stringify(currentMatchState),
	},
});

const zipBase64 = await zip.generateAsync({ type: "base64" });
await sendToMyServer(zipBase64);
Building the zip writes its own progress lines (“Zipping: …”) into the log. Pass { silent: true } in the options to suppress them.

FAQ

submitFeedback resolves to undefined and the failure is written to the log - it does not throw for a failed request, and it does not queue or retry the submission. Check the return value and show an error state so the user can try again later.
Up to 7 days. Log files are created per day and automatically deleted after 7 days, so the zip contains at most a week of your app’s logs, plus Overlayed’s own log files from the same window.
No. Only messages sent through the logging module (overlay.log and its scopes) are written to the log files that get zipped. If you want it in bug reports, log it through overlay.log.
Only if you shipped with debug mode enabled. When debug mode is off, debug calls are dropped entirely - not written to the console or the file. See Debug Mode.
No - crash dumps are a separate flow. When a monitored game closes, Overlayed scans for new dump files and emits a new event on overlay.crashDumps; you submit those with overlay.crashDumps.submit(id). See Crash Dumps.
Partially, automatically. Every submission carries an anonymous per-install user identifier and, when available, the release ID of the running build - so you can group reports by install and by version without doing anything. For a real identity, collect email or username in your form (both are optional), or put your own account ID in extra.
No. submitFeedback uploads directly to Overlayed, and you read submissions in the dashboard. Only reach for getLogsZip if you additionally want the logs in your own systems.
Yes - submitFeedback works in development too. Submit a test report and check your dashboard’s feedback page for the entry and its attached zip.

Next Steps

User Feedback Reference

The full submitFeedback and getLogsZip API, including all feedback fields.

How Logging Works

Log levels, file output, and where log files live on disk.

Crash Dumps

Detect game crashes and let users submit the dump files.

Debug Mode

Enable verbose logging during development and troubleshooting.