Cómo mostrar valores de campos personalizados en WordPress (guía 2026)

Cómo mostrar valores de campos personalizados en WordPress (guía 2026)

Última verificación: 14 de octubre de 2026
20 min de lectura
Guía
Desarrollador full-stack

Los campos personalizados (custom fields) son una de las características más potentes de WordPress. Permiten almacenar datos adicionales asociados a cualquier entrada, página o tipo de contenido personalizado. Desde precios de productos hasta fechas de eventos, los campos personalizados extienden WordPress más allá de un simple blog. Se guardan como filas en la tabla wp_postmeta y se leen con la Metadata API.

Esta guía recorre el ciclo completo: lectura con get_post_meta() frente a la obsoleta the_meta(), escape de la salida, guardado seguro, ACF, temas de bloques con render_block y Block Bindings, rendimiento de meta_query y registro para la REST API.

Conoce más sobre el desarrollo profesional de WordPress en WPPoland.

#Cómo usar get_post_meta() en WordPress

get_post_meta() es la puerta de entrada a todos los metadatos de una entrada:

// Valor único
$valor = get_post_meta( $post_id, 'nombre_campo', true );

// Array con todos los valores guardados bajo la misma clave
$valores = get_post_meta( $post_id, 'nombre_campo', false );

// Todos los metadatos de la entrada
$todos = get_post_meta( $post_id );

#Parámetros de get_post_meta()

ParámetroTipoDescripción
$post_idintID de la entrada (usa get_the_ID() dentro del Loop)
$keystringClave del campo; vacía devuelve todas las claves
$singlebooltrue devuelve un valor único, false un array (por defecto)

El tercer argumento es una fuente clásica de errores. Con $single = true, un metadato inexistente devuelve una cadena vacía. Con false recibes siempre un array, aunque tenga un solo elemento. Un código que hace if ( $valor ) se comporta distinto con una cadena y con un array.

Pon a las claves un prefijo con el nombre del proyecto (wppoland_ciudad_evento, wppoland_sku). Evita nombres genéricos como price, date o city sin espacio de nombres: tarde o temprano WooCommerce o un plugin de SEO usa el mismo nombre.

#Qué devuelve get_post_meta() para números, booleanos y claves inexistentes

Todo lo que está en wp_postmeta vuelve como texto salvo que se haya serializado. El docblock de get_post_meta() en el núcleo de WordPress enumera las conversiones: false vuelve como cadena vacía, true como '1', y los enteros y decimales como cadenas. Los arrays y objetos conservan su tipo, porque WordPress los serializa al guardar y los deserializa al leer.

Casos límite del mismo docblock: un ID de entrada no válido (no numérico, cero o negativo) devuelve false. Un ID válido sin la clave devuelve '' con $single = true y un array vacío con $single = false. Un default registrado con register_meta() (disponible desde WordPress 5.5) nunca se guarda, así que vuelve con el tipo registrado.

update_post_meta( $post_id, 'wppoland_stock', 0 );

$stock = get_post_meta( $post_id, 'wppoland_stock', true );
var_dump( $stock );        // string(1) "0"
var_dump( 0 === $stock );  // bool(false)

$stock = (int) $stock;     // convierte antes de operar o comparar de forma estricta

Una comparación estricta con un entero nunca coincide con un número guardado. Convierte con (int), (float) o absint() antes de hacer cuentas y compara con '0' === $value cuando el cero tiene significado.

#Cómo comprobar si existe un campo personalizado

Una cadena vacía de get_post_meta() no indica si la clave se guardó vacía o nunca se guardó. metadata_exists() (desde WordPress 3.3) responde exactamente a eso y lee la misma caché de metadatos, así que no añade consultas cuando los metadatos de la entrada ya están cargados:

if ( metadata_exists( 'post', $post_id, 'wppoland_sku' ) ) {
    $sku = get_post_meta( $post_id, 'wppoland_sku', true );
    // La clave existe, aunque su valor sea una cadena vacía.
}

La función pasa primero por el filtro get_post_metadata, como muestra el código de meta.php, de modo que un plugin que intercepte las lecturas de metadatos puede cambiar la respuesta. Úsala cuando «vacío» y «sin definir» necesitan un trato distinto, por ejemplo cuando la redacción vacía un campo a propósito y la plantilla no debe recurrir a un valor por defecto.

#get_post_meta() frente a the_meta()

the_meta() está obsoleta desde WordPress 6.0.2. Llamarla dispara _deprecated_function() con get_post_meta() como sustituta, lo que genera un aviso con WP_DEBUG activo. Imprime dentro del Loop una lista <ul class='post-meta'> con todos los campos personalizados de la entrada actual. Escapa claves y valores con esc_html() y omite las claves protegidas (las que empiezan por guion bajo), pero no admite una clave concreta ni da control sobre el marcado o sobre qué claves aparecen.

// Obsoleta: imprime todas las claves no protegidas de la entrada actual
the_meta();

// Recomendado: lees una clave, decides el HTML y escapas según el contexto
$cif = get_post_meta( get_the_ID(), 'wppoland_cif', true );
if ( $cif ) {
    echo '<p class="cif">' . esc_html( $cif ) . '</p>';
}

Deja the_meta() para temas antiguos que todavía la llaman o para una vista de depuración en staging. En producción, las plantillas de archivo, las de un CPT individual y los parciales de tarjeta deben leer claves explícitas. Así los metadatos internos (IDs de ERP, tokens de integración, notas de redacción) guardados sin guion bajo no acaban en la página pública. El código está en post-template.php.

#Cómo mostrar un campo personalizado en el tema

#Mostrar un campo personalizado dentro del Loop

// Dentro del Loop de WordPress
$precio = get_post_meta(get_the_ID(), 'precio_producto', true);

if ($precio) {
    echo '<div class="precio">';
    echo '<strong>Precio:</strong> ' . esc_html($precio) . ' EUR';
    echo '</div>';
}

#Obtener un campo personalizado fuera del Loop

$post_id     = 42;
$autor       = get_post_meta( $post_id, 'custom_author', true );
echo esc_html( $autor );

En archivos de CPT no te fíes a ciegas del $post global fuera del Loop: toma el ID de get_queried_object_id() o pásalo como argumento al parcial. Es una causa frecuente de «aparecen los metadatos de la entrada anterior» en listados con barra lateral compartida. En parciales reutilizables (tarjeta de evento, ficha de inmueble), pasa siempre el ID como argumento para que funcionen igual dentro de un Query Loop.

#Mostrar múltiples campos personalizados

// O obtener campos específicos
$campos = [
    'precio'       => 'Precio',
    'ubicacion'    => 'Ubicación',
    'fecha_evento' => 'Fecha del evento',
    'capacidad'    => 'Capacidad',
];

echo '<dl class="detalles-evento">';
foreach ($campos as $key => $label) {
    $valor = get_post_meta(get_the_ID(), $key, true);
    if ($valor) {
        echo '<dt>' . esc_html($label) . '</dt>';
        echo '<dd>' . esc_html($valor) . '</dd>';
    }
}
echo '</dl>';

#Cómo escapar y sanitizar campos personalizados

Nunca confíes en los datos de campos personalizados sin sanitizarlos al guardar y escaparlos al mostrar.

#Escapar la salida con esc_html, esc_url y esc_attr

// Para texto plano
echo esc_html($valor);

// Para URLs
echo esc_url($valor);

// Para atributos HTML
echo esc_attr($valor);

// Para contenido HTML (permite etiquetas seguras)
echo wp_kses_post($valor);

El contexto elige la función. Texto visible: esc_html. Atributo: esc_attr. URL: esc_url. HTML editorial de confianza: wp_kses_post. Los metadatos que llegan de un formulario público (petición de presupuesto, candidatura) son siempre no fiables, incluso en una intranet.

#Sanitizar campos al guardar con sanitize_text_field y absint

// Guardar campo de texto
update_post_meta($post_id, 'titular', sanitize_text_field(wp_unslash($_POST['titular'])));

// Guardar URL
update_post_meta($post_id, 'enlace', esc_url_raw(wp_unslash($_POST['enlace'])));

// Guardar número entero
update_post_meta($post_id, 'cantidad', absint($_POST['cantidad']));

// Guardar email
update_post_meta($post_id, 'correo', sanitize_email(wp_unslash($_POST['correo'])));

No des por hecho que el metabox «solo existe en el admin»: la REST API y la edición masiva también escriben metadatos.

#Cómo guardar campos personalizados con un metabox

update_post_meta() crea la fila si no existe y la actualiza si existe. add_post_meta() con el valor por defecto $unique = false permite varias filas con la misma clave, algo que debe ser una decisión consciente y no un efecto secundario de una importación CSV. delete_post_meta() borra la clave.

#Registrar un metabox con add_meta_box()

// Registrar metabox
add_action('add_meta_boxes', function() {
    add_meta_box(
        'detalles_evento',
        'Detalles del evento',
        'renderizar_metabox_evento',
        'evento',
        'normal',
        'high'
    );
});

// Renderizar metabox
function renderizar_metabox_evento($post) {
    wp_nonce_field('guardar_evento', 'evento_nonce');

    $precio = get_post_meta($post->ID, 'precio', true);
    $fecha = get_post_meta($post->ID, 'fecha_evento', true);

    echo '<p>';
    echo '<label for="precio">Precio (EUR):</label>';
    echo '<input type="number" id="precio" name="precio" value="' . esc_attr($precio) . '">';
    echo '</p>';

    echo '<p>';
    echo '<label for="fecha_evento">Fecha:</label>';
    echo '<input type="date" id="fecha_evento" name="fecha_evento" value="' . esc_attr($fecha) . '">';
    echo '</p>';
}

// Guardar metabox
add_action('save_post_evento', function($post_id) {
    if (!isset($_POST['evento_nonce']) ||
        !wp_verify_nonce($_POST['evento_nonce'], 'guardar_evento')) {
        return;
    }

    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
    if (!current_user_can('edit_post', $post_id)) return;

    if (isset($_POST['precio'])) {
        update_post_meta($post_id, 'precio', absint($_POST['precio']));
    }
    if (isset($_POST['fecha_evento'])) {
        update_post_meta($post_id, 'fecha_evento', sanitize_text_field(wp_unslash($_POST['fecha_evento'])));
    }
});

Nonce, comprobación de capacidad y protección frente al autoguardado: tres puntos que los tutoriales cortos omiten y que las auditorías de seguridad señalan primero.

#Cómo mostrar el panel de campos personalizados en el editor de bloques

La caja nativa de campos personalizados también existe en el editor de bloques, pero está oculta por defecto si no se ha usado antes. La documentación de WordPress.org sobre campos personalizados describe el camino. Con la interfaz en español, los nombres salen del paquete de idioma oficial: guarda antes la entrada, abre el menú Opciones (los tres puntos de la barra superior), elige Preferencias, abre la pestaña Generales, activa Campos personalizados en la sección Avanzado y pulsa Mostrar y recargar página. La caja aparece bajo el contenido, donde Introducir nueva y Añadir campo personalizado crean una clave y su valor.

Dos cosas ocultan campos en esa caja. Las claves que empiezan por guion bajo están protegidas y nunca aparecen en la lista; por eso no ves las claves de Yoast SEO o WooCommerce. Y ACF elimina la caja por defecto: la referencia de ajustes de ACF incluye remove_wp_meta_box (desde ACF 5.6.0, por defecto true). Para recuperar la caja nativa con ACF activo:

add_filter( 'acf/settings/remove_wp_meta_box', '__return_false' );

Sirve para depurar importaciones en staging. En producción, los redactores que ven claves en bruto junto a los campos de ACF acaban editando el mismo valor en dos sitios, así que mejor dejarla desactivada.

#Cómo mostrar campos de ACF con get_field()

ACF simplifica mucho el trabajo con campos personalizados: interfaz de edición y tipos de campo como repeater, flexible content o relationship. En lugar de get_post_meta(), puedes usar las funciones de ACF:

// ACF: obtener campo
$valor = get_field('nombre_campo');

// ACF: mostrar campo directamente
the_field('nombre_campo');

// ACF: obtener campo de imagen (devuelve array con ese formato de retorno)
$imagen = get_field('imagen_destacada');
if ($imagen) {
    echo '<img src="' . esc_url($imagen['url']) . '"
              alt="' . esc_attr($imagen['alt']) . '"
              width="' . esc_attr($imagen['width']) . '"
              height="' . esc_attr($imagen['height']) . '">';
}

En un campo de imagen, get_field devuelve un ID, una URL o un array según el formato de retorno configurado en el campo. El ejemplo anterior supone el formato array; ajusta el formato a lo que espera la plantilla antes de pasar el valor a wp_get_attachment_image o esc_url.

Para entradas, ACF sigue guardando los valores en postmeta, así que meta_query usa a menudo las mismas claves que la API nativa. En listados grandes, evita llamar a get_field en cada tarjeta sin cargar antes los metadatos en bloque (update_meta_cache o un WP_Query que ya los precarga). Si el tema debe seguir funcionando con ACF desactivado, mantén una alternativa:

$sku = function_exists( 'get_field' )
    ? get_field( 'wppoland_sku', $post_id )
    : get_post_meta( $post_id, 'wppoland_sku', true );

#La salida de the_field() en ACF se escapa

La documentación de get_field() indica que los valores devueltos deben escaparse con la función adecuada antes de imprimirlos, porque ACF no sabe dónde los vas a usar. Con echo get_field(), el escape es cosa tuya.

the_field() se comporta de otra manera desde ACF 6.2.7. La guía de escape HTML de ACF explica que desde la 6.2.5 la salida del shortcode de ACF pasa por wp_kses(), y desde la 6.2.7 lo mismo se aplica a the_field() y the_sub_field(). Las notas de la versión de seguridad 6.2.5 advierten de que esto elimina HTML inseguro, como scripts o iframes, de los campos textarea y WYSIWYG. Desde ACF 6.2.6, get_field() acepta además un cuarto argumento, $escape_html, que devuelve el valor escapado y requiere $format_value en true.

// Escapado por ACF (wp_kses) desde la 6.2.7
the_field( 'resumen_evento' );

// Sin escapar: eliges la función según el contexto
echo esc_html( get_field( 'resumen_evento' ) );

// Desde la 6.2.6: pide al tipo de campo un valor escapado
echo get_field( 'resumen_evento', false, true, true );

Si un embed dentro de un campo WYSIWYG desapareció tras actualizar ACF, esta suele ser la causa. Decide campo a campo si el HTML es de confianza e imprímelo con wp_kses_post() o con tu propia lista de etiquetas permitidas, en lugar de desactivar el escape de forma global.

#Cómo mostrar metadatos en un tema de bloques con render_block

En los temas de bloques, buena parte de la presentación sale del single.php clásico y pasa a plantillas HTML y al filtro render_block. Los metadatos se siguen leyendo en PHP; lo que cambia es el punto de inyección.

add_filter( 'render_block', 'wppoland_inject_sku_after_title', 10, 2 );

function wppoland_inject_sku_after_title( $block_content, $block ) {
    if ( ( $block['blockName'] ?? '' ) !== 'core/post-title' ) {
        return $block_content;
    }

    if ( ! is_singular( 'product' ) ) {
        return $block_content;
    }

    $sku = get_post_meta( get_the_ID(), 'wppoland_sku', true );
    if ( ! $sku ) {
        return $block_content;
    }

    $extra = '<p class="sku">' . esc_html( $sku ) . '</p>';
    return $block_content . $extra;
}

En los patrones del tema no escribas a mano IDs de entradas de staging. Lee los metadatos del contexto actual (get_the_ID() dentro del Query Loop) o pasa atributos del bloque.

#Cómo vincular campos personalizados a bloques con Block Bindings

Desde WordPress 6.5, la Block Bindings API conecta bloques del núcleo con valores de campos personalizados sin crear un bloque propio. La fuente core/post-meta lee el valor con get_post_meta() al renderizar.

#Qué bloques admiten Block Bindings

La lista de atributos vinculables está en el núcleo y ha crecido con cada versión, como muestran las ramas de versión de wordpress-develop:

Versión de WordPressBloques y atributos vinculables
6.5 a 6.8Párrafo (content), Encabezado (content), Imagen (id, url, title, alt), Botón (url, text, linkTarget, rel)
6.9añade caption de Imagen, Fecha de la entrada (datetime), Enlace y Submenú de navegación (url)
7.1añade Elemento de lista (content)

Fuentes: class-wp-block.php en la rama 6.5 y block-bindings.php en la rama 7.1.

#Registrar el metadato y vincularlo a un párrafo

add_action( 'init', function () {
    register_post_meta( 'post', 'wppoland_nota_autor', [
        'show_in_rest'  => true,
        'single'        => true,
        'type'          => 'string',
        'auth_callback' => function () {
            return current_user_can( 'edit_posts' );
        },
    ] );
} );
<!-- wp:paragraph {"metadata":{"bindings":{"content":{"source":"core/post-meta","args":{"key":"wppoland_nota_autor"}}}}} -->
<p>Texto de reserva cuando el metadato está vacío.</p>
<!-- /wp:paragraph -->

La fuente core/post-meta no devuelve nada en cuatro casos: la clave no está registrada con show_in_rest, la clave está protegida (empieza por guion bajo), la entrada no es pública y el visitante no puede leerla, o la entrada está protegida con contraseña. Una clave como _wppoland_nota_autor nunca se mostrará mediante un vínculo, aunque esté registrada. Usa claves sin guion bajo para todo lo que quieras vincular. Los vínculos no sustituyen el escape en el servidor del HTML que escribas a mano.

#Cómo registrar una fuente propia de Block Bindings

Cuando el valor necesita procesarse (un precio con formato, una fecha en el formato del sitio), registra una fuente propia en PHP con register_block_bindings_source() en el hook init. El callback recibe los argumentos del vínculo, la instancia del bloque y el nombre del atributo, como describe el docblock de block-bindings.php:

add_action( 'init', function () {
    register_block_bindings_source( 'wppoland/formatted-price', [
        'label'              => __( 'Precio con formato', 'wppoland' ),
        'uses_context'       => [ 'postId' ],
        'get_value_callback' => function ( array $source_args, $block_instance ) {
            $post_id = $block_instance->context['postId'] ?? get_the_ID();
            $raw     = get_post_meta( $post_id, $source_args['key'] ?? '', true );

            return '' === $raw ? null : number_format_i18n( (float) $raw, 2 );
        },
    ] );
} );

El marcado del bloque usa entonces "source": "wppoland/formatted-price" con la clave del metadato en args. El nombre de la fuente necesita un prefijo de espacio de nombres, como wppoland/, y solo admite minúsculas, dígitos, guiones y barras.

#Filtrar entradas por campos personalizados

Usa WP_Query con meta_query para filtrar entradas según los valores de campos personalizados:

$args = [
    'post_type'  => 'evento',
    'meta_query' => [
        'relation' => 'AND',
        [
            'key'     => 'fecha_evento',
            'value'   => date('Y-m-d'),
            'compare' => '>=',
            'type'    => 'DATE',
        ],
        [
            'key'     => 'ciudad',
            'value'   => 'Madrid',
            'compare' => '=',
        ],
    ],
    'orderby'    => 'meta_value',
    'meta_key'   => 'fecha_evento',
    'order'      => 'ASC',
];

$query = new WP_Query($args);

#Por qué meta_query es lento y cómo indexar wp_postmeta

Cada condición de meta_query añade un JOIN sobre wp_postmeta. El núcleo indexa meta_key, pero no meta_value, que es de tipo longtext, así que ordenar o comparar por valor en tablas grandes obliga a recorrer muchas filas. Cuando una consulta filtra siempre por clave y valor, un índice compuesto con prefijo ayuda; pruébalo antes en una copia de la base de datos y compara el EXPLAIN antes y después:

-- Índice compuesto para consultas por clave y valor
CREATE INDEX idx_postmeta_key_value
ON wp_postmeta (meta_key, meta_value(50));

Para clasificar contenidos que se filtran a menudo (ciudades, categorías de evento), una taxonomía suele ser mejor que meta_query. Para filtros de mucho volumen sobre varios campos a la vez, considera una tabla propia con columnas indexadas.

#Por qué los arrays serializados hacen lento el SQL

WordPress serializa automáticamente los arrays y objetos PHP que guardas como metadato. Es cómodo, pero no puedes buscar ni ordenar eficazmente por datos serializados en SQL: una consulta que busque «eventos en Madrid» dentro de un array serializado tiene que usar un LIKE '%Madrid%' lento. Si necesitas buscar u ordenar por un valor, guárdalo en su propio campo.

Por la misma razón, get_post_meta( $id, $clave, true ) puede devolver un array cuando la base de datos contiene un array serializado de una migración antigua. El código que espera una cadena lanza avisos o imprime la palabra «Array». Comprueba el tipo antes de esc_html o normaliza el valor al guardar.

#Caché de metadatos y update_meta_cache()

En un WP_Query estándar, WordPress llama a update_meta_cache() y carga en bloque los metadatos de las entradas encontradas. Varias llamadas a get_post_meta() sobre el mismo ID en la misma petición no generan consultas extra, porque los valores quedan en la caché de objetos (persistente con Redis o Memcached, o en memoria durante la petición).

Antipatrón: un archivo de 48 tarjetas que lanza un WP_Query adicional por tarjeta para leer un solo metadato. Mejor una consulta con meta_query, o dos pasos (IDs primero, luego get_posts con la caché ya cargada). Los plugins de caché de página sirven HTML estático, así que un metadato solo se vuelve a leer cuando se regenera la página; si cambia por cron o por un webhook del ERP, purga esa URL.

#Campos personalizados en la REST API

Para que un metadato aparezca en el editor de bloques y en la REST API, regístralo con register_post_meta():

register_post_meta( 'post', 'wppoland_sku', [
    'show_in_rest'      => true,
    'single'            => true,
    'type'              => 'string',
    'sanitize_callback' => 'sanitize_text_field',
    'auth_callback'     => function () {
        return current_user_can( 'edit_posts' );
    },
] );

Sin show_in_rest, el JavaScript del editor no ve el campo. Sin auth_callback, puedes abrir la escritura a un grupo más amplio de lo previsto. El type tiene que coincidir con los datos reales: number frente a una cadena en el JSON produce errores silenciosos en la barra lateral. Para campos compuestos (objetos, listas), configura show_in_rest con un esquema. Los campos sensibles (tokens de pasarela, IDs internos de facturación) deben quedarse con show_in_rest desactivado; el tema puede leerlos en PHP sin publicarlos en /wp-json/.

Para integraciones headless que necesitan un campo calculado, register_rest_field() añade un campo con sus propios callbacks:

add_action('rest_api_init', function() {
    register_rest_field('evento', 'precio', [
        'get_callback' => function($post) {
            return get_post_meta($post['id'], 'precio', true);
        },
        'update_callback' => function($value, $post) {
            update_post_meta($post->ID, 'precio', absint($value));
        },
        'schema' => [
            'type'        => 'integer',
            'description' => 'Precio del evento en EUR',
        ],
    ]);
});

#Por qué los campos personalizados no aparecen en la REST API de un CPT

En un tipo de contenido personalizado hay una condición más, escrita en el docblock de register_meta(): el tipo de contenido tiene que declarar soporte para campos personalizados, o los metadatos registrados se quedan fuera de la respuesta REST.

register_post_type( 'evento', [
    'public'       => true,
    'show_in_rest' => true,
    'supports'     => [ 'title', 'editor', 'custom-fields' ],
] );

register_post_meta( 'evento', 'wppoland_lugar', [
    'show_in_rest'      => true,
    'single'            => true,
    'type'              => 'string',
    'sanitize_callback' => 'sanitize_text_field',
    'auth_callback'     => function () {
        return current_user_can( 'edit_posts' );
    },
] );

El mismo docblock recoge dos argumentos más recientes. revisions_enabled (WordPress 6.4) guarda el metadato junto con las revisiones de la entrada y solo funciona para el tipo de objeto post. label (WordPress 6.7) da a la clave un nombre legible.

#Errores comunes con campos personalizados en WordPress

  1. Imprimir metadatos sin esc_html, esc_attr o esc_url.
  2. Guardar desde $_POST sin nonce y sin current_user_can.
  3. La misma clave usada por dos plugins (o por WooCommerce y el tema).
  4. get_field dentro del Loop de un archivo sin caché de metadatos cargada.
  5. Suponer que $single = true siempre devuelve una cadena (arrays serializados de importaciones antiguas).
  6. Construir un bloque sin register_post_meta: «funciona en PHP, falla en el editor».
  7. Usar the_meta() en producción y mostrar claves internas en la página pública.
  8. Vincular una clave protegida _clave con Block Bindings y no entender por qué no aparece nada.

#Cómo nombrar claves meta y migrar desde ACF

Cuando heredes un tema antiguo, haz inventario de claves: SELECT meta_key, COUNT(*) FROM wp_postmeta GROUP BY meta_key ORDER BY COUNT(*) DESC LIMIT 50. Verás enseguida qué prefijos son del proyecto y cuáles quedaron de plugins eliminados hace tiempo. Un prefijo de proyecto (wppoland_, cliente_) protege frente a colisiones con ACF o WooCommerce.

Al migrar de ACF a register_post_meta nativo, no copies a ciegas las referencias field_xxxxx. Mantén una clave de negocio legible y, si hace falta, un script único que traduzca IDs antiguos a nuevos. Primero en staging: compara el número de entradas con metadatos no vacíos antes y después.

En multisite, get_post_meta trabaja siempre sobre el sitio actual. Rodear lecturas de metadatos con switch_to_blog sin restore_current_blog en un bloque finally es una forma clásica de mezclar datos de otro sitio de la red.

Prueba el guardado como editor, no solo como administrador. Un metabox o un grupo de ACF debería llevar una línea de ayuda para la redacción: de dónde sale el valor, si es obligatorio y qué muestra la web cuando está vacío. En la plantilla, trata el estado vacío a propósito (oculta la sección o muestra una alternativa) en lugar de pintar etiquetas vacías.

#Lista de comprobación de campos personalizados antes de publicar

  1. Cada salida usa esc_html, esc_attr, esc_url o wp_kses_post según el contexto.
  2. Los guardados comprueban nonce y current_user_can.
  3. Los metadatos usados en REST tienen show_in_rest y auth_callback, y los CPT declaran custom-fields.
  4. No hay ORDER BY meta_value en archivos grandes sin motivo e índice.
  5. Las claves antiguas de plugins están documentadas o eliminadas con un script de limpieza probado en staging.
  6. Ninguna plantilla de producción llama a the_meta() para contenido público.
  7. Las claves vinculadas con Block Bindings no empiezan por guion bajo.

#Resumen

Los campos personalizados son esenciales para cualquier proyecto WordPress que vaya más allá de un blog simple:

  1. Usa get_post_meta() como función base, y recuerda que los números vuelven como cadenas.
  2. Escapa siempre la salida con esc_html(), esc_url() y compañía, también después de get_field().
  3. Sanitiza al guardar con sanitize_text_field(), absint() y wp_unslash().
  4. Verifica nonces y capacidades en los formularios.
  5. Vigila el rendimiento de meta_query en sitios grandes.
  6. Sustituye the_meta(), obsoleta desde la 6.0.2.
  7. Usa ACF para interfaces de edición más ricas, o Block Bindings con claves sin guion bajo para mostrar metadatos en temas de bloques.

Conoce más sobre los servicios de desarrollo WordPress y el rediseño WordPress en WPPoland.

Siguiente paso

Transforma el artículo en una implementación real

Este bloque refuerza el enlazado interno y lleva al lector al siguiente paso más útil dentro de la arquitectura del sitio.

¿Quieres implementar esto en tu sitio?

Si quieres transformar el artículo en mejoras concretas, rediseño o un plan de implementación, puedo cerrar el alcance y ejecutar.

Explora otros servicios WordPress y base de conocimiento

Refuerza tu negocio con soporte técnico profesional en áreas clave del ecosistema WordPress.

Preguntas frecuentes

Respuestas prácticas para aplicar el tema en la ejecución real.

¿Cuándo conviene mostrar campos personalizados desde una plantilla?#
Tiene sentido cuando el campo forma parte del modelo de la página y debe aparecer de manera consistente. Para contenido editorial puntual, un bloque o shortcode suele ser más fácil de mantener.
¿Cuál es la regla principal de seguridad al imprimir campos personalizados?#
Escapa la salida según el contexto. Usa esc_html para texto, esc_url para URLs y escape de atributos cuando corresponda, incluso si el valor lo guardó un editor de confianza.
¿Las consultas por campos personalizados pueden afectar rendimiento?#
Sí, los meta_query pesados pueden ser lentos en sitios grandes. Usa claves claras, evita comparaciones amplias y considera tablas propias o datos indexados para filtros de mucho volumen.

¿Necesitas un FAQ adaptado a tu sector y mercado? Preparo una versión alineada con tus objetivos de negocio.

Hablemos

Artículos Relacionados