Salesforce의 File 뜯어보기
Salesforce에서 사용하는 File 오브젝트의 구조
2026년 2월 5일
Salesforce 프로젝트를 진행하면서 여러 파일들을 일괄 업로드, 다운로드, 수정, 삭제 등 여러 액션을 취하게 되는데 단순히 단일 업로드는 Org 내의 UI에서 진행하면 되니 문제가 되지 않지만 여러 건을 업로드하려면 코드를 통해 진행하는 수 밖에 없습니다.
이를 위해 업로드하려면 결국 어디에 어떤 구조로 업로드 해야하는지 알아야하는데 막상 처음 시도하는 개발자 입장에서는 꽤나 복잡한 구조를 가지고 있습니다. 실제로 필자도 처음 업로드를 진행하려고 할 때 애를 먹은 기억이 있습니다.
그런고로 Salesforce의 File이 정확히 뭔지, 어떤 구조를 가지고 있는지 알아봅시다.
0️⃣ “File 오브젝트”라는 말의 정확한 의미
Salesforce에서 “File 오브젝트”라고 부를 때, 단일 오브젝트 하나를 의미하지 않는 경우가 대부분입니다.
왜냐하면 Salesforce Files는 정규화된 데이터 모델로 설계되어 있어서, 파일 하나가 실제로는 다음 세 축으로 관리되기 때문입니다.
- 문서(파일)의 정체/컨테이너:
ContentDocument - 파일 데이터(바이너리) + 버전:
ContentVersion - 어디에 붙어있는지(공유/연결):
ContentDocumentLink
그리고 “배포/외부 공유/라이브러리/피드(Chatter)” 등 부가 기능에 따라 주변 객체들이 추가로 엮입니다.
1️⃣ 핵심 3대 오브젝트
1.1 ContentDocument — 파일(문서) 자체의 “정체(마스터)”
- 역할: “이 파일이 무엇인지”를 나타내는 문서 단위 컨테이너
- 특징
- 파일 1개 = ContentDocument 1개
- 버전이 여러 개여도 문서는 하나(버전은 아래
ContentVersion이 담당) - “최신 버전이 무엇인지”를 가리키는 포인터가 있음
대표 필드(성격)
Id: 문서 IDTitle: 문서 제목LatestPublishedVersionId: 최신(게시된) 버전(ContentVersion) IDFileType/FileExtension(조직/버전에 따라 제공 범위가 조금 다를 수 있음)ContentSize,OwnerId,CreatedById,LastModifiedById,IsDeleted등
핵심 포인트:
ContentDocument에는 “파일 데이터(바이너리)”가 없습니다.실제 파일 데이터는
ContentVersion에 있습니다.
1.2 ContentVersion — “버전 + 실제 파일 데이터”의 실체
- 역할: 파일의 각 버전(Revision)과 실제 바이너리 데이터를 보관
- 특징
- 문서 1개(ContentDocument)에 대해 버전이 N개(ContentVersion) 생길 수 있음
- 사용자가 “새 버전 업로드”를 하면 ContentDocument는 유지되고, ContentVersion만 추가됨
- 최신 버전은 ContentDocument의
LatestPublishedVersionId가 가리킴
대표 필드(성격)
Id: 버전 IDContentDocumentId: 상위 문서(ContentDocument) 참조Title,PathOnClient: 업로드 파일명/경로 정보(업로드 시점)VersionNumber: 버전 번호FileType,FileExtensionContentSizeVersionData: 실제 파일 바이너리(중요)IsLatest: 최신 여부(컨텍스트에 따라 유용)
실무 포인트: “pptx만 골라내기”처럼 확장자 기반의 정확한 판단이 필요하면, 보통 최신 버전 기준으로 보려는 요구가 많습니다.
이때는
ContentDocument.LatestPublishedVersionId → ContentVersion을 타고 최신 버전의FileExtension/FileType을 확인하는 접근이 안전합니다.
1.3 ContentDocumentLink — 파일이 “어디에 붙었는지(공유 링크)”
- 역할: 파일(ContentDocument)을 어떤 대상(레코드/유저/그룹 등)에 연결하는 조인(Join) 객체
- 특징
- 파일 하나가 여러 레코드에 붙을 수 있고(링크 여러 개)
- 레코드 하나도 여러 파일을 가질 수 있음(링크 여러 개)
- 그래서 관계는 사실상 N:M 구조
대표 필드(성격)
ContentDocumentId: 어떤 파일인가LinkedEntityId: 어디에 붙었나(예: Opportunity/Account/Case/User/Group 등)ShareType: 공유 유형(조직/설정에 따라 의미가 달라지거나 제한될 수 있음)Visibility: 가시성(InternalUsers / AllUsers 등, 제한 픽리스트인 경우가 많음)
핵심 포인트: “레코드에서 파일을 제거”는 대부분
ContentDocumentLink삭제입니다.파일 자체(ContentDocument)가 삭제되는 것과는 다릅니다.
2️⃣ 관계 구조
텍스트로 구조를 그리면 아래와 같습니다.
- 문서(파일)
ContentDocument (1)ContentVersion (N): 버전들(실제 데이터)ContentDocumentLink (M): 연결들(어디에 붙었는지)
즉,
- 문서 1개는 버전 N개를 가질 수 있고
- 문서 1개는 링크 M개를 가질 수 있습니다.
3️⃣ “파일 업로드/버전 업로드/공유” 시 실제로 무엇이 생기나
3.1 레코드(예: Opportunity)에 파일 업로드
일반적인 흐름:
ContentVersion1개 생성(파일 바이너리 포함)- 시스템이
ContentDocument1개 생성(문서 컨테이너) ContentDocumentLink1개 생성(LinkedEntityId = OpportunityId)
3.2 기존 파일에 “새 버전” 업로드
ContentVersion이 하나 더 생성ContentDocument.LatestPublishedVersionId가 새 버전을 가리키도록 갱신
3.3 동일한 파일을 다른 레코드에도 “공유(붙이기)”
ContentDocument/ContentVersion은 그대로ContentDocumentLink만 추가 생성(LinkedEntityId만 다름)
4️⃣ 삭제 관점에서의 구조 이해(가장 실무적)
Salesforce Files에서 “삭제”는 크게 두 가지가 있습니다.
4.1 “레코드에서만 떼기”(연결 해제)
ContentDocumentLink삭제- 파일(문서)은 다른 곳에 공유되어 있다면 그대로 남아 있음
4.2 “파일 자체 삭제”(문서 삭제)
ContentDocument삭제(휴지통 이동 포함)- 관련 링크/버전이 함께 영향을 받음(정확한 동작은 권한/정책/공유 상태에 따라 달라질 수 있음)
실무 체크리스트
- 이 파일이 다른 레코드에도 붙어 있는가? (Link가 여러 개인가?)
- “완전 삭제”가 필요한가, 특정 레코드에서만 제거하면 되는가?
- 실행 주체(사용자/통합유저)가 파일 소유/권한을 갖고 있는가?
- Visibility/ShareType 같은 제한 픽리스트나 공유 정책 제약이 없는가?
5️⃣ 권한/공유 모델에서 추가로 등장하는 객체들
핵심은 Link지만, 실제로는 아래 객체들이 추가로 등장할 수 있습니다.
5.1 ContentDocumentShare
- 역할: 파일 공유/권한을 보조하는 공유 레코드 성격
- 다만 실무에서 “일반적으로 내가 직접 만들어야 하는가?”는 케이스가 갈립니다.
- 많은 경우
ContentDocumentLink중심으로 공유가 해결됩니다. - 반면 특정 시나리오(내부 정책/외부 사용자/커뮤니티/세밀한 공유)에서 Share 계열이 관여합니다.
- 많은 경우
Visibilityrestricted picklist 오류나FIELD_INTEGRITY_EXCEPTION같은 문제는, 대개 공유 모델(링크/공유 객체의 필드 제약) 과 맞물려 발생하는 경우가 많습니다.
6️⃣ 라이브러리(Workspace) / 배포 / 노트 등 주변 오브젝트들
Salesforce Files는 “레코드 첨부” 외에도 기능이 많고, 그 기능에 따라 객체가 더 생깁니다.
6.1 라이브러리(“Libraries”) 관련
ContentWorkspace: 라이브러리(워크스페이스) 자체ContentWorkspaceDoc: 어떤 문서(ContentDocument)가 어떤 라이브러리에 들어있는지 연결(Join)- (조직/설정/기능에 따라) 폴더 계열 객체가 관여하기도 하나, 최근에는 라이브러리 중심으로 이해하는 편이 안전합니다.
6.2 외부 공유/퍼블릭 링크 배포
ContentDistribution: 파일을 외부로 배포(퍼블릭 URL)하는 기능에서 등장- 다운로드 허용 여부, 만료, 암호 등의 정책이 연결될 수 있음
6.3 노트(Notes)
- Salesforce “Notes”도 실제로는 Files 모델과 연결되는 경우가 많습니다(내부적으로 콘텐츠 구조 사용).
- 노트를 파일처럼 레코드에 연결하는 형태가 되면 링크(
ContentDocumentLink) 관점으로 접근하는 일이 생깁니다.
6.4 Chatter 피드(Feed)와의 결합
- Chatter에 파일을 올리는 시나리오에서는 FeedItem/FeedAttachment 같은 피드 관련 객체와 함께 보이는 경우가 있습니다.
- 그러나 “파일의 실체”는 여전히 ContentDocument/Version/Link 축으로 이해하는 것이 기본입니다.
7️⃣ 실무 조회 패턴(구조 기반으로 생각하는 방법)
7.1 “어떤 레코드에 붙은 파일 목록” 조회
- 1차:
ContentDocumentLink에서LinkedEntityId = :recordId - 2차: 그 결과의
ContentDocumentId로ContentDocument조회 - 3차(정확한 확장자/크기/최신 버전 기준):
ContentDocument.LatestPublishedVersionId→ContentVersion조회
7.2 “파일 하나가 어디에 붙어있는지(영향 범위)” 조회
ContentDocumentLink에서ContentDocumentId = :docIdLinkedEntityId목록을 보면 “이 파일이 공유된 대상”이 드러납니다.
7.3 “확장자(pptx) 기반 정리/삭제”를 안전하게 하려면
- ContentDocument만 보고 지우기보다, 최신 버전(ContentVersion)을 기준으로 확장자를 확정한 뒤
- “링크만 삭제” vs “문서 삭제” 정책을 결정하는 흐름이 안전합니다.
8️⃣ 정리: File 구조를 한 문장으로 요약
Salesforce Files는
ContentDocument(문서/파일 정체)ContentVersion(버전 + 실제 파일 데이터)ContentDocumentLink(어디에 붙었는지/공유 링크)
이 3개가 핵심 뼈대이고,
- 라이브러리(
ContentWorkspace,ContentWorkspaceDoc), - 배포(
ContentDistribution), - 공유 보조(
ContentDocumentShare), - 피드/노트(기능에 따라)
등이 주변에서 붙는 구조입니다.