If I understood you correctly, you will first need to redirect your user to http://domain.com/cpt1/%postname%/ and afterward, you'll need to rewrite the URL in order to get the correct post and avoid 404 errors.
So here you go:
add_action( 'template_redirect', 'redirect_postid_to_postname' );
function redirect_postid_to_postname() {
$uri = $_SERVER['REQUEST_URI'];
// Checks if the URI matches what we want. This means the URL should be sth like http://example.com/cpt1/POST_ID
if ( !preg_match( "/^\/cpt1\/(\d+)/", $uri, $matches ) ) {
return;
}
$post_id = $matches[1];
// Checks if $post_id is actually an existent post ID.
$post_obj = get_post( $post_id );
// Checks if a post object was retrieved for the given post ID.
if ( !$post_obj ) {
return;
}
$post_name = $post_obj->post_name; // Keeps post slug.
$site_url = get_site_url(); // Keeps site URL (it's supposed to be retrieved without trailing slash).
$location = $site_url . "/cpt1/$post_name"; // Builds the new URL to redirect the user to.
// Redirects!
wp_redirect( $location, 302 );
exit;
}
add_action( 'init', 'rewrite_custom_postname', 100 );
function rewrite_custom_postname() {
$uri = $_SERVER['REQUEST_URI'];
// Checks if the URI matches what we want. This means the URL should be sth like http://example.com/cpt1/POST_NAME
if ( !preg_match( "/^\/cpt1\/(.+)/", $uri, $matches ) ) {
return;
}
$post_name = $matches[1];
$query_args = array(
'name' => $post_name,
'post_type' => 'cpt1',
'post_status' => 'publish',
'numberposts' => 1
);
$post_obj = get_posts( $query_args );
// Checks if a post object was retrieved for the given query arguments.
if ( !$post_obj ) {
return;
}
$post_id = $post_obj[0]->ID;
$regex = "^cpt1/{$matches[1]}";
$query = "index.php?p=$post_id";
$after = "top";
add_rewrite_rule( $regex, $query, $after );
}
IMPORTANT!
If you are still making use of Permalink Manager and you're also changing the uri from %postname% to %post_id% you might CREATE an infinite loop of redirections, so be careful.