Certificate library: live This adds: in-app viewer Training Tracker only

Add An In-App Certificate Viewer

The certificate library migration is done: TT - Save Certificate Library saves into the locked TT Certificates_LTD library, and TT - Email Certificate Library looks the file up with the same authorisation check and returns it as one JSON payload field. This guide covers the one remaining piece: getting that certificate to actually open on screen when someone clicks the button, instead of trying to email it or force a download.

Nothing about the flow changes. TT - Save Certificate Library, btnSaveCert, and TT - Email Certificate Library itself are all correct and stay exactly as they are. This guide only touches btnOpenCertificate and adds four new controls to scrCourseDetail.

1. What This Adds And Why

The point of this app is one click, done — find the person, open the record, click the certificate, it opens. Two things were tried and both failed for the same underlying reason:

TriedResultWhy
Launch("data:...;base64,...")Nothing happened, no error.Browsers (and Power Apps Studio's own Preview player, which runs in an iframe) block top-level navigation to a data: URI as an anti-phishing measure.
Download("data:...;base64,...")"The URL passed to the function is not valid."Power Apps' own Download() function expects a real https:// address, the same as Launch() — neither function is actually built to accept base64 data URIs, despite older examples online suggesting otherwise.

Both routes try to make the browser navigate to or fetch the file. The fix is to stop trying to navigate anywhere, and instead hand the base64 data straight to a control that renders it inline, inside the app itself. Two native Power Apps controls do exactly this and are not subject to the navigation block, because setting a property is not the same as navigating:

Since your certificate picker allows both PDFs and images, this guide adds both controls in a small popup over scrCourseDetail, and shows whichever one matches the file that came back.

2. Add The Viewer Controls To scrCourseDetail

Add these four controls as the last controls on the screen (after everything else in the tree), so they render on top of the rest of the screen when visible. None of them need inserting inside any existing group — add them directly on the screen.

2.1 Dimmed Background

Insert → search Button → classic Button (not ModernButton). Rename it grpCertViewerBG.

PropertyValue
Text=""
Fill=RGBA(0,0,0,0.75)
BorderThickness=0
X=0
Y=0
Width=Parent.Width
Height=Parent.Height
DisplayMode=DisplayMode.View
Visible=varCertViewerOpen

This is decorative only (DisplayMode.View makes it non-interactive) — it just dims everything behind the viewer so it's obvious you're in a popup.

2.2 PDF Viewer Control

Insert → search PDF viewer → add it. Rename it pdfCertViewer.

PropertyValue
Document=varCertDataUri
X=Parent.Width * 0.1
Y=Parent.Height * 0.1
Width=Parent.Width * 0.8
Height=Parent.Height * 0.75
Visible=varCertViewerOpen && varCertIsPdf

2.3 Image Control

Insert → search Image → add it. Rename it imgCertViewer.

PropertyValue
Image=varCertDataUri
ImagePosition=ImagePosition.Fit
X=Parent.Width * 0.1
Y=Parent.Height * 0.1
Width=Parent.Width * 0.8
Height=Parent.Height * 0.75
Visible=varCertViewerOpen && !varCertIsPdf

2.4 Close Button

Insert → search ModernButton → add it, same control type as btnSaveCert. Rename it btnCloseCertViewer.

PropertyValue
Text="Close"
BasePaletteColor=RGBA(108,117,125,1)
Color=RGBA(255,255,255,1)
X=Parent.Width * 0.1
Y=(Parent.Height * 0.1) + (Parent.Height * 0.75) + 12
Width=140
Height=44
Visible=varCertViewerOpen
OnSelect=Set(varCertViewerOpen, false); Set(varCertDataUri, "")

3. Update btnOpenCertificate

The button itself keeps its existing Text ("Open certificate") and Visible properties from the earlier cutover section — only OnSelect changes. Select btnOpenCertificate, open OnSelect, delete everything currently in there, and paste this complete formula in full:

Power Fx - btnOpenCertificate.OnSelect (full formula)
=Set(varCertError, "");
Set(varCertStatusText, "Fetching certificate...");
Set(varCertBusy, true);
Set(
    varCertRaw,
    IfError(
        'TT - Email Certificate Library'.Run(varRecord.ID, varMyEmail, varIsAdmin),
        Blank()
    )
);
Set(varCertBusy, false);
If(
    IsBlank(varCertRaw),
    Set(varCertError, "Flow could not be reached. Check your connection and try again."); Notify("Could not open certificate — see the error below.", NotificationType.Error),
    With(
        {parsed: ParseJSON(varCertRaw.payload)},
        If(
            Boolean(parsed.success),
            Set(varCertStatusText, "");
            Set(varCertFileNameLower, Lower(Text(parsed.fileName)));
            Set(varCertIsPdf, EndsWith(varCertFileNameLower, ".pdf"));
            Set(
                varCertMimeType,
                If(
                    varCertIsPdf, "application/pdf",
                    EndsWith(varCertFileNameLower, ".png"), "image/png",
                    "image/jpeg"
                )
            );
            Set(varCertDataUri, "data:" & varCertMimeType & ";base64," & Trim(Text(parsed.fileContentBase64)));
            Set(varCertViewerOpen, true),
            Set(varCertError, Text(parsed.message)); Notify("Could not open certificate — see the error below.", NotificationType.Error)
        )
    )
)

Note the Boolean(...) and Text(...) wrappers around every field read off parsedParseJSON returns an untyped value in Power Fx, and Power Apps will throw a type-mismatch error if you use a parsed field directly without coercing it first. This is not optional.

New variables used here: varCertFileNameLower, varCertIsPdf, varCertMimeType, varCertDataUri, varCertViewerOpen are all created the first time this formula runs — Power Apps creates variables on first use, you do not need to initialize them separately anywhere.

4. Test Script

  1. Open a record that already has a certificate saved in TT Certificates_LTD.
  2. Click Open certificate.
  3. Confirm the screen dims and the certificate renders inline — PDF viewer for a .pdf, Image control for a .jpg/.jpeg/.png.
  4. Click Close. Confirm the popup disappears and the rest of the screen is usable again.
  5. Open the same record again and click Open certificate a second time. Confirm it still works — this catches anything that only cleared on close but didn't reset properly for a second open.
  6. Test as a user who is not the record's owner and not an admin. Confirm they get the "not authorized" message, not the file.

5. Checklist

  1. TT - Save Certificate Library, btnSaveCert, and TT - Email Certificate Library all untouched and still working.
  2. grpCertViewerBG, pdfCertViewer, imgCertViewer, btnCloseCertViewer added to scrCourseDetail, last in the control tree.
  3. btnOpenCertificate.OnSelect replaced with the full formula above — no Launch() or Download() anywhere in it.
  4. PDF certificates render in the PDF viewer; image certificates render in the Image control; the correct one shows automatically based on file extension.
  5. Close button clears the viewer and it can be reopened cleanly.
  6. Tested as both an authorised person and an unauthorised person.
  7. grpCertRetentionNotice pasted onto scrCourseDetail and visible above the Save/Cancel buttons (section 6).
  8. TT - Save Certificate Library deletes any existing file for the record before creating the new one, confirmed by re-uploading a test record and checking the library has exactly one file (section 7).
  9. ctrlNewCertificate made transparent and btnCertDropZoneBG added behind it, so the panel shows a styled drag-and-drop zone instead of native browser chrome (section 9). Drag-and-drop and click-to-browse both tested working, and the save formula confirmed untouched.

6. Retention Notice Badge

There is no download from inside the app — that's confirmed, not a gap we missed (see section 1). Since users can't retrieve their own uploaded file back out through the app afterwards, this adds a small rounded notice badge on scrCourseDetail telling them to keep their own copy before they submit, worded as a deliberate design choice rather than a limitation.

Two things worth checking that may soften this in practice, though neither is guaranteed: a certificate saved as an image renders as a real <img> in the Image control, so a normal browser right-click → "Save image as" should work on it — that's a native browser feature, nothing to do with the Launch/Download restriction. For PDFs, the PDF viewer control's own built-in toolbar sometimes includes a download/print icon, depending on version — worth a look next time one's open.

Paste This Directly Into Power Apps Studio

Select the screen (not any control inside it) in the tree view, then paste this. It adds one new top-level control, positioned above the Save/Cancel buttons regardless of the certificate panel's current height, since it's anchored off btnSaveCert.Y rather than a fixed Y position.

YAML - paste onto scrCourseDetail
      - grpCertRetentionNotice:
          Control: GroupContainer@1.5.0
          Variant: ManualLayout
          Properties:
            BorderColor: =RGBA(168,101,0,0.35)
            BorderThickness: =1
            DropShadow: =DropShadow.None
            Fill: =RGBA(255,247,232,1)
            Height: =60
            RadiusBottomLeft: =18
            RadiusBottomRight: =18
            RadiusTopLeft: =18
            RadiusTopRight: =18
            Width: =796
            X: =315
            Y: =btnSaveCert.Y - 72
          Children:
            - icoCertRetentionNotice:
                Control: Classic/Icon@2.5.0
                Properties:
                  Color: =RGBA(168,101,0,1)
                  Height: =22
                  Icon: =Icon.Information
                  Width: =22
                  X: =14
                  Y: =19
            - lblCertRetentionNotice:
                Control: Label@2.5.1
                Properties:
                  Align: =Align.Left
                  Color: =RGBA(140,84,0,1)
                  Font: =Font.'Open Sans'
                  FontWeight: =FontWeight.Semibold
                  Height: =Parent.Height
                  PaddingLeft: =8
                  PaddingTop: =8
                  Size: =9
                  Text: ="Uploading a new certificate permanently replaces the old one - it can't be recovered. Keep your own copy, since certificates can only be viewed here, not downloaded."
                  VerticalAlign: =VerticalAlign.Middle
                  Width: =734
                  Wrap: =true
                  X: =44
                  Y: =0

Width 796 matches the combined span of the date and certificate panels (X 315 to 1111), so it sits flush under both. If your panels are positioned differently now, adjust Width/X to match after pasting — everything else (colours, icon, wording) will paste in correctly regardless. Height went from 44 to 60 to fit the longer wording on three lines comfortably.

7. Replace The Old Certificate On Re-Upload

Confirmed gap, not a guess: TT - Save Certificate Library currently only ever runs Create file when a new certificate is attached. There is no step that looks for an existing file for that TrainingRecordID and removes it first. Today, a re-upload adds a second file alongside the first — it does not replace it. The IsCurrent column in the library exists but nothing in the flow sets or checks it to filter old files out.

This is more than clutter. The read flow (TT - Email Certificate Library, used by the viewer) filters by TrainingRecordID eq X with Top Count: 1 and no sort order. Once two files exist for the same record, SharePoint decides which one comes back — not necessarily the newest. A re-upload could silently leave the viewer showing someone's old certificate, with no error and no way to tell from the app. Fix this before it comes up in a real audit.

Add A Delete Step Before Create File

Open TT - Save Certificate Library. Find the existing SharePoint – Create file action (inside the HasNewFile? If yes branch). Insert two new actions directly above it, still inside the same branch:

  1. Add SharePoint – Get files (properties only).
    FieldValue
    Site AddressSame Training Tracker SharePoint site.
    Library NameTT Certificates_LTD / TT Certificates (LTD).
    Filter QueryTrainingRecordID eq followed by RecordID from When Power Apps calls a flow (V2).

    No Top Count here — leave it blank, so this catches every existing file for the record, including any duplicates that may have already piled up before this fix.

  2. Add Control – Apply to each. For the loop input, choose the value output from the Get files (properties only) action you just added.

    Inside that loop, add SharePoint – Delete file:

    FieldValue
    Site AddressSame Training Tracker SharePoint site.
    File IdentifierIdentifier from the current item in this loop's Get files (properties only). Click the folder icon and switch to the dynamic content/expression view rather than browsing, since Identifier is not something you can navigate to by hand.

    This action only has two fields, Site Address and File Identifier — there is no separate Library Name field, unlike most of the other SharePoint actions in this guide. The identifier alone is enough to locate the file.

The existing Create file and Update file properties actions stay exactly as they are, running straight after this new loop finishes. Save the flow.

Test

  1. Pick a test record that already has a certificate saved.
  2. Upload a different file and click Save record.
  3. Open TT Certificates_LTD in SharePoint directly and confirm there is exactly one file for that TrainingRecordID — the new one, not two.
  4. Open the record in the app and click Open certificate. Confirm it shows the new file, not the old one.

9. Reskin The Certificate Upload As A Drag-And-Drop Zone

Correction to an earlier version of this section: it previously said to set icoUploadCert.OnSelect = Select(ctrlNewCertificate) to wire up the paperclip icon. Tested live, that does nothing — the classic Attachments control has no documented OnSelect property (only OnAddFile/OnRemoveFile/OnUndoRemoveFile), and Select() only works on controls that expose one. Remove that formula if you added it — it's a harmless no-op, but it isn't doing anything.

The real fix doesn't try to trigger the control programmatically at all. The classic Attachments control (ctrlNewCertificate) already supports native drag-and-drop and click-to-browse — the only problem is how it looks. So instead of hiding or shrinking it, this makes it fully transparent and stacks a nicer-looking decorative box directly underneath it, in the exact same position and size. You see the pretty box; every click and drag actually lands on the real, still fully-functional control sitting invisibly on top of it. This is a known, real pattern — every property used below is a documented property of the Attachments control, nothing invented.

Nothing about the save formula, btnSaveCert, or how the file gets read off ctrlNewCertificate.Attachments changes. This section is entirely cosmetic.

9.1 Add The Decorative Drop-Zone Background

Insert → search Button → classic Button (not ModernButton). Rename it btnCertDropZoneBG. Its size and position are set relative to ctrlNewCertificate's current values, so it always matches exactly, however that control is currently sized — you don't need to know or copy any pixel numbers by hand.

PropertyValue
Text="Click to select a file, or drag and drop"
Fill=RGBA(232,244,239,1) (adjust to taste — this is a light tint of this app's green)
BorderColor=RGBA(0,78,66,0.4)
BorderStyle=BorderStyle.Dashed
BorderThickness=2
Color=RGBA(0,78,66,1)
X=ctrlNewCertificate.X
Y=ctrlNewCertificate.Y
Width=ctrlNewCertificate.Width
Height=ctrlNewCertificate.Height
DisplayMode=DisplayMode.View

DisplayMode.View makes this purely decorative and non-interactive — it can never intercept a click or a dropped file, so there's no risk of it stealing input from the real control above it.

Z-order matters here. This new button must render behind ctrlNewCertificate, not in front of it, or it'll visually cover the real control and block drags/clicks from reaching it. Studio adds newly inserted controls at the front by default. In the tree view (left-hand panel), drag btnCertDropZoneBG so it sits directly above ctrlNewCertificate in the list — controls listed earlier in the tree render further back. If you're unsure which is in front after moving it, right-click btnCertDropZoneBG and check for a "Send backward" option, or just drag a test file onto the zone and confirm the file gets picked up rather than nothing happening.

9.2 Make The Real Attachments Control Invisible

Select ctrlNewCertificate and change these properties. Nothing else on it — not Items, not OnAddFile, not its size or position — needs to change.

PropertyValue
Fill=RGBA(255,255,255,0)
BorderThickness=0
AddAttachmentText=""
NoAttachmentsText=""
DropTargetBackgroundColor=RGBA(0,78,66,0.12)
DropTargetBorderColor=RGBA(0,78,66,0.6)
DropTargetBorderThickness=2
DropTargetTextColor=RGBA(0,78,66,1)

Blanking AddAttachmentText and NoAttachmentsText removes the native "Click to select a file, or drag and drop" / "No file chosen" text — that's now btnCertDropZoneBG's job. The DropTarget* properties still show a highlight while a file is actively being dragged over the zone, so there's visual feedback mid-drag even though the control is otherwise invisible.

If ctrlNewCertificate's current Height is taller than it needs to be for one file (from the earlier attempt to shrink it), leave it as-is rather than trying to compress it further — the control's own minimum internal height doesn't change just because it's transparent now, and forcing it smaller still produces the same internal scrollbar as before. Whatever footprint it naturally needs, btnCertDropZoneBG now covers with a deliberately-styled box instead of blank native space, so the size no longer needs fighting.

9.3 Retire The Paperclip Icon

icoUploadCert is now redundant — the entire zone is clickable and drag-droppable, so a separate icon prompting the same action is one more thing to keep aligned for no benefit. Delete its OnSelect formula (the non-functional Select(ctrlNewCertificate) from the earlier version of this guide) if present, and either delete the icon control entirely or leave it purely as static decoration inside the new zone with no OnSelect at all.

Test

  1. Open a record's certificate panel. Confirm you see the dashed drop-zone box with the custom text, not native browser chrome.
  2. Drag a PDF or image file onto the box. Confirm the drop-target highlight appears while dragging, and the file attaches on drop.
  3. Clear it and instead click the box, browse to a file, and select it. Confirm that works too — drag-and-drop and click-to-browse both go through the same underlying control.
  4. Click Save record. Confirm the save flow works exactly as before — this step touches nothing in btnSaveCert, so it should be unaffected.

10. Sources

← Back to the main Training Tracker guide