---
name: ng-forge-dynamic-forms
description: Write and validate @ng-forge/dynamic-forms FormConfig objects for Angular. Use when creating, editing, or debugging dynamic form schemas, field types, validation, conditional logic, or value derivations in a project that depends on @ng-forge/dynamic-forms.
---

<!-- Generated by scripts/generate-skills.ts. Do not edit by hand. -->

# ng-forge Dynamic Forms

Authoring guidance for `@ng-forge/dynamic-forms` FormConfig objects.

This skill documents version **1.2.0**.

## Before you start

Check what the project actually has installed:

```bash
node -p "require('@ng-forge/dynamic-forms/package.json').version"
```

That reads the resolved package, so it works in a monorepo and when the library
is a dev dependency. Reading `dependencies` from the nearest package.json
reports a range like `^1.2.0` rather than what is installed, and misses both
cases.

If it does not match 1.2.0, say so before generating a config. The rules
below track 1.2.0 and some of them have changed between releases.

## The loop

Do not hand back a config you have not checked. The library ships a validator:

```bash
npx --yes @ng-forge/dynamic-forms-cli@next "path/to/your.form.ts" --ui material
```

`--yes` matters: without it npx prompts before its first install and waits for
an answer that will never come. `@next` matters too: the `latest` tag is still a
placeholder release with no executable, so the unpinned command fails with
"could not determine executable to run". Requires Node 24 or newer.

It reports the exact property that is wrong and how to fix it. Exit code 1
means a config failed, 2 means the invocation was wrong. Run it after writing
or editing any config, and fix what it reports before replying.

Pass `--require-config` when the file is supposed to contain a config: without
it, a file the extractor finds nothing in exits 0, which reads the same as a
clean run.

If the config is written in TypeScript, `as const satisfies FormConfig` plus
a typecheck already catches a large share of mistakes. Use both.

## Non-negotiable rules

These are the mistakes that come up most often. The full set is in
`references/rules.md`.

1. **`options` goes at field level**, never inside `props`.
2. **Hidden fields require `value`** and support nothing else: no validators,
   no `required`, no `props`, no `col`.
3. **Containers (page, group, row, array) accept only `hidden` logic.** Put
   `disabled`, `required`, `readonly`, and `derivation` on child fields.
4. **Container fields take no `label`.** Use a `text` field inside for headings.
5. **There is no `hideWhen`, `showWhen`, or `expressions` shorthand.** Use
   `logic: [{ type: 'hidden', condition: {...} }]`.
6. **Derivations live on the target field**, via `derivation: '...'` or
   `logic: [{ type: 'derivation', expression: '...' }]`. `targetField` no
   longer exists.
7. **Keys are unique within their group scope.** Page, row, and array
   containers do not introduce a scope.
8. **Navigation buttons go inside the page they belong to**, not beside it.

## References

Read these on demand rather than up front:

| File | When to read it |
| ---- | --------------- |
| `references/rules.md` | The complete authoring contract, including expression syntax and i18n |
| `references/field-types.md` | Which field types exist, their props, and where each may be nested |
| `references/patterns.md` | Working configs to adapt: wizards, arrays, conditionals, validation |
| `references/pitfalls.md` | Error-to-fix table, the same one the validator emits |

## UI adapters

A config is validated against one adapter: `material`, `bootstrap`,
`primeng`, or `ionic`. Field types are shared, but `props` differ per
adapter, and this skill documents none of them.

Install the skill for the adapter the project uses, which carries its `props`:

| Adapter | Skill |
| ------- | ----- |
| `material` | `ng-forge-dynamic-forms-material` |
| `bootstrap` | `ng-forge-dynamic-forms-bootstrap` |
| `primeng` | `ng-forge-dynamic-forms-primeng` |
| `ionic` | `ng-forge-dynamic-forms-ionic` |

Two adapters cannot both provide the same field type, so a project has one
adapter per field type. Check which one it provides before writing `props`.
