add this to your functions.php and it will add Word Count named page to the dashboard and it will show the total word count for all posts.
// Add a custom page to the WordPress admin menu
function word_count_admin_menu() {
add_menu_page(
'Word Count',
'Word Count',
'manage_options',
'word-count',
'word_count_admin_page',
'dashicons-editor-spellcheck',
20
);
}
add_action('admin_menu', 'word_count_admin_menu');
// Display the total word count of all posts in the custom admin page
function word_count_admin_page() {
global $wpdb;
$total_word_count = 0;
$results = $wpdb->get_results("SELECT post_content FROM $wpdb->posts WHERE post_status = 'publish' AND post_type = 'post'");
foreach ($results as $result) {
$word_count = str_word_count(strip_tags($result->post_content));
$total_word_count += $word_count;
}
?>
<div class="wrap">
<h1>Total Word Count</h1>
<p>The total number of words in all posts: <?php echo esc_html($total_word_count); ?></p>
</div>
<?php
}
UPDATED
this coutns bouth wordpress editor and briks eeditor contents as well.
<?php
/**
* Word Count admin page (classic/Gutenberg + Bricks).
* Counting logic mirrors WPML (WPML_TM_Word_Calculator) and the Bricks WPML integration
* (only text / textarea / editor / repeater controls are treated as content).
*/
function word_count_admin_menu() {
add_menu_page( 'Word Count', 'Word Count', 'manage_options', 'word-count', 'word_count_admin_page', 'dashicons-editor-spellcheck', 20 );
}
add_action( 'admin_menu', 'word_count_admin_menu' );
// WPML-style word counter: decode entities, strip tags/URLs/shortcode tags, split on whitespace + punctuation
function word_count_count( $string ) {
if ( ! is_string( $string ) || $string === '' ) {
return 0;
}
$s = html_entity_decode( $string, ENT_QUOTES, 'UTF-8' );
$s = wp_strip_all_tags( $s );
$s = preg_replace( '#(https?|ftp|file)://[^[:space:]]+#i', '', $s );
$s = preg_replace( '#[[]/?[^]/]+/?[]]#', '', $s );
$words = preg_split( '#[[:space:][:punct:]]+#u', trim( $s ), -1, PREG_SPLIT_NO_EMPTY );
return $words ? count( $words ) : 0;
}
// Text of one Bricks element, using the element's own control definitions (same as Bricks' WPML integration)
function word_count_bricks_element_text( $element ) {
$settings = isset( $element['settings'] ) && is_array( $element['settings'] ) ? $element['settings'] : array();
if ( ! $settings || empty( $element['name'] ) ) {
return '';
}
$types = array( 'text', 'textarea', 'editor' );
$exclude = array( 'customTag', '_gridTemplateColumns', '_gridTemplateRows', '_cssId', 'targetSelector' );
$controls = array();
$bricks_elements = 'Bricks' . chr( 92 ) . 'Elements'; // no backslashes: snippet plugin strips them
if ( class_exists( $bricks_elements ) ) {
$config = call_user_func( array( $bricks_elements, 'get_element' ), array( 'name' => $element['name'] ) );
$controls = ! empty( $config['controls'] ) && is_array( $config['controls'] ) ? $config['controls'] : array();
}
$out = '';
// Fallback when Bricks isn't loaded / element unknown: common text keys
if ( ! $controls ) {
foreach ( array( 'text', 'title', 'content', 'label', 'subtitle', 'description' ) as $k ) {
if ( ! empty( $settings[ $k ] ) && is_string( $settings[ $k ] ) ) {
$out .= ' ' . $settings[ $k ];
}
}
return $out;
}
foreach ( $controls as $key => $control ) {
if ( empty( $settings[ $key ] ) || in_array( $key, $exclude, true ) ) {
continue;
}
$type = isset( $control['type'] ) ? $control['type'] : '';
if ( in_array( $type, $types, true ) && is_string( $settings[ $key ] ) ) {
$out .= ' ' . $settings[ $key ];
} elseif ( $type === 'repeater' && is_array( $settings[ $key ] ) && ! empty( $control['fields'] ) ) {
foreach ( $settings[ $key ] as $item ) {
foreach ( (array) $item as $f_key => $f_val ) {
$f_type = isset( $control['fields'][ $f_key ]['type'] ) ? $control['fields'][ $f_key ]['type'] : '';
if ( in_array( $f_type, $types, true ) && is_string( $f_val ) ) {
$out .= ' ' . $f_val;
}
}
}
}
}
return $out;
}
function word_count_admin_page() {
global $wpdb;
$post_types = array( 'post', 'page' ); // add CPTs here
$in = implode( ',', array_fill( 0, count( $post_types ), '%s' ) );
// Direct query so language/translation plugins can't filter posts out
$posts = $wpdb->get_results( $wpdb->prepare(
"SELECT ID, post_title, post_type, post_content FROM {$wpdb->posts} WHERE post_status = 'publish' AND post_type IN ($in) ORDER BY post_type, ID",
$post_types
) );
$rows = array();
$total = $total_bricks = $total_classic = 0;
foreach ( $posts as $p ) {
$elements = get_post_meta( $p->ID, '_bricks_page_content_2', true );
$is_bricks = get_post_meta( $p->ID, '_bricks_editor_mode', true ) === 'bricks' && is_array( $elements ) && $elements;
if ( $is_bricks ) {
$text = '';
foreach ( $elements as $el ) {
$text .= ' ' . word_count_bricks_element_text( $el );
}
$count = word_count_count( $text );
$total_bricks += $count;
} else {
$count = word_count_count( $p->post_content );
$total_classic += $count;
}
$total += $count;
$rows[] = array( $p, $is_bricks ? 'Bricks' : 'Classic/Gutenberg', $count );
}
?>
<div class="wrap">
<h1>Total Word Count</h1>
<p>Total words: <strong><?php echo esc_html( number_format_i18n( $total ) ); ?></strong>
(<?php echo esc_html( count( $posts ) ); ?> published items)</p>
<p>Bricks: <?php echo esc_html( number_format_i18n( $total_bricks ) ); ?> ·
Classic/Gutenberg: <?php echo esc_html( number_format_i18n( $total_classic ) ); ?></p>
<table class="widefat striped" style="max-width:800px">
<thead><tr><th>ID</th><th>Title</th><th>Type</th><th>Source</th><th>Words</th></tr></thead>
<tbody>
<?php foreach ( $rows as $r ) : ?>
<tr>
<td><?php echo (int) $r[0]->ID; ?></td>
<td><a href="<?php echo esc_url( get_edit_post_link( $r[0]->ID ) ); ?>"><?php echo esc_html( $r[0]->post_title ); ?></a></td>
<td><?php echo esc_html( $r[0]->post_type ); ?></td>
<td><?php echo esc_html( $r[1] ); ?></td>
<td><?php echo esc_html( number_format_i18n( $r[2] ) ); ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php
}