Sebuah situs klien punya deretan halaman long-form yang seluruhnya dibangun di Elementor. Halaman pertama yang saya buka berisi 176 widget terpisah: heading sendiri, paragraf sendiri, spacer sendiri, terus begitu sampai bawah. Tugasnya terdengar sepele: ganti seluruh isi halaman itu dengan konten baru. Masalahnya, halaman seperti ini ada 34, dan mengganti konten satu halaman secara manual di editor Elementor makan waktu berjam-jam. Klik widget, hapus, paste, klik widget berikutnya, ulangi ratusan kali. Dikali 34 halaman, itu bukan pekerjaan lagi, itu hukuman.
Rencana saya: lewati UI sepenuhnya. Elementor menyimpan seluruh layout halaman sebagai satu string JSON besar di post meta bernama _elementor_data. Kalau meta itu bisa dibaca dan ditulis lewat REST API, satu halaman bisa di-swap dengan satu request, bukan ratusan klik.
Jalur normal yang ternyata buntu
Langkah pertama yang paling wajar: expose meta-nya ke REST lewat jalur resmi. WordPress punya register_post_meta() dengan argumen show_in_rest, jadi saya daftarkan _elementor_data untuk custom post type yang dipakai halaman-halaman ini.
register_post_meta('client_page', '_elementor_data', [
'show_in_rest' => true,
'single' => true,
'type' => 'string',
]);Hasilnya: request GET ke endpoint CPT-nya jalan, field meta muncul di respons, tapi isinya kosong. Bukan error, bukan pesan permission, cuma objek kosong {} yang seolah bilang halaman ini tidak punya apa-apa. Padahal buka halaman yang sama di editor Elementor, 176 widget itu jelas ada semua.
Saya sempat curiga ke arah yang salah: salah nama post type, salah argumen registrasi, atau Elementor menyimpan datanya di tempat lain. Semua saya cek ulang, semua benar. Datanya duduk manis di wp_postmeta, registrasinya jalan, tapi REST tetap mengembalikan kosong.
Akar masalahnya: meta berawalan underscore itu protected
Jawabannya ternyata ada di satu karakter pertama nama meta-nya. Di WordPress, meta yang key-nya berawalan underscore dianggap protected. REST API menolak meng-expose meta protected untuk CPT, dan show_in_rest di register_post_meta() tidak mengubah keputusan itu. _elementor_data berawalan underscore, jadi dia diam-diam disaring keluar dari respons. Tidak ada warning, tidak ada log, hanya {}.
Dan jalur alternatifnya, edit widget per widget di UI, sudah gugur sejak awal: tidak masuk akal untuk 34 halaman.
Perbaikannya: route custom dengan backup dan bust cache
Kalau pintu bawaan REST menolak menyentuh meta protected, solusinya bikin pintu sendiri. Saya tulis route custom af/v1/edata/{id} dengan tiga method yang kontraknya saya tentukan sendiri:
- GET: baca
_elementor_datamentah dari post meta, apa adanya. - POST: tulis JSON baru, dengan backup otomatis sebelum menimpa.
- PUT: restore dari backup kalau hasil swap-nya kacau.
Inti callback POST-nya begini:
$current = get_post_meta($id, '_elementor_data', true);
update_post_meta($id, '_elementor_data_afbackup', wp_slash($current));
update_post_meta($id, '_elementor_data', wp_slash($body['data']));
delete_post_meta($id, '_elementor_css');Ada dua detail yang tidak boleh dilewatkan di sini. Pertama, wp_slash(). update_post_meta() menjalankan unslash pada nilai yang masuk, dan JSON milik Elementor penuh dengan escaped quote. Tanpa wp_slash(), backslash di dalam JSON hilang saat disimpan dan layout halamannya korup. Kedua, delete_post_meta($id, '_elementor_css'). Elementor menyimpan CSS hasil generate per halaman di meta itu. Kalau tidak dihapus setiap kali _elementor_data berubah, konten baru tampil dengan style lama. Menghapusnya memaksa Elementor me-regenerate CSS untuk post itu saja.
Satu keputusan lagi yang membuat semuanya masuk akal jangka panjang: konten baru tidak saya pecah lagi jadi ratusan widget. Body halaman dikonsolidasi jadi 1 sampai 3 widget text-editor plus satu snippet kecil berisi token CSS. Halaman yang tadinya 176 widget turun jadi 35. Konten barunya sendiri ditarik dari Google Doc lewat URL export /export?format=html, jadi penulis tetap kerja nyaman di Doc dan saya tinggal ambil HTML-nya. Hasil akhirnya: satu POST atomik per halaman, dan karena ada backup otomatis plus PUT untuk restore, seluruh operasinya reversible.
Pelajaran
- Meta berawalan underscore itu protected.
show_in_restdiregister_post_meta()tidak akan meng-expose-nya untuk CPT, jadi jangan habiskan waktu debugging di jalur itu. - Respons kosong tanpa error adalah gejala klasik data yang disaring, bukan data yang hilang. Pastikan dulu datanya memang ada di database sebelum curiga ke mana-mana.
- Selalu
wp_slash()sebelum menulis JSON ke post meta, dan hapus_elementor_csssetiap kali_elementor_databerubah. - Operasi bulk yang menimpa data wajib reversible. Backup otomatis sebelum menulis itu bukan fitur tambahan, itu syarat.
- Kalau struktur kontennya yang bikin editing menyakitkan, 176 widget untuk satu halaman, perbaiki strukturnya sekalian. Jangan cuma mengotomasi rasa sakitnya.
