How to preview PDF files in Power Pages using the Nutrient SDK: Part 2 – Saving annotations to Dataverse and loading them back

michelcarloMicrosoft 3658 hours ago44 Views

On my previous post, I explained how to view contents from a PDF file using Nutrient Web SDK, this post is a continuation of that.

Nutrient has a very nice feature to handle the annotations, it’s own proprietary format called “Instant JSON”. Essentially, when we want to save annotations on a PDF file, we can export them as JSON, save wherever we want, then handle loading them into the file when we want. So the logic to load, handle permissions etc will be with your custom application. This is very neat because we don’t need to save a whole new PDF file.

In this post I will showcase how to use Nutrient’s “Instant JSON” format, which extracts just the annotations as a lightweight text string and then save this content to a Multiline Text column in Dataverse using the Power Pages Web API.

Summary of the functionality

When clicking on the small ‘comment’ I icon in the viewer, we go to adding notes mode in the viewer and can be making annotations on a PDF file, but we’ll need to programatically handle saving them.

The “Save Notes” button is a custom button that will trigger saving the contents. If you do text highlights also or drawings in the PDF file it will also be included when exporting the notes.

Save Annotations to Dataverse

When the user clicks the “Save Notes” button, we grab annotations and send them to Dataverse.

We do this by exporting the annotations as Instant JSON and making a POST or PATCH request to a custom table. Let’s pretend we have a table called pnp_pdfannotations with a text column called pnp_notejson.

Important: Make sure to also configure proper table permissions on this table and on the Rich Text Files table to enable the user save the notes record. You also need to enable the table for web api access.

document.getElementById("btnSaveAnnotations").addEventListener("click", async () => {
    if (!nutrientInstance) return;
 
    const instantJSON = await nutrientInstance.exportInstantJSON({ includeUnsavedChanges: true });    
   
    const record = {
        "pnp_notejson": JSON.stringify(instantJSON),
        "pnp_Document@odata.bind": `/msdyn_richtextfiles(${currentDocumentId})` 
    };

    console.log("Saving annotations...");
       $(".loader").show();
    webapi.safeAjax({
        type: currentPDFAnnotationID ? "PATCH" : "POST",
        url: "/_api/pnp_pdfannotations" + (currentPDFAnnotationID ? "(" +currentPDFAnnotationID+")" : ""),
        contentType: "application/json",
        data: JSON.stringify(record),
        success: function (data, textStatus, xhr) {
            console.log("Annotations saved!");
            alert("Your notes have been saved.");
               $(".loader").hide();
        },
        error: function (xhr, textStatus, errorThrown) {
            console.error("Error saving annotations:", xhr);
            alert("An error occurred while saving.");
               $(".loader").hide();
        }
    });
});

Load existing annotations

Finally, if a user opens a document they annotated yesterday, we need those notes to reappear. Based on the content from the previous post, we need to do slight modifications to load the annotations before setting up the viewer, because we need to pass the JSON contents into the viewer properties.

We need to make a GET request to the Power Pages Web API to fetch the JSON string, parse it, and pass it to the viewer, below the function to do it:

var currentPDFAnnotationID = null;
async function loadAnnotationsFromDataverse(documentId) {
        let data = await webapi.safeAjax({
                type: "GET",
                url: `/_api/pnp_pdfannotations?$filter=_pnp_document_value eq '${documentId}'&$top=1`,
                contentType: "application/json"
        });
        if (data.value.length > 0 &&  data.value[0]["pnp_notejson"]) {   
                const annotationsToLoad = JSON.parse(data.value[0]["pnp_notejson"]); //const savedJsonString = data.value[0]["pnp_notejson"];
                if (annotationsToLoad.pdfId) {
                        delete annotationsToLoad.pdfId;//avoids the mismatch error since the ids are dynamic and might vary after each load
                }//https://www.nutrient.io/guides/web/troubleshooting/common-issues/#pdf-id-mismatch-error-when-loading-instant-json

                currentPDFAnnotationID = data.value[0]["pnp_pdfannotationid"];
//global var, use this to save back to dv
                return annotationsToLoad;
               
        }
        return null;
}

And also the previous functions to load the viewer amended to handle loading the annotations:

function loadNutrientPdfViewer(base64FileData, annotationsToLoad) {
        
        window.NutrientViewer.unload(containerId);      

      
        const documentUri = `data:application/pdf;base64,${base64FileData}`;
        var annotationsObject = annotationsToLoad ? { instantJSON: annotationsToLoad } : {};
        window.NutrientViewer.load({
                ...annotationsObject,               
                container: containerId,
                document: documentUri,
                initialViewState: new NutrientViewer.ViewState({
                        sidebarMode: NutrientViewer.SidebarMode.THUMBNAILS
                })
        }).then((instance) => {               
                nutrientInstance = instance;
                $(".loader").hide();
        }).catch(error => {
                console.error("Failed to load document:", error);
        });
}


document.getElementById("btnSaveAnnotations").addEventListener("click", async () => {
        if (!nutrientInstance) return;

        const instantJSON = await nutrientInstance.exportInstantJSON({ includeUnsavedChanges: true });

        const record = {
                "pnp_notejson": JSON.stringify(instantJSON),
                "pnp_Document@odata.bind": `/msdyn_richtextfiles(${currentDocumentId})`
        };

        console.log("Saving annotations...");
        $(".loader").show();
        webapi.safeAjax({
                type: currentPDFAnnotationID ? "PATCH" : "POST",
                url: "/_api/pnp_pdfannotations" + (currentPDFAnnotationID ? "(" + currentPDFAnnotationID + ")" : ""),
                contentType: "application/json",
                data: JSON.stringify(record),
                success: function (data, textStatus, xhr) {                        
                        alert("Your notes have been saved.");
                        $(".loader").hide();
                },
                error: function (xhr, textStatus, errorThrown) {                        
                        alert("An error occurred while saving.");
                        $(".loader").hide();
                }
        });
});

Results/Demo

Clicking the save notes button, will save the note and show a success message:

Notes content will appear in the Dataverse table:

When the document is reloaded, the notes will appear:

Conclusion

By passing Base64 strings to the Nutrient SDK and utilizing their Instant By passing Base64 strings to the Nutrient SDK and utilizing their Instant JSON format, you avoid dealing with massive file uploads every time a user makes a simple text highlight.

Considerations: This sample does not take contact permissions into account, if needed you can also enable contact permissions on the table, so that the annotations can be private.

Resources

Nutrient – Exporting annotations in Instant JSON

The post How to preview PDF files in Power Pages using the Nutrient SDK: Part 2 – Saving annotations to Dataverse and loading them back appeared first on michelcarlo.

Original Post https://michelcarlo.com/2026/04/04/how-to-preview-pdf-files-in-power-pages-using-the-nutrient-sdk-part-2-saving-annotations-to-dataverse-and-loading-them-back/

0 Votes: 0 Upvotes, 0 Downvotes (0 Points)

Leave a reply

Follow
Search
Loading

Signing-in 3 seconds...

Signing-up 3 seconds...

Discover more from 365 Community Online

Subscribe now to keep reading and get access to the full archive.

Continue reading