AJSF on Angular 22, and the way up from 0.8.0
Published
AJSF turns a JSON Schema into an Angular form. @ajsf/core renders plain HTML, and five framework packages render it with a UI library: @ajsf/material, @ajsf/primeng, @ajsf/bootstrap3, @ajsf/bootstrap4 and @ajsf/bootstrap5.
In the week of 15 to 21 September 2026, @ajsf/core had 2,932 npm downloads, over five days of data, since npm reports none for 15 and 17 September. As npm’s versions tab showed them on 24 September, 2,289 of those were version 0.8.0, from August 2022, and 216 in total were 14.0.0 or later. So this note is mostly for people still on 0.8.0: which version to move to, what changes on the way, and what a new Angular 22 app needs.
Why so many installs are still on 0.8.0
No AJSF release shipped between 0.8.0, published on 27 August 2022, and 14.0.0, published on 15 August 2026.
@ajsf/core@0.8.0 declares @angular/core >=14.0.0, an open range, so npm installs it next to any later Angular without a warning. @ajsf/material@0.8.0 also peers on @angular/flex-layout, whose matching releases require Angular 14, so on Angular 15 or later npm stops with ERESOLVE unless it runs with --legacy-peer-deps. Neither means the combination was ever built or tested.
From 14.0.0, the Angular peer ranges are bounded, so npm reports a resolution error instead of installing a combination nobody built. AJSF then followed Angular one major at a time, up to 22.0.0 on 12 September 2026. The current release is 22.3.1, published on 24 September 2026.
Pick the version that matches your Angular
From 14.0.0 onward, the AJSF major equals the Angular major it targets, as Angular Material does. Minor and patch numbers are not tied to Angular, so a major can have several releases, such as 22.0.0, 22.1.0, 22.2.0, 22.3.0 and 22.3.1.
The majors that exist are 14, 16, 17, 18, 19, 20, 21 and 22. There is no release for Angular 15: 16.0.0 crossed 15 on the way from 14 to 16.
A plain npm install @ajsf/material takes the newest major, which fails to resolve on an older Angular, so name the major that matches the Angular you run:
npm install @ajsf/core@17 @ajsf/material@17 # for Angular 17
@ajsf/*@17 installs 17.1.0, which has none of the fixes listed below for 18 and later. Fixes ship only in the newest release, with no backports to earlier majors, 0.8.0 or angular6-json-schema-form, so an app on an older Angular gets them by moving to Angular 22.
14.0.0 is not a rewrite: it targets the same Angular 14 as 0.8.0, and the version jump is the Angular-aligned numbering starting.
Every @ajsf release since 14.0.0 is published from the repository’s release workflow with an npm provenance attestation that records the repository and workflow, and npm audit signatures verifies it.
Upgrade from 0.8.0, one major at a time
- Remove flex-layout.
@ajsf/materialno longer depends on@angular/flex-layout, which is deprecated and has no Angular 16 release. Unless something else uses it, runnpm uninstall @angular/flex-layout --legacy-peer-depsfirst: while its14.0.0beta sits next to a newer Angular, the next install stops withERESOLVE. ThefxFlex,fxFlexOrder,fxFlexOffset,fxFlexAlign,fxLayoutAlignandfxLayoutGapoptions and theflexandsectionlayout types still apply, as native CSS, for values written with a unit such as30%or10px. - Match your Angular. Install the
@ajsfmajor for the Angular you run, for every@ajsfpackage you use, as above. - Move Angular and AJSF up one major together with one
ng updatethat names every@ajsfpackage you use, as Angular’s update guide describes, for exampleng update @angular/core@18 @angular/cli@18 @angular/material@18 @ajsf/core@18 @ajsf/material@18. Without the@ajsfpackages,ng updatestops withIncompatible peer dependencies found, because@ajsf/material@17peers on@angular/material ^17.0.0. - Check your forms against the list below, then repeat step 3 up to Angular 22.
What a form can notice between 0.8.0 and 22
The 20.0.0, 21.0.0 and 22.0.0 releases changed no export, selector or input, while minors inside those majors changed Bootstrap markup. The changes below, from 16 to 22, can affect what a form shows, validates or submits, or what your project needs to build it. Upgrade notes for 19 to 22 are in the release notes directory; for 14 to 18, read each release’s page in the GitHub releases.
-
16.0.0, Material: the widgets use Angular Material’s MDC components (#367). A field without anappearanceoption renders asfillinstead ofstandard. CSS written against.mat-form-field-*classes needs the.mat-mdc-*names. -
17.1.0, messages: validator and locale messages are corrected (#374), for example the message for a decimalmultipleOfstep. -
18.x, field validators: duplicates under
uniqueItems(#379), exclusive bounds (#418) and an emptied array underminItems(#396) now mark the field, anddependenciesis checked per field (#376). DecimalmultipleOfsteps and emoji in length limits stop showing false errors (#396).isValidand submit come from ajv, which already judged all of these cases correctly, so what changes is the message on the field. -
19.0.0,format:0.8.0already checkedformatwith ajv 6. From 19.0.0, ajv 8 with ajv-formats is stricter, so2021-02-30no longer passesformat: dateanda@bno longer passesformat: email. -
19.0.0, drafts: the draft is read from$schema, and a schema without it is read as draft 7, though draft 4 keywords are still converted, so a draft 4 schema without$schemakeeps working. Draft 4 is the oldest draft supported: a schema declaring draft 3 still renders, with its property-level keywords handled as in the next bullet. -
19.0.0, property-levelrequired: a property-levelrequired: true,optionalorrequiresis dropped with a console warning naming the properties, so that field is no longer required. Move eachrequired: truename into the parent object’srequiredarray, list there every property withoutoptional: true, and turnrequiresinto adependenciesentry. This is the change most likely to need a schema edit. -
19.0.0, references and selects:$refagainst a schema’s ownidresolves. A select over a numeric enum submits numbers instead of strings, and clearing an optional select with its None option no longer makes the form invalid. -
19.0.0, counts and buttons:minPropertiesandmaxPropertiescount only entered values: undefined, null and the empty string do not count,falseand0do. A layout-declared submit button honoursdisableInvalidSubmit, and fixed tuple positions show no remove button. -
20.0.0and22.0.0, toolchain: moving to 20 needs Node^20.19.0 || ^22.12.0 || >=24.0.0(20 notes). At 22, TypeScript must be>=6.0 <6.1and Node^22.22.3 || ^24.15.0 || >=26.0.0(22 notes). TypeScript 6 turnsstricton whentsconfig.jsondoes not set it, so set"strict": falseor fix the new errors, and it deprecatesbaseUrlwithTS5101: withoutbaseUrl, eachpathstarget needs a leading./. -
20.1.0, Bootstrap 4 classes:@ajsf/bootstrap4emits Bootstrap 4 class names instead of Bootstrap 3 ones, for exampleis-invalidinstead ofhas-error. The package README tables every replacement. -
22.1.0, Bootstrap 4 and 5 checkboxes and radios: the input and its label render as siblings instead of the label wrapping the input, except Bootstrap 4 toggle buttons, which stay nested. CSS selectinglabel > inputno longer matches.<div class="form-check"> <input class="form-check-input" id="control1" type="checkbox"> <label class="form-check-label" for="control1">Accept</label> </div>The snippet matches Bootstrap 4 and the Bootstrap 5 radios and checkbox lists. For a single Bootstrap 5 checkbox, the input and label share a
divnested inside.form-check, so a descendant selector matches every case. -
22.2.0, Bootstrap arrays: an array title renders once, on the<legend>, in all three Bootstrap packages. In Bootstrap 4 and 5, every field sits in a wrapper row that carriesis-invalidand, for an array item, holds the remove button beside the field, so a selector such as.input-group.is-invalidno longer matches. -
22.3.0, Bootstrap messages: in all three Bootstrap packages, a field’s validation message appears under it once the field is touched or changed (#524). Bootstrap 5 putsis-invalidon the wrapper around the field, not on the input, and the red border on a touched invalid input comes from AJSF’s own.ng-invalid.ng-touchedrule. -
New packages:
@ajsf/bootstrap5is released from18.0.0and@ajsf/primengfrom19.2.0. Neither existed in0.8.0.
Before each release, 74 recorded schemas are rendered in every framework, 444 cases in all, and their control count and validity are compared with a recorded baseline. That baseline holds no class names, so the Bootstrap markup changes above do not show in it.
A new Angular 22 app
I built and ran this setup in Chrome on 24 September 2026 with Angular CLI 22.2.0, Angular Material 22.2.0 and @ajsf 22.3.1. The CLI scaffolds a standalone, zoneless app, and this Material setup needs neither zone.js nor @angular/animations.
npx @angular/cli@22 new ajsf-demo --defaults --style=css
cd ajsf-demo
npx ng add @angular/material
npm install @ajsf/material
ng add @angular/material asks whether to proceed and which palette, and its defaults give the setup below: it installs Material and the CDK, writes a theme and links the fonts. @ajsf/material brings @ajsf/core with it; an app that already lists @ajsf/core names it in the install too, so both stay on the same version.
Nothing goes in app.config.ts: MaterialDesignFrameworkModule goes in the component’s imports and provides what the form needs. An app that still bootstraps an NgModule adds it to that module’s imports instead.
In src/app/app.ts:
import { Component, signal } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { MaterialDesignFrameworkModule } from '@ajsf/material';
@Component({
selector: 'app-root',
imports: [MaterialDesignFrameworkModule, JsonPipe],
templateUrl: './app.html',
styleUrl: './app.css',
})
export class App {
protected readonly schema = {
type: 'object',
properties: {
name: { type: 'string', title: 'Name', minLength: 1 },
age: { type: 'integer', title: 'Age', minimum: 18 },
plan: { type: 'string', title: 'Plan', enum: ['free', 'pro', 'team'] },
},
required: ['name'],
};
protected readonly submitted = signal<unknown>(null);
protected onSubmit(data: unknown): void {
this.submitted.set(data);
}
}
In src/app/app.html:
<json-schema-form
[schema]="schema"
framework="material-design"
(onSubmit)="onSubmit($event)">
</json-schema-form>
<pre>{{ submitted() | json }}</pre>
The browser showed Name, Age and Plan as Material form fields and a select, with the required asterisk on Name and Submit disabled. Touching Name and leaving it empty showed This field is required., and typing 16 in Age and leaving the field showed Must be 18 or more.
With valid values, Submit called the handler with:
{ "name": "Ada Lovelace", "age": 36, "plan": "pro" }
Age arrived as a number.
The production bundle was 1.40 MB, and a new project fails the build above 1 MB. Raise the initial entry under budgets in the production configuration of angular.json:
{
"type": "initial",
"maximumWarning": "2MB",
"maximumError": "2.5MB"
}
The build also warns that ajv and ajv-formats are CommonJS. Listing them in allowedCommonJsDependencies in the build options silences the warning:
"allowedCommonJsDependencies": ["ajv", "ajv-formats"]
The example leaves loadExternalAssets off. If your templates set it, bind it as [loadExternalAssets]="true": a project created by ng new rejects the plain attribute loadExternalAssets="true" with TS2322. It loads CDN assets for trying the library out (for Material only the Roboto and Material Icons fonts, not a theme), so a production app loads its own and leaves the input off.
Switching framework
Four things change for Bootstrap 5: the package, the module import, the framework value and the stylesheet.
npm install @ajsf/bootstrap5 bootstrap@5
-import { MaterialDesignFrameworkModule } from '@ajsf/material';
+import { Bootstrap5FrameworkModule } from '@ajsf/bootstrap5';
- imports: [MaterialDesignFrameworkModule, JsonPipe],
+ imports: [Bootstrap5FrameworkModule, JsonPipe],
- framework="material-design"
+ framework="bootstrap-5"
In angular.json, replace src/material-theme.scss in the styles array with the Bootstrap stylesheet:
"styles": [
"node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
]
The Bootstrap 5 bundle is 827 kB, which passes the default 1 MB error budget, though the build still warns above 500 kB. Bootstrap 4 works the same way with @ajsf/bootstrap4, bootstrap@4, Bootstrap4FrameworkModule and framework="bootstrap-4".
PrimeNG works the same way with PrimengFrameworkModule and framework="primeng", plus providePrimeNG with a theme preset in app.config.ts, as the @ajsf/primeng README shows. Its bundle is 1.55 MB, so it needs the raised budget too.
An app can use several framework packages at once: import each framework module in the component that renders its form, and give that form the matching framework value. The playground also switches between them at runtime. The framework values are material-design, primeng, bootstrap-3, bootstrap-4, bootstrap-5 and no-framework, the default, which JsonSchemaFormModule from @ajsf/core renders as plain HTML.
Which JSON Schema it understands
AJSF understands draft 4, draft 6 and draft 7. Draft 4 keywords are converted whether or not a schema declares $schema, so a draft 4 schema without it keeps working.
A declared draft 4, 6 or 7 $schema is recognised over http or https and with or without the trailing #, from 22.3.1. 2019-09 and 2020-12 are not supported: a schema declaring either fails to compile, and the form does not render.
if, then and else validate, so a conditionally required field keeps Submit disabled. The layout is built once, though, so that field shows no required marker and no message, even after it is touched. readOnly and writeOnly are accepted and have no effect.
The playground has 71 example schemas, each viewable with Material, PrimeNG, Bootstrap 3, 4 or 5, or plain HTML.
What isn’t there yet
- Support for 2019-09 and 2020-12, and a layout that follows
if,thenandelse, both described above. - A single validator. JSON Schema is validated twice today, by
ajvforisValidandvalidationErrorsand by hand-written per-field validators, and the two can disagree. - A daisyUI package is next on the roadmap. ng-zorro and Ionic wait until users ask for them in an issue.
History and links
AJSF has continued dschnelldavis/angular2-json-schema-form in its own repository since July 2018. It first shipped as the single package angular6-json-schema-form, whose last release is 8.0.0, and later moved to the @ajsf scope. What it led to is in my note on Texaryn.
- Playground
- API reference
llms.txtfor AI assistants- README
- Release notes
- Issues. Security reports go through GitHub private vulnerability reporting, not a public issue.