Module accessibility
Note
This "module accessibility" is about restricting access to a module's elements (Public or Internal) — enforced in metadata (YAML), transpiled UI C# code, and UI templates alike. It is unrelated to the CLI accessibility field used by dependency analysis (Application, Frontend, ...), which scopes elements for the usage detection algorithm, not for access control.
Module dependencies have been checked automatically in metadata (YAML) since Neos 1.10, but that only validated whether a module was declared as a dependency at all — it had no concept of some of a module's elements being off-limits even to a module that legitimately depends on it. And that dependency check stopped at the metadata boundary: nothing prevented handwritten or transpiled C# code, or a UI template, from reaching into a module that was never declared as a dependency, or from reaching a module's internal implementation details through a module that only re-exports it. Such accesses often worked by accident (transitive references, generated AllModules facades) and surfaced as broken builds only once a module was reused in a different cluster, far from the code that created the coupling.
Accessibility closes both gaps: a metadata element can now declare whether it is meant to be used by other modules at all, the metadata persistence layer enforces it on every modeled cross-module reference, and — for generated C# code (server and transpiled UI alike) and UI templates specifically (see "How it is enforced" below) — code that accesses a module outside its dependency graph, or accesses an Internal element without explicit permission, is flagged too.
The Accessibility element property
Accessibility is a property of individual metadata elements, not of the module itself. It is only meaningful — neos check-metadata rejects Internal on any other type — on these element types: Entity, EntityView, ServerMethod, DataObject, EnumType, Interface, Report, ReportStyle, RetryPolicy, ConstantsStaticClass, StringResource, UIView, UIComponent, UIPackage, Menu, MenuItem, Theme, Lookup, Snippet, Notification, Image, UISharedMethod, AIModel, DataTable, ProgrammableObject. Each element of one of those types declares its own Accessibility, set to Public (the default) or Internal:
Public— any module that declares this element's module as a dependency may reference the element.Internal— only the modules listed in the element's own module'sInternalAccessGrantedTomay reference it, in addition to the module itself. Every other module attempting to access it is flagged, even if it declares the correctModuleAssociation.
Internal is the right choice for an element that exists to be split out for organizational reasons, but was never meant to be consumed as a standalone public building block — for example an image or a DataObject inside a *Shared module, reused only by its own sibling modules.
Beyond preventing accidental coupling, Internal is also a maintenance tool for a published module: it guarantees that no other module — including modules published to a different cluster — depends on that element, so the module's internal behavior can be refactored or reworked freely, without the risk of a breaking change for a consumer. Public elements are the module's actual contract with the rest of the cluster; keeping everything else Internal keeps that contract small and safe to evolve.
Warning
Being a legal type for Accessibility does not mean every reference to that element type is checked everywhere. N108 (metadata persistence, below) is generic and covers any modeled cross-module reference for any of the types above. The Origin and UI-template checks further down only add coverage for reference kinds N108 cannot see structurally (an embedded C# expression, a template attribute) — and only for the narrower lists given there. See "How it is enforced" below for how coverage differs between transpiled UI code and handwritten server code.
The module itself does not declare an Accessibility — it declares InternalAccessGrantedTo instead, which grants other modules access to its Internal elements. For example, an InvoicingShared module used by OrderProcessing and Payments might grant those two modules access to its Internal elements, at the module level:
# InvoicingShared.yml (module)
Caption: Shared code for invoicing
InternalAccessGrantedTo:
- OrderProcessing
- Payments
MetadataFormatVersion: 39
RootNamespace: MyCompany.Invoicing
An individual element inside that module then marks itself Internal, at the element level — for example an InvoiceNumberGenerator entity that exists purely as an internal implementation detail:
# InvoiceNumberGenerator.yml (element)
Accessibility: Internal
...
Only elements marked Internal this way are restricted to OrderProcessing and Payments; every Public element in InvoicingShared (the default) remains usable by any module that depends on it.
How it is enforced
- Metadata persistence (YAML),
N108: any cross-module YAML reference to anInternalelement, for any of the element types listed above — the broadest and earliest check. - Generated C# code (
[Origin]attribute +NEOS0001-NEOS0003):Entity,DataObject,Interface,EnumType,ConstantsStaticClass,EntityView,Notification,ProgrammableObject,ServerMethod,UIView,UIComponent,UISharedMethod,Lookup,Theme,Image,Report,ReportStyle,StringResource. The sameOriginmechanism (see below) covers both generated server code and transpiled UI code — only where the check actually runs differs: transpiled UI code is checked live in the Neos Studio code editor and whileneos generatetranspiles it, independent of any build tooling; handwritten server C# gets the same three diagnostics too, but only via Visual Studio's live analysis or Sonar in CI — never a plaindotnet build(see the note below). - UI template references:
UIView,Image,Menu,Lookup,StringResource,UIComponent.
Note
The GroupeIsa.Neos.CodeAnalysis.Analyzers package (NEOS0001-NEOS0005) ships with every generated business project, but RunAnalyzersDuringBuild is deliberately left off there — a plain dotnet build does not enforce any of these diagnostics. They still run live in Visual Studio locally, and in CI through Sonar when the pipeline is configured for it. See Analyzers for exactly where and how NEOS0001-NEOS0003 run.
Metadata persistence (YAML)
The persistence engine itself validates every modeled cross-module reference between metadata elements — the same mechanism that already enforces module dependencies (N102). Saving a reference from a module B element to an Internal element owned by a module A fails unless A's InternalAccessGrantedTo lists B — reported as error N108:
YamlIgnoredErrors: [108]
This check runs both when neos check-metadata loads the cluster and automatically when saving in Neos Studio, exactly like the N102 module-dependency check. It also covers overrides: a module cannot override a value of an Internal element it doesn't have access to, and if a module's InternalAccessGrantedTo grant shrinks, existing overrides that depended on it are revalidated in the same save, even if they weren't otherwise touched.
Because this check works directly on the metadata model's structural references (foreign keys between entities), it applies wherever such a reference exists, regardless of element type — it isn't limited to the lists below.
The Origin mechanism (generated C# code)
For every generated class or interface of the types listed above, the generator emits an Origin attribute encoding the declaring module, layer, and the element's own accessibility level. This applies to server-generated code just as much as to UI-transpiled code — the entity below, for instance, generates straight into the module's server-side Domain layer:
[Origin("InvoicingShared.Domain.Internal")]
public partial class InvoiceNumberGenerator
{
...
}
(InvoiceNumberGenerator is the Internal entity from the earlier example; entities generate directly into the Domain layer.)
NEOS0001-NEOS0003 check every reference against this Origin, regardless of whether that reference sits in transpiled UI code or in handwritten server code sharing the same generated project — see "How a project's allowed origins are computed" below for exactly how, and the Analyzers article for exactly where each kind of code is checked today and how to resolve each diagnostic.
UI templates
The generator applies the same rule when a UI template references a UI view, an image, a menu, a lookup, a string resource, or a UI component that belongs to another module. Whether the offending reference is even reported as ignorable depends on which rule it breaks: a reference outside the template's module dependencies is a neos generate warning (see Generation message level configuration); a reference to an Internal element the template's module was not granted access to is a hard generation error and cannot be silenced through message-level configuration.
Granting targeted access with InternalAccessGrantedTo
InternalAccessGrantedTo is a module-level property: it lists the modules allowed to use this module's Internal elements, in addition to the module itself. It only matters once the module actually has Internal elements — it has no effect on a module where every element stays Public, since every dependent module can already use those.
In the earlier example, InvoicingShared holds elements (such as InvoiceNumberGenerator) that only make sense as implementation details reused by OrderProcessing and Payments, each individually marked Accessibility: Internal, so the module grants access explicitly to those two via its InternalAccessGrantedTo.
A generic, more broadly reusable element that happens to live in the same module (for example a generic "document" icon also used elsewhere in the cluster) should stay Public rather than being granted access module by module.
How a project's allowed origins are computed
This section is internal working detail — useful for understanding what NEOS0001-NEOS0003 actually check, not something you configure directly.
A Domain-layer project can only see the Domain layer — never Application — of its own module and of its dependencies; an Application-layer project can see both layers of its own module and of its dependencies (its Domain layer included, since Application code can call into its own Domain). Within whichever layer(s) a project can reach, it sees all of its own module's elements, but only a dependency's Public elements — unless that dependency's InternalAccessGrantedTo lists this module, in which case its Internal elements are included too.
The generator writes this out as an AllowedOrigins MSBuild property in a NeosReferences.{Layer}.gen.props file alongside each generated business project — the same project the handwritten server code for that module and layer lives in. For OrderProcessing's Domain project, granted access to InvoicingShared's Internal elements (the earlier example), that file looks like:
<!-- OrderProcessing/Domain/NeosReferences.Domain.gen.props (auto-generated, gitignored) -->
<Project>
<PropertyGroup>
<AllowedOrigins>OrderProcessing.Domain.*,InvoicingShared.Domain.*</AllowedOrigins>
</PropertyGroup>
<ItemGroup>
<CompilerVisibleProperty Include="AllowedOrigins" />
</ItemGroup>
</Project>
That file is regenerated by every neos generate and gitignored (**/businessAssembly/build/*.gen.props): it is never meant to be opened, edited, or committed, which keeps the whole mechanism transparent day to day — the property is simply there whenever NEOS0001-NEOS0003 need it.
See also
- Module dependencies for declaring the
ModuleAssociationa module needs beforeAccessibilityeven comes into play. - Metadata check for the
N108persistence-level check and how to ignore it temporarily. - Analyzers for the full list of module-accessibility-related diagnostics and how to resolve them.
- Generation message level configuration for the equivalent checks on UI templates.