* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
(워드프레스 해부 ⑳) CPT
워드프레스 기본은 “글”과 “페이지”뿐이다. 그런데 “책”, “포트폴리오”, “이벤트”처럼 나만의 콘텐츠 종류가 필요할 때가 있다. 이때 만드는 게 커스텀 포스트 타입(CPT)이다.
놀라운 건, 새 콘텐츠 종류를 만들어도 DB 테이블은 새로 안 생긴다는 점이다. 8편에서 봤듯 전부 wp_posts에 post_type='book'으로 들어간다. 등록은 register_post_type()을 init 훅에 걸어서 한다.
직접 해보기
add_action( 'init', function() {
register_post_type( 'book', array(
'labels' => array( 'name' => '책', 'singular_name' => '책' ),
'public' => true,
'has_archive' => true,
'menu_icon' => 'dashicons-book',
'supports' => array( 'title', 'editor', 'thumbnail' ),
'show_in_rest'=> true, // 블록 편집기·REST 지원
) );
} );
show_in_rest => true가 중요하다. 이게 있어야 블록 편집기로 편집되고 REST API에도 노출된다. 없으면 옛날 편집기로 뜬다.
✅ 확인 — 등록 후 관리자 메뉴에 “책”이 생기고,
wp post-type list에book이 나오면 성공. 책을 하나 저장한 뒤wp_posts에서post_type='book'행을 확인하면, 새 종류도 결국 같은 테이블에 산다는 게 보인다.
실무에서 쓰는 등록 한 벌
개념만 보면 register_post_type('book', ...) 한 줄이지만, 실제로 쓸 만하게 만들려면 아카이브·블록 에디터·REST까지 챙긴 한 벌을 쓴다. 아래가 내가 새 CPT를 만들 때 복사해서 시작하는 뼈대다.
add_action( 'init', function () {
register_post_type( 'book', array(
'labels' => array( 'name' => '책', 'singular_name' => '책' ),
'public' => true,
'has_archive' => true, // /book/ 목록 페이지 생성
'show_in_rest' => true, // 블록 에디터·REST 노출 (사실상 필수)
'menu_icon' => 'dashicons-book',
'supports' => array( 'title', 'editor', 'thumbnail', 'excerpt' ),
'rewrite' => array( 'slug' => 'books' ),
) );
} );
supports 배열이 편집 화면에 뭘 띄울지를 정한다. editor가 빠지면 본문 편집기가 아예 안 나오고, thumbnail이 있어야 대표 이미지 칸이 생긴다. 필요 없는 건 빼서 편집 화면을 깔끔하게 유지한다.
show_in_rest를 빠뜨리면 벌어지는 일
이 값 하나가 은근히 사람을 잡는다. 블록 에디터는 화면 뒤에서 REST API로 데이터를 읽고 쓴다. 그래서 show_in_rest가 false면 이 CPT 편집 화면이 옛날 클래식 에디터로 떨어지거나, 쿼리 루프 블록이 이 타입을 목록에서 못 찾는다. 헤드리스로 프런트를 따로 만든다면 아예 데이터를 못 가져온다. 요즘은 true가 기본값이나 다름없다.
아카이브와 단일 페이지에 전용 디자인 주기
has_archive => true를 켜면 /books/ 주소로 이 타입 글이 모인 목록 페이지가 생긴다. 여기에 전용 디자인을 주고 싶으면 템플릿 계층을 따라 파일을 만들면 된다. 클래식 테마는 목록에 archive-book.php, 개별 글에 single-book.php, 블록 테마는 archive-book.html·single-book.html이다. 없으면 일반 archive·single로 자연스럽게 폴백한다.
퍼머링크 flush, 언제 진짜 필요한가
CPT가 목록에 안 보이거나 주소가 404면 습관적으로 설정 → 고유주소를 저장해 flush한다. 그런데 flush가 실제로 필요한 건 rewrite 슬러그나 has_archive를 바꿨을 때뿐이다. 라벨이나 supports 같은 걸 손볼 땐 flush가 필요 없다. 반대로 코드 안에서 매 요청마다 flush_rewrite_rules()를 호출하는 실수는 페이지 성능을 갉아먹으니 절대 넣지 말자 — flush는 플러그인 활성화 같은 특별한 순간에 한 번이면 충분하다.
분류를 붙여야 진짜 콘텐츠 타입이 된다
책 CPT를 만들었으면 다음은 분류다. 커스텀 택소노미로 ‘장르’를 만들어 연결하면, 글(책)과 분류(장르)가 짝을 이뤄 필터·아카이브가 돌아간다. CPT 하나만으론 반쪽이고, 여기에 택소노미가 붙어야 관리할 만한 콘텐츠 구조가 완성된다.
실제로 써먹는 예 — 이벤트 타입
‘책’은 설명용이고, 현장에서 자주 만드는 CPT는 이벤트·후기·채용공고·FAQ 같은 것들이다. 예를 들어 이벤트라면 supports에 title·editor·thumbnail을 두고, 시작일 같은 부가 정보는 타입이 아니라 커스텀 필드로 붙인다. 그런 다음 WP_Query에서 그 날짜 메타로 정렬하면 ‘다가오는 이벤트’ 목록이 완성된다.
포인트는 역할 분담이다. CPT는 ‘무엇의 종류인가’(이벤트냐 후기냐)를, 커스텀 필드는 ‘그 하나에 딸린 값’(날짜·장소·평점)을, 택소노미는 ‘어떻게 묶을까’(카테고리·태그)를 맡는다. 이 셋을 구분해서 쓰면 어떤 콘텐츠 구조든 깔끔하게 모델링된다.
콘텐츠 종류를 만들었으니, 다음 편은 그걸 분류할 나만의 택소노미(분류 체계)다.
자주 묻는 질문 (FAQ)
커스텀 포스트 타입은 왜 만드나요?
글·페이지와 다른 종류의 콘텐츠(제품·포트폴리오 등)를 별도로 관리하려고요. register_post_type으로 만듭니다.
CPT가 목록에 안 보여요.
퍼머링크를 한 번 저장(flush)하거나, register 시 rewrite·has_archive 설정을 확인하세요.

답글 남기기