Atualizado em 19/06/2026
Um produto "Camiseta" que existe em três cores e quatro tamanhos não é um produto: é um produto variável com (até) doze variações, cada uma com seu próprio SKU, preço, estoque e imagem. O WooCommerce modela isso com uma hierarquia de objetos e dois tipos de atributo — globais e locais — que se comportam de formas bem diferentes. Quem só usa o painel raramente percebe a diferença; quem cria produtos por código, em importações ou integrações, precisa entendê-la a fundo, ou acaba com variações fantasmas, preços que não atualizam e taxonomias poluídas. Este artigo cobre a mecânica completa pela CRUD API.
Atributos globais × locais: a decisão que define tudo
Antes de uma linha de código, a escolha estrutural. O WooCommerce tem dois tipos de atributo:
- Atributo global: é uma taxonomia de verdade, registrada com o prefixo
pa_(de product attribute) — por exemplopa_cor,pa_tamanho. Os valores são termos (azul,vermelho) compartilhados por toda a loja. Cadastrado uma vez em Produtos › Atributos, é reutilizável, alimenta filtros por atributo, widgets de "navegação por camadas" e mantém consistência entre produtos. - Atributo local (ou "custom"): existe apenas dentro de um único produto. Os valores são texto livre, separados por barra vertical (
Pequeno | Médio | Grande), não viram taxonomia e não são reaproveitáveis nem filtráveis globalmente.
A regra: se o mesmo conjunto de valores se repete entre produtos, ou se você quer filtrar/relatar por ele, use global. Use local só para um detalhe genuinamente único daquele item. Em lojas que crescem, o erro caro é criar tudo como local: depois não dá para filtrar por cor, e migrar para global exige reprocessar o catálogo.
| Aspecto | Atributo global (pa_*) | Atributo local (do produto) |
|---|---|---|
| Natureza | Taxonomia (pa_cor, pa_tamanho) | Metadado do próprio produto |
| Valores | Termos (WP_Term) reutilizáveis | Texto livre, separado por | |
| Reutilizável entre produtos | Sim | Não |
| Filtros / navegação por camadas | Sim | Não |
| Identificado no código por | set_id() (ID do atributo) | set_name() (rótulo em texto) |
Valor em set_attributes() | slug do termo (azul) | o próprio texto (Azul) |
A hierarquia de objetos: variável × variação
No lado dos produtos, três classes interessam aqui. O produto pai é um WC_Product_Variable: ele não tem preço próprio, mas guarda a lista de atributos (alguns marcados como "usados para variações") e funciona como guarda-chuva. Cada combinação vendável é um WC_Product_Variation — um filho com parent_id apontando para o variável, e com seu próprio preço, SKU, estoque e imagem.
WC_Product_Variable | WC_Product_Variation | |
|---|---|---|
| Papel | Produto pai (guarda-chuva) | Combinação vendável (filho) |
post_type | product | product_variation |
| Tem preço próprio? | Não (faixa derivada dos filhos) | Sim |
| Guarda | Atributos + quais variam | Valores escolhidos por atributo |
| Leitura agregada | get_available_variations(), get_variation_prices() | getters individuais |
Produtos não são HPOS. O HPOS (High-Performance Order Storage) reorganizou apenas os pedidos. Produtos e variações continuam sendo posts (product e product_variation) e os atributos globais continuam taxonomias. Mas a disciplina é a mesma: use a CRUD API, não get_post_meta(). Detalhes em produtos e pedidos via código.
Passo 1 — Criar o atributo global e seus termos
Um atributo global tem duas camadas: o registro do atributo (que cria a taxonomia pa_cor) e os termos dentro dela. Para registrar o atributo em si, use wc_create_attribute(); os termos são inseridos com wp_insert_term() na taxonomia pa_cor. Cheque sempre o retorno, porque wp_insert_term() devolve WP_Error se o termo já existir:
<?php
// 1. Registra o atributo global "Cor" -> cria a taxonomia pa_cor
// (se ele já existir, wc_create_attribute devolve WP_Error)
$attribute_id = wc_create_attribute( array(
'name' => 'Cor', // rótulo legível
'slug' => 'cor', // vira a taxonomia pa_cor
'type' => 'select',
'order_by' => 'menu_order',
'has_archives' => false,
) );
if ( is_wp_error( $attribute_id ) ) {
// já existe? recupere o ID a partir do slug
$attribute_id = wc_attribute_taxonomy_id_by_name( 'cor' );
}
// É preciso registrar a taxonomia antes de inserir termos na mesma request.
$taxonomy = 'pa_cor'; // prefixo pa_ é obrigatório
if ( ! taxonomy_exists( $taxonomy ) ) {
register_taxonomy( $taxonomy, 'product', array( 'hierarchical' => false ) );
}
// 2. Insere os termos (valores) na taxonomia pa_cor
foreach ( array( 'Azul', 'Vermelho', 'Preto' ) as $valor ) {
if ( ! term_exists( $valor, $taxonomy ) ) {
$res = wp_insert_term( $valor, $taxonomy, array( 'slug' => sanitize_title( $valor ) ) );
if ( is_wp_error( $res ) ) {
error_log( 'Falha ao criar termo: ' . $res->get_error_message() );
}
}
}
O prefixo pa_ não é opcional nem decorativo: é como o WooCommerce reconhece uma taxonomia de atributo de produto. Inserir termos em uma taxonomia sem esse prefixo não os torna atributos. E lembre que, fora do fluxo administrativo, a taxonomia pa_cor pode ainda não estar registrada na requisição — registrá-la (ou chamar o código em init) evita o erro "Invalid taxonomy".
Passo 2 — Definir os atributos no produto variável
Com a taxonomia e os termos prontos, monte o produto pai. Cada atributo do produto é um objeto WC_Product_Attribute. Para um atributo global, você informa o ID (via set_id()) e a taxonomia em set_name(); para um local, deixa o ID em 0 e usa set_name() com o rótulo. O método decisivo é set_variation( true ): ele marca o atributo como "usado para variações" — sem isso, nenhuma variação aparece.
<?php
$product = new WC_Product_Variable();
$product->set_name( 'Camiseta Dev WP' );
$product->set_status( 'publish' );
$product->set_sku( 'DEVWP-CAM' );
// --- Atributo GLOBAL (pa_cor) ---
$attr_cor = new WC_Product_Attribute();
$attr_cor->set_id( wc_attribute_taxonomy_id_by_name( 'cor' ) ); // ID = global
$attr_cor->set_name( 'pa_cor' ); // a taxonomia
$attr_cor->set_options( array( 'azul', 'vermelho', 'preto' ) ); // SLUGS dos termos
$attr_cor->set_visible( true );
$attr_cor->set_variation( true ); // ESSENCIAL: usado para variações
// --- Atributo LOCAL (sem taxonomia) ---
$attr_estilo = new WC_Product_Attribute();
$attr_estilo->set_id( 0 ); // 0 = local
$attr_estilo->set_name( 'Gola' ); // rótulo em texto
$attr_estilo->set_options( array( 'Redonda', 'V' ) ); // o próprio texto
$attr_estilo->set_visible( true );
$attr_estilo->set_variation( true );
// Atribui os dois ao produto e salva
$product->set_attributes( array( $attr_cor, $attr_estilo ) );
$parent_id = $product->save(); // retorna o ID do produto pai
Repare na assimetria que confunde muita gente: em set_options(), o atributo global recebe os slugs dos termos (azul), enquanto o local recebe o texto exibido (Redonda). Misturar os dois — passar "Azul" para o global, por exemplo — faz a opção não casar com nenhum termo e a variação silenciosamente não funciona.
Passo 3 — Criar uma variação
Cada combinação é um WC_Product_Variation. Você define o parent_id, passa os atributos escolhidos (no formato taxonomia => slug para globais; rótulo => texto para locais) e seus dados de venda. Como sempre, nada é gravado até save():
<?php
$variation = new WC_Product_Variation();
$variation->set_parent_id( $parent_id );
// Atributos escolhidos por ESTA variação.
// Global: chave 'pa_cor' + SLUG do termo. Local: chave do rótulo (em minúsculas) + texto.
$variation->set_attributes( array(
'pa_cor' => 'azul',
'gola' => 'Redonda',
) );
$variation->set_regular_price( '79.90' );
$variation->set_sku( 'DEVWP-CAM-AZ-R' );
$variation->set_manage_stock( true );
$variation->set_stock_quantity( 25 );
$variation->set_status( 'publish' );
$variation_id = $variation->save(); // SEM isto, a variação não existe
// Reconcilia o produto pai (faixa de preços, "em estoque", etc.)
WC_Product_Variable::sync( $parent_id );
Para deixar uma variação válida para "qualquer" valor de um atributo (o "any" do painel), passe string vazia naquele atributo: 'pa_cor' => ''. A correspondência prioriza variações com valor explícito; só recai nas de valor vazio quando nenhuma específica bate. Combinar as duas estratégias no mesmo produto sem critério gera ambiguidade — defina o conjunto de variações de forma a cobrir as combinações sem sobreposição.
Passo 4 — Ler atributos e variações
Na leitura, o ponto de entrada é sempre wc_get_product( $id ). Para inspecionar os atributos do produto, use get_attributes(). Para listar as combinações exibíveis de um variável, get_available_variations() devolve um array rico (atributos resolvidos, preço, estoque, imagem) — ótimo para alimentar um seletor de variação, mas pesado:
<?php
$variable = wc_get_product( $parent_id );
if ( $variable && $variable->is_type( 'variable' ) ) {
// (a) Atributos do produto (globais e locais)
foreach ( $variable->get_attributes() as $taxonomy => $attribute ) {
echo esc_html( $attribute->get_name() ) . ': '
. esc_html( implode( ', ', $attribute->get_options() ) ) . '<br>';
}
// (b) Combinações exibíveis (pesado — evite em loops de listagem)
foreach ( $variable->get_available_variations() as $v ) {
printf(
'#%d — %s — R$ %s%s',
(int) $v['variation_id'],
esc_html( implode( ' / ', $v['attributes'] ) ),
esc_html( $v['display_price'] ),
'<br>'
);
}
// (c) Preços com cache, indexados por ID da variação (mais leve)
$precos = $variable->get_variation_prices( true );
// $precos['price'] = [ variation_id => preco_ativo, ... ]
}
Em listagens e grids, prefira get_variation_prices(): ele é cacheado e devolve só os números necessários para montar a faixa "de R$ X a R$ Y". Reserve get_available_variations() para a página do produto, onde você realmente precisa de imagem, estoque e atributos por combinação.
Quando você já sabe a combinação escolhida e quer o ID da variação correspondente (por exemplo, ao montar um pedido por código), use o WC_Data_Store de produtos com find_matching_product_variation() — é assim que o próprio WooCommerce resolve a seleção do cliente, respeitando inclusive os valores "any":
<?php
$variable = wc_get_product( $parent_id );
$data_store = WC_Data_Store::load( 'product' );
// Combinação escolhida: as chaves DEVEM vir prefixadas com "attribute_"
$match_id = $data_store->find_matching_product_variation( $variable, array(
'attribute_pa_cor' => 'azul',
'attribute_gola' => 'Redonda',
) );
if ( $match_id ) {
$variation = wc_get_product( $match_id ); // WC_Product_Variation
echo esc_html( $variation->get_sku() . ' — R$ ' . $variation->get_price() );
} else {
echo 'Nenhuma variação corresponde a essa combinação.';
}
Repare que aqui as chaves levam o prefixo attribute_ (attribute_pa_cor), diferente do set_attributes() da criação — é uma das inconsistências da API que mais geram erro silencioso. O retorno é o ID da variação (ou 0 se nada casar).
Armadilhas comuns
- Esquecer o
save(). Vale para o pai e para cada variação. Setters só mexem no objeto em memória. - Não marcar
set_variation( true ). O atributo até aparece como informação, mas não gera variações. - Slug × nome. Global usa o slug do termo (
azul); local usa o texto. Trocar os dois mata a correspondência. - Não sincronizar o pai. Após criar/alterar variações em massa, chame
WC_Product_Variable::sync( $parent_id )para a faixa de preço e o status de estoque baterem. - Explosão combinatória. 3-4 atributos com muitos valores geram centenas de variações; só crie as que você vende.
- Taxonomia não registrada. Fora do admin,
pa_corpode não estar registrada — rode o código eminitou registre a taxonomia.
Boas práticas
- Global por padrão. Use atributos globais sempre que o valor possa se repetir ou precise de filtro; reserve local para detalhes únicos.
- Termos pelo slug. Trabalhe com slugs (
azul) no código, não com rótulos — é o que o WooCommerce casa. - CRUD API sempre. Mesmo que produtos sejam posts, leia e grave por
wc_get_product()e getters/setters. - Leitura barata.
get_variation_prices()em listagens;get_available_variations()só na página do produto. - Salve em lote. Em importações, faça todas as alterações de um objeto e chame
save()uma vez; depoissync()o pai uma vez.
Modelar atributos e variações por código é o coração de qualquer importação de catálogo, integração com ERP ou ferramenta de criação em massa de produtos. Combine isso com os hooks do WooCommerce para customizar a exibição e com a CRUD API de produtos e pedidos para o resto do fluxo. Se a sua loja precisa de importação ou reorganização de catálogo feita com segurança, conheça os serviços de suporte e manutenção WooCommerce ou veja os demais artigos de desenvolvimento.
Perguntas frequentes
Qual a diferença entre atributo global e atributo local no WooCommerce?
O atributo global é uma taxonomia (pa_cor, pa_tamanho) cadastrada em Produtos › Atributos e reutilizável por toda a loja: os valores são termos compartilhados entre produtos, o que permite filtros por atributo, widgets de camadas e relatórios consistentes. O atributo local existe só dentro de um produto, é digitado livremente e não vira taxonomia — bom para um detalhe único que nunca se repete. Regra prática: se mais de um produto usa o mesmo conjunto de valores, ou se você quer filtrar por ele, use global.
Por que minha variação não aparece na loja depois de criá-la por código?
As três causas mais comuns: (1) você esqueceu de chamar $variation->save() — os setters só mexem no objeto em memória; (2) o atributo não foi marcado como set_variation( true ) no produto pai, então o WooCommerce não o considera "usado para variações"; (3) o valor passado em set_attributes() não corresponde a um termo válido do atributo global (em pa_cor deve ser o slug do termo, como azul, não o nome "Azul"). Depois de tudo, vale chamar WC_Product_Variable::sync( $parent_id ) para reconciliar preços e estado do pai.
O que significa o valor vazio ("any") em um atributo de variação?
Uma variação pode não fixar um valor para determinado atributo — no painel isso aparece como "Qualquer Cor", e na CRUD API corresponde a uma string vazia naquele atributo. Significa que a variação atende a qualquer opção daquele atributo. O WooCommerce casa a escolha do cliente primeiro com variações que têm valor explícito e, se nenhuma bater, recorre às de valor "any". Misturar variações específicas e "any" sem critério gera correspondências ambíguas; defina-as com cuidado.
Atributos e produtos usam HPOS como os pedidos?
Não. O HPOS (High-Performance Order Storage) afeta apenas o armazenamento de pedidos. Produtos continuam sendo posts (post_type product e product_variation) e os atributos globais continuam sendo taxonomias (pa_*) com seus termos. Mesmo assim, a recomendação é idêntica à dos pedidos: leia e grave pela CRUD API (wc_get_product(), getters/setters, save()) em vez de mexer em wp_postmeta direto. Veja produtos e pedidos via código.
Como leio o preço de uma variação específica sem instanciar cada uma?
No produto variável, $variable->get_variation_prices( true ) devolve arrays associativos (price, regular_price, sale_price) indexados pelo ID da variação, já com cache. É bem mais barato que iterar e instanciar cada WC_Product_Variation só para ler preço. Para o conjunto completo de combinações exibíveis (com imagem, estoque e atributos resolvidos), use get_available_variations() — porém ele é mais pesado, então evite chamá-lo em loops de listagem.
Centenas de variações deixam a loja lenta — o que fazer?
Sim, esse é um gargalo clássico. Cada variação é um post (product_variation) com seus metadados; produtos com 3-4 atributos de muitos valores geram explosão combinatória (10×10×5 = 500 variações). Mitigações: não crie combinações que você não vende; prefira get_variation_prices() a get_available_variations() em listagens; mantenha o cache de objeto ativo; e considere desmembrar em produtos separados quando os eixos forem realmente independentes. Veja WooCommerce lento.