{
- type: 'workspaceView',
- name: 'Example Counter Workspace View',
- alias: 'example.workspaceView.counter',
- element: () => import('./example-workspace-view.js'),
- weight: 900,
- meta: {
- label: 'Counter',
- pathname: 'counter',
- icon: 'icon-lab',
- },
- conditions: [
- {
- alias: UMB_WORKSPACE_CONDITION_ALIAS,
- match: 'Umb.Workspace.Document',
- },
- ],
+### In Workspace Views
+
+```typescript
+export class MyWorkspaceView extends UmbElementMixin(LitElement) {
+ constructor() {
+ super();
+ this.consumeContext(MY_WORKSPACE_CONTEXT, (context) => {
+ this.observe(context.data, (data) => this.requestUpdate());
+ });
+ }
}
-
+```
+
+## Best Practices
+
+### State Encapsulation
+
+```typescript
+// ✅ Private state with public observables
+#data = new UmbObjectState(initialData);
+readonly data = this.#data.asObservable();
+
+// ❌ Direct state exposure
+data = new UmbObjectState(initialData);
+```
+
+### Context Token Consistency
+
+```typescript
+// ✅ Use workspace scoping
+new UmbContextTokenWorkspace Actions
import { extensionRegistry } from '@umbraco-cms/extension-registry';
-import { MyWorkspaceAction } from './my-workspace-action';
+- Actions execute when clicked
+- Can be async for complex operations
+- Have access to workspace state during execution
+- Can modify workspace contexts
-const manifest = {
- type: 'workspaceAction',
- alias: 'My.WorkspaceAction',
- name: 'My Workspace Action',
- api: MyWorkspaceAction,
- meta: {
- label: 'My Action',
- },
- conditions: [
- {
- alias: 'Umb.Condition.WorkspaceAlias',
- match: 'My.Workspace',
- },
- ],
-};
+### Conditional Execution
-extensionRegistry.register(manifest);
-
+Check workspace state before performing actions:
+
+```typescript
+override async execute() {
+ const entityContext = await this.getContext(ENTITY_CONTEXT);
+
+ if (!entityContext.canPerformAction()) {
+ return; // Silently skip if not available
+ }
+
+ await entityContext.performAction();
+}
+```
-## The Workspace Action Class
+## Action Menu Integration
-As part of the Extension Manifest you can attach a class that will be instantiated as part of the action. It will have access to the host element and the Workspace Context. When the action is clicked the `execute` method on the API class will be run. When the action is completed, an event on the host element will be dispatched to notify any surrounding elements.
+Actions can be extended with dropdown menu items using `forWorkspaceActions`:
-```ts
-import { UmbWorkspaceActionBase } from '@umbraco-cms/backoffice/workspace';
+{% code caption="action-with-menu.ts" %}
+```typescript
+// Primary Action
+{
+ type: 'workspaceAction',
+ alias: 'example.action.save',
+ api: () => import('./save-action.js'),
+ meta: { label: 'Save' },
+}
-export class MyWorkspaceAction extends UmbWorkspaceActionBase {
- execute() {
- this.workspaceContext.myAction(this.selection);
- }
+// Menu Item Extension
+{
+ type: 'workspaceActionMenuItem',
+ alias: 'example.menuItem.saveAndClose',
+ api: () => import('./save-close-action.js'),
+ forWorkspaceActions: 'example.action.save', // Extends the save action
+ meta: { label: 'Save and Close' },
}
```
+{% endcode %}
+
+## Action Events
-**Default Element**
+Workspace actions dispatch a generic `action-executed` event when they complete:
```typescript
-interface UmbWorkspaceActionElement {}
+// Event is automatically dispatched by the action UI element
+export class UmbActionExecutedEvent extends Event {
+ constructor() {
+ super('action-executed', { bubbles: true, composed: true, cancelable: false });
+ }
+}
```
+
+### Event Characteristics
+
+- **Generic signal** - No action-specific data included
+- **Always dispatched** - Fires on both success and failure
+- **DOM bubbling** - Event bubbles up through the workspace
+- **No payload** - Contains no information about the action or results
+
+### Listening for Action Events
+
+Components can listen for action completion:
+
+```typescript
+// In a workspace component
+this.addEventListener('action-executed', (event) => {
+ // Action completed (success or failure)
+ // Refresh UI, close modals, etc.
+});
+```
+
+### When to Use Events
+
+- **UI cleanup** - Close dropdowns, modals after action execution
+- **General refresh** - Update displays when any action completes
+- **State synchronization** - Trigger broad UI updates
+
+{% hint style="info" %}
+For action-specific communication, use workspace contexts rather than events. Events provide only generic completion signals.
+{% endhint %}
+
+## Common Patterns
+
+### Entity Operations
+
+```typescript
+export class SaveAction extends UmbWorkspaceActionBase {
+ override async execute() {
+ const workspace = await this.getContext(DOCUMENT_WORKSPACE_CONTEXT);
+ await workspace.save();
+ }
+}
+```
+
+### State-Dependent Actions
+
+```typescript
+export class PublishAction extends UmbWorkspaceActionBase {
+ override async execute() {
+ const workspace = await this.getContext(DOCUMENT_WORKSPACE_CONTEXT);
+
+ if (workspace.hasValidationErrors()) {
+ // Action doesn't execute if invalid
+ return;
+ }
+
+ await workspace.saveAndPublish();
+ }
+}
+```
+
+### Multi-Step Operations
+
+```typescript
+export class ComplexAction extends UmbWorkspaceActionBase {
+ override async execute() {
+ const workspace = await this.getContext(MY_WORKSPACE_CONTEXT);
+
+ workspace.setStatus('processing');
+ await workspace.validate();
+ await workspace.process();
+ await workspace.save();
+ workspace.setStatus('complete');
+ }
+}
+```
+
+## Best Practices
+
+### Action Availability
+
+Only show actions when they are meaningful:
+```typescript
+conditions: [
+ {
+ alias: UMB_WORKSPACE_CONDITION_ALIAS,
+ match: 'Umb.Workspace.Document',
+ },
+ {
+ alias: 'My.Condition.EntityState',
+ match: 'draft', // Only show for draft entities
+ },
+],
+```
+
+### Visual Hierarchy
+
+Use appropriate styling for action importance:
+```typescript
+// Primary action (most important)
+meta: { look: 'primary', color: 'positive' }
+
+// Secondary action
+meta: { look: 'secondary' }
+
+// Destructive action
+meta: { look: 'primary', color: 'danger' }
+```
+
+### Context Dependencies
+
+Always check that the context is available before performing operations.
diff --git a/17/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-footer-app.md b/17/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-footer-app.md
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/17/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-footer-apps.md b/17/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-footer-apps.md
new file mode 100644
index 00000000000..849404b5aa7
--- /dev/null
+++ b/17/umbraco-cms/customizing/extending-overview/extension-types/workspaces/workspace-footer-apps.md
@@ -0,0 +1,217 @@
+---
+description: >-
+ Learn how to create workspace footer apps that provide persistent status information and contextual data in workspace environments.
+---
+
+# Workspace Footer App
+
+Workspace Footer Apps provide persistent status information and contextual data in the workspace footer area. They offer a non-intrusive way to display important information that remains visible while users work with workspace content.
+
+## Purpose
+
+Footer Apps provide:
+- **Persistent status** information visible while editing
+- **Contextual data** related to the current entity
+- **Non-intrusive monitoring** without taking up the main workspace space
+- **Real-time updates** through workspace context integration
+
+{% hint style="info" %}
+Footer apps appear at the bottom of workspaces and are ideal for displaying status indicators, counters, and contextual information.
+{% endhint %}
+
+## Manifest
+
+{% code caption="manifest.ts" %}
+```typescript
+{
+ type: 'workspaceFooterApp',
+ alias: 'example.workspaceFooterApp.counterStatus',
+ name: 'Counter Status Footer App',
+ element: () => import('./counter-status-footer-app.element.js'),
+ weight: 900,
+ conditions: [
+ {
+ alias: UMB_WORKSPACE_CONDITION_ALIAS,
+ match: 'Umb.Workspace.Document',
+ },
+ ],
+}
+```
+{% endcode %}
+
+### Key Properties
+- **`element`** - Points to the Lit element implementation
+- **`weight`** - Controls positioning within footer area
+- **`conditions`** - Determines workspace availability
+
+## Implementation
+
+Implement your workspace footer app as a Lit element that extends `UmbElementMixin`. This provides access to workspace contexts and reactive state management:
+
+{% code caption="counter-status-footer-app.element.ts" %}
+```typescript
+import { customElement, html, state, LitElement } from '@umbraco-cms/backoffice/external/lit';
+import { UmbElementMixin } from '@umbraco-cms/backoffice/element-api';
+import { EXAMPLE_COUNTER_CONTEXT } from './counter-workspace-context.js';
+
+@customElement('example-counter-status-footer-app')
+export class ExampleCounterStatusFooterAppElement extends UmbElementMixin(LitElement) {
+ @state()
+ private _counter = 0;
+
+ constructor() {
+ super();
+ this.#observeCounter();
+ }
+
+ async #observeCounter() {
+ const context = await this.getContext(EXAMPLE_COUNTER_CONTEXT);
+ if (!context) return;
+
+ this.observe(context.counter, (counter: number) => {
+ this._counter = counter;
+ });
+ }
+
+ override render() {
+ return html`Counter: ${this._counter}`;
+ }
+}
+
+export default ExampleCounterStatusFooterAppElement;
+
+declare global {
+ interface HTMLElementTagNameMap {
+ 'example-counter-status-footer-app': ExampleCounterStatusFooterAppElement;
+ }
+}
+```
+{% endcode %}
+
+## Footer App Lifecycle
+
+### Initialization
+- Footer apps initialize when the workspace loads
+- Context consumption happens during construction
+- Apps persist for the workspace lifetime
+
+### Updates
+- Use `observe()` for reactive updates from workspace contexts
+- Apps update automatically when observed state changes
+- Efficient rendering keeps footer responsive
+
+## Common Patterns
+
+### Status Indicators
+```typescript
+@customElement('entity-status-footer-app')
+export class EntityStatusFooterApp extends UmbElementMixin(LitElement) {
+ @state()
+ private status = 'loading';
+
+ constructor() {
+ super();
+ this.consumeContext(ENTITY_CONTEXT, (context) => {
+ this.observe(context.status, (status) => {
+ this.status = status;
+ });
+ });
+ }
+
+ override render() {
+ return html`
+ Workspace Views
Workspace Views
Current count value: ${this.count}
+This workspace view consumes the Counter Context and displays the current count.
+
Workspace View Example
+ Property action in Block List
Workspace
.png)
Use the "Rebuild index" button under Tools on the Examine Management dashboard in the Settings section.

Click the DeliveryAPIContentIndex on the Examine Management dashboard in the Settings section.

Use the "Rebuild index" button in the DeliveryAPIContentIndex under Tools on the Examine Management dashboard in the Settings section.
Scan the code above with your authenticator app
and enter the resulting code here to validate:
Scan the code above with your authenticator app
and enter the resulting code here to validate: