D
P
0

WordPress & Elementor

Ganti Isi Halaman Elementor 176 Widget Lewat REST: `_elementor_data` Tidak Ter-expose di CPT

16 Juli 2026·3 menit baca
Ganti Isi Halaman Elementor 176 Widget Lewat REST: `_elementor_data` Tidak Ter-expose di CPT

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_data mentah 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_rest di register_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_css setiap kali _elementor_data berubah.
  • 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.