Atualizado em 19/06/2026
Um shortcode é uma etiqueta entre colchetes — [galeria], [contato email="..."] — que o WordPress substitui por HTML gerado pelo seu código no momento da exibição. É a forma clássica de dar ao editor um "bloco dinâmico" sem expô-lo a PHP. A API é minúscula (basicamente add_shortcode()), mas há um punhado de armadilhas que derrubam quem está começando — e a primeira delas, o uso de echo no lugar de return, é tão comum que vale começar por ela.
O mapa da API de shortcodes
| Função | O que faz |
|---|---|
add_shortcode( $tag, $cb ) | Registra a tag e o callback que gera o HTML. O callback recebe ($atts, $content, $tag) e retorna uma string. |
shortcode_atts( $padroes, $atts ) | Mescla os atributos recebidos com os padrões, descartando chaves não previstas e normalizando o conjunto. |
Enclosing [tag]...[/tag] | Forma de fechamento: entrega o conteúdo entre as tags no parâmetro $content do callback. |
do_shortcode( $texto ) | Processa shortcodes dentro de uma string — em templates, ou no $content para suportar aninhamento. |
O callback DEVE retornar (nunca echo)
Esta é a regra de ouro e o erro nº 1. O callback de um shortcode tem que retornar a string final; ele nunca deve imprimir com echo ou print. Entender o porquê resolve o problema de uma vez por todas.
Shortcodes são expandidos pela função do_shortcode(), que está registrada como um filter em the_content. Quando o WordPress vai exibir um post, ele monta o texto do conteúdo em memória e passa essa string pelos filtros. O do_shortcode() procura suas tags nessa string e substitui cada tag pelo valor retornado pelo callback, na posição exata. Se o seu callback faz echo, ele escreve direto no fluxo de saída antes de essa string montada ser impressa — resultado: o conteúdo do shortcode aparece deslocado, tipicamente no topo da página, e a tag em si vira string vazia.
<?php
add_shortcode( 'saudacao', 'dwp_sc_saudacao' );
// ERRADO: imprime fora de ordem (sobe para o topo da página).
function dwp_sc_saudacao_errado( $atts ) {
echo '<p>Olá!</p>'; // NUNCA faça isto num shortcode
}
// CERTO: retorna a string para o WordPress encaixá-la no lugar.
function dwp_sc_saudacao( $atts ) {
return '<p>Olá!</p>';
}
Se você precisa de um trecho que naturalmente imprime (por exemplo, uma função de terceiros que só faz echo), capture a saída num buffer e retorne-a: ob_start(); funcao_que_imprime(); return ob_get_clean();. Assim você converte o echo em valor de retorno e mantém a ordem correta.
Shortcode com atributos e shortcode_atts()
Atributos chegam ao callback no primeiro parâmetro, $atts. Dois fatos definem como você os trata: eles sempre chegam como string (não há tipagem) e $atts pode até ser uma string vazia quando nenhum atributo é informado. Por isso você nunca lê $atts direto — passa por shortcode_atts(), que mescla com seus padrões, descarta chaves desconhecidas e garante um array previsível:
<?php
add_shortcode( 'botao', 'dwp_sc_botao' );
function dwp_sc_botao( $atts, $content = null, $tag = '' ) {
// Mescla com padrões; o 3º arg habilita filtros por tag.
$a = shortcode_atts(
array(
'url' => '#',
'texto' => 'Clique aqui',
'cor' => 'azul',
),
$atts,
$tag
);
// Atributos são entrada NÃO confiável: escape conforme o contexto.
$url = esc_url( $a['url'] );
$texto = esc_html( $a['texto'] );
$cor = sanitize_html_class( $a['cor'] ); // vira classe CSS segura
return sprintf(
'<a class="btn btn--%1$s" href="%2$s">%3$s</a>',
esc_attr( $cor ),
$url,
$texto
);
}
// Uso no editor: [botao url="https://exemplo.com" texto="Comprar" cor="verde"]
Três pontos de especialista aqui. Primeiro, shortcode_atts() só mantém as chaves que você declarou nos padrões — se alguém passar [botao onmouseover="..."], esse atributo é simplesmente ignorado. Segundo, os nomes dos atributos são minúsculos: [botao COR="verde"] chega como cor. Terceiro, escapamos cada valor segundo o contexto: esc_url() para a URL, esc_html() para o texto visível e esc_attr()/sanitize_html_class() para a classe — porque atributos do editor são dados não confiáveis como quaisquer outros.
Shortcodes de fechamento (enclosing)
Quando o shortcode envolve conteúdo — [destaque]texto aqui[/destaque] — o WordPress entrega o miolo no segundo parâmetro, $content. O mesmo callback pode servir aos dois formatos: basta tratar o caso em que $content chegou. E, crucialmente, se você quer que shortcodes dentro do conteúdo também sejam processados, precisa chamar do_shortcode( $content ) — o WordPress não faz isso recursivamente sozinho:
<?php
add_shortcode( 'destaque', 'dwp_sc_destaque' );
function dwp_sc_destaque( $atts, $content = null, $tag = '' ) {
$a = shortcode_atts(
array( 'tipo' => 'info' ),
$atts,
$tag
);
// Sem conteúdo? Devolve vazio em vez de quebrar.
if ( null === $content ) {
return '';
}
// do_shortcode() processa shortcodes ANINHADOS dentro do conteúdo.
$interno = do_shortcode( $content );
return sprintf(
'<div class="destaque destaque--%1$s">%2$s</div>',
esc_attr( $a['tipo'] ),
$interno
);
}
// Uso: [destaque tipo="aviso"]Texto com [botao texto="ação"] dentro.[/destaque]
Note que não escapamos $content com esc_html(): ele é HTML do editor (pode conter <strong>, links, outros shortcodes) e escapá-lo exibiria as tags como texto cru. Passamos por do_shortcode() e confiamos na sanitização que o WordPress já aplica ao conteúdo do post. O cuidado com escaping recai sobre os atributos, que são os valores realmente arbitrários.
Rodando shortcodes em templates: do_shortcode()
Shortcodes só são expandidos onde o filtro de shortcode roda — ou seja, no the_content. Se você está num arquivo de template do tema, num cabeçalho, num campo personalizado ou em qualquer string que não passe por aquele filtro, a tag aparece literalmente. A solução é processar a string explicitamente com do_shortcode():
<?php
// Em um template do tema (ex.: page.php, sidebar.php):
echo do_shortcode( '[botao url="/contato" texto="Fale conosco"]' );
// Ou processando um campo personalizado que pode conter shortcodes:
$texto = get_post_meta( get_the_ID(), 'rodape_promocional', true );
echo do_shortcode( $texto );
Aqui o echo é legítimo: estamos num template, fora do filtro the_content, e do_shortcode() nos retorna a string já expandida — somos nós que decidimos imprimir. Isso não contradiz a regra anterior: a proibição de echo vale dentro do callback do shortcode, não em quem chama do_shortcode().
Utilitários: existência e remoção
Como o registro é global, a última chamada a add_shortcode() para uma tag vence — sobrescrevendo silenciosamente quem registrou antes. Para conviver bem com outros plugins, há dois utilitários:
shortcode_exists( 'tag' )— retornatruese a tag já está registrada. Útil para não atropelar um plugin existente ou para registrar só se ninguém o fez.remove_shortcode( 'tag' )— desregistra uma tag (por exemplo, para substituir o comportamento de um shortcode de outro plugin). Existe tambémremove_all_shortcodes(), que raramente deve ser usado em produção por afetar tudo.
<?php
// Registre cedo, mas só se a tag ainda não existir.
add_action( 'init', function () {
if ( ! shortcode_exists( 'destaque' ) ) {
add_shortcode( 'destaque', 'dwp_sc_destaque' );
}
} );
// Substituir um shortcode de terceiro pelo seu:
remove_shortcode( 'destaque' );
add_shortcode( 'destaque', 'dwp_sc_destaque' );
Onde registrar? Registre seus shortcodes em um hook como init (dentro de um plugin), não solto no arquivo. Assim você garante que o WordPress já carregou e que a tag estará disponível antes de qualquer conteúdo ser renderizado.
Armadilhas comuns
| Sintoma | Causa | Correção |
|---|---|---|
| Conteúdo aparece no topo da página | echo dentro do callback | Use return (ou ob_start()/ob_get_clean()) |
| Tag aparece literal no template | Fora do filtro the_content | Envolva com do_shortcode() |
| Atributo numérico se comporta como texto | Atributos chegam sempre como string | Converta: (int), FILTER_VALIDATE_BOOLEAN |
| Atributo "some" ou é ignorado | Chave não declarada em shortcode_atts() ou em maiúsculas | Declare o padrão; use nomes minúsculos |
| Shortcodes aninhados não processam | Faltou do_shortcode( $content ) | Processe o $content no enclosing |
| Shortcode não roda em widget | Widget não aplica o filtro de shortcode | Ative widget_text ou use bloco/do_shortcode() |
| HTML quebrado / risco de XSS | Atributo do usuário sem escape | esc_attr()/esc_url()/esc_html() conforme o contexto |
Boas práticas
- Sempre
return, jamaisechodentro do callback. Se algo só imprime, capture comob_start()/ob_get_clean(). - Use
shortcode_atts()com padrões explícitos. Ele documenta os atributos aceitos, descarta o resto e evita avisos de chave indefinida. - Trate atributos como entrada não confiável. Converta tipos e escape na saída segundo o contexto — atributos vêm do editor, que pode ser um usuário com permissões limitadas.
- Prefixe a tag com o slug do seu projeto (
[dwp_botao]) para reduzir colisão com outros plugins no namespace global de shortcodes. - Mantenha o callback barato. Ele roda a cada exibição do conteúdo; consultas pesadas pedem cuidado e, idealmente, cache.
- Registre em um hook (
init), não no escopo do arquivo, e considereshortcode_exists()antes de sobrescrever. - Em blocos modernos, lembre que o editor de blocos oferece o bloco "Shortcode"; ainda assim, a sintaxe
[tag]continua válida e amplamente compatível.
Resumindo a mentalidade: um shortcode é uma função pura que recebe atributos e conteúdo e devolve uma string de HTML seguro. Quem internaliza isso — retornar, normalizar com shortcode_atts(), escapar atributos e usar do_shortcode() onde o filtro não chega — escreve shortcodes que não brigam com o tema, com outros plugins nem com a segurança do site.
Perguntas frequentes
Por que meu shortcode imprime o conteúdo no lugar errado?
Porque o callback está usando echo em vez de return. O shortcode é processado durante o filtro the_content, quando o WordPress já montou (em buffer) o texto do post. Se o callback faz echo, ele imprime imediatamente na saída — fora de ordem, geralmente no topo da página, antes mesmo do conteúdo. O callback precisa retornar a string para que o WordPress a encaixe no ponto exato onde o shortcode estava no texto.
Qual a diferença entre [tag] e [tag]...[/tag]?
[tag] é um shortcode autônomo: o callback recebe os atributos e devolve algo. [tag]conteúdo[/tag] é um shortcode de fechamento (enclosing): o WordPress passa o que está entre as tags no segundo parâmetro do callback, $content. O mesmo callback pode atender aos dois casos — basta verificar se $content chegou. Para processar shortcodes aninhados dentro de $content, chame do_shortcode( $content ).
Por que os atributos numéricos chegam quebrados?
Porque todo atributo de shortcode chega como string — não existe tipagem no parser. [box largura=300] entrega '300' (texto), não o inteiro 300. Converta explicitamente no callback: (int) $atts['largura'] para números, filter_var( $atts['ativo'], FILTER_VALIDATE_BOOLEAN ) para booleanos. E lembre que os nomes dos atributos são normalizados para minúsculas pelo WordPress.
Meu shortcode não funciona dentro de um widget. Por quê?
Shortcodes só são processados onde o filtro de shortcode roda — por padrão, no the_content. Um widget de texto clássico não processa shortcodes a menos que o filtro widget_text esteja ativo (o WordPress habilita isso para o widget de Texto padrão). Em outros contextos — um template de tema, um campo personalizado, uma área que você controla — chame do_shortcode( $texto ) explicitamente para forçar o processamento.
Como sei se um shortcode já existe antes de registrá-lo?
Use shortcode_exists( 'minha_tag' ), que retorna true se a tag já estiver registrada. Isso evita sobrescrever silenciosamente um shortcode de outro plugin (a última chamada a add_shortcode vence). Para remover um registro, use remove_shortcode( 'minha_tag' ); para limpar todos, remove_all_shortcodes() — mas este último raramente é uma boa ideia em produção.
Preciso escapar a saída de um shortcode?
Sim. A string retornada vai parar no HTML da página, então atributos vindos do usuário precisam ser tratados conforme o contexto: esc_attr() dentro de atributos HTML, esc_url() em URLs, esc_html() em texto. Atributos de shortcode são entrada não confiável como qualquer outra. Veja as regras em segurança no código.