CC Fields
CC Fields is a custom-fields framework for WordPress. It lets administrators build field groups in the WordPress dashboard and lets theme and plugin developers read, update, expose, and render those fields through a PHP API, shortcodes, REST, front-end forms, local JSON, and Gutenberg integrations.
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/smalezan/cc-fields/archive/refs/heads/main.zipReadme
CC Fields
CC Fields is a custom-fields framework for WordPress. It lets administrators build field groups in the WordPress dashboard and lets theme and plugin developers read, update, expose, and render those fields through a PHP API, shortcodes, REST, front-end forms, local JSON, and Gutenberg integrations.
Current version: 1.0.3
Requirements
- WordPress 6.4 or newer
- PHP 8.0 or newer
- Administrator access to create and manage field groups
- A writable theme directory or another writable directory if local JSON sync is used
Installation
- Copy the plugin directory into
wp-content/plugins/cc-fields. - In WordPress, go to Plugins > Installed Plugins.
- Activate CC Fields.
- Open CC Fields in the WordPress admin menu.
For development dependencies and tests:
composer install
vendor/bin/phpunit
Composer packages are development-only; the plugin itself does not require Composer at runtime.
Quick start
1. Create a field group
Go to CC Fields > Add New, enter a group title such as “Page Details,” then select + Add Field.
For each field:
- Label is the human-readable title shown to editors.
- Name is the stable machine-readable name used in templates, for example
hero_title. Use lowercase letters, numbers, and underscores. - Field Key is an automatically generated internal identifier. Do not change or reuse it.
- Required prevents the form from accepting an empty value for the field.
- Instructions are displayed to editors near the input.
- Type-specific settings control the input, validation, and formatted return value.
Drag field rows to change their order.
2. Choose where the group appears
In Location, create one or more rules. Rules in the same group use AND. Separate rule groups use OR.
For example:
Post Type is equal to Page
AND Page Template is equal to Landing Page
OR
Post is equal to Home
The group is shown when all rules in any one rule group match.
3. Configure and save the group
Available group settings are:
- Active: enables or disables the group.
- REST API: includes its values in the
ccf_fieldsproperty on supported WordPress REST responses and enables the Gutenberg sidebar integration. - Position: normal, high, or side metabox position.
- Style: standard WordPress metabox or seamless presentation.
- Label Placement: above or to the left of inputs.
- Instruction Placement: below the label or above the input.
- Order: lower-numbered matching groups appear first.
Save the group, edit a matching WordPress object, enter values, and update it.
4. Display a value in a theme
<?php if (ccf_has_field('hero_title')) : ?>
<h1><?php echo esc_html(ccf_get_field('hero_title')); ?></h1>
<?php endif; ?>
ccf_get_field() returns the field's formatted value. Always escape for the output context even though CC Fields sanitizes values when they are stored.
Location rules
The == and != operators are supported for all location rules.
| Rule | What it matches |
|---|---|
| Post Type | A post, page, attachment, or another UI-enabled post type |
| Page Template | The default template or a template supplied by the active theme |
| Post Taxonomy | A post assigned to a particular taxonomy term |
| Post Format | Standard or a registered post format |
| Post Status | Published, draft, pending, private, or scheduled |
| User Role | The role of the current user viewing the editing screen |
| Page Type | Front page, posts page, top-level page, child page, or parent page |
| Options Page | A programmatically registered CC Fields options page |
| User Form | All users or a specific role on user-profile screens |
| Taxonomy Term | A term add/edit screen for a particular taxonomy |
| Post | One specific published post |
| Post Category | A post assigned to a selected category |
| Post Tag | A post assigned to a selected tag |
| Page Parent | A page whose direct parent is the selected page |
| Attachment | All attachments or an image, video, audio, or application MIME family |
| Comment | All comments, comments, trackbacks, or pingbacks |
Post-dependent rules may not match a brand-new unsaved post until WordPress has assigned it an ID or the relevant property has been saved.
Field types
Every data field supports a label, name, required state, instructions, and conditional logic. Layout-only fields do not store a value.
Basic
| Type | Important settings | Formatted value |
|---|---|---|
| Text | Default, placeholder, min/max length, regex pattern | String |
| Text Area | Default, placeholder, rows, max length | String |
| Number | Default, placeholder, minimum, maximum, step | Integer or float |
| Range | Default, minimum, maximum, step | Integer or float |
| Default, placeholder | Sanitized email string | |
| URL | Default, placeholder | Sanitized URL string |
| Password | Placeholder | String; this is stored as post meta, not hashed as a WordPress login password |
| Link | Return an array or URL only | ['url' => ..., 'title' => ..., 'target' => ...] or URL string |
Choice
| Type | Important settings | Formatted value |
|---|---|---|
| Select | Choices, default, multiple selection | String or array of strings |
| Checkbox | Choices, comma-separated defaults, vertical/horizontal layout | Array of selected values |
| Radio Button | Choices, default, vertical/horizontal layout | Selected string |
| Button Group | Choices, default | Selected string |
| True / False | Toggle message, default | Boolean |
Enter choices one per line. A line can contain value : Label; if no label is supplied, the value is also used as its label.
Content
| Type | Important settings | Formatted value |
|---|---|---|
| Image | Return format, preview size, allowed extensions, max size | Image array, URL, or attachment ID |
| File | Return format, allowed extensions, max size | File array, URL, or attachment ID |
| Gallery | Minimum/maximum images, preview size | Array of image data |
| WYSIWYG Editor | Default, toolbar, visual/text tabs, media button, rows | HTML processed through the_content |
| oEmbed | Width and height | Provider embed HTML when WordPress can resolve the URL |
An image array contains id, url, width, height, alt, title, and sizes. A file array contains id, url, filename, filesize, and mime_type.
Relational
| Type | Important settings | Formatted value |
|---|---|---|
| Post Object | Post type, single/multiple, object/ID return, bidirectional | WP_Post, ID, or an array of either |
| Relationship | Post type, taxonomy filter, min/max, object/ID return, bidirectional | Array of WP_Post objects or IDs |
| Page Link | Post type, single/multiple, allow null | Permalink string or array of permalinks |
| User | Role filter, single/multiple, object/ID/array return | WP_User, ID, compact user array, or arrays of them |
| Taxonomy Term | Taxonomy, single/multiple, object/ID/slug return | WP_Term, ID, slug, or arrays of them |
For a bidirectional Post Object or Relationship field, create the back-reference field on the related content and use the same field name. When the source relationship changes, CC Fields adds and removes the source post ID in that field on related posts. Only post relationships are synchronized.
Interactive
| Type | Important settings | Formatted value |
|---|---|---|
| Date Picker | Display format, min/max date, default | Stored Y-m-d date rendered in the configured display format |
| Time Picker | Display format, default | Stored time rendered in the configured display format |
| Date Time Picker | Display format, min/max date, default | Formatted date/time string |
| Color Picker | Default color, opacity, swatches | Color string |
| Google Map | Center, zoom, height | Array containing lat, lng, zoom, and address |
Configure Google Maps before using the map field:
add_filter('ccf/google_maps_api_key', function (): string {
return 'YOUR_GOOGLE_MAPS_API_KEY';
});
Restrict the key appropriately in Google Cloud and enable the Maps JavaScript API and Places library.
Layout
| Type | Purpose |
|---|---|
| Message | Displays editor guidance; stores no value |
| Tab | Starts a tab section; stores no value |
| Accordion | Starts an accordion section; stores no value |
| Group | Stores a structured set of sub-fields |
| Repeater | Stores zero or more rows using one sub-field schema |
| Flexible Content | Stores ordered rows selected from one or more layout schemas |
| Clone | Reuses the fields from another field group and stores them as flattened individual meta values |
Repeaters and Flexible Content support minimum and maximum rows and a configurable add button. A Repeater can display rows as a table, block, or row layout. Flexible Content layouts each have a stable name, label, and their own sub-fields.
Clone fields can prefix each cloned field name with the clone field name. For example, cloning subtitle into a clone named hero with prefixing enabled stores and reads hero_subtitle. Clone fields do not have a top-level meta value.
Conditional logic
Enable Conditional Logic in a field's settings to show or hide it based on other fields in the same form.
You can choose whether all or any rules must match. Operators are:
- is equal to
- is not equal to
- is greater than
- is less than
- contains
- does not contain
- is empty
- is not empty
Conditional logic controls presentation. It should not be treated as an authorization boundary or as a replacement for server-side validation.
PHP template API
Context identifiers
Most API functions accept an optional second argument:
| Argument | Context |
|---|---|
Omitted or false |
Current post from get_the_ID() |
123 |
Post ID 123 |
'option' |
Global options storage |
'user_5' |
User ID 5 |
'term_12' |
Term ID 12 |
'comment_9' |
Comment ID 9 |
Read values
$title = ccf_get_field('hero_title');
$other_title = ccf_get_field('hero_title', 123);
$phone = ccf_get_field('phone', 'option');
$job_title = ccf_get_field('job_title', 'user_5');
$term_color = ccf_get_field('color', 'term_12');
$rating = ccf_get_field('rating', 'comment_9');
Use ccf_get_raw_field() when you need the stored value without field-type formatting:
$attachment_id = ccf_get_raw_field('hero_image');
Use ccf_get_field_object() to retrieve the field definition together with value, raw_value, and context information:
$field = ccf_get_field_object('hero_title');
if ($field) {
echo esc_html($field['label']);
}
Check for a non-empty value:
if (ccf_has_field('subtitle')) {
echo esc_html(ccf_get_field('subtitle'));
}
ccf_the_field() echoes a scalar as escaped text and JSON-encodes arrays. For markup-sensitive output, return the value with ccf_get_field() and escape it for its actual context.
Update and delete values
ccf_update_field('hero_title', 'A new title', 123);
ccf_update_field('support_email', 'help@example.com', 'option');
ccf_delete_field('hero_title', 123);
Updates are sanitized through the registered field type when a field definition with that name exists. Field names should therefore be unique when their definitions or return formats differ.
Output examples
Text:
<p><?php echo esc_html(ccf_get_field('subtitle')); ?></p>
URL:
<?php $url = ccf_get_field('cta_url'); ?>
<?php if ($url) : ?>
<a href="<?php echo esc_url($url); ?>">Learn more</a>
<?php endif; ?>
Image configured to return an array:
<?php $image = ccf_get_field('hero_image'); ?>
<?php if (is_array($image) && !empty($image['id'])) : ?>
<?php echo wp_get_attachment_image($image['id'], 'large', false, [
'alt' => $image['alt'],
]); ?>
<?php endif; ?>
Relationship configured to return post objects:
<?php foreach ((array) ccf_get_field('related_articles') as $article) : ?>
<?php if ($article instanceof WP_Post) : ?>
<a href="<?php echo esc_url(get_permalink($article)); ?>">
<?php echo esc_html(get_the_title($article)); ?>
</a>
<?php endif; ?>
<?php endforeach; ?>
WYSIWYG content is already processed through the_content. Render trusted editor content with wp_kses_post():
echo wp_kses_post(ccf_get_field('body_copy'));
Repeater loops
<?php if (ccf_have_rows('features')) : ?>
<ul>
<?php while (ccf_have_rows('features')) : ccf_the_row(); ?>
<li>
<strong><?php echo esc_html(ccf_get_sub_field('title')); ?></strong>
<?php echo esc_html(ccf_get_sub_field('description')); ?>
</li>
<?php endwhile; ?>
</ul>
<?php endif; ?>
Pass a context to the first ccf_have_rows() call when looping rows outside the current post:
while (ccf_have_rows('office_hours', 'option')) {
ccf_the_row();
echo esc_html(ccf_get_sub_field('day'));
}
Nested repeater loops are supported:
while (ccf_have_rows('sections')) {
ccf_the_row();
echo esc_html(ccf_get_sub_field('heading'));
while (ccf_have_rows('items')) {
ccf_the_row();
echo esc_html(ccf_get_sub_field('label'));
}
}
ccf_get_raw_sub_field() returns an unformatted sub-field value.
Flexible Content loops
while (ccf_have_rows('page_sections')) {
ccf_the_row();
switch (ccf_get_row_layout()) {
case 'hero':
get_template_part('template-parts/section', 'hero', [
'title' => ccf_get_sub_field('title'),
'image' => ccf_get_sub_field('image'),
]);
break;
case 'text_block':
echo wp_kses_post(ccf_get_sub_field('content'));
break;
}
}