James pointed me at this thread. I borrowed this from another plugin, but have modified it over the years. Here is the relevant portion from install.php
$num=0;
$pages[$num]['post_name'] = 'shopping-cart';
$pages[$num]['post_title'] = 'Shopping Cart';
$pages[$num]['tag'] = '[eshop_show_cart';
$pages[$num]['option'] = 'cart';
// I create 6 pages in total
$newpages = false;
$i = 0;
$post_parent = 0;
$qtable=$wpdb->prefix . "posts";
foreach($pages as $page) {
$check_page = $wpdb->get_row("SELECT * FROM $qtable WHERE post_type='page' && (post_status='publish' OR post_status='draft' OR post_status='private') && <code>post_content</code> LIKE '%".$page['tag']."%' LIMIT 1",ARRAY_A);
if($check_page == null){
if($i == 0){
$page['post_parent'] = 0;
}else{
$page['post_parent'] = $first_id;
}
if(isset($page['top']) && $page['top']=='yes'){
$page['post_parent']=0;
}
$page['post_status']='publish';
$page['ping_status'] ='closed';
$page['comment_status'] ='closed';
$page['post_content']=$page['tag'].']';
$page['post_type'] = 'page';
$post_id=wp_insert_post( $page );
if($i == 0){
$first_id = $post_id;
}
$eshopoptions[$page['option']]=$post_id;
$newpages = true;
$i++;
}else{
$eshopoptions[$page['option']]=$check_page['ID'];
}
}
if($newpages == true){
wp_cache_delete('all_page_ids', 'pages');
$wp_rewrite->flush_rules();
}
I run a check to see if the page exists, a little slow and there may be a better way of accomplishing it, but it has worked for me.
$post_id=wp_insert_post( $page ); is the portion that actually inserts the post, and as you can see it returns the $post_id. This is useful if like me you need to reference it in your options.
I can't recall whether the cache/flush was in the original code, but I'm fairly sure it is required. If you need more info, feel free to ask and I'll do my best to answer any queries.