Skip to content
Shiny.NET

Photos

NuGet package Shiny.AppDeviceBridge.Photos

The photos bridge shows the system photo picker, and browses the photo library — pages, thumbnails and full-size exports — as files in a file root.

Terminal window
dotnet add package Shiny.AppDeviceBridge.Photos
builder.UseAppDeviceBridge(
bridge => bridge.AddPhotosBridge(),
webApp => { /* … */ }
);
Client C# TypeScript
IPhotosBridge AddPhotosBridgeClient() PhotosBridge
Topic Details
Picker POST photos/pick shows the system photo picker — no permission needed — and copies what the user chose into a file root (cache by default) under photos/. The answer lists each as a { root, path } the page reads through the files bridge. An empty list means the user cancelled. Every head has a picker.
Library browse every photo on the device, newest first: GET photos/library?offset=&limit= (200 at most), GET photos/library/{id}/thumbnail?size= for a JPEG that fits a square (32–1024 px), and POST photos/library/{id}/export to copy the original into a file root. It needs access — POST photos/access — and answers 403 without it.
Platforms PhotoKit on iOS, Mac Catalyst and macOS; MediaStore on Android; the user’s Pictures folder on Windows. Linux has no photo library, so the library endpoints return 501 there.
Platform setup NSPhotoLibraryUsageDescription on Apple platforms, plus the com.apple.security.personal-information.photos-library entitlement where the app is sandboxed. READ_MEDIA_IMAGES on Android 13 and later, READ_EXTERNAL_STORAGE before.

Picked and exported photos are copied into a file root, so the page reads them through the files bridge like any other file.

import { PhotosBridge, FilesBridge } from "@shinyorg/appdevicebridge";
const photos = new PhotosBridge();
const picked = await photos.pick({ limit: 3, root: "cache" }); // the system picker, no permission needed
const files = new FilesBridge();
for (const p of picked) {
const blob = await files.readBytes(p.file.root, p.file.path);
gallery.append(Object.assign(new Image(), { src: URL.createObjectURL(blob) }));
}
GET/_bridge/photos#

What works here, and whether the app has library access — without prompting.

C#IPhotosBridge.GetStatusAsyncTSPhotosBridge.getStatus
Response
200 PhotoStatus
PhotoStatus · 3 fields
pickerSupportedbooleanWhether the system photo picker is available.
librarySupportedbooleanWhether the photo library can be browsed here.
libraryAccessAccessStateLibrary access. Restricted is a limited selection the user chose on iOS or Android.
POST/_bridge/photos/access#

Requests photo library access.

C#IPhotosBridge.RequestAccessAsyncTSPhotosBridge.requestAccess
Response
200 PhotoAccessResult
PhotoAccessResult · 1 field
accessAccessState
POST/_bridge/photos/pick#

Shows the system photo picker and copies what the user chose into a file root. Empty when the user cancels. Not every platform enforces limit.

C#IPhotosBridge.PickAsyncTSPhotosBridge.pick
Body
application/json · PhotoPickRequest
PhotoPickRequest · 2 fields
limitnumberHow many the user may choose, 1–50.
rootstringThe file root the photos are copied into.
Response
200 PhotoFile[]
PhotoFile · 4 fields
fileBridgeFileWhere it is: read it through the files bridge.
fileNamestringThe name it had on the device, where the platform says.
contentTypestring | null
sizenumber
GET/_bridge/photos/library#

A page of the photo library, newest first. Fails with 403 without library access.

C#IPhotosBridge.GetLibraryAsyncTSPhotosBridge.getLibrary
ParameterInType
offsetquerynumberoptional
limitquerynumberoptional
Response
200 PhotoPage
PhotoPage · 4 fields
itemsLibraryPhoto[]
offsetnumber
limitnumber
hasMorebooleanWhether another page follows.
Errors
403Forbidden
GET/_bridge/photos/library/{id}/thumbnail#

A JPEG thumbnail that fits a square of size pixels, 32–1024.

C#IPhotosBridge.GetThumbnailAsyncTSPhotosBridge.getThumbnail
ParameterInType
idpathstringrequired
sizequerynumberoptional
Response
200 Binary content — Blob in TypeScript, Stream / byte[] in C#
POST/_bridge/photos/library/{id}/export#

Copies a library photo at full size into a file root.

C#IPhotosBridge.ExportAsyncTSPhotosBridge.export
ParameterInType
idpathstringrequired
Body
application/json · PhotoExportRequest
PhotoExportRequest · 1 field
rootstringThe file root the photo is copied into.
Response
200 PhotoFile
PhotoFile · 4 fields
fileBridgeFileWhere it is: read it through the files bridge.
fileNamestringThe name it had on the device, where the platform says.
contentTypestring | null
sizenumber

This bridge raises no events — everything it does is a request and its response.

JSON property names are camelCase and enums travel as their names. A ? marks a property the payload may omit;| null one that may be null.

PhotoStatusobject#
pickerSupportedbooleanWhether the system photo picker is available.
librarySupportedbooleanWhether the photo library can be browsed here.
libraryAccessAccessStateLibrary access. Restricted is a limited selection the user chose on iOS or Android.
AccessStateenum#

Where a permission stands. Reported by every bridge that needs one; the names match Shiny's own.

"Unknown""NotSupported""NotSetup""Disabled""Restricted""Denied""Available"
PhotoPickRequestobject#
limit?numberHow many the user may choose, 1–50.
root?stringThe file root the photos are copied into.
PhotoFileobject#

A photo copied into a file root.

fileBridgeFileWhere it is: read it through the files bridge.
fileNamestringThe name it had on the device, where the platform says.
contentTypestring | null
sizenumber
BridgeFileobject#

A file the page can reach through the files bridge: a named root and a path inside it. Bridges that take or return a file use this, so nothing reaches the page that /_bridge/files would not also serve.

rootstring
pathstring
PhotoPageobject#
itemsLibraryPhoto[]
offsetnumber
limitnumber
hasMorebooleanWhether another page follows.
LibraryPhotoobject#

A photo in the library.

idstringPass to getThumbnail and export.
createdAtstring | null
widthnumber
heightnumber
fileNamestring | null
PhotoExportRequestobject#
root?stringThe file root the photo is copied into.

These routes sit under AppDeviceBridgePolicies.Bridges like every other bridge: callers on this device by default. See Security.