Hide properties, groups and tabs per site in the Umbraco backoffice.
When several sites in one Umbraco installation share a document type, some of its fields often matter on one site only. Property Visibility hides those fields from editors, per site, without a second document type.
- Hides properties, tabs (with every group and property in them) and groups, by alias, per document type or element type.
- Rules apply on every site, on one site, or on the sites that include a shared rule set. A site is a root node, identified by its key, its name, or as the default site.
- The same rules work inside blocks: Block List, Block Grid, Single Block and blocks in the rich text editor, for the content and the settings of a block.
- Rules live in appsettings or in a separate JSON file, reload without a restart, and come with JSON schemas for IntelliSense.
- A health check reports every root node, site or alias in the rules that does not resolve, with stable issue codes.
- Hidden values are kept: a hidden property keeps its value through save, publish and reload.
It changes what editors see, not what they are allowed to do. Read What it does not do before you rely on it.
One document type on two sites: the Seo tab, the banner image and the related links only where they are needed.
Blocks follow the site of the document that holds them: the promo banner's overlay colour is hidden on Corporate site only.
The health check reports rules that do not resolve, with an issue code and a suggested fix.
dotnet add package Umbraco.Community.PropertyVisibility
The package major follows the site's Umbraco major, both on .NET 10: 1.x for Umbraco 17 (from 17.6.2) and 2.x for Umbraco 18 (from 18.2.1), see Supported Umbraco versions. The command above adds the latest version, which is 2.x; on an Umbraco 17 site, add --version with the latest 1.x from the version history on NuGet. A version for the other Umbraco major fails the restore with NU1107; do not work around that error, because 1.x does not run on Umbraco 18 and 2.x does not run on Umbraco 17.
It registers itself through a composer, so there is no startup code to add, and without rules it hides nothing. Build the site once after installing: the build copies the JSON schemas that give IntelliSense for the configuration.
For a single site, add a PropertyVisibility section to appsettings.json, next to the existing Umbraco section, and replace the example aliases with your own:
{
"PropertyVisibility": {
"ContentTypes": {
"landingPage": { "Containers": ["seoTab"], "Properties": ["relatedLinks"] },
"promoBanner": { "Properties": ["overlayColour"] }
}
}
}Save the file (no restart needed) and reopen a document in the backoffice. On every landingPage the seoTab tab and the relatedLinks property are gone, and in every promoBanner block the overlayColour property is gone.
- The keys under
ContentTypesare content type aliases: document types, and element types for blocks. Propertieslists property aliases, including properties that come from a composition.Containerslists tab and group aliases:seoTabhides a tab with everything in it,settingsTab/advancedhides one group inside a tab, and a group alias on its own hides a group that is not inside a tab.- The backoffice does not show tab and group aliases. Umbraco derives them from the name in camelCase ("Seo tab" becomes
seoTab), and the tab's URL segment (seo-tab) is not the alias. For a container alias that does not exist, the health check'sPV202lists everytabandtab/groupalias of the type. With the rules in the rules file, the alias schema the site writes in Development lists them as you type (Multi-site and the rules file). - Aliases are matched case-insensitively. An alias that does not exist hides nothing and shows up in the health check:
PV104for a content type,PV201for a property,PV202for a tab or group.
Sites adds rules for one site. The top-level ContentTypes keep applying on every site.
{
"PropertyVisibility": {
"ContentTypes": {
"siteSettings": { "Containers": ["legacyTab"] }
},
"Sites": {
"corporate": {
"RootNodeKey": "5c2b4d7e-9f1a-4c3e-8b6d-2a1f0e9d8c7b",
"ContentTypes": {
"landingPage": { "Properties": ["bannerImage", "relatedLinks"], "Containers": ["seoTab", "settingsTab/advanced"] },
"promoBanner": { "Properties": ["overlayColour"] }
}
},
"campaign": {
"RootNodeName": "Campaign site",
"ContentTypes": {
"landingPage": { "Properties": ["metaKeywords"] }
}
},
"everythingElse": {
"IsDefault": true,
"ContentTypes": {
"landingPage": { "Properties": ["bannerImage"] }
}
}
}
}
}The site label (corporate, campaign, ...) is free text (without :) that appears in diagnostics. Each site names its root node with RootNodeKey (recommended: it survives renames), RootNodeName, or IsDefault for every root that no other site matches.
Rules that several sites share can be written once, in a rule set that each of those sites includes:
"RuleSets": {
"simplePages": {
"ContentTypes": {
"landingPage": { "Containers": ["seoTab", "settingsTab/advanced"] }
}
}
},
"Sites": {
"corporate": { "RootNodeKey": "5c2b4d7e-9f1a-4c3e-8b6d-2a1f0e9d8c7b", "Include": ["simplePages"] },
"campaign": { "RootNodeName": "Campaign site", "Include": ["simplePages"] }
}A root node that is not a site, such as a settings or shared content root, gets a site with only its RootNodeKey and no rules. Without one the health check reports the root (PV103); a default site would silence that warning for every root, including a new site added later without rules. Details: rule sets and roots that are not sites.
The rules can live in one of two places:
appsettings (section PropertyVisibility) |
Rules file (PropertyVisibility.config.json) |
|
|---|---|---|
| Holds | every option | ContentTypes, RuleSets, Sites and HideEmptiedContainers |
| Where | any .NET configuration source: appsettings.json, appsettings.{Environment}.json, environment variables |
the content root, next to appsettings.json; another path with the ConfigFile option |
| When both define rules | ignored; PV301 warns |
wins |
| A broken edit | a value that cannot be bound or fails validation: nothing is hidden until it is fixed (PV008); invalid JSON in appsettings.json is handled by .NET configuration, not by this package |
invalid JSON or an unknown key keeps the last valid version (PV001, PV002); rules that fail validation hide nothing (PV008) |
| Reload | on save, no restart | on save, no restart |
When the rules file exists and is valid, its ContentTypes, RuleSets and Sites replace the appsettings ones as a whole: nothing is merged. Enabled (the kill switch) and ConfigFile can only be set in appsettings. On Docker bind mounts, volumes and network shares, file change events may not arrive; set DOTNET_USE_POLLING_FILE_WATCHER=1 so edits to the rules file are picked up. A rules file that is a symbolic link (a Kubernetes ConfigMap or Secret, or a link into a shared folder) is not reloaded by the default watcher either, and nothing reports it: point ConfigFile at the real file, or set DOTNET_USE_POLLING_FILE_WATCHER=1. When the file cannot be watched at all, the health check shows PV304.
The package ships a JSON schema for each place. After the first build, editors that follow the "$schema": "appsettings-schema.json" line of the Umbraco template (Visual Studio, VS Code, Rider) complete and check the PropertyVisibility section. Start a rules file with "$schema": "./PropertyVisibility.config-schema.json"; the build copies that schema next to it. In the Development environment the site also writes PropertyVisibility.config-schema.local.json next to an existing rules file, with your own content type, property, tab and group aliases and your root nodes filled in: point $schema at that file and the editor completes your aliases and root node keys and underlines a typo while you type (details). Both files are generated: add PropertyVisibility.config-schema*.json to the site's .gitignore, which already ignores appsettings-schema*.json.
Every option, the alias grammar and the reload rules: docs/configuration.md.
For each document the package finds the root node (the document's top-level ancestor; for a document that is not saved yet, the root of the parent it is created under) and picks one site:
| Order | The site that matches | Notes |
|---|---|---|
| 1 | the site whose RootNodeKey is the root's key |
Recommended. A renamed root keeps matching, and PV105 reports a RootNodeName that no longer agrees. |
| 2 | the site whose RootNodeName is the root's name |
Case-insensitive and trimmed. On a culture-variant root: the default-culture name as of the last save. |
| 3 | the site with IsDefault: true |
At most one site. |
| 4 | no site | Only the top-level ContentTypes apply. |
A document created at the content root has no root before its first save, so it gets the default site; after the save it is its own root. Documents in the recycle bin match no site, not even the default one, and get the top-level rules only.
The rules for a document or a block are the union of the top-level ContentTypes entry, the matched site's entry and the entries of the rule sets that site includes, for its content type and for each of its compositions. Hiding only adds up: no rule can show what another rule hides. With HideEmptiedContainers (on by default), a group whose properties are all hidden disappears too, and so does a tab whose own properties and groups are all hidden.
Compositions: a rule keyed by a composition applies wherever the composition is used, to the composition itself and to every document or element type composed of it, directly or through another composition. A parent document type counts as a composition. The rule resolves against the composition's own properties, tabs and groups, so "siteSettings": { "Containers": ["legacyTab"] } removes the Legacy tab that siteSettings contributes, but not a tab with the same alias that the composing type defines itself. To hide something on one composing type only, key the rule by that type. The health check lists the types each composition rule reaches (PV205). Details: docs/configuration.md.
Rules keyed by an element type apply inside blocks, for the site of the document that holds the block. A block's content follows the rules of its content element type, its settings the rules of its settings element type:
"corporate": {
"RootNodeKey": "5c2b4d7e-9f1a-4c3e-8b6d-2a1f0e9d8c7b",
"ContentTypes": {
"promoBanner": { "Properties": ["overlayColour"] },
"promoBannerSettings": { "Properties": ["anchorId"] }
}
}Covered by the acceptance suite on every verified Umbraco version (Supported Umbraco versions): Block List in a modal and inline, Block Grid in a modal, blocks inside blocks (two levels deep), blocks in a document that is not saved yet, blocks in a culture-variant document in split view, Single Block in a modal and inline, blocks in the rich text editor, and blocks pasted from the clipboard into another site's document (they follow the site they are pasted into).
Where it does not apply:
- Blocks edited outside a document: nothing is hidden in a media item, a member or a document blueprint, also when the media item or member is opened from inside a document.
- Block Grid inline editing: the property the grid renders inline (the element type's first property) is always shown. The block's modal still hides it.
- A block label that uses a hidden property still shows its value.
Details: docs/configuration.md.
Settings > Health Check > Configuration > "Property Visibility configuration" checks the rules against the site it runs on:
- a
RootNodeKeythat is not a root node, aRootNodeNamethat matches no root, a root that no site matches, and a rule set that no site includes; - a content type, property or container alias that does not exist, with a "Did you mean" suggestion or the list of the type's tab and group aliases;
- hidden properties that are mandatory (
PV203), and entries that hide nothing (PV206, informational); - configuration errors: invalid JSON or an unknown key in the rules file, a site without an identity, two sites for one root, more than one default site, a rule set name in
Includethat does not exist; - a summary with the rules source, when the rules were loaded, a rules hash to compare servers, and the Umbraco version against the tested versions.
Every issue has a stable code (PV001 to PV304) whose meaning never changes. The same issues are written to the server log once per configuration change, never per request. Every code is explained in docs/configuration.md.
To see what the package does in the backoffice, run this in the browser console:
localStorage['Umbraco.Community.PropertyVisibility.Debug'] = '1'Every hidden-fields response and every completed pass is then logged at the Verbose level with the prefix [PropertyVisibility]: the matched site and why it matched, the warnings, and how many properties, tabs and groups were hidden. That summary is in the text of the line, so it survives copying from the console (site 'corporate' (Key), 3 properties, 2 containers, no warnings); the object after it has the details. Remove the key to stop. The log never adds the key or name of the document's site root; when the open document is itself a root node, its own key is logged as the document key.
After every completed pass the backoffice dispatches umbraco-community-property-visibility:applied on window, with documentKey, contentTypeKey, propertyCount, containerCount and target (document, block-content or block-settings). It is a stable integration point for tests and other packages. Details: docs/configuration.md.
Up to Umbraco 13, fields were often hidden per site by a SendingContentNotification handler (EditorModelEventManager.SendingContentModel on Umbraco 8) that removed properties and tabs from the editor model. Umbraco 14 and later have no such notification. To replace a handler like that, or other code of your own:
- Write its rules as configuration: a site per root node, and a rule set for rules that several sites share.
- Open the health check and fix what it reports.
PV104,PV201andPV202name content types, properties, tabs and groups that do not exist, so a rule that never matched anything in the old code, for example because a tab was renamed since, shows up here. - Turn on the debug flag, open a document of each site, and compare the matched site and the counts with what the old code hid.
- Remove the old code.
| Umbraco | Package | Status |
|---|---|---|
| 17.6.x from 17.6.2 | 1.x | Verified on 17.6.2, the lowest supported 17.x |
| 17.7.x | 1.x | Verified on 17.7.0 to 17.7.2 |
| 17.8.x | 1.x | Verified on 17.8.0 |
| 17.0 to 17.6.1 | Not supported: the package relies on backoffice APIs verified from 17.6.2 on | |
| 18.2.x from 18.2.1 | 2.x | Verified on 18.2.1 and 18.2.2, the lowest supported 18.x |
| 18.3.x | 2.x | Verified on 18.3.0 |
| 18.0 to 18.2.0 | Not supported: not verified |
1.x depends on Umbraco [17.6.2, 18.0.0), 2.x on [18.2.1, 19.0.0), both on .NET 10. Umbraco minors are verified one by one; a later patch of a verified minor counts as verified, and a weekly compatibility run tests the latest patch of each major. A later minor of the same major can be installed; until it is verified here, the health check reports it as PV303 (Info). What each verification covered: docs/compatibility.md.
- It is not authorization. Hiding is a backoffice user-interface feature. Hidden values stay in the document: they are loaded into the browser, saved with the document unchanged, readable and writable through the Management API, returned by the Delivery API and rendered on the website as before. To restrict who may read or change a property, use Umbraco's user group permissions (Document Property Value permissions, see Users in the Umbraco documentation).
- Hidden mandatory properties still block publishing. Umbraco saves the document and reports that it could not be published, but shows no hint on the hidden field, so the editor cannot see why. The health check warns about every hidden mandatory property (
PV203). - No rules per user group. Every user sees the same result. Umbraco's property permissions on user groups cover that.
- No rules per culture or segment. A hidden property is hidden in every language.
- No rules per subtree or per document. A site is a root node and everything under it.
- No media types or member types. Rules apply to documents and to blocks in documents. Document blueprints hide nothing.
- Blocks outside documents get nothing hidden: blocks in media items, members and document blueprints, also when those are opened from inside a document.
- Block Grid inline editing shows a block's first property regardless of the rules, because Umbraco renders it without the property view guard. The block's modal hides it.
- Block labels can show hidden values: a label template that uses a hidden property still shows it.
- Only the document and block editors change. Collection (list view) columns, the content tree, search, preview and the website show hidden properties as before.
- No per-site property editor configuration. The package does not change labels, descriptions, the order of properties or tabs, data type settings, or the block types a property allows.
- Tabs and groups are removed through an Umbraco API that is not a documented extension point. Umbraco has a view guard for properties but none for tabs and groups, so the package removes them from the workspace's in-memory structure (
removeContainer). Each Umbraco minor is re-verified before it is listed as supported. If a version drops that method, properties stay hidden, the emptied tabs and groups stay visible, and the backoffice shows one warning. - A content type whose every property is hidden keeps its Content view. The view shows no fields; when every tab and group was removed, it shows Umbraco's "Not found" message after a few seconds.
- Hidden fields can show for a moment. The rules arrive with a request the backoffice sends when a document or block opens, so its fields can render for that one round trip before they are hidden.
- Rule changes reach a document when it is opened again. An open document, and the blocks in it, keep the rules they were opened with.
- A document type changed while a document is open does not reach that document once a tab or group was hidden in it. Reopen the document.
- Name matching on culture-variant roots uses the default-culture name as of the last save. Use
RootNodeKeywhere you can. - No per-site rules in the recycle bin. A trashed document matches no site, not even the default one; only the top-level
ContentTypesapply until it is restored. - Errors never hide more. Rules that cannot be read or fail validation (
PV008), a failed request or an unknown content type hide nothing; a broken edit of the rules file keeps the last valid version. When the package's request fails, the backoffice shows one Property Visibility warning until the page is reloaded, instead of Umbraco's generic error notification on every document. - Umbraco 17 and 18 only: 1.x from 17.6.2, 2.x from 18.2.1.
- Discussion #16329: Umbraco's property-level permissions do not cover tabs and groups. This is why the package uses
removeContainer(see above) and why each Umbraco minor is re-verified. - Discussion #20538: a tab whose properties are all hidden by a view guard stays visible in Umbraco. For its own rules the package removes such tabs and groups (
HideEmptiedContainers). - Issue #21253: a guard rule added as soon as a workspace context appears can be missed. The package adds its rules after its own request returns, and the acceptance suite has not shown the problem.
- Issue #21254: Umbraco's write guard (read-only properties) does not stop pickers and block editors. The package only uses view guards; a read-only mode would depend on this issue.
- On Umbraco 17.6.2 to 17.8.0 and 18.2.1 to 18.3.0 an inline-edited Block List item that is re-rendered while it initializes throws "Context request was cancelled, host was disconnected" as an uncaught promise rejection in the browser console, most often after a click on the already selected Content tab while the page loads. It was reproduced on a stock 17.6.2 site without this package on 2026-09-25 (counts in docs/testing.md); the same code is unchanged in 17.7.0, 17.7.1, 17.7.2, 17.8.0, 18.2.1, 18.2.2 and 18.3.0.
- On the same versions the Copy and Paste property actions of a Block List or Block Grid property throw a similar uncaught rejection ("UmbPropertyContext with API Alias: UmbClipboardPropertyContext"), once per action, when the property is re-rendered before its editor has loaded, again most often after a click on the already selected Content tab while the page loads. It was reproduced without this package on stock 17.6.2 and 17.7.0 sites on 2026-09-30, as often as with it (counts in docs/testing.md); the code is unchanged up to 17.8.0 and 18.3.0.
Update the package to 2.x in the same step as Umbraco: 1.x does not run on Umbraco 18, and 2.x does not run on Umbraco 17. 2.x reads the same configuration, so the PropertyVisibility section and the rules file stay as they are, and the hidden-fields API is unchanged. Two things differ:
- The package's OpenAPI document moves from
/umbraco/swagger/property-visibility/swagger.jsonto/umbraco/openapi/property-visibility.json, because Umbraco 18 serves OpenAPI documents through Microsoft.AspNetCore.OpenApi instead of Swashbuckle. - The public classes
PropertyVisibilitySwaggerGenOptionsSetupandPropertyVisibilitySecurityFilterare gone. They registered that document on Umbraco 17; a site never had to reference them.
Property Visibility is an actively maintained community package.
- 1.x follows Umbraco 17, a long-term support release, and 2.x follows Umbraco 18. Both are built from the same source, so a fix or a feature ships in both.
- A new 17.x or 18.x version is listed under Supported Umbraco versions once it has passed the checks in docs/compatibility.md;
compat.ymlruns the automated part against the latest 17.x and 18.x every week. - Bugs and questions go in GitHub issues. Report security issues privately, see SECURITY.md.
Umbraco itself and other packages cover related needs:
- Per user group instead of per site: Umbraco's Document Property Value permissions on user groups (properties only), or Umbraco.Community.AdminOnly, which hides document types, tabs, properties and tree nodes from users who are not administrators.
- Depending on the value of another property in the same document type: Our.Umbraco.ConditionalDisplayers.
- The same properties hidden on every site, managed in a dashboard: Our.Umbraco.BlockFieldVisibility hides properties of a page type or a Block List block type wherever that type is used.
Property Visibility fits when several sites share document or element types and properties, tabs or groups should disappear on some of those sites.
Issues and pull requests are welcome, see CONTRIBUTING.md. Building from source needs the .NET 10 SDK and Node 24 LTS (24.13 or later). dotnet build Umbraco.Community.PropertyVisibility.slnx builds the package, its backoffice client and the test site (skip the client with -p:SkipClientBuild=true); dotnet run --project src/Umbraco.Community.PropertyVisibility.TestSite --launch-profile https starts the test site on https://localhost:44300/umbraco. Tests are described in docs/testing.md. Report security issues privately, see SECURITY.md.
MIT. Third-party code in the repository and in the package, with its licence: THIRD-PARTY-NOTICES.md.
Property Visibility is a community package. It is not made, endorsed or supported by Umbraco A/S. Umbraco is a trademark of Umbraco A/S.


