DazScript Framework is a TypeScript toolkit for writing Daz Studio scripts. It layers a full TypeScript development experience on top of DAZ Script (Qt Script / ECMAScript 5.1), and ships a fluent dialog builder so you can build UIs in code without touching the Qt widget API directly.
- Why use it?
- Quick Start: Hello World
- Quick Start: A Simple Dialog
- Documentation
- Installation & Setup
- Project Configuration
- The
action(...)Entrypoint - Build Output: Launcher Shims
- Generated Setup Script
- Setup Keyboard Shortcuts
- Action-Level Bundles
- Building UIs: Dialogs & Observables
- Observables
- Dialog Builder Reference
- Available Helpers
- Directory Structure
- Development & Publishing
- Resources
- Examples
DAZ Script gives you direct access to the entire Daz Studio API. The DazScript Framework builds on that foundation and adds:
- TypeScript everywhere — full autocompletion and type checking for every Daz Studio API, your own models, and every helper in the framework.
- Fast UI development — a fluent builder API lets you describe dialogs declaratively without touching the Qt widget API by hand.
- Two-way data binding — link your data model to UI controls so they stay in sync automatically. The user types in a field and your model updates; you update the model in code and the UI reflects it instantly. No manual synchronization needed.
- One-command build —
npm run buildcompiles TypeScript to.dsafiles that Daz Studio runs directly. - Stable launcher shims — built scripts use a launcher/implementation layout so iterating on your code never requires reinstalling actions in Daz Studio.
- Automated installer generation —
npm run installerproduces a full setup dialog by reading action metadata from your source code.
A script that shows a message box in Daz Studio.
npm install dazscript-framework dazscript-typesnpx dazscript initFollow the prompt for your AppData author namespace (e.g. YourName/my-project). This creates dazscript.config.ts, tsconfig.json, and wires the build, build:encrypted, build:release, watch, encrypt, icons, and installer scripts into package.json.
Create src/hello-world.dsa.ts:
import { action } from '@dsf/core/action';
import { info } from '@dsf/helpers/message-box-helper';
action({ text: 'Hello World' }, () => {
info('Hello World!');
});Files ending in .dsa.ts are compiled as runnable Daz Studio entry points.
npm run buildOutput lands in ./out/.
Option A — Copy: copy the out/ folder into your Daz Studio scripts directory.
Option B — Symlink (recommended for development, re-runs pick up the latest build automatically):
# Windows — run as Administrator
mklink /D "C:\Users\[Username]\Documents\DAZ 3D\Studio\My Library\Scripts\MyScripts" "C:\path\to\project\out"
# macOS
ln -s /path/to/project/out ~/Documents/DAZ\ 3D/Studio/My\ Library/Scripts/MyScriptsThen in Daz Studio: Scripts > MyScripts > Hello World. A message box appears.
A dialog with a name input and an OK/Cancel button pair.
import { action } from '@dsf/core/action';
import { BasicDialog } from '@dsf/dialog/basic-dialog';
import { Observable } from '@dsf/lib/observable';
import { info } from '@dsf/helpers/message-box-helper';
// The model holds state
class GreetModel {
name$ = new Observable<string>('World');
}
// The dialog describes the UI
class GreetDialog extends BasicDialog {
constructor(private model: GreetModel) {
super('Greet');
}
protected build(): void {
this.add.label('Enter your name:');
this.add.edit().value(this.model.name$);
this.add.button('Say Hello').clicked(() => this.dialog.accept());
}
}
action({ text: 'Greet Dialog' }, () => {
const model = new GreetModel();
const dialog = new GreetDialog(model);
if (dialog.ok()) {
info(`Hello, ${model.name$.value}!`);
}
});add.edit().value(observable) creates a two-way binding: typing in the field updates name$.value, and assigning name$.value in code updates the field.
Install the framework and its peer dependency:
npm install dazscript-framework dazscript-typesScaffold a new project:
npx dazscript initIf --app-data-path is not provided, init prompts for the AppData author namespace and uses the current folder name as the product segment.
This generates:
dazscript.config.tstsconfig.jsonpackage.jsonscript wiring forbuild,build:encrypted,watch,encrypt,icons, andinstallerbuild:releaseuses--log-level warnbefore encryption so release packages suppress debug and trace output
Available init flags:
npx dazscript init --menu-path /MyScripts --scripts-path ./src --out-dir ./out --app-data-path YourName/my-project| Flag | Description |
|---|---|
--menu-path |
Default menu where scripts appear in Daz Studio |
--scripts-path |
Where the generator scans for runnable .dsa.ts entry files |
--out-dir |
Where build writes .dsa files and copies icons |
--app-data-path |
AppData namespace for launcher fallback (Author/Product format) |
Builds also accept --log-level <trace|debug|info|warn|error|off>. This sets the minimum runtime log level for the compiled scripts. Use debug or trace during development and warn for release packages.
Use --scripts-path ./src/scripts when runnable files live under a subfolder; use --scripts-path ./src when they are at the source root.
dazscript.config.ts is the single configuration file for a project:
import { defineConfig } from 'dazscript-framework/config';
export default defineConfig({
scriptsPath: './src',
outDir: './out',
defaultMenuPath: '/MyScripts',
appDataPath: 'YourName/my-project', // required
bundleName: 'My Project', // optional — used in the setup dialog title
});appDataPath is required for builds that generate launcher shims and must be unique across your projects.
Every runnable .dsa.ts file calls action(...) at module scope. This defines how the script registers in Daz Studio and what it executes.
action({
text: 'My Script',
menuPath: '#{defaultMenuPath}/Tools',
shortcut: 'CTRL+SHIFT+M',
toolbar: 'MyToolbar',
group: 'Tools',
description: 'Does something useful',
}, () => {
info('Running!');
});action(...) also accepts a class with a run() method:
class MyScript {
run(): void {
info('Running!');
}
}
action({ text: 'My Script' }, MyScript);| Parameter | Description |
|---|---|
text |
Label shown in Daz Studio |
menuPath |
Menu path where the action is registered. Set to false to skip. Defaults to defaultMenuPath from config. |
shortcut |
Keyboard shortcut (e.g. CTRL+SHIFT+H) |
toolbar |
Toolbar name the action should appear on |
group |
Grouping label for related actions in Daz Studio |
description |
Longer description for the action |
icon |
Image path used for the installed custom action. Overrides discovered icon files. |
bundle |
Generates a setup script beside the action. true → Setup.dsa.ts, a string → Setup <name>.dsa.ts |
Each built action produces two files:
out/<script>.dsa— the stable launcher registered with Daz Studio (menus, toolbars, shortcuts)out/<folder>/lib/<script-name>/<script-name>.dsa— the implementation bundle the launcher executes
When you rebuild, only the implementation bundle changes. The launcher path stays stable, so re-registering the action in Daz Studio is normally not required.
At runtime the launcher looks for a local encrypted script.dse bundle first, then the local script.dsa bundle, then the same encrypted/plain fallback paths under App.getAppDataPath()/<appDataPath>.
To encrypt implementation bundles before packaging, run the build and then call Daz Studio through the framework CLI:
npx dazscript encrypt --out-dir ./out --daz-studio "C:/Program Files/DAZ 3D/DAZStudio4/DAZStudio.exe"The encrypt command launches Daz Studio with -headless -noPrompt, converts each implementation bundle under out/**/lib/**/*.dsa to a matching .dse file, and deletes the source .dsa after the matching encrypted file is written. Add --keep-source to leave the plain implementation bundles in place.
Individual script packages can wrap this command in package.json scripts such as npm run encrypt or npm run build:encrypted so release packaging does not depend on remembering the full Daz Studio executable argument.
npm run installerThis scans all .dsa.ts entry files, reads each top-level action(...) call, and generates src/Setup.dsa.ts automatically. No installer code to maintain by hand.
The generated setup dialog:
- Shows an install checkbox per action with columns for Action, Shortcut, Description, Menu, and Toolbar
- Adds a
Keyboard Shortcutstab when a project defines shortcut JSON - Includes a search box that filters across all columns
- Supports Select All / Deselect All on the visible rows
- Lets the user right-click to set or reset a shortcut (overrides shown with
[ovr]) - Initializes from the current Daz Studio install state — already-installed actions show as checked
- Uses
bundleNamefromdazscript.config.tsin the window title
Applying the dialog:
- Checked rows are installed or updated
- Unchecked rows are removed from their menu and toolbar targets
- Affected toolbars are rebuilt; empty framework-created toolbars are removed
- Selected keyboard shortcut rows are applied after actions are installed
Custom action icons are selected from action(...) metadata and sibling image files in this order:
- Explicit
action({ icon: '...' }) scriptname.action.pngscriptname.pngscriptname.dsa.pnglegacy fallback
scriptname.action.png is the installed custom action icon. Daz Studio uses the same action icon for menu and toolbar placements. scriptname.png is the preferred script/content icon fallback. scriptname.dsa.png is a legacy fallback kept for older projects and will be removed in a future breaking release.
Setup dialog header assets are optional and are discovered beside src/Setup.dsa.ts:
src/Setup.header.pngsrc/Setup.tip.pngsrc/Setup.pngsrc/Setup.dsa.pnglegacy fallback
Header text can be placed in src/Setup.header.html, src/Setup.header.md, or src/Setup.header.txt, with src/Setup.html, src/Setup.md, and src/Setup.txt as script-named fallbacks. The installer generator embeds that text into Setup.dsa.ts, so Daz Studio does not need to read the text file at setup time. The setup dialog renders the header body with DzTextBrowser rich text support; no Markdown conversion is performed. The image remains a deployed PNG asset and is resolved relative to the generated setup script at runtime.
The same layout is available to custom dialogs through add.header({ imagePath, html, text, height, imageWidth }).build(). Use html for rich text, or text for escaped plain text.
This replaces the older Install.dsa.ts / Uninstall.dsa.ts pattern. The installer generator removes those legacy files if they exist.
Projects can define keyboard shortcuts for both framework custom actions and built-in Daz Studio actions. The installer generator looks for shortcut JSON in this order:
keyboardShortcutsPath,shortcutsPath, oractionAcceleratorsPathindazscript.config.tssrc/keyboard-shortcuts.jsonsrc/action-accelerators.jsonkeyboard-shortcuts.jsonaction-accelerators.json
The JSON can be an array or an object containing actions, shortcuts, or accelerators. Each entry can use the Action Accelerator Finder style fields:
[
{
"name": "DzRenderAction",
"text": "Render",
"shortcut": "CTRL+R"
}
]Accepted shortcut fields are shortcut, accelerator, or key. Accepted action-name fields are name or action.
At build time the JSON is embedded into generated Setup.dsa.ts; Daz Studio does not need to read the original JSON file at setup time. During setup, the Keyboard Shortcuts tab shows the action label, current shortcut, new shortcut, action type, and conflicts. The user chooses which shortcut rows to apply.
Before changing a non-custom Daz Studio action shortcut, setup writes the original value to:
App.getAppDataPath()/<appDataPath>/Installer/keyboard-shortcuts-backup.json
Shortcut setup is part of the generated setup dialog. The generator does not create a separate shortcut-only uninstall script.
The bundle property on action(...) is separate from the project-level bundleName in config.
When bundle is set, the installer generator also writes a setup script beside that action:
bundle: true→ writesSetup.dsa.tsbundle: 'Utilities'→ writesSetup Utilities.dsa.ts
Those bundle-scoped setup files use the same setup dialog helper and also receive the project bundleName.
The framework uses a Model-View pattern with reactive bindings.
import { AppSettings } from '@dsf/lib/settings';
import { Observable } from '@dsf/lib/observable';
// AppSettings adds automatic persistence under the given namespace
class MyModel extends AppSettings {
constructor() {
super('YourName/MyDialog');
}
name$ = new Observable<string>();
enabled$ = new Observable<boolean>(false);
}import { BasicDialog } from '@dsf/dialog/basic-dialog';
class MyDialog extends BasicDialog {
constructor(private readonly model: MyModel) {
super('My Dialog');
}
protected build(): void {
const { add, model } = this;
add.group('Settings').build(() => {
add.label('Name:');
add.edit().value(model.name$);
add.checkbox('Enabled').value(model.enabled$);
});
}
}action({ text: 'My Dialog Script' }, () => {
const model = new MyModel();
const dialog = new MyDialog(model);
if (dialog.ok()) {
// model.name$.value holds whatever the user typed
}
});Observable<T> is lightweight reactive state. Controls bound with .value(observable) stay in sync automatically.
const name$ = new Observable<string>('initial');
// Subscribe to changes
name$.connect((value) => console.log(value));
// Set value — fires all subscribers
name$.value = 'updated';
// Transform values before they are applied
name$.intercept((prev, next) => next.trim());
// Batch updates without firing subscribers mid-batch
name$.pause(() => {
name$.value = 'a';
name$.value = 'b'; // only 'b' fires after the pause block
});Use this.add inside build() to construct the UI declaratively.
Widgets
| Builder | Description |
|---|---|
add.label(text) |
Static text label |
add.edit() |
Single-line text input |
add.button(text) |
Push button |
add.checkbox(text) |
Checkbox |
add.radio(text) |
Radio button |
add.comboBox() |
Drop-down list |
add.listBox() |
Scrollable list |
add.slider(min, max) |
Numeric slider |
add.colorPicker() |
Color picker |
add.nodeSelection() |
Daz Studio node selector |
Layout
| Builder | Description |
|---|---|
add.group(text) |
Group box |
add.tab(text) |
Tab page |
add.horizontal(fn) |
Horizontal layout row |
add.splitter() |
Resizable splitter |
Most widget builders expose a fluent chain:
add.edit()
.value(model.name$) // two-way binding
.toolTip('Enter your name')
.readOnly(false);
add.button('Apply')
.clicked(() => applyChanges());
add.tab('Options').build(() => {
add.group('Colors').build(() => {
add.colorPicker().value(model.color$);
});
});The framework ships helpers for common Daz Studio tasks, all importable from @dsf/helpers/*.
| Module | Key functions |
|---|---|
scene-helper |
getRoot(), getSelectedNode(), getNodes() |
node-helper |
Type checks (isFigure, isBone), transforms, visibility |
property-helper |
Find, read, and adjust node properties |
array-helper |
distinct(), flatten(), groupBy() |
string-helper |
Case, trimming, splitting |
directory-helper |
File and path operations |
message-box-helper |
info(), warn(), error() message boxes |
progress-helper |
Progress dialogs |
menu-helper |
Custom menus |
undo-helper |
Undo stack integration |
import * as SceneHelper from '@dsf/helpers/scene-helper';
import * as NodeHelper from '@dsf/helpers/node-helper';
const figures = SceneHelper.getNodes().filter(n => NodeHelper.isFigure(n));my-daz-scripts/
├── src/
│ ├── hello-world.dsa.ts # runnable entry point → compiles to .dsa
│ ├── my-dialog-model.ts # plain TypeScript — model or helper class
│ ├── my-dialog.ts
│ └── my-dialog-script.dsa.ts # runnable entry point
├── out/ # build output — launchers, bundles, icons
├── dazscript.config.ts
├── tsconfig.json
└── package.json
Files ending in .dsa.ts are treated as runnable entry points and compiled to .dsa. Plain .ts files are modules — imported by entry points but not compiled independently.
Common commands
| Command | What it does |
|---|---|
npm run build |
Compile TypeScript → Daz Script |
npm run build:release |
Build with --log-level warn, then encrypt implementation bundles |
npm run watch |
Recompile on every save |
npm run installer |
Generate the setup dialog |
npm run icons |
Copy icon assets to the output folder |
This package uses semantic-release for automatic versioning and npm publishing.
| Prefix | Effect |
|---|---|
fix: ... |
Patch bump (1.0.0 → 1.0.1) |
feat: ... |
Minor bump (1.0.0 → 1.1.0) |
BREAKING CHANGE: ... in commit body |
Major bump (1.0.0 → 2.0.0) |
| No prefix | No version bump |
Examples:
fix: resolve layout overflow in group builder
feat: add tree view builder
feat: refactor action entrypoint
BREAKING CHANGE: action() now requires an explicit menuPath
Every push to master automatically:
- Analyzes commit messages since the last release
- Updates the version in
package.json - Builds the project
- Creates a GitHub release with changelog
- Publishes to npm
No manual steps required.
- DAZ Script Reference — official Daz Studio scripting documentation
- dazscript-types — TypeScript type definitions for the Daz Studio API
The src/examples/ folder contains ready-to-run scripts demonstrating common patterns and fuller reference implementations for common Daz Studio workflows.