by Johann Heyne
4.9 (59 reviews)
Table Field Add-on for ACF and SCF
A Table Field Add-on for the Advanced Custom Fields and Secure Custom Fields Plugin.
Compatible with WP 6.9
v1.3.33
Current Version v1.3.33
Updated 1 month ago
Last Update on 08 Dec, 2025
Synced 15 hours ago
Last Synced on
Rank
#719
—
No change
Active Installs
50K+
-16.7%
KW Avg Position
110.3
+0.3 better
Downloads
1M
+59 today
Support Resolved
0%
—
No change
Rating
98%
Review 4.9 out of 5
4.9
(59 reviews)
Next Milestone 60K
50K+
60K+
5
Ranks to Climb
-
Growth Needed
8,000,000
Active Installs
Pro
Unlock Exact Install Count
See the precise estimated active installs for this plugin, calculated from real-time ranking data.
- Exact install estimates within tiers
- Track install growth over time
- Milestone progress predictions
Need 516 more installs to reach 60K+
Rank Changes
Current
#719
Change
Best
#
Downloads Growth
Downloads
Growth
Peak
Upgrade to Pro
Unlock 30-day, 90-day, and yearly download history charts with a Pro subscription.
Upgrade NowReviews & Ratings
4.9
59 reviews
Overall
98%
5
55
(93%)
4
3
(5%)
3
0
(0%)
2
0
(0%)
1
1
(2%)
Tracked Keywords
Showing 3 of 3| Keyword | Position | Change | Type | Updated |
|---|---|---|---|---|
| scf | 9 | — | Tag | 16 hours ago |
| secure custom fields | 128 | — | Tag | 16 hours ago |
| acf | 194 | — | Tag | 16 hours ago |
Unlock Keyword Analytics
Track keyword rankings, search positions, and discover new ranking opportunities with a Pro subscription.
- Full keyword position tracking
- Historical ranking data
- Competitor keyword analysis
Track This Plugin
Get detailed analytics, keyword tracking, and position alerts delivered to your inbox.
Start Tracking FreePlugin Details
- Version
- 1.3.33
- Last Updated
- Dec 08, 2025
- Requires WP
- 5.3+
- Tested Up To
- 6.9
- PHP Version
- 7.4 or higher
- Author
- Johann Heyne
Support & Rating
- Rating
- ★ ★ ★ ★ ★ 4.9
- Reviews
- 59
- Support Threads
- 0
- Resolved
- 0%
Keywords
Upgrade to Pro
Unlock keyword rankings, search positions, and detailed analytics with a Pro subscription.
Upgrade NowSimilar Plugins
WP Adminify – White Label WordPress, Admin Menu Editor, Login Customizer
7K+ installs
#2,735
Master Addons For Elementor - White Label, Free Widgets, Hover Effects, Conditions, & Animations
40K+ installs
#929
Query Monitor - The developer tools panel for WordPress
200K+ installs
#255
Header and Footer Scripts
200K+ installs
#258
Photo Gallery by 10Web – Mobile-Friendly Image Gallery
200K+ installs
#267
Frequently Asked Questions
Common questions about Table Field Add-on for ACF and SCF
$table = get_field( 'your_table_field_name' );
if ( ! empty ( $table ) ) {
echo '<table>';
if ( ! empty( $table['caption'] ) ) {
echo '<caption>' . $table['caption'] . '</caption>';
}
if ( ! empty( $table['header'] ) ) {
echo '<thead>';
echo '<tr>';
foreach ( $table['header'] as $th ) {
echo '<th>';
echo $th['c'];
echo '</th>';
}
echo '</tr>';
echo '</thead>';
}
echo '<tbody>';
foreach ( $table['body'] as $tr ) {
echo '<tr>';
foreach ( $tr as $td ) {
echo '<td>';
echo $td['c'];
echo '</td>';
}
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
}
Table field returns no data on get_field()?
If the table has only one empty cell, then get_field() returns FALSE. get_field() returns NULL when a field is not stored in the database. That happens when a page is copied but not their fields content. You can check both with empty()…
$table = get_field( 'your_table_field_name' );
if ( ! empty( $table ) ) {
// $table is not FALSE and not NULL.
// Field exists in database and has content.
}
if ( ! empty ( $table ) ) {
echo '<table>';
if ( ! empty( $table['caption'] ) ) {
echo '<caption>' . $table['caption'] . '</caption>';
}
if ( ! empty( $table['header'] ) ) {
echo '<thead>';
echo '<tr>';
foreach ( $table['header'] as $th ) {
echo '<th>';
echo $th['c'];
echo '</th>';
}
echo '</tr>';
echo '</thead>';
}
echo '<tbody>';
foreach ( $table['body'] as $tr ) {
echo '<tr>';
foreach ( $tr as $td ) {
echo '<td>';
echo $td['c'];
echo '</td>';
}
echo '</tr>';
}
echo '</tbody>';
echo '</table>';
}
Table field returns no data on get_field()?
If the table has only one empty cell, then get_field() returns FALSE. get_field() returns NULL when a field is not stored in the database. That happens when a page is copied but not their fields content. You can check both with empty()…
$table = get_field( 'your_table_field_name' );
if ( ! empty( $table ) ) {
// $table is not FALSE and not NULL.
// Field exists in database and has content.
}
Converting Line Breaks for HTML Output
To convert line breaks to <br> in tables HTML output the PHP function nl2br() can be used:
For line breaks in table header cells replace…
echo $th['c'];
with…
echo nl2br( $th['c'] );
For line breaks in table body cells replace…
echo $td['c'];
with…
echo nl2br( $td['c'] );
Displaying Line Breaks in Editing Tables
To display natural line breaks in the editing tables in the admin area, add the following styles to the admin area.
.acf-table-header-cont,
.acf-table-body-cont {
white-space: pre-line;
}
One way to add these styles to the WordPress admin area is adding the following code to your functions.php file of the theme.
add_action('admin_head', 'acf_table_styles');
function acf_table_styles() {
echo '<style>
.acf-table-header-cont,
.acf-table-body-cont {
white-space: pre-line;
}
</style>';
}
How to use the table field in Elementor or other Page Builders?
In general, its up to the page builder plugins to support ACF field types. But because the table field is not a native ACF field, the support for this field may never happen in page builders.
For now the way to go is using a shortcode. Elementor provides for example a shortcode widget. Before you can use a shortcode to display a table fields table, you have to setup a shortcode in functions.php. The following code does this. You can modify the table html output for your needs.
function shortcode_acf_tablefield( $atts ) {
$param = shortcode_atts( array(
'field-name' => false,
'subfield-name' => false,
'post-id' => false,
'table-class' => '',
), $atts );
$class = new class( $param ) {
public $atts;
public $field_names;
public $field_data = '';
public $html = '';
public function __construct( $atts ) {
if ( is_string( $atts['subfield-name'] ) ) {
$atts['field-name'] = $atts['subfield-name'];
}
if ( ! is_string( $atts['field-name'] ) ) {
return '';
}
$this->atts = $atts;
$this->field_names = explode( '/', $this->atts['field-name'] );
$this->subfield( 0 );
}
private function may_get_table_html( $data ) {
if ( isset( $data['body'] ) ) {
$return = '<table class="' . $this->atts['table-class'] . '">';
if ( ! empty( $data['caption'] ) ) {
echo '<caption>' . $data['caption'] . '</caption>';
}
if ( $data['header'] ) {
$return .= '<thead>';
$return .= '<tr>';
foreach ( $data['header'] as $th ) {
$return .= '<th>';
$return .= $th['c'];
$return .= '</th>';
}
$return .= '</tr>';
$return .= '</thead>';
}
$return .= '<tbody>';
foreach ( $data['body'] as $tr ) {
$return .= '<tr>';
foreach ( $tr as $td ) {
$return .= '<td>';
$return .= $td['c'];
$return .= '</td>';
}
$return .= '</tr>';
}
$return .= '</tbody>';
$return .= '</table>';
$this->html .= $return;
}
}
private function subfield( $level = 0, $data = null ) {
if ( isset( $data['body'] ) ) {
$this->may_get_table_html( $data );
return;
}
else if ( ! isset( $this->field_names[ $level ] ) ) {
return;
}
else if ( $data === null ) {
if ( $this->atts['subfield-name'] === false ) {
$data = get_field( $this->field_names[0], $this->atts['post-id'] );
}
else {
$data = get_sub_field( $this->field_names[0] );
}
}
else if ( isset( $data[ $this->field_names[ $level ] ] ) ) {
$data = $data[ $this->field_names[ $level ] ];
}
// repeater/group field
if (
is_array( $data ) &&
isset( $data[0] ) &&
! isset( $data[0]['acf_fc_layout'] )
) {
if ( is_numeric( $this->field_names[ $level + 1 ] ) ) {
if ( isset( $data[ $this->field_names[ $level + 1 ] ] ) ) {
$this->subfield( $level + 1, $data[ $this->field_names[ $level + 1 ] ] );
}
}
else {
foreach( $data as $key => $item ) {
$this->subfield( $level + 1, $item );
}
}
}
// flexible content field
else if (
is_array( $data ) &&
isset( $data[0] ) &&
isset( $data[0]['acf_fc_layout'] )
) {
foreach( $data as $key => $item ) {
if (
$item['acf_fc_layout'] === $this->field_names[ $level + 1 ] &&
isset( $item[ $this->field_names[ $level + 2 ] ] )
) {
$this->subfield( $level + 2, $item[ $this->field_names[ $level + 2 ] ] );
}
}
}
// table field
else {
$this->subfield( $level + 1, $data );
}
}
};
return $class->html;
}
add_shortcode( 'tablefield', 'shortcode_acf_tablefield' );
Then use the shortcode with the corresponding shortcode options of the Page Builder.
Getting a table field from the current page or post…
[tablefield field-name="table_field_name"]
Getting a table field from a group field…
[tablefield field-name="group_field_name/table_field_name"]
Getting a table field from a repeater field…
[tablefield field-name="repeater_field_name/table_field_name"]
Getting a table field from a specific repeater field item…
[tablefield field-name="repeater_field_name/item_index/table_field_name"]
Getting a table field from a flexible content field…
[tablefield field-name="flexible_content_field_name/layout_name/table_field_name"]
You can also get a table field from any kind of nested fields like in this example, where the table field is part of a layout of an flexible content field, that is a subfield of a repeater field, that is a subfield of a group field…
[tablefield field-name="group_field_name/repeater_field_name/flexible_content_field_name/layout_name/table_field_name"]
Getting a table field from inside a group, repeater or flexible content field loop…
[tablefield subfield-name="table_field_name"]
Getting a table field from another page or post…
[tablefield field-name="table_field_name" post-id="123"]
Getting a table field from a ACF option page…
[tablefield field-name="table_field_name" post-id="option"]
Adding a CSS class to the table HTML element…
[tablefield field-name="table_field_name" table-class="my-table-style"]
Updating a table using update_field()
You can use the ACF PHP function update_field() to change a tables data.
Notice
Make sure that the number of entries in the header array matches the number of cells in the body rows.
The array key 'c' stands for the content of the cells to have the option of adding other cell setting in future development.
The table data obtained by get_field() are formatted and differ by the original database data obtained by get_post_meta().
Example of changing table data using get_field() and update_field()
// the post ID where to update the table field
$post_id = 123;
$table_data = get_field( 'my_table', $post_id );
$table_data = array(
'use_header' => true, // boolean true/false
'caption' => 'My Caption',
'header' => array(
0 => array(
'c' => 'A',
),
1 => array(
'c' => 'B',
),
),
'body' => array(
0 => array(
0 => array(
'c' => 'The content of first cell of first row',
),
1 => array(
'c' => 'The content of second cell of first row',
),
),
1 => array(
0 => array(
'c' => The content of first cell of second row',
),
1 => array(
'c' => 'The content of second cell of second row',
),
),
)
);
update_field( 'my_table', $table_data, $post_id );
Example of adding a new row
// the post ID where to update the table field
$post_id = 123;
// gets the table data
$table_data = get_field( 'my_table', $post_id );
// defines the new row and its columns
$new_row = array(
// must define the same amount of columns as exists in the table
// column 1
array(
// the 'c' stands for content of the cell
'c' => 'Cell Content of Column 1',
),
// column 2
array(
'c' => 'Cell Content of Column 2',
)
);
// adds the new row to the table body data
array_push( $table_data['body'], $new_row );
// saves the new table data
update_field( 'my_table', $table_data, $post_id );
Third party plugins issues
Since version 1.3.1 of the table plugin, the storing format of the table data changes from JSON string to serialized array for new or updated tables. The issue with JSON is because of third party plugins that do not properly applying wp_slash() to a post_meta value before updating with update_post_metadata(). This can break JSON strings because update_post_metadata() removes backslashes by default. Backslashes are part of the JSON string syntax escaping quotation marks in content.
The table field plugin prevents broken JSON strings to save as a table field data and throws an error message that explains the issue. But this may also breaks the functionality of the third party plugin trying to update the table data. You could disable the JSON string check in the table field plugin using the following code in the wp-config.php file. But then the table JSON data are no longer protected from destroing by update_post_metadata(). Use the following code in wp-config.php only, if you understand the risk…
define( "ACF_TABLEFIELD_FILTER_POSTMETA", false );
UI Sanitizing Options
Since version 1.3.33, you can configure the UI sanitizing options.
This requires a script to be enqueued as an ACF admin script. You can enqueue it in your theme’s functions.php file using the following action.
add_action( 'acf/input/admin_enqueue_scripts', function() {
wp_enqueue_script(
'table-field-config', // Table field config script handle
get_template_directory_uri() . '/js/table-field-config.js', // Path to the script in the theme
);
});
The sanitization of the table fields UI is handled by DOMPurify.
Use the following table field hook in your table field config script to modify the DOMPurify options.
To convert line breaks to <br> in tables HTML output the PHP function nl2br() can be used:
For line breaks in table header cells replace…
echo $th['c'];
with…
echo nl2br( $th['c'] );
For line breaks in table body cells replace…
echo $td['c'];
with…
echo nl2br( $td['c'] );
Displaying Line Breaks in Editing Tables
To display natural line breaks in the editing tables in the admin area, add the following styles to the admin area.
.acf-table-header-cont,
.acf-table-body-cont {
white-space: pre-line;
}
One way to add these styles to the WordPress admin area is adding the following code to your functions.php file of the theme.
add_action('admin_head', 'acf_table_styles');
function acf_table_styles() {
echo '<style>
.acf-table-header-cont,
.acf-table-body-cont {
white-space: pre-line;
}
</style>';
}
How to use the table field in Elementor or other Page Builders?
In general, its up to the page builder plugins to support ACF field types. But because the table field is not a native ACF field, the support for this field may never happen in page builders.
For now the way to go is using a shortcode. Elementor provides for example a shortcode widget. Before you can use a shortcode to display a table fields table, you have to setup a shortcode in functions.php. The following code does this. You can modify the table html output for your needs.
function shortcode_acf_tablefield( $atts ) {
$param = shortcode_atts( array(
'field-name' => false,
'subfield-name' => false,
'post-id' => false,
'table-class' => '',
), $atts );
$class = new class( $param ) {
public $atts;
public $field_names;
public $field_data = '';
public $html = '';
public function __construct( $atts ) {
if ( is_string( $atts['subfield-name'] ) ) {
$atts['field-name'] = $atts['subfield-name'];
}
if ( ! is_string( $atts['field-name'] ) ) {
return '';
}
$this->atts = $atts;
$this->field_names = explode( '/', $this->atts['field-name'] );
$this->subfield( 0 );
}
private function may_get_table_html( $data ) {
if ( isset( $data['body'] ) ) {
$return = '<table class="' . $this->atts['table-class'] . '">';
if ( ! empty( $data['caption'] ) ) {
echo '<caption>' . $data['caption'] . '</caption>';
}
if ( $data['header'] ) {
$return .= '<thead>';
$return .= '<tr>';
foreach ( $data['header'] as $th ) {
$return .= '<th>';
$return .= $th['c'];
$return .= '</th>';
}
$return .= '</tr>';
$return .= '</thead>';
}
$return .= '<tbody>';
foreach ( $data['body'] as $tr ) {
$return .= '<tr>';
foreach ( $tr as $td ) {
$return .= '<td>';
$return .= $td['c'];
$return .= '</td>';
}
$return .= '</tr>';
}
$return .= '</tbody>';
$return .= '</table>';
$this->html .= $return;
}
}
private function subfield( $level = 0, $data = null ) {
if ( isset( $data['body'] ) ) {
$this->may_get_table_html( $data );
return;
}
else if ( ! isset( $this->field_names[ $level ] ) ) {
return;
}
else if ( $data === null ) {
if ( $this->atts['subfield-name'] === false ) {
$data = get_field( $this->field_names[0], $this->atts['post-id'] );
}
else {
$data = get_sub_field( $this->field_names[0] );
}
}
else if ( isset( $data[ $this->field_names[ $level ] ] ) ) {
$data = $data[ $this->field_names[ $level ] ];
}
// repeater/group field
if (
is_array( $data ) &&
isset( $data[0] ) &&
! isset( $data[0]['acf_fc_layout'] )
) {
if ( is_numeric( $this->field_names[ $level + 1 ] ) ) {
if ( isset( $data[ $this->field_names[ $level + 1 ] ] ) ) {
$this->subfield( $level + 1, $data[ $this->field_names[ $level + 1 ] ] );
}
}
else {
foreach( $data as $key => $item ) {
$this->subfield( $level + 1, $item );
}
}
}
// flexible content field
else if (
is_array( $data ) &&
isset( $data[0] ) &&
isset( $data[0]['acf_fc_layout'] )
) {
foreach( $data as $key => $item ) {
if (
$item['acf_fc_layout'] === $this->field_names[ $level + 1 ] &&
isset( $item[ $this->field_names[ $level + 2 ] ] )
) {
$this->subfield( $level + 2, $item[ $this->field_names[ $level + 2 ] ] );
}
}
}
// table field
else {
$this->subfield( $level + 1, $data );
}
}
};
return $class->html;
}
add_shortcode( 'tablefield', 'shortcode_acf_tablefield' );
Then use the shortcode with the corresponding shortcode options of the Page Builder.
Getting a table field from the current page or post…
[tablefield field-name="table_field_name"]
Getting a table field from a group field…
[tablefield field-name="group_field_name/table_field_name"]
Getting a table field from a repeater field…
[tablefield field-name="repeater_field_name/table_field_name"]
Getting a table field from a specific repeater field item…
[tablefield field-name="repeater_field_name/item_index/table_field_name"]
Getting a table field from a flexible content field…
[tablefield field-name="flexible_content_field_name/layout_name/table_field_name"]
You can also get a table field from any kind of nested fields like in this example, where the table field is part of a layout of an flexible content field, that is a subfield of a repeater field, that is a subfield of a group field…
[tablefield field-name="group_field_name/repeater_field_name/flexible_content_field_name/layout_name/table_field_name"]
Getting a table field from inside a group, repeater or flexible content field loop…
[tablefield subfield-name="table_field_name"]
Getting a table field from another page or post…
[tablefield field-name="table_field_name" post-id="123"]
Getting a table field from a ACF option page…
[tablefield field-name="table_field_name" post-id="option"]
Adding a CSS class to the table HTML element…
[tablefield field-name="table_field_name" table-class="my-table-style"]
Updating a table using update_field()
You can use the ACF PHP function update_field() to change a tables data.
Notice
Make sure that the number of entries in the header array matches the number of cells in the body rows.
The array key 'c' stands for the content of the cells to have the option of adding other cell setting in future development.
The table data obtained by get_field() are formatted and differ by the original database data obtained by get_post_meta().
Example of changing table data using get_field() and update_field()
// the post ID where to update the table field
$post_id = 123;
$table_data = get_field( 'my_table', $post_id );
$table_data = array(
'use_header' => true, // boolean true/false
'caption' => 'My Caption',
'header' => array(
0 => array(
'c' => 'A',
),
1 => array(
'c' => 'B',
),
),
'body' => array(
0 => array(
0 => array(
'c' => 'The content of first cell of first row',
),
1 => array(
'c' => 'The content of second cell of first row',
),
),
1 => array(
0 => array(
'c' => The content of first cell of second row',
),
1 => array(
'c' => 'The content of second cell of second row',
),
),
)
);
update_field( 'my_table', $table_data, $post_id );
Example of adding a new row
// the post ID where to update the table field
$post_id = 123;
// gets the table data
$table_data = get_field( 'my_table', $post_id );
// defines the new row and its columns
$new_row = array(
// must define the same amount of columns as exists in the table
// column 1
array(
// the 'c' stands for content of the cell
'c' => 'Cell Content of Column 1',
),
// column 2
array(
'c' => 'Cell Content of Column 2',
)
);
// adds the new row to the table body data
array_push( $table_data['body'], $new_row );
// saves the new table data
update_field( 'my_table', $table_data, $post_id );
Third party plugins issues
Since version 1.3.1 of the table plugin, the storing format of the table data changes from JSON string to serialized array for new or updated tables. The issue with JSON is because of third party plugins that do not properly applying wp_slash() to a post_meta value before updating with update_post_metadata(). This can break JSON strings because update_post_metadata() removes backslashes by default. Backslashes are part of the JSON string syntax escaping quotation marks in content.
The table field plugin prevents broken JSON strings to save as a table field data and throws an error message that explains the issue. But this may also breaks the functionality of the third party plugin trying to update the table data. You could disable the JSON string check in the table field plugin using the following code in the wp-config.php file. But then the table JSON data are no longer protected from destroing by update_post_metadata(). Use the following code in wp-config.php only, if you understand the risk…
define( "ACF_TABLEFIELD_FILTER_POSTMETA", false );
UI Sanitizing Options
Since version 1.3.33, you can configure the UI sanitizing options.
This requires a script to be enqueued as an ACF admin script. You can enqueue it in your theme’s functions.php file using the following action.
add_action( 'acf/input/admin_enqueue_scripts', function() {
wp_enqueue_script(
'table-field-config', // Table field config script handle
get_template_directory_uri() . '/js/table-field-config.js', // Path to the script in the theme
);
});
The sanitization of the table fields UI is handled by DOMPurify.
Use the following table field hook in your table field config script to modify the DOMPurify options.
ACFTableField.addFilter( 'core', 'sanitize_html', function( options ) {
// DOMPurify Options, @see: https://github.com/cure53/DOMPurify?tab=readme-ov-file#can-i-configure-dompurify
options.ADD_ATTR = ['target']; // For instance, the target attribute can be permitted
return options;
});
});
// DOMPurify Options, @see: https://github.com/cure53/DOMPurify?tab=readme-ov-file#can-i-configure-dompurify
options.ADD_ATTR = ['target']; // For instance, the target attribute can be permitted
return options;
});
});