Generate only the strongly typed quantities and units your application needs.
You can select quantities and units from the UnitsNet catalog and add your own custom quantities and units with JSON definitions. A source generator emits the types directly into your project.
The result keeps the familiar UnitsNet API without requiring the complete catalog:
- Generate only the quantities and units you use
- Combine built-in and custom definitions in one strongly typed model
- Get unit conversions, quantity relations, arithmetic, parsing, and formatting
- Catch definition and relationship errors at compile time
- Get trimming and Native AOT-friendly code
Experimental: UnitsNet.Modular is a proof of concept and currently in pre-release. Its API, package structure, and compatibility guarantees may change as the architecture is evaluated.
Try the UnitsNet.Modular samples in GitHub Codespaces without installing anything.
- Quick start
- How it differs from UnitsNet
- Choose a project structure
- Use generated quantities
- Configure generation
- Add custom quantities
- Add quantity relationships
- Publish a definition package
- Dynamic lookup and serialization
- Diagnostics
- Troubleshooting
- Samples and design documents
UnitsNet.Modular supports .NET 8, .NET 9, and .NET 10. It replaces UnitsNet in a project; the two
packages cannot be referenced together. Remove UnitsNet if it is installed, then add the
prerelease package:
dotnet remove package UnitsNet
dotnet add package UnitsNet.Modular --prereleaseGenerated built-in quantities use the UnitsNet namespace and unit enums use UnitsNet.Units by
default. Pass a namespace to [UnitsNetModule("MyApplication.Units")] to change it.
Add these two files to the project that references UnitsNet.Modular.
ApplicationUnits.cs declares the generation boundary and selects the built-in quantities:
using UnitsNet.Modular;
using Catalog = UnitsNet.Modular.BuiltIns;
namespace MyApplication.Units;
[UnitsNetModule]
internal interface ApplicationUnits :
IInclude<Catalog.LengthSpec>,
IInclude<Catalog.DurationSpec>,
IInclude<Catalog.SpeedSpec>;Program.cs uses the generated API:
using UnitsNet;
using UnitsNet.Units;
Length route = Length.FromKilometers(1.2);
Length remaining = Length.Parse("500 m");
Length total = route + remaining;
Speed pace = total / Duration.FromMinutes(2);
Console.WriteLine($"Total: {total.ToUnit(LengthUnit.Meter):F0}");
Console.WriteLine($"Pace: {pace:F1}");Build the project. The source generator sees ApplicationUnits, then emits Length, Duration,
Speed, and their unit enums into your project. Because every participant is selected, it also
emits the Length / Duration = Speed relationship used above.
The *Spec interfaces are compile-time selections, not the generated quantities themselves. Each
selected quantity includes all its units unless a unit set filters them. The project containing the
module owns the generated CLR types, so other projects should reference that project rather than
declare another module.
UnitsNet ships the complete catalog as precompiled types and supports runtime configuration.
UnitsNet.Modular source-generates your selected catalog at compile time and uses immutable generated
metadata instead of mutable global registrations.
- Select only the quantities and units your application needs
- Generate custom quantities and units from JSON alongside the built-in catalog
- Generate relationships and operators across built-in and custom quantities
- Put the generated types in a namespace and assembly you control
- Use generated discovery and serialization without runtime assembly scanning
- Produce trimming and Native AOT-friendly code
- UnitsNet.Modular is a pre-release proof of concept, so its API and package structure may change
- It is not binary-compatible with
UnitsNet; libraries compiled againstUnitsNet.dllneed a migration boundary - Quantity selection and configuration happen at compile time; runtime mutation of conversions, abbreviations, and global defaults is not supported
- Each generated assembly owns distinct CLR types, so multi-project applications should share one generated units project
- Generated quantity values currently use
double Length.ParseFeetInchesandPressureelevation modeling are not yet supported- The System.Text.Json integration is still a proof of concept, and applications own serialized contract versioning
See the migration guide for a detailed API comparison and migration options.
For a small application, place the module interface in the application project itself. The generated types become part of that application's assembly.
MyTool
├── MyTool.csproj -> UnitsNet.Modular
├── ApplicationUnits.cs
└── Program.cs
For a multi-project application, generate quantities once in a dedicated units project and reference that project everywhere else:
MyApplication.slnx
└── src
├── MyApplication.Units
│ ├── MyApplication.Units.csproj -> UnitsNet.Modular + definition packages
│ └── ApplicationUnits.cs
├── MyApplication.Domain -> MyApplication.Units
├── MyApplication.Persistence -> MyApplication.Units
├── MyApplication.Api -> MyApplication.Units
└── MyApplication.Cli -> MyApplication.Units
flowchart LR
BuiltIns["UnitsNet.Modular built-in specs"]
ThirdParty["Third-party definition packages"]
Units["MyApplication.Units<br/>selection + generation"]
Assembly["MyApplication.Units.dll<br/>generated quantity types"]
Apps["Domain, API, UI, persistence, services"]
BuiltIns --> Units
ThirdParty --> Units
Units --> Assembly
Assembly --> Apps
This is the recommended setup. A generated public type belongs to the assembly into which it is
generated. Generating Length independently in two assemblies creates two different CLR types,
even when both came from the same spec. One application-owned generation boundary gives all
application projects the same type identity.
Generate the complete catalog into the established UnitsNet and UnitsNet.Units namespaces when
source compatibility is more important than assembly size:
using UnitsNet.Modular;
using UnitsNet.Modular.Profiles;
[UnitsNetModule]
internal interface CompatibilityUnits : IIncludeProfile<AllQuantitiesProfile>;This targets source compatibility for common construction, conversion, parsing, formatting,
arithmetic, and aggregation code. It does not make the generated structs binary-compatible with
types from UnitsNet.dll.
A third-party definition package supplies specs rather than precompiled quantity structs. Reference the package from your units project, select its public quantity specs, and generate the third-party and built-in quantities together:
using Acme.Measurements.Definitions;
using UnitsNet.Modular;
using Catalog = UnitsNet.Modular.BuiltIns;
[UnitsNetModule]
internal interface ApplicationUnits :
IInclude<Catalog.LengthSpec>,
IInclude<WidgetCountSpec>,
IInclude<WidgetDistanceSpec>;This lets the generator emit operators between custom and built-in quantities and avoids type identity conflicts between independently compiled quantity packages.
Length a = Length.FromKilometers(1.5);
Length b = new(500, LengthUnit.Meter);
Length c = Length.From(2, LengthUnit.Mile);
double meters = a.As(LengthUnit.Meter);
Length inMeters = a.ToUnit(LengthUnit.Meter);
double converted = Length.Convert(1.5, LengthUnit.Kilometer, LengthUnit.Meter);As() returns the numeric value in another unit. ToUnit() returns a quantity storing that unit.
The static Convert() method converts a raw numeric value without constructing a quantity.
Linear quantities support ordinary arithmetic:
Length total = Length.FromMeters(2) + Length.FromCentimeters(50);
Length scaled = total * 3;
double ratio = total / Length.FromMeters(1);Cross-quantity operators are emitted when all participating quantities are selected:
Area area = Length.FromMeters(2) * Length.FromMeters(3);
Speed speed = Length.FromKilometers(10) / Duration.FromHours(1);
Force force = Mass.FromKilograms(5) * Acceleration.FromMetersPerSecondSquared(9.81);
Pressure pressure = force / Area.FromSquareMeters(2);Multiplication relationships are generated in both operand orders when commutative. Division is inferred unless the relation explicitly disables it.
Affine quantities use a selected linear offset quantity:
Temperature freezing = Temperature.FromDegreesCelsius(0);
Temperature boiling = Temperature.FromDegreesCelsius(100);
TemperatureDelta range = boiling - freezing;
Temperature adjusted = freezing + TemperatureDelta.FromDegreesCelsius(2);Selecting an affine quantity without its offset quantity produces diagnostic UNM015.
Logarithmic quantities retain logarithmic arithmetic instead of being treated as linear values.
using System.Globalization;
Length distance = Length.Parse("1.5 km", CultureInfo.InvariantCulture);
bool parsed = Length.TryParse("500 m", CultureInfo.InvariantCulture, out Length result);
LengthUnit unit = Length.ParseUnit("km", CultureInfo.InvariantCulture);
string abbreviation = Length.GetAbbreviation(LengthUnit.Kilometer, CultureInfo.InvariantCulture);
string text = distance.ToString("F2", CultureInfo.InvariantCulture); // 1.50 kmUnit singular names and plural names parse case-insensitively. Configured abbreviations are case-sensitive. The requested culture is used first, with the definition's localization fallback behavior supplying the default abbreviation.
Generated extension methods delegate reusable algorithms to the UnitsNet runtime:
Length sum = new[]
{
Length.FromKilometers(1),
Length.FromMeters(500),
}.Sum();
Length average = new[]
{
Length.FromMeters(1),
Length.FromCentimeters(300),
}.Average(LengthUnit.Meter);Linear quantities provide Sum() and Average() overloads, including selector and target-unit
forms. Affine quantities provide meaningful averages. Logarithmic quantities provide Sum(),
ArithmeticMean(), and GeometricMean() with logarithmic semantics.
Each generated quantity exposes one strongly typed, immutable metadata object. Less-common discovery data lives there instead of being duplicated across the quantity API, and the generated registry stores that exact same instance:
QuantityInfo<Length, LengthUnit> info = Length.Info;
UnitInfo<LengthUnit> kilometer = info[LengthUnit.Kilometer];
Length value = Length.From(1.5, kilometer.Value);
string abbreviation = kilometer.GetDefaultAbbreviation(CultureInfo.InvariantCulture);
Debug.Assert(info.BaseUnit.Value == LengthUnit.Meter);
Debug.Assert(info.Units.Contains(kilometer));
Debug.Assert(ReferenceEquals(info, Quantity.Registry.Get(typeof(Length))));The quantity type owns common value behavior (Value, Unit, Zero, From, Convert, As,
ToUnit, parsing, and formatting). Info owns identity, base-unit metadata, the immutable Units
collection, and base dimensions. UnitInfo<TUnit>.Value is the represented enum value;
SingularName and PluralName describe it. BaseUnitInfo, UnitInfos, and UnitInfo.Name are
hidden source-compatibility aliases. Unlike the legacy mutable metadata model, generated metadata
does not expose configurable conversion expressions or global registration.
UnitSystem and BaseUnits describe a preferred set of constituent units without changing global
state:
using UnitsNet;
Length length = Length.From(1.5, UnitSystem.SI);
double meters = Length.FromKilometers(1.5).As(UnitSystem.SI);
Length normalized = length.ToUnit(UnitSystem.SI);Resolution considers only units selected into the current module.
Authoring types describe generation inputs, so their names distinguish them from the concrete types they produce:
| Suffix | Purpose | Example |
|---|---|---|
Spec |
Identifies one built-in or custom quantity specification | LengthSpec, HowMuchSpec |
UnitSet |
Selects a reusable subset of a spec's units | MetricLengthUnitSet |
Profile |
Composes several specs and unit sets | MechanicsProfile |
The module interface names the generation boundary and can use an application-oriented name such as
ApplicationUnits. The *Spec suffix is the authoring convention for the corresponding generated
quantity and unit enum: LengthSpec specifies the Length quantity and its LengthUnit enum.
[UnitsNetModule] marks the single generation boundary in a compilation:
[UnitsNetModule]
internal interface ApplicationUnits :
IInclude<UnitsNet.Modular.BuiltIns.LengthSpec>;A compilation can contain one module marker. Compose a larger selection with profiles rather than
declaring multiple modules. UNM014 reports multiple module markers before they emit colliding
types.
Without a target namespace, each definition keeps its declared namespace:
- built-in definitions generate quantities into
UnitsNetand unit enums intoUnitsNet.Units; - custom definitions generate into their JSON
Namespace; - the generated
Quantityfacade is placed inUnitsNetwhen the module includes built-ins, or in the module interface's namespace for a custom-only module.
Pass a target namespace to override every selected definition and emit it into one namespace:
[UnitsNetModule("Contoso.Measurements")]
internal interface ApplicationUnits :
IInclude<UnitsNet.Modular.BuiltIns.LengthSpec>;Targeting UnitsNet explicitly has the same compatibility behavior as the built-in default:
quantity types use UnitsNet and unit enums use UnitsNet.Units.
Include every unit of a definition:
IInclude<UnitsNet.Modular.BuiltIns.LengthSpec>Include a filtered unit set:
IInclude<UnitsNet.Modular.BuiltIns.LengthSpec, MetricLengthUnitSet>Built-in spec names add the Spec suffix to quantity definition names in the UnitsNet catalog, so
the Length specification is selected with UnitsNet.Modular.BuiltIns.LengthSpec. The specs are generated
by the analyzer in UnitsNet.Modular.BuiltIns; generated quantities retain their familiar names,
such as Length and LengthUnit. Built-in and custom specs both declare their stable semantic ID
with [QuantitySpec]; the namespace and Spec suffix are naming conventions, not lookup
rules.
UnitsNet.Modular currently supplies two profiles:
| Profile | Selection |
|---|---|
UnitsNet.Modular.Profiles.AllQuantitiesProfile |
Every built-in catalog quantity and unit |
UnitsNet.Modular.Profiles.AllSiProfile |
Focused SI mechanics chain used by the POC sample |
Include a profile and add individual definitions:
[UnitsNetModule]
internal interface ApplicationUnits :
IIncludeProfile<UnitsNet.Modular.Profiles.AllQuantitiesProfile>,
IInclude<MyCustomSpec>;Create reusable application profiles from the same authoring interfaces:
internal interface MechanicsProfile :
IInclude<UnitsNet.Modular.BuiltIns.LengthSpec>,
IInclude<UnitsNet.Modular.BuiltIns.DurationSpec>,
IInclude<UnitsNet.Modular.BuiltIns.SpeedSpec>;
internal interface ProductProfile :
IIncludeProfile<MechanicsProfile>,
IInclude<WidgetCountSpec>;
[UnitsNetModule]
internal interface ApplicationUnits :
IIncludeProfile<ProductProfile>;Profiles can be nested. A direct IInclude<TQuantitySpec, TUnitSet> on the module overrides profile
unit selections for that quantity. Direct IInclude<TQuantitySpec> selects every unit.
Declare a reusable unit set with [UnitSet]:
[UnitSet("Meter", "Millimeter", "Kilometer")]
internal interface CommonLengthUnitSet;
[UnitSet("glob:*Meter")]
internal interface MeterUnitSet;
[UnitSet("regex:^(Meter|Centi.*|Kilo.*)$")]
internal interface MetricUnitSet;Pattern behavior:
| Syntax | Behavior |
|---|---|
Meter |
Bare glob; exact name when it contains no * |
glob:*Meter |
Case-insensitive glob where * matches any characters |
regex:.*Meter$ |
Case-insensitive, culture-invariant regular expression with a timeout |
Patterns match expanded singular unit names, not abbreviations. Prefix expansion happens before
filtering, so .*Meter$ can match Meter, Millimeter, and Kilometer. The base unit is always
included to keep the quantity convertible. Invalid patterns and patterns matching no unit are
compile-time errors.
Use Roslyn's native AdditionalFiles item for custom quantity and relation files:
<ItemGroup>
<AdditionalFiles Include="Definitions/*.unitsnet.json"
UnitsNetDefinition="true" />
<AdditionalFiles Include="Definitions/*.unitsnet.relations.json"
UnitsNetRelation="true" />
</ItemGroup>The metadata is optional when the files use the conventional suffixes:
*.unitsnet.json;*.unitsnet.relations.json.
Metadata is useful for ordinary names such as Length.json.
The package also supports the custom item aliases below:
<ItemGroup>
<UnitsNetDefinition Include="Definitions/Length.json" />
<UnitsNetRelation Include="Definitions/Relations.json" />
</ItemGroup>The packaged MSBuild targets map these aliases to AdditionalFiles. Native AdditionalFiles is
recommended because some IDE project models, including current Rider versions, do not reliably pass
custom build actions to the design-time Roslyn host.
Generated files normally stay in Roslyn's in-memory compilation. To write them beneath obj for
debugging or review, add:
<PropertyGroup>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>
$(BaseIntermediateOutputPath)Generated
</CompilerGeneratedFilesOutputPath>
</PropertyGroup>Do not compile that output directory explicitly; the compiler already receives the generated sources from Roslyn.
Add a JSON file to the units project:
<ItemGroup>
<AdditionalFiles Include="HowMuch.unitsnet.json"
UnitsNetDefinition="true" />
</ItemGroup>Bind a public or internal quantity spec interface to the definition's stable semantic ID:
using UnitsNet.Modular;
namespace Fictional.Measurements.Definitions;
[QuantitySpec("Fictional.Measurements.HowMuch")]
public interface HowMuchSpec;Select the spec in the module:
[UnitsNetModule]
internal interface ApplicationUnits : IInclude<HowMuchSpec>;The spec ID must match Namespace.Name in the JSON definition. If Namespace is omitted, it
defaults to UnitsNet. Treat this semantic ID as a package-boundary identifier: use a namespace
you own so independently authored definition packages cannot describe unrelated quantities with
the same identity.
Define the quantity, its units, abbreviations, and conversions in the MyQuantity.unitsnet.json
file. Each unit provides one expression for converting to the quantity's base unit and another for
converting back. The two expressions should round-trip every supported value.
See HowMuch.unitsnet.json for a complete example and the Quantity and Unit Definition Schema for the full field reference.
Add a relation file as an AdditionalFiles item:
<AdditionalFiles Include="Fictional.unitsnet.relations.json"
UnitsNetRelation="true" />Structured relations use stable semantic quantity IDs and invariant unit names:
[
{
"result": {
"quantity": "Fictional.Measurements.HowMuchDistance",
"unit": "SomeMeter"
},
"left": {
"quantity": "Fictional.Measurements.HowMuch",
"unit": "Some"
},
"operator": "*",
"right": {
"quantity": "UnitsNet.Length",
"unit": "Meter"
},
"noInferredDivision": false
}
]Only multiplication relations are declared. The generator:
- emits both operand orders when the operands are different;
- infers the corresponding division operator;
- skips inferred division when
noInferredDivisionis true; - emits operators only when every participating quantity is selected;
- uses the relation's anchor units internally even when a unit filter omits those units publicly.
The special endpoint quantities "double" and "1" describe scalar and reciprocal relationships.
The older UnitsNet string form is also accepted:
[
"Area.SquareMeter = Length.Meter * Length.Meter",
"Speed.MeterPerSecond = Length.Meter * Frequency.Hertz -- NoInferredDivision"
]Structured relations are recommended for third-party definitions because fully qualified semantic IDs remain unambiguous across namespaces.
A definition package distributes public quantity specs, JSON definitions, relationships, and an MSBuild props file, but does not generate or ship the resulting quantity structs.
Acme.Measurements.Definitions
├── DefinitionMarkers.cs
├── Definitions
│ ├── WidgetCount.unitsnet.json
│ └── Acme.unitsnet.relations.json
├── build
│ └── Acme.Measurements.Definitions.props
└── Acme.Measurements.Definitions.csproj
Pack the definition files and props:
<ItemGroup>
<None Include="Definitions/*.unitsnet.json"
Pack="true"
PackagePath="build/definitions" />
<None Include="Definitions/*.unitsnet.relations.json"
Pack="true"
PackagePath="build/definitions" />
<None Include="build/Acme.Measurements.Definitions.props"
Pack="true"
PackagePath="build/Acme.Measurements.Definitions.props" />
</ItemGroup>The props file contributes those files directly to the referencing project's compilation:
<Project>
<ItemGroup>
<AdditionalFiles Include="$(MSBuildThisFileDirectory)definitions/*.unitsnet.json"
UnitsNetDefinition="true" />
<AdditionalFiles Include="$(MSBuildThisFileDirectory)definitions/*.unitsnet.relations.json"
UnitsNetRelation="true" />
</ItemGroup>
</Project>Quantity specs should be public so projects can select them. Keep their [QuantitySpec] semantic
IDs stable once published.
An organization can instead publish one canonical compiled units assembly for several controlled applications. That is a deployment choice, not the primary composition model. Independently compiled modules do not share type identity or automatically gain cross-module operators.
Every module receives one immutable registry containing only its selected quantities:
using UnitsNet;
var registry = GeneratedQuantityRegistry.Instance;
IQuantityDescriptor length = registry.Get("Length");
IQuantity<double> parsed = length.Parse("1.5 km");
double meters = length.Convert(1.5, "Kilometer", "Meter");
IQuantity<double> created = registry.Create(
500,
"Length",
"Meter");The registry supports lookup by semantic QuantityId, definition name, quantity CLR type, and unit
enum CLR type. Descriptors expose selected units, abbreviations, base dimensions, creation,
conversion, parsing, formatting, and stored value/unit access. TryGet, TryCreate, TryConvert,
and TryParse variants are available for non-throwing workflows.
When a module includes built-ins, the generator emits the source-compatible Quantity facade into
UnitsNet:
using UnitsNet;
IQuantity<double> value =
UnitsNet.Quantity.From(1.5, "Length", "Kilometer");
IQuantity<double> parsed =
UnitsNet.Quantity.Parse(typeof(Length), "1.5 km");A custom-only module places its facade in the module interface's namespace unless the
UnitsNetModule attribute specifies an explicit target namespace.
Register the generated, AOT-safe converter factory:
using System.Text.Json;
using UnitsNet;
var options = new JsonSerializerOptions();
options.Converters.Add(GeneratedQuantityRegistry.JsonConverter);
string json = JsonSerializer.Serialize(Length.FromKilometers(1.5), options);
Length restored = JsonSerializer.Deserialize<Length>(json, options);The shape is:
{
"Value": 1.5,
"Unit": "Kilometer"
}Applications own the compatibility and versioning of serialized contracts. Semantic quantity IDs
and invariant unit names are the recommended boundary between independently compiled modules. The
generated converter handles selected concrete quantity types. Deserializing a polymorphic
IQuantity<double> directly is deliberately unsupported because the serialized shape does not
carry a CLR type or semantic quantity ID; resolve a descriptor by semantic ID at that boundary
instead.
UnitsNet.Modular reports authoring problems at compile time:
| ID | Meaning |
|---|---|
UNM001 |
A selected quantity spec has no built-in or JSON definition |
UNM002 |
A unit pattern matched no unit |
UNM003 |
A definition does not contain its declared base unit |
UNM004 |
A JSON quantity definition is invalid |
UNM005 |
Multiple JSON files provide the same semantic quantity ID |
UNM006 |
A glob or regular expression is invalid |
UNM010 |
A relation file is invalid |
UNM011 |
Selected relations are ambiguous or cannot be resolved |
UNM012 |
A selected unit set has no patterns |
UNM013 |
Definitions collide after applying the target namespace |
UNM014 |
A compilation declares more than one module |
UNM015 |
An affine quantity's offset quantity is not selected |
UNM016 |
The module project also references the incompatible legacy UnitsNet assembly |
Each diagnostic links to the relevant configuration documentation from IDEs that display analyzer help links.
Confirm that the project either declares a single [UnitsNetModule] or references the shared units
project that does. Select the quantity with IInclude<TSpec> or a profile, then build the module
project. Check the build output for UNM diagnostics; the generator does not emit a quantity that
was not selected.
If the build succeeds but editor completion remains stale, inspect the IDE's source-generator node
to confirm that quantity sources were emitted. Rebuild and reload the project or solution to refresh
the design-time Roslyn host. This can be necessary after changing analyzer packages or
AdditionalFiles inputs even though command-line builds already see the generated code.
Use Roslyn's native AdditionalFiles item so command-line and design-time builds receive the same
input:
<ItemGroup>
<AdditionalFiles Include="Definitions/*.unitsnet.json" />
</ItemGroup>The definition's Namespace.Name must match the semantic ID on [QuantitySpec], and the spec must
also be selected by the module. See Add custom quantities.
Relationships are emitted only when every participating quantity is selected. For example,
Length / Duration requires LengthSpec, DurationSpec, and SpeedSpec. Add the missing result or
operand quantity and rebuild.
This is intentional. Every selected quantity keeps its base unit as a conversion anchor even when a unit-set pattern does not match it.
UnitsNet and UnitsNet.Modular are alternative implementations and cannot be referenced together
in the module project. Remove the legacy package reference or move the generated quantities behind a
separate assembly boundary. See the migration guide for compatibility options.
| Start here | Scenario |
|---|---|
| Samples overview | Choose a focused scenario and switch between project references, local packages, and published packages |
| Getting started | Minimal Length, Duration, and Speed application matching the quick start |
| Quantity selection | Select individual quantities and filter their units |
| Custom quantity | Generate an application-owned quantity from JSON |
| All SI profile | Exercise the quantities and relationships selected by AllSiProfile |
| Playground | Explore relationships, aggregation, metadata, serialization, and custom definitions |
| Shared units library | Share one generated quantity assembly across several projects |
For design rationale and compatibility details, continue with Architecture and Migration notes.