Capacitor 파일 핸들링은 몇 가지 기본 요소로 정리됩니다: @capacitor/filesystem 앱 디렉토리에서 파일 읽고 쓰기, Capacitor.convertFileSrc 파일을 표시하는 방법, 사용자 파일을 가져오는 피커, 다운로드·업로드·열기·공유를 위한 네이티브 플러그인을 다룹니다. 이 가이드는 Capacitor 8 code로 각 기능을 보여주고, iOS와 Android에서 어떤 디렉토리를 사용해야 하는지 설명하며, 대부분의 파일 버그를 일으키는 메모리와 권한 관련 함정도 짚습니다.
파일 핸들링 툴박스
| 작업 | 플러그인 |
|---|---|
| 앱 스토리지에서 읽기, 쓰기, 목록 조회, 복사, 삭제 | @capacitor/filesystem |
로컬 파일 표시 <img>, <video>, <iframe> |
Capacitor.convertFileSrc (핵심) |
| 사용자가 파일 또는 폴더를 선택하도록 허용 | @capgo/capacitor-file-picker |
| 진행률 표시 다운로드 | @capacitor/file-transfer 또는 @capgo/capacitor-downloader 백그라운드, 일시 중지 가능한 다운로드용 |
| 진행률 표시 업로드 | @capacitor/file-transfer 또는 @capgo/capacitor-uploader 백그라운드 업로드용 |
| 파일 공유 또는 다운로드 폴더에 저장 | @capgo/capacitor-file-sharer 또는 @capacitor/share |
| 다른 앱에서 파일 열기 | @capacitor-community/file-opener |
| 이미지 압축 | @capgo/capacitor-file-compressor |
| Zip 압축 및 해제 | @capgo/capacitor-zip |
먼저 코어 플러그인을 설치하세요:
bun add @capacitor/filesystem
bunx cap sync
올바른 디렉토리 선택
모든 Filesystem 호출에는 디렉토리가 필요합니다 directory. 잘못된 디렉토리를 선택하는 것이 파일 유실과 앱 거절의 가장 흔한 원인입니다.
Directory |
iOS 위치 | Android 위치 | 백업됨 | 용도 |
|---|---|---|---|---|
Data |
Documents |
앱 파일 디렉터리 (/data/data/<pkg>/files) |
iOS iCloud 및 Android 자동 백업 | 앱이 소유한 사용자 데이터 |
Documents |
Documents (...와 동일 Data) |
공용 Documents 폴더 |
지원 | Android 11+에서는 지양 (아래 참조) |
Library |
Library |
앱 파일 디렉터리 | iOS 지원 | 앱 지원 파일, 데이터베이스 |
LibraryNoCloud |
Library/NoCloud |
앱 파일 디렉터리 | 아니요 | 대용량, 재다운로드 가능한 콘텐츠 |
Cache |
Library/Caches |
앱 캐시 디렉터리 | 아니요 | OS가 지울 수 있는 임시 파일 |
External |
Documents |
앱 외부 파일 디렉터리 | 아니요 | Android 대용량 앱 파일, 앱 제거 시 삭제됨 |
ExternalStorage |
Documents |
공유 저장소 루트 | 해당 없음 | 레거시 전용, Android 11 이상에서는 접근 불가 |
대략적인 원칙:
- 재다운로드 가능한 대용량 미디어 (오프라인 비디오, 지도 타일)는
LibraryNoCloud에 저장합니다. Apple은 iOS 데이터 저장 가이드라인에 설명된 대로, 재다운로드 가능한 대용량 콘텐츠를 iCloud에 백업되는 폴더에 저장하는 앱을 거부합니다. - 임시 파일 (한 번만 공유하는 내보내기 파일, 썸네일)은
Cache에 저장합니다. 사용자 데이터의 유일한 사본은 절대 거기에 저장하지 마세요. - 사용자가 나중에 앱 외부에서 찾아야 하는 파일은 앱 디렉토리에 아예 저장해서는 안 됩니다. 공유 시트, MediaStore Downloads 또는 시스템 파일 피커를 통해 저장하세요. 자세한 내용은 Capacitor의 Android scoped storage 가이드를 참조하세요..
파일 쓰기
import { Filesystem, Directory, Encoding } from '@capacitor/filesystem';
// Text file
await Filesystem.writeFile({
path: 'notes/today.txt',
data: 'Hello from Capacitor',
directory: Directory.Data,
encoding: Encoding.UTF8,
recursive: true, // create notes/ if missing
});
// JSON
await Filesystem.writeFile({
path: 'settings.json',
data: JSON.stringify({ theme: 'dark' }),
directory: Directory.Data,
encoding: Encoding.UTF8,
});
// Binary file: pass base64 and no encoding
await Filesystem.writeFile({
path: 'images/logo.png',
data: base64Png,
directory: Directory.Data,
recursive: true,
});
이 encoding 옵션에 따라 data 해석 방식이 달라집니다. Encoding.UTF8를 사용하면 문자열이 텍스트로 기록됩니다. 사용하지 않으면 네이티브 플랫폼은 data 를 base64로 디코딩해 원시 바이트를 기록합니다. 이걸 놓치면 “PDF가 깨졌다”는 전형적인 버그가 생깁니다.
로그와 같은 기존 파일에 내용을 추가하려면 appendFile 에 동일한 옵션을 사용하세요.
파일 읽기
const { data } = await Filesystem.readFile({
path: 'settings.json',
directory: Directory.Data,
encoding: Encoding.UTF8,
});
const settings = JSON.parse(data as string);
// Binary: returns base64 on native, a Blob on web
const image = await Filesystem.readFile({
path: 'images/logo.png',
directory: Directory.Data,
});
파일이 있는지 확인하려면 stat를 사용하세요. 파일이 없으면 reject됩니다:
async function exists(path: string) {
try {
await Filesystem.stat({ path, directory: Directory.Data });
return true;
} catch {
return false;
}
}
대용량 파일을 청크 단위로 읽기
readFile 전체 파일을 하나의 base64 문자열로 반환합니다. 200MB 비디오라면 JS 메모리에 수백 MB의 문자열이 올라갑니다. readFileInChunks 대신 스트리밍합니다:
await Filesystem.readFileInChunks(
{ path: 'videos/clip.mp4', directory: Directory.Data, chunkSize: 1024 * 1024 },
(chunk, err) => {
if (err) return console.error(err);
if (chunk === null) return console.log('done');
processChunk(chunk.data); // base64 chunk
},
);
더 좋은 방법은 바이트를 JS로 전혀 넘기지 않는 것입니다. 대부분의 '큰 파일 읽기' 작업은 사실 '업로드'나 '표시'이고, 둘 다 아래에 네이티브 경로가 있습니다.
목록, 복사, 이동, 삭제
const { files } = await Filesystem.readdir({ path: 'notes', directory: Directory.Data });
for (const f of files) console.log(f.name, f.type, f.size, f.mtime);
await Filesystem.mkdir({ path: 'archive', directory: Directory.Data, recursive: true });
await Filesystem.copy({
from: 'notes/today.txt',
to: 'archive/today.txt',
directory: Directory.Data,
});
await Filesystem.rename({
from: 'archive/today.txt',
to: 'archive/2026-10-01.txt',
directory: Directory.Data,
});
await Filesystem.deleteFile({ path: 'notes/today.txt', directory: Directory.Data });
await Filesystem.rmdir({ path: 'notes', directory: Directory.Data, recursive: true });
copy 및 rename 허용 toDirectory 디렉터리 간 이동에 사용합니다. 예를 들어 Cache 대상 Data 다운로드가 완료된 후에 사용합니다. 또한 절대 file:// 경로도 허용합니다. 생략하면 directory, 이는 다른 플러그인에서 반환된 파일을 가져오는 방식입니다.
WebView에서 파일 표시
가능하다면 base64 데이터 URL로 이미지나 동영상을 로드하지 마세요. 네이티브 경로를 Capacitor의 로컬 서버가 제공하는 URL로 변환하세요:
import { Capacitor } from '@capacitor/core';
const { uri } = await Filesystem.getUri({ path: 'images/logo.png', directory: Directory.Data });
img.src = Capacitor.convertFileSrc(uri);
iOS에서는 결과가 다음과 같습니다 capacitor://localhost/_capacitor_file_/..., Android에서는 https://localhost/_capacitor_file_/.... 이는 <img>, <video>, <audio>, 그리고 fetch()에서도 작동하므로, 파일을 ArrayBuffer 로 base64 없이 읽을 수 있습니다:
const buffer = await fetch(Capacitor.convertFileSrc(uri)).then((r) => r.arrayBuffer());
경로(path)를 저장하세요. 변환된 URL은 현재 앱 세션에서만 유효합니다.
기기에서 파일 선택
시스템 파일 선택기는 사용자에게 제어권을 제공하며, Android 13+ 또는 iOS에서는 저장소 권한이 필요하지 않습니다:
bun add @capgo/capacitor-file-picker
bunx cap sync
import { CapgoFilePicker } from '@capgo/capacitor-file-picker';
const { files } = await CapgoFilePicker.pickFiles({
types: ['application/pdf', 'image/*'],
limit: 5,
readData: false, // keep false for anything bigger than a few MB
});
for (const file of files) {
console.log(file.name, file.mimeType, file.size, file.path);
}
또한 pickImages, pickVideos, pickMedia 및 pickDirectory, 그리고 convertHeicToJpeg iOS의 경우입니다. iOS에서는 types 확장자가 아닌 MIME 타입만 허용됩니다.
선택한 파일은 임시 위치에 있거나 content:// 잠시 동안만 읽을 수 있는 URI에 있을 수 있습니다. 나중에 파일이 필요하다면 즉시 다음 명령어로 본인의 디렉토리에 복사하세요: Filesystem.copy.
파일 다운로드
진행률 표시: @capacitor/file-transfer
Filesystem.downloadFile Filesystem 8에서 deprecated되었습니다. 대체 구현은 응답을 디스크로 바로 스트리밍합니다:
bun add @capacitor/file-transfer
bunx cap sync
import { FileTransfer } from '@capacitor/file-transfer';
import { Directory, Filesystem } from '@capacitor/filesystem';
const { uri } = await Filesystem.getUri({ path: 'downloads/report.pdf', directory: Directory.Data });
const listener = await FileTransfer.addListener('progress', (p) => {
if (p.lengthComputable) setProgress(p.bytes / p.contentLength);
});
await FileTransfer.downloadFile({
url: 'https://example.com/report.pdf',
path: uri,
progress: true,
headers: { Authorization: `Bearer ${token}` },
});
await listener.remove();
백그라운드 실행: @capgo/capacitor-downloader
사용자가 앱을 떠나도 계속 다운로드되어야 하고 일시 중지 및 재개가 가능한 대용량 파일에는 Capgo 다운로더를 사용하세요. 이 다운로더는 Android에서는 DownloadManager , iOS에서는 백그라운드 URLSession 에 의존합니다:
import { CapacitorDownloader } from '@capgo/capacitor-downloader';
await CapacitorDownloader.addListener('downloadProgress', ({ id, progress }) => {
console.log(id, progress);
});
await CapacitorDownloader.addListener('downloadCompleted', ({ id }) => {
console.log('done', id);
});
await CapacitorDownloader.download({
id: 'course-video-12',
url: 'https://cdn.example.com/course/12.mp4',
destination: 'videos/12.mp4',
network: 'wifi-only',
notification: 'progress', // Android
});
상대 경로 destination 는 Android에서는 앱의 외부 파일 디렉터리, iOS에서는 Documents 디렉터리로 해석되며, 이는 Directory.External.
파일 업로드
전송하기 위해 파일을 base64로 읽지 마세요. 두 업로드 옵션 모두 디스크에서 네이티브로 읽습니다:
import { FileTransfer } from '@capacitor/file-transfer';
const result = await FileTransfer.uploadFile({
url: 'https://api.example.com/upload',
path: uri, // full file:// path
fileKey: 'file',
mimeType: 'application/pdf',
headers: { Authorization: `Bearer ${token}` },
progress: true,
});
console.log(result.responseCode, result.bytesSent);
앱이 백그라운드로 전환되어도 살아남아야 하는 업로드(동영상, 배치 사진 업로드)에는 @capgo/capacitor-uploader를 사용하세요. 백그라운드에서 실행됩니다 URLSession iOS에서는, 그리고 Android에서는 진행률 알림이 있는 업로드 서비스:
import { Uploader } from '@capgo/capacitor-uploader';
const { id } = await Uploader.startUpload({
filePath: uri,
serverUrl: 'https://api.example.com/upload',
method: 'PUT', // binary body, ideal for presigned S3 or R2 URLs
mimeType: 'video/mp4',
});
Presigned PUT 대용량 파일에는 객체 스토리지로의 Presigned URL이 가장 안정적인 패턴입니다: API가 URL에 서명하고, 디바이스는 스토리지로 바로 업로드합니다.
파일 공유 또는 내보내기
사용자가 파일을 다른 앱으로 보내거나 찾을 수 있는 곳에 저장하게 하려면:
import { FileSharer } from '@capgo/capacitor-file-sharer';
await FileSharer.share({
filename: 'report.pdf',
path: uri,
contentType: 'application/pdf',
});
// Android: write to Downloads via MediaStore. iOS: opens the share sheet ("Save to Files")
await FileSharer.save({
filename: 'report.pdf',
path: uri,
contentType: 'application/pdf',
});
Capgo 파일 공유기는 파일을 자체 캐시 폴더로 복사한 뒤 전용 FileProvider을(를) 통해 제공하므로 수정할 필요가 없습니다 file_paths.xml. 공식 @capacitor/share을(를) 사용하면 Android는 다른 폴더를 추가하지 않는 한 캐시 폴더에서만 공유할 수 있습니다 android/app/src/main/res/xml/file_paths.xml.
다른 앱에서 파일 열기
bun add @capacitor-community/file-opener
bunx cap sync
import { FileOpener } from '@capacitor-community/file-opener';
await FileOpener.open({ filePath: uri, contentType: 'application/pdf' });
iOS에서는 Quick Look 미리보기를 엽니다. Android에서는 MIME 타입의 기본 앱을 열거나 앱 선택기를 표시합니다. 해당 타입을 처리할 앱이 없으면 호출이 거부되므로, catch한 뒤 대신 공유를 제안하세요.
자주 쓰는 파일 레시피
PDF 생성: 로 HTML 렌더링 @capgo/capacitor-pdf-generator 또는 로 JS에서 빌드 pdf-lib, 그런 다음 base64 출력을 로 작성 writeFile. 저희 스캔을 PDF로 변환하는 튜토리얼 전체 흐름을 보여줍니다.
업로드 전 사진에서 GPS 정보 제거: @capgo/capacitor-file-compressor 리사이즈 시 모든 플랫폼에서 EXIF 메타데이터를 제거합니다.
이미지 리사이즈 또는 변환: 동일한 압축기로 너비, 높이, 품질 및 출력 형식(HEIC에서 JPEG 포함)을 처리합니다.
여러 파일 번들링: 폴더를 zip으로 압축하려면 @capgo/capacitor-zip 수백 개의 요청 대신 아카이브 하나를 업로드하세요.
웹 지원
@capacitor/filesystem IndexedDB를 사용해 웹에서도 동작하므로, 개발 중 브라우저에서 code가 실행됩니다. 유의할 차이점: 바이너리 읽기는 a를 반환합니다. Blob파일은 브라우저 프로필에만 존재하고 저장 공간 할당량은 훨씬 낮습니다. 웹 빌드에서는 URL.createObjectURL(blob) 표시용으로 사용하세요.
문제 해결
“파일이 존재하지 않습니다”: 디렉터리를 섞었거나, 절대 경로를 함께 전달했습니다 directory. 둘 중 하나만 사용하세요.
손상된 바이너리 파일: base64를 ...와 함께 작성했습니다 Encoding.UTF8. 인코딩을 제거하세요.
대용량 파일로 앱이 충돌합니다: 브리지를 통해 base64로 전송. 청크, file-transfer 또는 fetch(convertFileSrc(uri)).
Android에서 이미지가 표시되지 않습니다: 다음을 사용했습니다 file:// URL을 직접 사용하지 마세요. 다음을 사용해 변환하세요: Capacitor.convertFileSrc.
Failed to find configured root Android에서 공유할 때: 파일이 다음 경로 밖에 있습니다: file_paths.xml. Capgo 파일 공유 기능을 사용하거나 다음으로 복사하세요: Cache 먼저.
iOS에서 파일이 사라지는 경우: 파일이 다음 위치에 있었습니다: Cache, iOS는 저장 공간이 부족하면 이 경로를 비웁니다.
iCloud 저장소 사용으로 인한 App Store 거절: 다음 위치의 대용량 다운로드 콘텐츠: Data 또는 Documents. 이를 다음으로 이동하세요: LibraryNoCloud.
다음 단계
파일 저장 위치, 재시도 규칙, 네이밍 등 대부분의 파일 로직은 JavaScript에 있으며, 다음으로 조정할 수 있습니다: Capgo Live Update 사용하기 릴리스 후에도 가능합니다. Android의 공유 저장소 및 권한 관련 내용은 다음을 참고하세요: Scoped Storage 가이드, 그리고 파일 선택기 문서 모든 picker 옵션에 대해.