DesenvolvedorWordPress WhatsApp

Shortcodes no WordPress: criar e usar com segurança

Shortcodes deixam o editor inserir blocos dinâmicos com uma sintaxe simples como [minha_tag]. Por trás disso há detalhes que separam o shortcode profissional do amador: retornar em vez de imprimir, normalizar atributos e escapar a saída. Veja como fazer certo.

Precisa de um shortcode sob medida?

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çãoO 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:

<?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

SintomaCausaCorreção
Conteúdo aparece no topo da páginaecho dentro do callbackUse return (ou ob_start()/ob_get_clean())
Tag aparece literal no templateFora do filtro the_contentEnvolva com do_shortcode()
Atributo numérico se comporta como textoAtributos chegam sempre como stringConverta: (int), FILTER_VALIDATE_BOOLEAN
Atributo "some" ou é ignoradoChave não declarada em shortcode_atts() ou em maiúsculasDeclare o padrão; use nomes minúsculos
Shortcodes aninhados não processamFaltou do_shortcode( $content )Processe o $content no enclosing
Shortcode não roda em widgetWidget não aplica o filtro de shortcodeAtive widget_text ou use bloco/do_shortcode()
HTML quebrado / risco de XSSAtributo do usuário sem escapeesc_attr()/esc_url()/esc_html() conforme o contexto

Boas práticas

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.

Precisa de um shortcode robusto e seguro?

Crio shortcodes WordPress que retornam HTML escapado, normalizam atributos e funcionam dentro e fora do conteúdo — sem furos de segurança. Me chame no WhatsApp.

WhatsApp: (43) 99932-9697

ou e-mail: [email protected]

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.

Referências oficiais

  1. WordPress.org — Shortcodes (Plugin Handbook)
  2. WordPress.org — add_shortcode() (Code Reference)
  3. WordPress.org — shortcode_atts() (Code Reference)
  4. WordPress.org — Shortcodes with Parameters
  5. WordPress.org — Enclosing Shortcodes
Tem alguma dúvida? Ficarei feliz em ajudar. Clique Aqui