.Zeugwerk/config.json reference
.Zeugwerk/config.json is the single configuration file used by both zkbuild-action and zkmake. It describes the solution structure, declares package dependencies, maps TwinCAT library references, and optionally configures source patches. The file lives at the root of your repository under .Zeugwerk/config.json.
The easiest way to create and maintain this file is through the Twinpack IDE extension, which generates it from your open solution.
Minimal example
A single library project with no external packages:
{
"fileversion": 1,
"solution": "MyProject.sln",
"projects": [
{
"name": "MyProject",
"plcs": [
{
"name": "MyPLC",
"version": "1.0.0.0",
"type": "Library"
}
]
}
]
}
Top-level fields
A config.json is either a solution root (declares solution + projects directly) or a modules root (declares only modules, which point at other folders that each have their own solution-root config.json). Don't mix the two: putting modules in a config that also has solution/projects doesn't give you both, it just disables the solution/projects part (see Modules below).
| Field | Required | Description |
|---|---|---|
fileversion |
Yes | Schema version. Always 1. |
solution |
Only for a solution root | Path to the Visual Studio solution file (.sln), relative to the .Zeugwerk/ folder's parent directory. Omit entirely for a modules root. |
projects |
Only for a solution root | Array of project objects (see Projects). Omit entirely for a modules root. |
modules |
Only for a modules root | Array of subdirectory paths, each containing its own solution-root .Zeugwerk/config.json. Used for monorepos or solutions split across multiple folders. Mutually exclusive with solution/projects (see Modules). |
Modules
When a solution is split across multiple folders, list the subdirectories under modules instead of setting solution/projects on that same config. Each listed folder must contain its own solution-root .Zeugwerk/config.json (with its own solution and projects).
{
"fileversion": 1,
"modules": [
"modules/AxisControl",
"modules/SafetyPLC"
]
}
Warning
modules and solution/projects are mutually exclusive on the same config, even though nothing stops you from writing both into the same JSON file. If modules is non-empty, Twinpack treats the file as a modules-only root: it skips resolving solution entirely, and commands that manage packages directly (add, remove, update, restore, list, ...) refuse to run against it with Modules are not supported, regardless of whether solution/projects are also present. Only set-version --sync-framework-packages and the internal "purge" step recurse into modules. So a config with both fields set doesn't do "solution build + modules build" - the solution/projects part is simply dead weight.
The build system processes each module in order, accumulating compiled libraries into a shared cache. Later modules can depend on libraries produced by earlier modules.
Projects
Each entry in projects maps to a Visual Studio project in the solution.
| Field | Required | Description |
|---|---|---|
name |
Yes | Must match the project name in the .sln file exactly. |
type |
No | Project file type. One of Plc, Hmi, Database. Defaults to Plc. Used to tell PLC projects apart from other project kinds (e.g. TwinCAT HMI or database projects) that may live in the same solution. |
plcs |
Yes | Array of PLC objects (see PLCs). Only meaningful for Plc projects. |
server-instance |
No | Name of the TF2000 runtime service instance folder for this project (under ProgramData\...\service\). Only relevant if you deploy this project as a TF2000 service. |
PLCs
Each entry in plcs describes one TwinCAT PLC project within the Visual Studio project.
| Field | Required | Description |
|---|---|---|
name |
Yes | PLC name as it appears in the TwinCAT solution tree. |
version |
Yes | Version in Major.Minor.Patch.Build format, e.g. 1.2.0.0. Used for the output artifact filename. |
type |
Yes | Project type. See PLC types. |
title |
No | Human-readable display name. Defaults to name if omitted. |
distributor-name |
No | Name of the distributor shown in the TwinCAT library manager. |
packages |
No | External packages to install before building. See Packages. |
references |
No | TwinCAT system library references. See References. |
bindings |
No | Reserved for future use. Currently written by Twinpack as an empty object and not consumed by zkbuild-action or zkmake. |
patches |
No | Source patches to apply before compiling. See Patches. |
Metadata that's only relevant when publishing this PLC as a package (description, authors, display name, project URL, entitlement, license, icon, ...) is deliberately not part of config.json. See Publishing a package.
PLC types
| Value | Description |
|---|---|
Library |
Standard TwinCAT library. The build produces a .library file. |
FrameworkLibrary |
Same as Library; indicates the PLC is part of the Zeugwerk Framework family. |
Application |
TwinCAT application (runtime project). No .library is produced; the solution is built and can be activated to a target. |
UnitTestApplication |
TwinCAT test application in the same solution (native TcUnit). Built with the release solution and executed in the test phase. See In-solution test PLC. |
Packages
The packages array declares external Twinpack packages that the PLC depends on. The build system downloads, resolves, and installs them automatically before compilation. This is the array that the Twinpack VSIX extension edits when you click "Add"/"Update" on a package in the Catalog, and it's also what you edit by hand for the fields that have no UI (see below).
{
"packages": [
{
"name": "ZCore",
"version": "1.2.19.0",
"branch": "release/1.2",
"target": "TC3.1",
"configuration": "Distribution",
"distributor-name": "Zeugwerk GmbH"
}
]
}
| Field | Required | Description |
|---|---|---|
name |
Yes | Package name as registered on the package source. |
version |
No | Exact version to install, e.g. 1.2.19.0. If omitted, Twinpack resolves and installs the latest version available on the configured sources every time (add/restore/update will keep "floating" to latest instead of being pinned). |
branch |
No | Source branch for the package (e.g. release/1.2, main). Defaults to main. |
target |
No | TwinCAT target platform (e.g. TC3.1). Defaults to TC3.1. |
configuration |
No | Package build configuration (e.g. Distribution, Release, Snapshot). Defaults to Release. |
distributor-name |
No | Distributor name shown in the TwinCAT library manager (e.g. Zeugwerk GmbH), used together with name to identify the package on the source. |
namespace |
No | Overrides the namespace TwinCAT uses for this reference. See Namespace. |
parameters |
No | Generic-library parameters written into the reference. See Parameters. |
options |
No | Flags controlling how the reference is added (optional, hidden, qualified-only, etc.). See Options. |
Note
There is no repository field. Older examples floating around sometimes show one (as a leftover from an earlier Twinpack version); it is silently ignored by current Twinpack versions if present. Which source a package comes from is determined by matching name/distributor-name/branch/target/configuration against your configured package sources (twinpack.exe config), not by a field in config.json.
Namespace
namespace overrides the TwinCAT namespace used for this specific reference (the Namespace VSProperty on a headed AddLibrary/AddPlaceholder reference, or the <Namespace> element written into the .plcproj in headless mode). This is useful when you need to reference a library under a namespace alias different from its default.
{
"name": "ZCore",
"version": "1.2.19.0",
"distributor-name": "Zeugwerk GmbH",
"namespace": "MyCoreAlias"
}
Important behavior to know:
- Nothing sets this automatically. Twinpack never infers or writes back a namespace on its own, whether from the
.plcproj, from "Create config", or fromadd/restore/update. If the field is absent, Twinpack leaves TwinCAT's own default namespace untouched. The only way to populate it is to add it toconfig.jsonyourself; there is currently no field for it in the Twinpack VSIX UI (it is shown read-only in the Catalog once set) or in the CLI. - Explicit values always win over whatever TwinCAT would otherwise compute, and are re-applied on every
add/restore/update. - It survives transitive dependencies. If a package that already has a
namespaceconfigured is later pulled in only as a dependency of another package you add (e.g.ZCoreis a dependency ofZPlatform), Twinpack still appliesZCore's configured namespace, it does not get dropped just becauseZCoreitself wasn't the package you added directly.
Parameters
parameters is a dictionary of key/value pairs written as generic-library parameters on the reference (the <Parameter ListName="..." Key="..." Value="..."/> entries under a PlaceholderReference in the .plcproj, for TwinCAT libraries that expose generic parameters). Each key must use the form ListName.Key; entries that don't match this format are skipped with a warning.
{
"name": "MyGenericLibrary",
"version": "1.0.0.0",
"distributor-name": "My Company",
"parameters": {
"GVL_Parameter.MAX_INSTANCES": "8",
"GVL_Parameter.BUFFER_SIZE": "1024"
}
}
Like namespace, parameters is never populated automatically, must be added to config.json by hand (there's no VSIX/CLI input for it), and is preserved across transitive dependency resolution once set.
Options
options is an object of boolean flags that control how the reference is added:
| Field | Default | Description |
|---|---|---|
library-reference |
false |
If true, the package is added as a compiled LibraryReference (AddLibrary) instead of a PlaceholderReference (AddPlaceholder). Most packages should leave this at the default (placeholder), which resolves against whatever matching version is installed. Manual-only, no UI/CLI control. |
optional |
false |
Marks the reference as optional in TwinCAT (missing/unresolved reference does not fail the build). Exposed as the "Optional" checkbox under "Advanced" in the Twinpack VSIX Catalog. |
hide |
false |
Sets "hide when referenced as dependency" on the reference. Manual-only, no UI/CLI control. |
publish-all-symbols |
false |
Publishes all IEC symbols of the referenced library in the container. Exposed as the "Publish all IEC symbols" checkbox under "Advanced" in the Twinpack VSIX Catalog. |
qualified-only |
false |
Requires qualified access (Namespace.Symbol) for everything from this library. Exposed as the "Qualified only" checkbox in the Twinpack VSIX Catalog. |
{
"name": "ZCore",
"version": "1.2.19.0",
"distributor-name": "Zeugwerk GmbH",
"options": {
"optional": true,
"qualified-only": true
}
}
When a package is pulled in only as a transitive dependency of another package, its options are not inherited from any existing config.json entry for it. Instead, qualified-only is carried over from the parent package (dependencies of a qualified-only library are qualified-only too), and the remaining flags reset to false for the dependency, since e.g. "optional" or "hide" only make sense relative to the package you actually added directly.
Full example with packages
{
"fileversion": 1,
"solution": "TwinCAT Project1.sln",
"projects": [
{
"name": "TwinCAT Project1",
"plcs": [
{
"name": "MyPLC",
"version": "1.0.0.0",
"type": "Library",
"distributor-name": "My Company",
"packages": [
{
"name": "ZCore",
"version": "1.2.19.0",
"branch": "release/1.2",
"target": "TC3.1",
"configuration": "Distribution",
"distributor-name": "Zeugwerk GmbH",
"namespace": "MyCoreAlias",
"options": {
"qualified-only": true
}
},
{
"name": "Tc2_MC2",
"version": "3.3.38.0",
"target": "TC3.1",
"configuration": "Release",
"distributor-name": "Beckhoff Automation GmbH"
}
],
"references": {
"*": [
"Tc2_Standard=*",
"Tc2_System=*",
"Tc3_Module=*"
]
}
}
]
}
]
}
References
The references field maps TwinCAT target platforms to a list of system library references that must be present in the PLC project. These are Beckhoff system libraries (e.g. Tc2_Standard) that ship with TwinCAT, not Twinpack packages.
{
"references": {
"*": [
"Tc2_Standard=*",
"Tc2_System=*",
"Tc2_MC2=*",
"Tc3_Module=*"
]
}
}
The key "*" applies to all TwinCAT target versions. You can also use a specific version string (e.g. "TC3.1.4024.56") to apply references only when building for that exact version.
Each entry in the array follows the format LibraryName=Version, where * means "any installed version".
Publishing a package
Pushing a PLC as a package to a Twinpack repository (twinpack push / the Twinpack VSIX publish
dialog) needs some extra metadata: description, author(s), display name, project URL,
entitlement, license, license files, icon. None of this lives in config.json. It's
intentionally kept out of the checked-in build configuration, since it's presentation metadata for
the catalog listing, not something zkbuild-action or zkmake need to build anything.
Instead, at push time:
description,authorsanddisplay-nameare derived automatically from the PLC's ownDescription/Author/TitleTwinCAT project properties (read from the.plcproj, or from the compiled.libraryfile once it exists). You don't need to set them anywhere.distributor-name(used as the publisher name for the package) is still the field under PLCs.- Everything else that has no TwinCAT-side equivalent -
project-url,entitlement,license,license-file,license-tmc-file,icon-file,binary-download-url- is supplied per push, either as a flag ontwinpack push(e.g.--project-url,--license-file) or as a one-off field typed into the VSIX publish dialog. Whatever you type in the dialog is only used for that push; it is not written back to config.json. If omitted, whatever is already published for that package on the server is kept as-is.
Run twinpack push --help for the full list of flags.
Patches
Patches modify the PLC source code before compilation. They are declared under patches at the PLC level and are never committed to the compiled output - they only affect the build.
Three types are supported:
Platform patches
Applied automatically when building for a specific TwinCAT version. Useful for version-specific compatibility fixes.
{
"patches": {
"platform": {
"TC3.1.4024.56": [
"patches/fix-4024.patch",
"patches/workaround.replacement"
]
}
}
}
Argument patches
Applied only when the build tool is explicitly told to apply a named patch (via --patch <identifier> in zkmake, or the patch argument in zkbuild-action). Useful for feature flags, toggling optional behaviour, or build variants.
Note
Argument patches are not available on the free tier of zkbuild-action. Contact us or open an issue if you need them for open source use.
{
"patches": {
"argument": {
"enable-feature-x": [
"patches/enable-feature-x.replacement"
]
}
}
}
Activate an argument patch:
:: zkmake (on-premises)
zkmake.exe build --patch enable-feature-x
# zkbuild-action (GitHub)
- name: Build
uses: Zeugwerk/zkbuild-action@1.0.0
with:
username: ${{ secrets.ACTIONS_ZGWK_USERNAME }}
password: ${{ secrets.ACTIONS_ZGWK_PASSWORD }}
patch: enable-feature-x
License patches
Declares OEM license keys to embed for a given build argument, keyed the same way as argument patches (i.e. applied only when that argument is passed to the build tool). Each entry is an object, not a file path:
| Field | Required | Description |
|---|---|---|
key |
Yes | The license key to embed. |
oem-id |
No | OEM identifier the key is issued for. |
comment |
No | Free-text note, e.g. what the key is for or who issued it. |
{
"patches": {
"license": {
"enable-feature-x": [
{
"key": "XXXX-XXXX-XXXX-XXXX",
"oem-id": "1234",
"comment": "OEM license for feature X, issued 2026-01-01"
}
]
}
}
}
Patch file formats
.patch files - Standard Git patches applied via git apply. Create one with:
git diff > patches/my-change.patch
.replacement files - Search-and-replace over source files. A JSON file with three fields:
| Field | Description |
|---|---|
filter |
Glob pattern selecting which files to modify (e.g. *.TcPOU, *.TcGVL). |
search |
Regular expression to find. Special regex characters must be escaped. |
replace |
Replacement string. Use $1…$N for regex capture groups. |
{
"filter": "*.TcPOU",
"search": "FEATURE_FLAG := FALSE",
"replace": "FEATURE_FLAG := TRUE"
}
Complete example
{
"fileversion": 1,
"solution": "MyProject.sln",
"projects": [
{
"name": "MyProject",
"plcs": [
{
"name": "MyPLC",
"version": "2.1.0.0",
"type": "Library",
"title": "My PLC Library",
"distributor-name": "My Company",
"packages": [
{
"name": "ZCore",
"version": "1.2.19.0",
"branch": "release/1.2",
"target": "TC3.1",
"configuration": "Distribution",
"distributor-name": "Zeugwerk GmbH",
"namespace": "MyCoreAlias",
"options": {
"qualified-only": true
}
}
],
"references": {
"*": [
"Tc2_Standard=*",
"Tc2_System=*",
"Tc3_Module=*"
]
},
"patches": {
"platform": {
"TC3.1.4024.56": [
"patches/compat-4024.patch"
]
},
"argument": {
"debug-mode": [
"patches/enable-debug.replacement"
]
},
"license": {
"debug-mode": [
{
"key": "XXXX-XXXX-XXXX-XXXX",
"oem-id": "1234",
"comment": "Debug OEM license"
}
]
}
}
}
]
}
]
}