Table of Contents

.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 from add/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 to config.json yourself; 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 namespace configured is later pulled in only as a dependency of another package you add (e.g. ZCore is a dependency of ZPlatform), Twinpack still applies ZCore's configured namespace, it does not get dropped just because ZCore itself 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, authors and display-name are derived automatically from the PLC's own Description/Author/Title TwinCAT project properties (read from the .plcproj, or from the compiled .library file 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 on twinpack 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"
                }
              ]
            }
          }
        }
      ]
    }
  ]
}