Atualizado em 19/06/2026
Quem vem do WordPress "puro" tende a tratar produtos e pedidos do WooCommerce como posts: get_post() para carregar, get_post_meta() para ler o preço, update_post_meta() para gravar. Funcionou por anos — mas é a forma errada, e hoje pode simplesmente não funcionar. O WooCommerce fornece uma CRUD API de objetos que você deve usar no lugar: você pede um produto ou pedido, lê e altera por métodos (getters e setters) e salva, sem nunca saber em qual tabela aquilo está guardado. Este artigo mostra o uso correto e explica por que o HPOS tornou essa API inegociável para pedidos.
Data stores: a camada que abstrai o armazenamento
A peça central é o conceito de data store. Cada objeto do WooCommerce (WC_Product, WC_Order, etc.) não fala diretamente com o banco: ele delega a leitura e a escrita a uma classe de data store. Quando você chama $product->save(), o objeto pergunta ao data store "grave-me" — e é o data store que decide o como e o onde.
Essa indireção parece burocracia, mas é exatamente o que deu ao WooCommerce a liberdade de mudar o armazenamento de pedidos sem quebrar o ecossistema. Se o seu código usa $order->get_total(), ele continua correto independentemente de o pedido estar em wp_postmeta ou na tabela wc_orders. Se o seu código usa get_post_meta( $order_id, '_order_total', true ), ele depende de um detalhe interno — e quebra quando esse detalhe muda.
Produtos: ler, alterar e salvar
O ponto de entrada é wc_get_product( $id ), que devolve um objeto WC_Product (ou false se o ID não for um produto). A partir dele, getters retornam dados e setters os alteram em memória; nada vai ao banco até o save():
<?php
$product = wc_get_product( 123 );
if ( $product ) {
// Leitura (getters)
$nome = $product->get_name();
$preco = $product->get_price();
$estoque = $product->get_stock_quantity();
// Alteração (setters) — ainda só em memória
$product->set_price( 149.90 );
$product->set_regular_price( 149.90 );
$product->set_stock_quantity( 25 );
// Persiste TODAS as mudanças de uma vez
$product->save();
}
Atenção a dois pares de preços: set_regular_price() define o preço cheio e set_sale_price() o promocional; set_price() ajusta o preço "ativo" (o que aparece). Para uma mudança definitiva, atualize o regular price — caso contrário, um recálculo futuro pode sobrescrever o que você gravou só em price.
Pedidos: carregar e iterar os itens
Para pedidos, o ponto de entrada é wc_get_order( $id ), que devolve um objeto WC_Order. Os itens do pedido não são propriedades soltas: você os obtém com $order->get_items() e itera, lendo nome e quantidade de cada linha:
<?php
$order = wc_get_order( 456 );
if ( $order ) {
$total = $order->get_total();
$status = $order->get_status();
$email = $order->get_billing_email();
// Itens de linha do pedido
foreach ( $order->get_items() as $item_id => $item ) {
$nome = $item->get_name();
$quantidade = $item->get_quantity();
$subtotal = $item->get_total();
echo esc_html( $quantidade . 'x ' . $nome ) . '<br>';
}
// Mudar status pela API (dispara os hooks e e-mails corretos)
$order->update_status( 'completed', 'Concluído via integração.' );
}
Prefira $order->update_status() a setar o status na mão: ele dispara os hooks de transição (como woocommerce_order_status_completed), envia os e-mails e registra a nota — comportamento que você perderia gravando o campo cru.
Consultando pedidos com wc_get_orders()
Para buscar vários pedidos, a função correta é wc_get_orders( $args ). Ela aceita argumentos de alto nível e funciona tanto no armazenamento legado quanto no HPOS, traduzindo a consulta para a tabela certa. É a substituta direta de qualquer WP_Query com post_type => 'shop_order':
<?php
$orders = wc_get_orders( array(
'status' => array( 'processing', 'on-hold' ),
'limit' => 20,
'orderby' => 'date',
'order' => 'DESC',
'date_created' => '>' . ( time() - 30 * DAY_IN_SECONDS ), // últimos 30 dias
'customer' => '[email protected]',
) );
foreach ( $orders as $order ) {
printf(
'Pedido #%d — %s — R$ %s%s',
$order->get_id(),
esc_html( $order->get_status() ),
esc_html( $order->get_total() ),
'<br>'
);
}
Para retornar apenas IDs (mais leve em listagens grandes), passe 'return' => 'ids'. Para produtos, o equivalente é wc_get_products( $args ), com a mesma filosofia.
Criando um produto simples por código
Criar um produto segue a mesma lógica: instancie a classe, use os setters e salve. save() retorna o ID do produto recém-criado:
<?php
$product = new WC_Product_Simple();
$product->set_name( 'Camiseta Dev WP' );
$product->set_status( 'publish' );
$product->set_regular_price( 79.90 );
$product->set_sku( 'DEVWP-CAM-001' );
$product->set_manage_stock( true );
$product->set_stock_quantity( 50 );
$product->set_description( 'Camiseta 100% algodão.' );
$product_id = $product->save(); // retorna o ID criado
Para variações, o caminho seria WC_Product_Variable + WC_Product_Variation; a mecânica de getters/setters/save() é idêntica.
HPOS: por que a CRUD API virou obrigatória
O HPOS (High-Performance Order Storage) é a mudança mais importante recente para quem programa pedidos. Historicamente, cada pedido era um post do tipo shop_order em wp_posts, com seus dados espalhados por wp_postmeta. Isso não escala: em lojas movimentadas, wp_postmeta incha e as consultas de pedido competem com todo o resto do site.
O HPOS move os pedidos para tabelas dedicadas (wc_orders e relacionadas), otimizadas para a estrutura de um pedido. O efeito colateral é direto e severo: pedidos deixam de ser posts. Logo:
get_post( $order_id )não devolve o pedido;get_post_meta( $order_id, ... )não enxerga os metadados do pedido;WP_Querycompost_type => 'shop_order'não retorna pedidos.
A CRUD API (wc_get_order, wc_get_orders, $order->get_meta()) continua funcionando porque pergunta ao data store, não ao banco. Por isso a regra: todo acesso a pedidos passa pela CRUD API. E o seu plugin precisa declarar compatibilidade com o HPOS, ou o WooCommerce o tratará como incompatível:
<?php
use Automattic\WooCommerce\Utilities\FeaturesUtil;
add_action( 'before_woocommerce_init', function () {
if ( class_exists( FeaturesUtil::class ) ) {
FeaturesUtil::declare_compatibility(
'custom_order_tables', // a feature do HPOS
__FILE__, // arquivo principal do plugin
true // compatível
);
}
} );
Migrar uma loja para o HPOS (e auditar plugins que ainda usam get_post_meta() em pedidos) é parte do trabalho de modernização — veja migrar loja WooCommerce.
O que usar e o que NÃO usar
| Tarefa | Use (CRUD API) | NÃO use |
|---|---|---|
| Carregar um produto | wc_get_product( $id ) | get_post( $id ) |
| Ler o preço | $product->get_price() | get_post_meta(..., '_price') |
| Alterar e salvar produto | setters + $product->save() | update_post_meta() |
| Carregar um pedido | wc_get_order( $id ) | get_post( $id ) |
| Ler dado do pedido | $order->get_total(), $order->get_meta() | get_post_meta() (não funciona no HPOS) |
| Iterar itens do pedido | $order->get_items() | Consultar woocommerce_order_items na mão |
| Mudar status do pedido | $order->update_status() | Setar o status cru sem disparar hooks |
| Buscar vários pedidos | wc_get_orders( $args ) | WP_Query( post_type=shop_order ) |
| Buscar vários produtos | wc_get_products( $args ) | get_posts( post_type=product ) |
Resumo prático
- Sempre pela CRUD API.
wc_get_product(),wc_get_order(),wc_get_orders()— nãoget_post()/get_post_meta(). - Setters não salvam. Altere tudo e chame
save()uma vez. - Status pela API.
update_status()dispara hooks e e-mails. - Declare HPOS. Use
FeaturesUtil::declare_compatibility()embefore_woocommerce_init. - Cheque o retorno.
wc_get_product()/wc_get_order()podem devolverfalse.
Manipular dados pela CRUD API e expor comportamento pelos hooks do WooCommerce são as duas metades de qualquer desenvolvimento sério para a plataforma. Se a sua loja precisa de integrações, importações em massa ou de migração para o HPOS feita com segurança, conheça os serviços de suporte e manutenção WooCommerce ou veja os demais artigos de desenvolvimento.
Perguntas frequentes
Por que não usar get_post_meta() para ler dados de produtos e pedidos?
Porque você acopla o seu código ao onde os dados estão guardados, em vez de pedir o que você quer. A CRUD API (wc_get_product(), wc_get_order()) abstrai a camada de armazenamento por trás dos data stores. Com pedidos isso virou crítico: no HPOS eles não ficam mais em wp_posts/wp_postmeta, então get_post_meta() simplesmente não enxerga os dados. Já getters como $order->get_total() funcionam independentemente de onde o pedido vive.
O que são data stores no WooCommerce?
São as classes que isolam a lógica de persistência dos objetos. Quando você chama $product->save(), o objeto não escreve no banco diretamente: ele delega a um data store, que decide como e onde gravar. Essa indireção é o que permitiu ao WooCommerce trocar o armazenamento de pedidos (do modelo de posts para tabelas próprias no HPOS) sem que o seu código que usa a CRUD API precise mudar.
O que é o HPOS (High-Performance Order Storage)?
É o armazenamento de pedidos em tabelas dedicadas (wc_orders e relacionadas), em vez de reaproveitar wp_posts/wp_postmeta. Ganha-se desempenho em consultas e escrita, porque os pedidos deixam de competir com posts e metadados de todo o site. A consequência prática para o desenvolvedor: pedidos não são mais posts, então funções como get_post(), WP_Query e get_post_meta() não servem para pedidos — use a CRUD API.
Como deixo meu plugin compatível com HPOS?
Declarando compatibilidade no hook before_woocommerce_init com FeaturesUtil::declare_compatibility( 'custom_order_tables', __FILE__, true ) e, claro, acessando pedidos somente pela CRUD API. Sem essa declaração, o WooCommerce marca o seu plugin como incompatível e pode desativar o HPOS. Se você ainda usa get_post_meta() em pedidos, é aí que precisa migrar para $order->get_meta().
wc_get_orders() substitui WP_Query para pedidos?
Sim, e deve. wc_get_orders( $args ) é a forma agnóstica de consultar pedidos: ela funciona tanto no armazenamento legado quanto no HPOS, traduzindo os argumentos para a tabela correta. WP_Query com post_type => 'shop_order' só funciona enquanto os pedidos forem posts — com HPOS ativo, ela não retorna nada. Para produtos, a consulta equivalente é wc_get_products( $args ).
Devo chamar save() depois de cada setter?
Não. Os setters (set_price(), set_stock_quantity(), etc.) apenas alteram o objeto em memória. O save() é que persiste tudo de uma vez no data store. Faça todas as alterações e chame $product->save() (ou $order->save()) uma única vez ao final — é mais eficiente e evita escritas parciais.