Explore our specialized services, tailored solutions, and industry expertise to elevate your digital presence. From custom WordPress development to seamless integrations, we build high-performing websites that deliver impact.
A WordPress custom post type, already registered on a site, holds content that moves through a full lifecycle. Content arrives, leaves for another install, gets reclassified from ordinary posts, and is eventually retired along with the type. Four operations govern that lifecycle for an agency developer working on client content: importing entries into the type, exporting them to another WordPress install, converting existing posts into the custom post type, and unregistering the type once its entries have served their purpose. Populate, transfer, reclassify, retire. Each acts on the content of an already-registered custom post type (CPT for short), never on the type’s own definition.
That distinction sets the scope. The definition of a custom post type, and the registration that brings it into a WordPress site, sit upstream on the hub reference and stay assumed here rather than taught a second time. These four operations work only on the content the type already contains: how entries get in, how they move out, how standard posts become custom entries, and what survives once the type is finally removed. The lifecycle begins when content first arrives and is imported into the custom post type.
How to Import Content into a Custom Post Type
Importing content into a custom post type means loading external entries in bulk into an already-registered post_type, so that many items land under the type in a single pass instead of being entered one at a time. The import reads a source file, matches each item to a post_type value, and writes the result as native entries the custom post type contains. The type itself is assumed to exist; the registration behind WordPress custom post types as a content type belongs to the hub reference, so this operation concerns entries alone.
WordPress ships a native importer at Tools → Import. It reads a WXR file, the WordPress eXtended RSS format, and imports each item as an entry. Every item in that file carries a post_type value, and the importer places the item under whatever post_type the value names. That single value is the whole difference between a post and a custom entry.
The one adjustment a custom post type needs is the post_type value itself. A stock WordPress export writes post for ordinary posts, so an unedited file drops its items under the built-in type rather than the target custom post type. Rewriting the <wp:post_type> element to the custom post type’s key before importing sends each item to the right place:
Here product is the target custom post type key. Swap post for it across every <item>, and the importer files each row under the custom post type. From Tools → Import, running the WordPress importer on the rewritten file walks each item in and reports how many entries landed.
A large file can stall on server limits before it finishes. upload_max_filesize caps the file the importer will accept (a 64 MB export will not upload against an 8 MB ceiling), and max_execution_time caps how long PHP runs a single import pass, often 30 seconds by default. Raising both in the server configuration lets a full custom post type import complete in one run rather than timing out halfway. For files the native importer will not read, or source formats it does not understand, WP All Import runs the same operation through a drag-and-drop mapping screen, matching each incoming field to the custom post type by hand.
However the file arrives, the outcome is identical: the imported entries now hold a post_type value pointing at the custom post type, and they behave as native content of that type. A spreadsheet export is the other common source, and it takes a slightly different mapping, a CSV import into the same custom post type.
How to Import a CSV into a Custom Post Type
A CSV import into a custom post type loads a spreadsheet’s rows into the type in bulk, turning each row into one entry. Where a WXR file carries WordPress’s own structure, a CSV carries plain columns (title, content, and any custom fields), and the import maps those columns onto the matching custom post type fields. Rows in, entries out: that is the shape of the operation. The native WordPress importer reads WXR, not CSV, so a spreadsheet import runs through WP All Import, which maps each column to a custom post type field.
Mapping is the entire job. Each column header names a destination on the custom post type, and each data row supplies the values for a single entry. A title column fills the entry title, a content column fills the body, and a custom field column fills a field that belongs to the custom post type alone. Set out plainly, the correspondence looks like this:
CSV column
Custom post type field
Example value
title
post title
Trail Running Shoe
content
post body
Lightweight shoe for wet terrain
product_sku
custom field (product_sku)
TRS-4410
The same column-to-field mapping governs importing mapped custom post type content when entries arrive from another platform, where each source field is matched to a custom post type field before the rows load. On a plain spreadsheet the file itself is unremarkable, a header row that names the fields, then one data row per entry:
title,content,product_sku
Trail Running Shoe,Lightweight shoe for wet terrain,TRS-4410
Standard columns like title and content map on their own. A custom field, the data that makes a custom post type more than a plain post, needs an explicit assignment in WP All Import, where the CSV column is matched to the custom post type’s field key by name. Miss that step and the values import as empty; match it and each entry carries its full set of fields.
Once the run finishes, every row exists as one entry under the custom post type’s post_type, indistinguishable from content entered by hand. The type now contains a complete set of entries, which raises the reverse operation: moving that content back out and exporting the custom post type to another site.
How to Export a Custom Post Type to Another Site
Exporting a custom post type to another site means moving its entries out of one WordPress install and into a second one. What moves is the content, not the registration. The destination install has to recognize the same post_type already, and the export file carries only the rows that belong to it. For a developer, the operation is a content transfer scoped to a single custom post type, not a whole-site platform migration.
WordPress ships a native exporter under Tools → Export. Selecting the target custom post type there produces a WXR file, an XML document that holds the chosen entries and nothing else. WP-CLI reaches the same result from the command line and scopes the output explicitly:
The --post_type argument restricts the WXR to one post_type, so the resulting file contains that custom post type’s entries alone. On projects where the exporter needs finer field control, WP All Export builds the same CPT-scoped file while letting a practitioner pick which columns ride along.
Migrating a custom post type this way keeps the work at the level of its content. That boundary matters. Moving one post_type’s entries between two WordPress installs is a different job from shifting an entire website off another platform, which reshapes every table and template at once. When a broader move is in progress, the custom-post-type step stays narrow, map custom post type content during a site migration, and the whole-site process owns everything around it.
Because the exporter filters on post_type, the file that lands on the destination holds this custom post type’s entries and no others, a clean payload the receiving install can import directly. One detail survives the trip only with attention: each moved entry keeps its slug, but the permalinkthe full public address WordPress builds from the custom post type’s URL structure) is rebuilt by the destination install. On a multilingual target those permalinks need re-deriving before the addresses resolve, which is where custom post type permalinks come back into play. Even a correctly scoped WXR, though, transfers only what the export was told to include, and a custom post type’s entries are rarely the whole story.
How to Export a Custom Post Type with Its Postmeta and Media
A data-complete export of a custom post type carries its postmeta and its attached media, not just the entries themselves. Each entry usually depends on custom fields, the postmeta rows keyed to its ID, and on images stored as attachments. An export that grabs only the entries lands them on the destination stripped of their fields and pictures, which reads as data loss even though the rows arrived.
The postmeta rows travel when the export is told to include them. WordPress core writes each entry’s wp_postmeta into the WXR automatically, so a straight WP-CLI export already folds them in:
wp export --post_type=product_review --dir=./cpt-export
# WXR carries each entry's wp_postmeta rows; attachments are a separate post_type, so this scoped export leaves the media behind
The attached media is a different matter. Because the --post_type filter matches one post_type and WordPress stores attachments under their own post_type, the scoped WXR leaves the images behind. The postmeta rows arrive, but the pictures do not. WP All Export closes that gap: a practitioner selects the postmeta and custom-field columns alongside the featured-image and attachment columns, so the meta and the images travel together rather than the fields arriving without their pictures. On larger transfers, the same care applies when mapping custom post type postmeta and media into a destination that started life on a different system.
On the destination, the importer rebuilds each entry of the custom post type with its postmeta intact and its media re-linked, so the receiving site holds the content the source did: fields, images, and all. Exporting assumes the entries already exist to be moved. The reverse case is just as common: content that already sits on a site under the wrong type, waiting to be reclassified.
How to Convert a Post into a Custom Post Type
Converting a post into a custom post type reclassifies existing content by changing its post_type value. Every entry in WordPress stores a post_type (post for a standard blog entry, or a registered key for a custom post type), and to change a post type is to rewrite that single stored value so the entry is filed under a different kind. The content on the page does not move; only its classification does. Because the change only rewrites a stored value, the reclassification is reversible, setting the post_type back files the entry under its original type again, with nothing on the page lost.
The reason to reclassify is fit. A batch of entries first published as ordinary posts often belongs under a specialized custom post type (product reviews, staff profiles, case studies), where its own fields, templates, and archives apply. Left as plain posts, those entries share a feed and a template with unrelated writing; once converted, they read as the type they actually are.
The no-code route runs through Post Type Switcher. Once active, it adds a Post Type dropdown to the post editor’s Publish box and to the Quick Edit row. Opening a post, selecting the target custom post type from that dropdown, and updating the post is enough to convert the post to a custom post type, a single reclassification with no code involved.
After the update, the reclassified post reads as the target custom post type everywhere WordPress lists it: its archive, its admin column, its template. The dropdown applies to one post at a time, which suits an occasional fix. A whole library of posts that all belong under the same custom post type is a different scale of job, and that scale is where a programmatic route becomes the practical choice.
How to Change a Post Type with wp_update_post
Changing a post type with wp_update_post() sets a single post’s post_type value to the target custom post type key through WordPress’s post-update API. Point the call at a post ID, hand it the new post_type, and the reclassified post resolves under the custom post type instead of the built-in post type. To change a post type in WordPress through code takes only the identifier and the destination key:
The call updates one post. Swap in the real post ID and the registered custom post type key, run it, and that entry now belongs to the custom post type.
Reclassifying many posts one call at a time is slow. A direct SQL UPDATE on post_type changes them in bulk, every matching row at once. That speed carries a cost the API path avoids: writing straight to wp_posts skips the save_post hooks and the cache and permalink invalidation that wp_update_post() performs, so object caches and rewrite rules may need a manual flush once the query finishes. Back up the database before any raw SQL runs; the query rewrites live rows in wp_posts and offers no undo:
UPDATE wp_posts SET post_type = 'your_cpt_key'
WHERE post_type = 'post' AND ID IN (123,124);
The no-code route through Post Type Switcher handles a single post in the editor, while these two code paths suit programmatic and bulk reclassification. Either way the outcome is identical: the reclassified posts resolve under the custom post type’s post_type. With content imported, exported, and now converted, one operation remains in the lifecycle, retiring the type itself.
Does Unregistering a Custom Post Type Delete Its Entries?
Unregistering a custom post type hides the type from WordPress but does not delete its existing entries, which stay as rows in the wp_posts table. The two acts are separate. Removing the registration stops the code that declares the type; the content that type organized remains in the database, untouched.
That distinction matters at retirement. A developer who removes a custom post type in WordPress ends up with orphaned rows, entries that still exist but no longer surface in the admin, because nothing declares the type that would list them. The data is intact. It is simply unreachable through the usual interface.
The safe order is content-first. Export or migrate the entries before the type is retired, so the content survives the move rather than sitting stranded behind a type that no longer runs. Back up the database as well, since the rows persist and a full backup captures them. Only after the content has left does retiring the type become a clean operation.
The function that performs that retirement, unregister_post_type(), hides the type without any database delete.
The unregister_post_type() Function
The unregister_post_type() function reverses an existing registration and retires a registered custom post type from WordPress. It is the deliberate counterpart to the registration step: one declares the custom post type, and unregister_post_type() removes it.
Timing decides whether the function works at all. unregister_post_type() runs on a late hook, one that fires after the type was registered, because the call only undoes a registration that has already executed. Hook it too early and there is nothing yet to reverse:
The priority of 20 places the call after the default registration, so the type exists by the time the function removes it. Registration itself is a separate operation owned elsewhere; the paired forward call, register_post_type(), declares the type in the first place and does not need repeating here.
Once the call runs, the custom post type stops appearing in the admin menu and drops out of front-end queries, while its entries persist as rows in the database. That closes the loop. Across its lifecycle a WordPress custom post type takes in content through import, moves that content between sites through export and migration, absorbs existing posts through conversion, and finally retires: the type gone, its entries preserved.