$npx -y skills add levantoan/DevVN-WordPress-Skills --skill devvn-html-to-wp-acfConvert static HTML landing pages into WordPress page templates with ACF (Advanced Custom Fields) or SCF (Secure Custom Fields) integration. Covers field registration via PHP, template conversion with get_field/have_rows, CSS minification, asset stripping, and page speed optimiza
| 1 | # HTML to WordPress + ACF/SCF Conversion |
| 2 | |
| 3 | > **ACF vs SCF**: Secure Custom Fields (SCF) is the WordPress.org-maintained fork of ACF. Both share the same API — `get_field()`, `have_rows()`, `acf_add_local_field_group()`, etc. All code in this skill works with either plugin without modification. When this document says "ACF", it applies equally to SCF. |
| 4 | |
| 5 | ## When to use |
| 6 | |
| 7 | Use this skill when converting a static HTML page (landing page, one-page site, campaign page) into a WordPress page template that uses ACF or SCF for dynamic content management: |
| 8 | |
| 9 | - Converting a standalone `.html` file into a `.php` page template |
| 10 | - Making hardcoded text, images, repeaters editable via ACF admin |
| 11 | - Registering ACF field groups in PHP (no JSON import needed) |
| 12 | - Optimizing the converted page for speed (inline CSS minification, stripping unnecessary WP assets) |
| 13 | - Setting up Classic Editor for specific page templates in a block theme |
| 14 | |
| 15 | ## Inputs required |
| 16 | |
| 17 | - The source HTML file (or existing `.php` template with hardcoded content) |
| 18 | - The target WordPress theme directory |
| 19 | - Whether ACF Pro is available (repeater, flexible content require Pro) |
| 20 | - Any existing `functions.php` to extend |
| 21 | |
| 22 | ## Procedure |
| 23 | |
| 24 | ### 1) Analyze the HTML structure |
| 25 | |
| 26 | 1. Read the full HTML file and identify every content section |
| 27 | 2. Map each section to a category: |
| 28 | - **Simple fields**: headings, descriptions, badge text, phone, email, address, URLs |
| 29 | - **Repeaters**: lists of cards, features, FAQ items, stats, gallery items, menu columns |
| 30 | - **Images**: hero backgrounds, product photos, icons, gallery |
| 31 | - **Rich text**: content blocks with links (footer menus, legal text) |
| 32 | 3. Group fields into logical **tabs** matching the page sections (Hero, Features, Products, Services, FAQ, Contact, Footer, etc.) |
| 33 | 4. Note which sections need **fallback HTML** (default content when ACF fields are empty) |
| 34 | |
| 35 | Read: references/field-mapping.md |
| 36 | |
| 37 | ### 2) Create the ACF field registration file |
| 38 | |
| 39 | 1. Create `acf-{template-name}-fields.php` in the theme root |
| 40 | 2. Use `acf_add_local_field_group()` inside an `acf/init` action hook |
| 41 | 3. Structure fields with tabs for each section |
| 42 | 4. Apply these rules: |
| 43 | - Guard with `if ( ! function_exists( 'acf_add_local_field_group' ) ) { return; }` |
| 44 | - Use descriptive, prefixed keys: `field_{prefix}_{section}_{name}` |
| 45 | - Use descriptive, prefixed names: `{prefix}_{field_name}` |
| 46 | - Set `default_value` for all text/textarea fields with the original hardcoded content |
| 47 | - Use `return_format => 'url'` for images (simpler output) |
| 48 | - Use `repeater` for any list of items (cards, features, FAQ, stats) |
| 49 | - Use `wysiwyg` with `toolbar => 'basic'` and `media_upload => 0` for rich link content (footer menus, legal text) |
| 50 | - Set location rule to match the page template file |
| 51 | 5. Include the file from `functions.php` with `require_once get_theme_file_path()` |
| 52 | |
| 53 | Read: references/acf-registration.md |
| 54 | |
| 55 | ### 3) Create helper functions in the template |
| 56 | |
| 57 | Add these at the top of the page template file, before any HTML output: |
| 58 | |
| 59 | ```php |
| 60 | // Helper: get ACF field with fallback. |
| 61 | function {prefix}_field( $name, $default = '' ) { |
| 62 | if ( ! function_exists( 'get_field' ) ) { |
| 63 | return $default; |
| 64 | } |
| 65 | $val = get_field( $name ); |
| 66 | return ( $val !== null && $val !== '' && $val !== false ) ? $val : $default; |
| 67 | } |
| 68 | ``` |
| 69 | |
| 70 | Also declare shared variables used across multiple sections (phone, email, address, logo URL) at the top so both contact and footer sections can reference them. |
| 71 | |
| 72 | ### 4) Convert HTML sections to ACF-powered PHP |
| 73 | |
| 74 | For each section in the HTML, apply these conversion patterns: |
| 75 | |
| 76 | **Simple text fields:** |
| 77 | ```php |
| 78 | <?php echo esc_html( {prefix}_field( 'field_name', 'Default text' ) ); ?> |
| 79 | ``` |
| 80 | |
| 81 | **Image fields:** |
| 82 | ```php |
| 83 | <?php $img = {prefix}_field( 'field_name', 'fallback-url' ); ?> |
| 84 | <img src="<?php echo esc_url( $img ); ?>" alt="..."> |
| 85 | ``` |
| 86 | |
| 87 | **Repeater fields:** |
| 88 | ```php |
| 89 | <?php if ( function_exists( 'have_rows' ) && have_rows( 'repeater_name' ) ) : ?> |
| 90 | <?php while ( have_rows( 'repeater_name' ) ) : the_row(); ?> |
| 91 | <div><?php echo esc_html( get_sub_field( 'sub_field' ) ); ?></div> |
| 92 | <?php endwhile; ?> |
| 93 | <?php else : ?> |
| 94 | <!-- Original hardcoded HTML as fallback --> |
| 95 | <?php endif; ?> |
| 96 | ``` |
| 97 | |
| 98 | **WYSIWYG fields (rich text with links):** |
| 99 | ```php |
| 100 | <?php echo wp_kses_post( {prefix}_field( 'field_name', '<a href="#">Default</a>' ) ); ?> |
| 101 | ``` |
| 102 | |
| 103 | **Inline background images:** |
| 104 | ```php |
| 105 | style="background: linear-gradient(...), url('<?php echo esc_url( $bg_url ); ?>') center/cover no-repeat;" |
| 106 | ``` |
| 107 | |
| 108 | **Form shortcodes (replace hardcoded `<form>`):** |
| 109 | ```php |
| 110 | <?php ec |