Firestore undefined 오류 해결: 선택 필드가 저장을 막을 때

··설명 약 8분·작성: 해비·블로그 소개

Firestore 저장 객체에 undefined가 있을 때 선택 필드는 어떻게 처리하나요?

Firestore undefined 오류가 나면 저장 객체의 선택 필드를 확인하세요. 값을 보내지 않을 때는 키를 생략하고 null 저장 여부는 별도로 정합니다. 허용 필드를 명시해 직렬화를 비교하며 실제 원격 저장 성공과는 구분합니다.

적용 조건·범위: 2026-09-13 검증: Node.js 24.19.0, firebase 12.19.0에서 before는 invalid-argument(종료 1), after는 직렬화 통과(종료 0)를 실제 확인했습니다. commit 호출은 하지 않았습니다.

본문 근거: FirestoreSettings 설정 기준 · firebase.google.com/docs/firestore/manage-data/add-data

학습 목표와 사전 지식

목표: undefined 선택 필드 오류를 원격 쓰기 없이 재현하고 저장 객체를 명시적으로 구성합니다.
사전 지식: JavaScript undefined/null, Node.js ESM, Firestore 쓰기

Unsupported field value: undefined는 값 직렬화 단계에서 발생할 수 있습니다. Rules를 열거나 재시도 횟수를 늘려 해결할 문제가 아닙니다. 선택 필드는 값이 없을 때 생략할지 null로 저장할지 먼저 정하세요.

환경·재현 범위

Node.js와 Firebase Web SDK 모듈형 API를 사용합니다. scripts/before.mjs와 scripts/after.mjs는 각각 전체 파일이며 npm install firebase@12.19.0 후 node로 실행합니다. writeBatch().set()만 호출하고 commit()하지 않아 서버에 문서를 쓰지 않습니다. 이 구분 덕분에 직렬화와 원격 권한을 따로 확인할 수 있습니다.

scripts/before.mjs · 전체 재현 파일

import { initializeApp, deleteApp } from 'firebase/app';
import { doc, getFirestore, writeBatch } from 'firebase/firestore';

const app = initializeApp({ projectId: 'demo-undefined' }, 'before');
try {
  const db = getFirestore(app);
  writeBatch(db).set(doc(db, 'profiles', 'fixture-user'), { name: '학습자', phone: undefined });
} finally {
  await deleteApp(app);
}
빈 선택 필드를 분리한 뒤 프로필 문서를 저장함에 넣는 일러스트
선택 필드가 없다는 상태와 빈 값을 저장한다는 의도를 구분해야 합니다.

수정 방법: 저장할 필드를 선택하기

scripts/after.mjs의 makeProfile에서 name을 확인하고 phone이 정의된 경우에만 필드를 만듭니다. Object.fromEntries로 모든 폼 필드를 복사하면 앞으로 추가된 admin 같은 필드까지 저장할 수 있고, 중첩 undefined도 그대로 남습니다. 허용할 필드를 명시하면 입력 계약을 검토하기 쉽습니다.

scripts/after.mjs · 전체 수정 파일

import { initializeApp, deleteApp } from 'firebase/app';
import { doc, getFirestore, writeBatch } from 'firebase/firestore';

export function makeProfile(form) {
  if (typeof form.name !== 'string' || !form.name.trim()) throw new Error('이름 필요');
  if (form.phone !== undefined && typeof form.phone !== 'string') throw new Error('전화번호 형식');
  return {
    name: form.name.trim(),
    ...(form.phone !== undefined ? { phone: form.phone.trim() } : {})
  };
}
const app = initializeApp({ projectId: 'demo-undefined' }, 'after');
try {
  const db = getFirestore(app);
  const profile = makeProfile({ name: '학습자', phone: undefined });
  writeBatch(db).set(doc(db, 'profiles', 'fixture-user'), profile);
  console.log('직렬화 통과: phone 필드 생략');
} finally {
  await deleteApp(app);
}

undefined·null·삭제는 다릅니다

선택하려는 의미 처리 기준
이번 요청에 선택값을 보내지 않습니다 그 키를 저장 객체에서 생략합니다. 이 글의 makeProfile이 사용하는 방식입니다.
값이 비어 있다는 상태를 저장합니다 null을 허용하는 입력 계약을 먼저 정합니다. 아래 makeProfile은 문자열만 허용하므로 그대로 null을 넣으면 거부됩니다.
undefined 키를 정책적으로 건너뜁니다 ignoreUndefinedProperties 설정을 검토합니다. 필수값과 타입 검사까지 대신하는 설정은 아닙니다.

필드를 생략하는 것은 그 키를 payload에 넣지 않는 것입니다. setDoc 기본 덮어쓰기, setDoc merge, updateDoc에서 생략이 기존 데이터에 미치는 영향은 다릅니다. null은 실제 저장되는 값이고 기존 필드 제거는 deleteField() 같은 삭제 연산을 사용합니다. JSON stringify/parse는 undefined 외에도 날짜·특수 객체 표현을 바꾸므로 만능 정리 함수로 쓰지 마세요.

ignoreUndefinedProperties를 쓰는 경우

공식 SDK 문서는 이 설정이 true이면 직렬화 중 undefined 속성을 건너뛰고, 생략하거나 false이면 예외를 던진다고 설명합니다. FirestoreSettings 설정 기준을 확인한 뒤 팀의 선택 필드 정책과 맞춰 사용하세요. 문서 확인일: 2026년 10월 6일.

initializeFirestore(app, { ignoreUndefinedProperties: true })는 초기 인스턴스를 만들 때 정하는 정책입니다. getFirestore로 사용한 뒤 뒤늦게 바꾸는 설정으로 안내하지 않습니다. 객체의 선택 필드를 건너뛰는 것과 배열 안의 undefined 처리는 같지 않으며 모든 잘못된 입력을 수정해 주지 않습니다. 정책으로 누락을 허용해도 필수 필드·타입 검사는 남겨야 합니다.

같은 명령으로 비교하기

node scripts/before.mjs는 undefined 관련 예외와 비정상 종료가 기대 결과입니다. node scripts/after.mjs는 phone을 생략한 객체의 직렬화 통과를 출력합니다. name 누락과 phone 숫자를 넣으면 입력 검사에서 거부되어야 합니다. 실제 commit 성공·Rules·네트워크·브라우저 화면은 이 검사 범위에 포함하지 않습니다.

실제 실행 결과

2026-09-13 검증: Node.js 24.19.0, firebase 12.19.0에서 before는 invalid-argument(종료 1), after는 직렬화 통과(종료 0)를 실제 확인했습니다. commit 호출은 하지 않았습니다.

오류를 분리해 확인하는 4가지 입력

먼저 글의 두 전체 파일을 scripts/before.mjs와 scripts/after.mjs로 저장하고, 빈 프로젝트 폴더에서 다음 명령을 실행하세요. before의 비정상 종료는 의도한 재현 결과입니다. after의 출력은 로컬 SDK 직렬화까지만 확인하며 서버 저장을 뜻하지 않습니다.

npm init -y
npm install firebase@12.19.0
node scripts/before.mjs
node scripts/after.mjs
입력 makeProfile 결과 이유
{ name: ‘학습자’, phone: undefined } { name: ‘학습자’ } 선택 키를 payload에서 생략
{ name: ‘학습자’, phone: null } ‘전화번호 형식’ 예외 null은 undefined가 아니며 이 예제는 문자열만 허용
{ name: ‘학습자’, phone: ‘010’ } { name: ‘학습자’, phone: ‘010’ } 문자열을 명시적으로 저장
{ name: ‘ ‘, phone: ‘010’ } ‘이름 필요’ 예외 필수값 검사에서 실패

after.mjs의 makeProfile 호출 인자를 한 번에 하나씩 바꾸고 같은 명령으로 다시 실행하세요. 위 표는 함수가 반환하거나 던지는 값입니다. after.mjs 자체는 예외 시 0이 아닌 코드로 끝나므로 ‘직렬화 통과’ 로그가 나오지 않아야 합니다. name·phone 외의 키를 추가해도 이 함수의 반환 payload에 포함되지 않는지 확인하세요.

기존 profiles/fixture-user에 phone이 이미 있는 상태에서 다른 쓰기 방법을 쓴다면 결과가 달라집니다. set의 기본 덮어쓰기는 생략된 기존 phone을 지울 수 있고, merge나 update에서는 생략만으로 기존 값을 지우지 않습니다. ‘전화번호 지우기’가 목적이라면 생략·null·deleteField() 중 어느 의미인지 먼저 결정하세요. 이 차이는 로컬 직렬화 검사로 증명되지 않으며 실제 Firestore 또는 Emulator에서 별도 확인이 필요합니다.

직접 확인할 결과

기존 2026-09-13 SDK 실행 기록: before 실패/after 통과; 이번 편집에서는 재실행하지 않음
입력 타입·필수값 경계 검사
commit·Auth·Rules 미실행

검증 범위와 기준일

공식 문서 확인일: 2026-09-13. 웹 SDK 예제는 모듈형 API 기준입니다. 이 글의 코드·구조 정적 검토와 실제 클라우드 인증·저장·배포 검증을 구분합니다. 본인 개발 Firebase 프로젝트의 실제 인증·권한·배포 요청은 이 편집 환경에서 실행하지 않았습니다.

실습 파일 다운로드

before·after 전체 코드와 README ZIP

이어서 학습하기

공식 문서

글 소개·학습 준비·전체 목차

Firestore에서 Unsupported field value: undefined가 나오면 먼저 저장하려는 객체의 선택 필드를 확인하세요. 값이 없을 때 그 키를 생략할지, null을 실제 값으로 저장할지 정해야 합니다. 이 글의 수정 예제는 전화번호가 undefined이면 phone 키를 생략하며, null을 허용하는 예제는 아닙니다.

이미 원인을 알고 바로 비교하려면 독립 재현 실행 명령과 4가지 입력으로 이동하세요. before·after 전체 파일은 아래에 있고, 비교 범위는 원격 저장 전 SDK 직렬화와 입력 검사입니다.

이 글이 도움이 되었나요?

조회 중

읽음 기록

읽음 — / 전체 20개 · 필수 14개

이 글을 쓴 해비

웹사이트를 제작·운영하고, 개인 서비스와 자동화·AI 도구를 만듭니다. 직접 만들며 배운 내용과 문제 해결 과정을 BlogFlow에 기록합니다.

새 글 받아보기

RSS 리더에서 BlogFlow의 새 글을 확인할 수 있습니다.

RSS 피드 구독하기

댓글 남기기