Use the legal pages post type in your theme and plugin code

TermsFeed AutoTerms stores every legal page as a post of the hierarchical post type wpautoterms_page, which your code queries, links, and displays like a WordPress page. The Privacy Policy, the Terms and Conditions, the Cookies Policy, and every custom legal page share this post type.

The examples use only WordPress core functions and the post type name, so they run in a child theme, a theme, or a plugin. Functions go in functions.php of a child theme or in a plugin, and template code goes in a theme template file.

Properties of the wpautoterms_page post type

The post type wpautoterms_page is public and hierarchical, and WordPress keeps it out of site search. Each property changes how a query, a link, or a template behaves for legal pages.

Property Value What it means for your code
Name wpautoterms_page The value of post_type in a query, and the post type argument of core functions such as get_page_by_path()
Hierarchical Yes A legal page takes a parent and a menu order, as a page does, and the address of a child page adds the path of its parent
Public and queryable Yes Each published legal page has its own address, and is_singular( 'wpautoterms_page' ) is true on it
Site search Excluded Legal pages never appear in site search results, and a query with 'post_type' => 'any' leaves them out
Archive Yes, at the legal pages slug get_post_type_archive_link( 'wpautoterms_page' ) returns the archive address, /termsfeed/ by default
Query var wpautoterms_page The query variable that holds the page slug in a plain permalink
Taxonomy category Legal pages take post categories; the Categories screen is hidden from the TermsFeed menu
Supports title, editor, revisions, page-attributes, custom-fields, excerpt, author The boxes the editor shows, and the fields your code reads with core functions
Export Yes Tools > Export includes legal pages
REST API Not exposed No /wp-json/wp/v2/ route lists legal pages, and the plugin does not register a REST route of its own
Editor Classic editor Legal pages open in the classic editor, never in the block editor

The plugin registers the post type on init at priority -999998, before any callback at the default priority 10. Find the wpautoterms_registered_cpt action, which runs right after the post type registers, with its example in TermsFeed AutoTerms Action Hooks and Filters.

Query legal pages with WP_Query or get_posts(), the post type wpautoterms_page, and the post status publish. Name the post type every time, because 'post_type' => 'any' leaves legal pages out. Name the status too, so drafts and private legal pages stay out of a list for visitors.

The first example prints every published legal page as a linked item of a <ul class="mytheme-legal-pages"> list, in the order of the Page Attributes box, then by title. It prints nothing while no legal page is published.

<?php
$mytheme_legal_pages = new WP_Query(
	array(
		'post_type'      => 'wpautoterms_page',
		'post_status'    => 'publish',
		'posts_per_page' => -1,
		'orderby'        => array(
			'menu_order' => 'ASC',
			'title'      => 'ASC',
		),
		'no_found_rows'  => true,
	)
);

if ( $mytheme_legal_pages->have_posts() ) {
	echo '<ul class="mytheme-legal-pages">';
	while ( $mytheme_legal_pages->have_posts() ) {
		$mytheme_legal_pages->the_post();
		printf(
			'<li><a href="%1$s">%2$s</a></li>',
			esc_url( get_permalink() ),
			esc_html( get_the_title() )
		);
	}
	echo '</ul>';
	wp_reset_postdata();
}
?>

The second example prints the 3 legal pages you changed most recently, each as a paragraph with its link and the date its "Last updated" line shows. get_the_modified_date() with an empty format uses the date format of your WordPress website, as the "Last updated" line does.

<?php
$mytheme_updated_pages = get_posts(
	array(
		'post_type'   => 'wpautoterms_page',
		'post_status' => 'publish',
		'numberposts' => 3,
		'orderby'     => 'modified',
		'order'       => 'DESC',
	)
);

foreach ( $mytheme_updated_pages as $mytheme_page ) {
	printf(
		'<p><a href="%1$s">%2$s</a>, last updated %3$s</p>',
		esc_url( get_permalink( $mytheme_page ) ),
		esc_html( get_the_title( $mytheme_page ) ),
		esc_html( get_the_modified_date( '', $mytheme_page ) )
	);
}
?>

Link a legal page by its slug, with get_page_by_path() in PHP or with the [wpautoterms page_link] shortcode in content. The slug is the last part of the page address: privacy-policy in /termsfeed/privacy-policy/. A child legal page takes its full path, such as policies/cookies-policy.

This function returns the address of a published legal page, or an empty string while the page is missing or still a draft.

/**
 * Returns the permalink of a published TermsFeed AutoTerms legal page, or ''.
 *
 * @param string $path Slug of the legal page, or parent/child path of a child page.
 * @return string
 */
function mytheme_legal_page_url( $path ) {
	$page = get_page_by_path( $path, OBJECT, 'wpautoterms_page' );

	if ( ! $page || 'publish' !== get_post_status( $page ) ) {
		return '';
	}

	return get_permalink( $page );
}

This template code prints a link to the Privacy Policy that the questionnaire created. It prints nothing while that page is missing or still a draft.

<?php
$mytheme_privacy_url = mytheme_legal_page_url( 'privacy-policy' );

if ( '' !== $mytheme_privacy_url ) {
	printf(
		'<a class="mytheme-privacy-link" href="%s">%s</a>',
		esc_url( $mytheme_privacy_url ),
		esc_html__( 'Privacy Policy', 'mytheme' )
	);
}
?>

The shortcodes give the same link, title, and date in post content and in the kit messages. Each shortcode finds only published legal pages, prints nothing for a missing slug, and uses a top-level page first when 2 legal pages share a slug. Copy every variant from Legal Page Shortcodes for WordPress.

Run a shortcode in PHP with do_shortcode(), and escape the link it returns. These 3 lines print the title, the address, and the "Last updated" date of the Privacy Policy.

<?php echo do_shortcode( '[wpautoterms page_title="privacy-policy"]' ); ?>
<?php echo esc_url( do_shortcode( '[wpautoterms page_link="privacy-policy"]' ) ); ?>
<?php echo do_shortcode( '[wpautoterms last_updated_date="privacy-policy"]' ); ?>

Each questionnaire gives its page a fixed slug: privacy-policy, terms-and-conditions, or cookies-policy. The plugin sets the slug once, when you click the "Create" button, and never renames the slug of any post afterward. A custom legal page takes its slug from its title, as any page does.

A second legal page of the same type keeps the slug of its type while it is a draft. WordPress adds a number, such as privacy-policy-2, when you publish it. Code that looks up privacy-policy then finds the first page, not the new one. The Shortcode column of TermsFeed > All Legal Pages shows the slug each page received.

The "Last updated" line of every generated page shows the modified date of the post at display time, so any save of the page moves it. The "Last Effective Date" column of All Legal Pages reads the same date, and so does the Update Notices kit, which skips a save within 60 seconds of publishing. Read the modified date in your code with get_the_modified_date() or get_post_modified_time(), never with the publish date.

Legal pages live under the legal pages slug, termsfeed by default, so the Privacy Policy address is /termsfeed/privacy-policy/ with pretty permalinks. A child page adds the path of its parent, such as /termsfeed/policies/cookies-policy/. The archive at /termsfeed/ lists the published legal pages through your theme archive template.

With plain permalinks, the address of a legal page carries the query variable instead, such as /?wpautoterms_page=privacy-policy. The query variable of a child page holds the path of its parent too, such as /?wpautoterms_page=policies/cookies-policy. The "Legal pages slug" setting has no effect with plain permalinks.

Build every link with get_permalink() and get_post_type_archive_link( 'wpautoterms_page' ), never with a typed path, so the links follow a change of the slug. The plugin refreshes the rewrite rules on the next WordPress admin page you open after a slug change. Change the slug with WordPress Legal Page Settings.

Your WordPress theme displays legal pages with its own templates, because the plugin adds none to the front end. WordPress picks the template the way it does for any custom post type.

Add one of these template files to your theme to change how legal pages display.

Template file Displays
The page template chosen in the Template select The legal page you chose it for
single-wpautoterms_page-privacy-policy.php The legal page with the slug privacy-policy
single-wpautoterms_page.php Every legal page
single.php, singular.php, index.php Every legal page with no chosen page template, in a theme without single-wpautoterms_page.php or a matching slug file
archive-wpautoterms_page.php The archive at the legal pages slug
archive.php, index.php The archive, in a theme without archive-wpautoterms_page.php

In a classic theme, the Template select in the Page Attributes box of a legal page lists the page templates of your theme, the files with a Template Name: header. In a block theme, it lists the custom templates of the theme instead. Pick one there to give a single legal page the layout of a page. A block theme takes the same file names as .html files in its templates folder, or as templates made in the Site Editor.

The generated text of a legal page starts with its "Last updated" line and never repeats the title as a heading. Print the title in the template, as this single-wpautoterms_page.php for a classic theme does.

<?php
/**
 * Template for TermsFeed AutoTerms legal pages.
 */
get_header();

while ( have_posts() ) :
	the_post();
	?>
	<main id="primary" class="site-main mytheme-legal-page">
		<article id="post-<?php the_ID(); ?>" <?php post_class(); ?>>
			<header class="entry-header">
				<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
			</header>
			<div class="entry-content">
				<?php the_content(); ?>
			</div>
		</article>
	</main>
	<?php
endwhile;

get_footer();

With this file in the theme folder, every published legal page prints its title as an <h1> heading and carries the mytheme-legal-page class on its <main> element.

The plugin prints its own markup for the notices and the footer legal links, and a theme file does not replace it. Style that markup with the style fields and Additional CSS of each compliance kit from WordPress Compliance Kits, or add your own markup with TermsFeed AutoTerms Action Hooks and Filters.

The AutoTerms plugin adds legal pages to the page list of Settings > Privacy and to 2 page lists of the WooCommerce Customizer. Settings > Privacy lists your legal pages after the regular pages, drafts included. The "Privacy policy page" and "Terms and conditions page" lists in Appearance > Customize > WooCommerce > Checkout show them too. WooCommerce fills those lists with a get_pages() call that leaves out its cart, checkout, and account pages, and the AutoTerms plugin adds the legal pages to that call.

The "Terms and conditions" search in WooCommerce > Settings > Advanced > Page setup queries regular pages only, so it never finds a legal page. WooCommerce locks the Customizer Checkout section while the checkout page holds the Checkout block. Set the WooCommerce terms page on a store with the Checkout block with wp option update woocommerce_terms_page_id <ID>, where <ID> is the ID of the legal page.

After you choose a legal page in Settings > Privacy, the ID that WordPress stores for the privacy page is the ID of that legal page. get_privacy_policy_url() and the_privacy_policy_link() link the legal page once it is published. Choose the pages in the admin with the steps in How to choose a legal page as the WordPress Privacy Policy page and How to choose legal pages in WooCommerce.