How do I get a Content from its UUID in a UFM filter?

I’m trying to create a Custom UFM Filter and I need to get a Content from its UUID. Let’s just say it’s very much related to this question

Inside the filter() method of my class that extends UmbUfmFilterBase I have no idea how to get the Content from the supplied value — I can’t find any examples of how to do this; if anybody knows how it’s done, or can point me to some documentation I’d be very happy!

Thanks!

/Chriztian

UPDATE: I put up a Gist with the component I made from Bishal’s suggestions

@greystate

I don’t think a UFM filter is the right place for this. Filters are synchronous value transformers, so for repository lookups I’d use a custom UFM component that renders a custom element.

In that element you can use UmbDocumentItemRepository.requestItems([unique]). If the value is a UDI from Contentment, convert it first with getGuidFromUdi() from @umbraco-cms/backoffice/utils.

The built-in {umbContentName: pickerAlias} component does something similar, but it expects the value shape it already understands. See the UFM component docs: https://docs.umbraco.com/umbraco-cms/reference/umbraco-flavored-markdown

1 Like

Hi @BishalTimalsina12 :waving_hand:

Thanks a lot - I’ve never understood why there are both “Components” and “Filters”; I bet to the vast majority they’re just two different syntaxes, but your explanation makes sense and I’ll try the component route instead and see where that gets me.

Thanks again :raising_hands:
/Chriztian

1 Like

I had to do something similar where I wanted the Name from the content picker, but the value being stored was a UDI, and used this:

In the collection view label template I put this {~clinicID}
where ~ is the marker mentioned in the umbraco-package.json
clinicID is the field alias

{
  "$schema": "../umbraco-package-schema.json",
  "name": "CustomListViewFilters",
  "version": "1.0.0",
  "extensions": [
    {
      "type": "ufmComponent",
      "alias": "CustomListViewFilters.UfmComponent.UdiToName",
      "name": "UDI to Name UFM Component",
      "api": "/App_Plugins/CustomListViewFilters/udi-to-name-column.js",
      "meta": {
        "alias": "umbDocumentName",
        "marker": "~"
      }
    }
  ]
}

and then the filter itself:

import { html, css } from "@umbraco-cms/backoffice/external/lit";
import { UmbLitElement } from "@umbraco-cms/backoffice/lit-element";
import { UMB_AUTH_CONTEXT } from "@umbraco-cms/backoffice/auth";

// ─── Name Cache (shared across all instances) ───
const nameCache = new Map();

// ─── Custom element that resolves UDIs to names asynchronously ───
class UfmUdiToNameElement extends UmbLitElement {
    static properties = {
        alias: { type: String },
        _displayName: { state: true },
    };

    #authContext;

    constructor() {
        super();
        this.alias = "";
        this._displayName = "";

        this.consumeContext(UMB_AUTH_CONTEXT, (ctx) => {
            this.#authContext = ctx;
        });

        this.consumeContext("UmbUfmRenderContext", (ctx) => {
            this.observe(ctx?.value, (val) => {
                // val is { value: <propertyValue> } from the collection column
                // The property value for a content picker is an array of UDI strings
                const rawValue = val?.value ?? val;
                this.#resolve(rawValue);
            });
        });
    }

    async #getAuthToken() {
        if (!this.#authContext) return null;
        try {
            const config = this.#authContext.getOpenApiConfiguration();
            if (typeof config.token === "function") {
                return await config.token({ name: "", scopes: [] });
            }
            if (config.token) {
                return await config.token;
            }
        } catch {
            // Fall back to cookie auth
        }
        return null;
    }

    #udiToGuid(udi) {
        if (!udi || typeof udi !== "string") return null;
        const match = udi.match(
            /umb:\/\/document\/([0-9a-f]{32}|[0-9a-f\-]{36})/i
        );
        if (!match) return null;
        const hex = match[1].replace(/-/g, "");
        return [
            hex.substring(0, 8),
            hex.substring(8, 12),
            hex.substring(12, 16),
            hex.substring(16, 20),
            hex.substring(20, 32),
        ].join("-");
    }

    async #resolveNameFromGuid(guid) {
        if (nameCache.has(guid)) {
            return nameCache.get(guid);
        }

        try {
            const headers = {};
            const token = await this.#getAuthToken();
            if (token) {
                headers["Authorization"] = `Bearer ${token}`;
            }

            const response = await fetch(
                `/umbraco/management/api/v1/document/${guid}`,
                { credentials: "same-origin", headers }
            );

            if (response.ok) {
                const data = await response.json();
                const name =
                    data.variants && data.variants.length > 0
                        ? data.variants[0].name || guid
                        : guid;
                nameCache.set(guid, name);
                return name;
            }
        } catch (err) {
            console.error("Failed to resolve UDI to name", err);
        }
        return guid;
    }

    async #resolve(rawValue) {
        if (!rawValue) {
            this._displayName = "";
            return;
        }
        
        let udis;
        try {
            const parsed =
                typeof rawValue === "string" ? JSON.parse(rawValue) : rawValue;
            udis = Array.isArray(parsed) ? parsed : [parsed];
        } catch {
            udis = typeof rawValue === "string" ? [rawValue] : [];
        }

        const names = [];
        for (const udi of udis) {
            if (!udi || typeof udi !== "string") continue;
            const guid = this.#udiToGuid(udi);
            if (guid) {
                names.push(await this.#resolveNameFromGuid(guid));
            }
        }

        this._displayName = names.join(", ");
    }

    static styles = css`
        :host { display: inline; }
    `;

    render() {
        return html`${this._displayName}`;
    }
}

if (!customElements.get("ufm-udi-to-name")) {
    customElements.define("ufm-udi-to-name", UfmUdiToNameElement);
}

// ─── UFM Component API ───
// Umbraco calls render() which returns HTML string containing our async custom element.
export class UfmUdiToNameComponent {
    render(token) {
        if (!token.text) return undefined;
        const alias = token.text.trim();
        return `<ufm-udi-to-name alias="${alias}"></ufm-udi-to-name>`;
    }

    destroy() {}
}

export { UfmUdiToNameComponent as api };