Scripts and exports
Run any GhidraScript through the daemon, as owner or contained and rolled back, and write large exports with progress, cancel and an inspectable partial file.
On this page
Anything the session has no command for is a GhidraScript away. script runs one the way Ghidra's Script Manager does: currentProgram, currentAddress, getScriptArgs(), println and monitor all work, and isRunningHeadless() is true. Scripts are Java, or Python through a Ghidra script provider, and take arguments in args.
Owner or contained
Who may change the program decides how a script runs:
| You are | The script runs | Its changes |
|---|---|---|
| the program's owner, or one of its shared writers | alone on the program (exclusive lock) | stay, unsaved unless "save": true |
| anyone else | alongside other readers (shared lock) | rolled back at the end, always |
A contained script runs inside a transaction that is always rolled back, so it cannot change what other clients see or what is saved. Pass "exclusive": true to run it alone anyway. With "program": null, a script runs with only the active project, or none.
The answer carries the printed text in output, the mode (owner or contained), changed and unsaved.
Where scripts come from
{"scriptPath": ["C:/re/scripts"]}Ghidra's own script directories are already on the path: s.call("script", script="HelloWorldScript.java") prints Hello World.
Exports: a script that writes a large file
A bulk export (every function, string or reference as JSONL, say) is a script that writes its own file. This one writes every function of the current program:
// ExportFunctions.java: one JSON line per function. args: <output path>
import java.io.BufferedWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.*;
import ghidra.app.script.GhidraScript;
import ghidra.program.model.listing.Function;
public class ExportFunctions extends GhidraScript {
@Override
protected void run() throws Exception {
Path out = Paths.get(getScriptArgs()[0]).toAbsolutePath();
Path part = out.resolveSibling(out.getFileName() + ".part");
monitor.initialize(currentProgram.getFunctionManager().getFunctionCount());
long n = 0;
try (BufferedWriter w = Files.newBufferedWriter(part, StandardCharsets.UTF_8)) {
for (Function f : currentProgram.getFunctionManager().getFunctions(true)) {
monitor.checkCancelled();
w.write("{\"name\":\"" + f.getName() + "\",\"entry\":\"" + f.getEntryPoint() +
"\",\"size\":" + f.getBody().getNumAddresses() + "}\n");
if (++n % 1000 == 0) {
w.flush();
monitor.setProgress(n);
}
}
}
Files.move(part, out, StandardCopyOption.REPLACE_EXISTING);
println("exported " + n + " functions to " + out);
}
}Run it on the long lane, without blocking your connection:
out = r"C:\re\out\functions.jsonl"
rid = s.send("script", script="ExportFunctions.java", args=[out], artifact=out, parallel=True)
# ... progress events arrive; s.call("status") shows the job and its artifact
r = s.wait(rid){"name":"FUN_140001010","entry":"140001010","size":211}The contract an integrator can rely on, which the engine's tests exercise with a 600,000-line, 55 MB export:
- The file is yours. The script writes wherever the daemon's user can write. Declare the same path as
artifact: the result andstatusecho it. - Not capped.
maxChars(200,000 by default, at most 16,000,000) limits only the printedoutput. Print a summary, not the data. - Progress.
monitor.initialize,setProgressandsetMessagebecomeprogressevents with the request's id, at most one a second however often the script calls them. - Cancel.
cancelwith the request id,timeout, a disconnect or the daemon stopping all cancel the script's monitor. A script that callsmonitor.checkCancelled()stops promptly and the request answerscancelled. One that never checks runs to the end. - Partial files. The session deletes nothing it did not create. Writing to
<path>.partand renaming at the end, as above, means a cancelled export leaves a.partfile that is valid up to its last complete line, and a reader that finds<path>knows it finished.
What scripts can reach
Most window-only workflows are already plain Ghidra API, so they need a script, not new commands: version tracking between two programs of a project, C headers parsed into a data type archive, auto-created structures, re-analysis of ranges, labels and bookmarks. The engine repository's examples/HeadlessApiProbe.java does each of these without a window.
Checked against the engine on 2026-10-26. Every detail is in the README on GitHub.