ID: 0x

|

DATE:

Como criar blocos no WordPress com create-block

AUTHOR:

|

READ_TIME: ~5 MIN

Quando publiquei meus primeiros tutoriais sobre blocos, era preciso registrar scripts manualmente e escrever boa parte do exemplo em ES5. Hoje, criar blocos no WordPress é um processo mais direto. O create-block gera a estrutura recomendada e permite começar pelo código do bloco.

Neste guia, vamos construir exemplos progressivos. Primeiro, criaremos um bloco estático. Depois, adicionaremos um campo editável, suporte aos estilos nativos do editor, blocos internos e, por fim, renderização dinâmica com PHP.

Se o modelo de conteúdo do Block Editor ainda for novo para você, comece pela explicação conceitual sobre blocos Gutenberg.

Ao terminar, você saberá:

  • como criar e executar um projeto com @wordpress/create-block;
  • qual é a responsabilidade de block.json, edit.js e save.js;
  • como armazenar atributos sem quebrar a validação do bloco;
  • quando usar um bloco estático ou dinâmico;
  • como usar useBlockProps, InnerBlocks e Block Supports;
  • como preparar o código para produção.

O que é o create-block do WordPress

O pacote @wordpress/create-block é a ferramenta oficial para iniciar um plugin de bloco. Ele cria os arquivos do plugin, instala as dependências e configura os scripts de desenvolvimento e produção.

Ele resolve a configuração inicial, mas não esconde a arquitetura do bloco. Você continuará trabalhando com três partes principais:

  • block.json: define metadados, atributos, recursos e arquivos carregados pelo bloco;
  • edit.js: controla a experiência dentro do editor;
  • save.js ou render.php: define o HTML salvo no conteúdo ou gerado no servidor.

Para blocos novos, use Block API versão 3. Além de ser a versão atual, ela prepara o bloco para o editor em iframe.

Pré-requisitos

Antes de começar, você precisa de:

  • uma instalação local do WordPress;
  • Node.js e npm instalados;
  • acesso à pasta wp-content/plugins da instalação;
  • familiaridade básica com JavaScript, React e PHP.

Confira as versões instaladas:

node --version
npm --version

Prefira uma versão LTS do Node.js suportada pelas ferramentas atuais do WordPress. Evite fixar o tutorial a um número de versão, pois esse requisito acompanha as atualizações do pacote.

1. Como criar blocos no WordPress com create-block

No terminal, acesse a pasta de plugins do WordPress e execute. Caso npm e npx ainda sejam novidade, consulte também esta introdução ao npm.

npx @wordpress/create-block@latest aviso-editorial
cd aviso-editorial
npm startCode language: CSS (css)

O primeiro comando baixa a versão atual do gerador e cria um plugin chamado aviso-editorial. O comando npm start inicia a compilação em modo de desenvolvimento e observa alterações nos arquivos.

Depois disso, abra Plugins no painel do WordPress e ative Aviso Editorial. Crie ou edite um post e procure o bloco pelo nome no inseridor.

Se quiser escolher namespace e título explicitamente, use as opções do gerador:

npx @wordpress/create-block@latest \
  aviso-editorial \
  --namespace="fellyph" \
  --title="Aviso editorial"Code language: CSS (css)

Executar o comando sem argumentos abre o modo interativo:

npx @wordpress/create-block@latestCode language: CSS (css)

2. Entendendo a estrutura gerada

A estrutura pode receber pequenos ajustes entre versões do gerador, mas os arquivos centrais são estes:

aviso-editorial/
├── aviso-editorial.php
├── package.json
├── src/
│   └── aviso-editorial/
│       ├── block.json
│       ├── edit.js
│       ├── editor.scss
│       ├── index.js
│       ├── save.js
│       └── style.scss
└── build/
    ├── aviso-editorial/
    └── blocks-manifest.php

Edite os arquivos em src/aviso-editorial. A pasta build é gerada pelos scripts e contém os arquivos que o WordPress carrega. Versões anteriores do scaffold colocavam os arquivos diretamente em src, portanto confira a estrutura do projeto antes de seguir um tutorial antigo.

O arquivo PHP registra o bloco no servidor a partir dos metadados compilados. Desde o WordPress 6.8, o scaffold usa blocks-manifest.php com wp_register_block_types_from_metadata_collection() para registrar a coleção. Você não precisa registrar manualmente cada script e cada folha de estilo com wp_register_script() e wp_register_style().

Os scripts mais usados ficam em package.json:

npm start
npm run build
npm run lint:js
npm run lint:css
npm run format
npm run plugin-zipCode language: CSS (css)
  • npm start compila e observa mudanças durante o desenvolvimento;
  • npm run build cria os arquivos otimizados para produção;
  • os comandos de lint e formatação ajudam a detectar problemas antes da publicação;
  • npm run plugin-zip gera um arquivo instalável do plugin.

3. Definindo o bloco em block.json

Substitua o conteúdo relevante de src/aviso-editorial/block.json pelo exemplo a seguir:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "fellyph/aviso-editorial",
  "version": "0.1.0",
  "title": "Aviso editorial",
  "category": "text",
  "icon": "info-outline",
  "description": "Destaca um aviso importante dentro do conteúdo.",
  "example": {},
  "supports": {
    "html": false,
    "align": ["wide", "full"],
    "color": {
      "background": true,
      "text": true
    },
    "spacing": {
      "margin": true,
      "padding": true
    }
  },
  "textdomain": "aviso-editorial",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css"
}Code language: JSON / JSON with Comments (json)

O campo name precisa ter o formato namespace/slug e não deve mudar depois que o bloco for publicado. O WordPress usa esse identificador para reconhecer o bloco no conteúdo.

O objeto supports ativa controles nativos do editor. Neste caso, o usuário poderá ajustar alinhamento, cores e espaçamento sem que você precise criar controles personalizados.

Os campos de assets também têm funções diferentes. style carrega CSS no editor e no site, enquanto editorStyle atende somente ao editor. Use viewStyle para CSS exclusivo do front-end. Para JavaScript, viewScript carrega um script clássico e viewScriptModule carrega um módulo, como os usados pela Interactivity API. Declare apenas os arquivos que o bloco realmente utiliza.

O arquivo src/aviso-editorial/index.js conecta os metadados às funções de edição e salvamento:

import { registerBlockType } from '@wordpress/blocks';

import metadata from './block.json';
import Edit from './edit';
import save from './save';

registerBlockType( metadata.name, {
  edit: Edit,
  save,
} );Code language: JavaScript (javascript)

4. Criando um bloco estático

Um bloco estático salva seu HTML no conteúdo do post. Ele é uma boa escolha quando o resultado depende apenas dos dados informados no próprio bloco.

Em src/aviso-editorial/edit.js, use useBlockProps no elemento principal:

import { useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';

import './editor.scss';

export default function Edit() {
  return (
    <div { ...useBlockProps() }>
      <strong>{ __( 'Aviso:', 'aviso-editorial' ) }</strong>
      <p>{ __( 'Revise este conteúdo antes de publicar.', 'aviso-editorial' ) }</p>
    </div>
  );
}Code language: JavaScript (javascript)

Em src/aviso-editorial/save.js, use a variação de useBlockProps destinada ao conteúdo salvo:

import { useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';

export default function save() {
  return (
    <div { ...useBlockProps.save() }>
      <strong>{ __( 'Aviso:', 'aviso-editorial' ) }</strong>
      <p>{ __( 'Revise este conteúdo antes de publicar.', 'aviso-editorial' ) }</p>
    </div>
  );
}Code language: JavaScript (javascript)

O hook useBlockProps aplica ao elemento principal a classe do bloco, os atributos necessários ao editor e os estilos habilitados em supports. Ele substitui padrões antigos que manipulavam className manualmente.

5. Adicionando conteúdo editável com atributos

Agora vamos permitir que cada instância do bloco tenha uma mensagem diferente. Primeiro, acrescente o atributo content em src/aviso-editorial/block.json:

"attributes": {
  "content": {
    "type": "string",
    "source": "html",
    "selector": ".wp-block-fellyph-aviso-editorial__content"
  }
}Code language: JavaScript (javascript)

O atributo usa o conteúdo do elemento com a classe indicada como fonte. O seletor precisa corresponder ao HTML produzido por save.js. Uma classe própria permanece estável se outros parágrafos forem adicionados ao wrapper.

Atualize edit.js:

import { RichText, useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';

import './editor.scss';

export default function Edit( { attributes, setAttributes } ) {
  const { content } = attributes;

  return (
    <div { ...useBlockProps() }>
      <strong>{ __( 'Aviso:', 'aviso-editorial' ) }</strong>
      <RichText
        tagName="p"
        className="wp-block-fellyph-aviso-editorial__content"
        value={ content }
        allowedFormats={ [ 'core/bold', 'core/italic', 'core/link' ] }
        placeholder={ __( 'Escreva o aviso…', 'aviso-editorial' ) }
        onChange={ ( value ) => setAttributes( { content: value } ) }
      />
    </div>
  );
}Code language: JavaScript (javascript)

Depois, atualize save.js:

import { RichText, useBlockProps } from '@wordpress/block-editor';
import { __ } from '@wordpress/i18n';

export default function save( { attributes } ) {
  const { content } = attributes;

  return (
    <div { ...useBlockProps.save() }>
      <strong>{ __( 'Aviso:', 'aviso-editorial' ) }</strong>
      <RichText.Content
        tagName="p"
        className="wp-block-fellyph-aviso-editorial__content"
        value={ content }
      />
    </div>
  );
}Code language: JavaScript (javascript)

No editor, RichText oferece uma experiência de edição compatível com o editor de blocos. No conteúdo salvo, RichText.Content mantém a marcação gerada pelos formatos permitidos.

Como os atributos são armazenados

Neste exemplo, o valor é extraído do HTML salvo porque declaramos source e selector. Um atributo sem source é armazenado no comentário delimitador do bloco.

Escolha o formato pensando na estabilidade. Alterar a estrutura retornada por save.js depois que o bloco já foi publicado pode fazer o WordPress marcar instâncias antigas como inválidas. Quando a alteração for necessária, crie uma depreciação capaz de reconhecer e migrar a versão anterior.

Migrando uma versão antiga do bloco

Imagine que a primeira versão usava o atributo message e salvava apenas um parágrafo. Crie src/aviso-editorial/deprecated.js para preservar essa estrutura e migrar o valor para content:

import { RichText, useBlockProps } from '@wordpress/block-editor';

const v1 = {
  attributes: {
    message: {
      type: 'string',
      source: 'html',
      selector: 'p',
    },
  },
  supports: {
    html: false,
  },
  migrate( { message } ) {
    return { content: message };
  },
  save( { attributes: { message } } ) {
    return (
      <p { ...useBlockProps.save() }>
        <RichText.Content value={ message } />
      </p>
    );
  },
};

export default [ v1 ];Code language: JavaScript (javascript)

Depois, importe a lista em index.js:

import deprecated from './deprecated';

registerBlockType( metadata.name, {
  edit: Edit,
  save,
  deprecated,
} );Code language: JavaScript (javascript)

Ordene as depreciações da mais recente para a mais antiga. Cada versão precisa declarar seus próprios attributes, supports e save, pois esses valores não são herdados da definição atual. Guarde também uma amostra do conteúdo serializado para testar cada migração.

Para aprofundar fontes, seletores e valores padrão, continue pelo guia sobre atributos em blocos customizados.

6. Aplicando estilos no editor e no site

Use src/aviso-editorial/style.scss para estilos compartilhados pelo editor e pelo site:

.wp-block-fellyph-aviso-editorial {
  border-inline-start: 4px solid currentColor;
  display: grid;
  gap: 0.5rem;

  p {
    margin: 0;
  }
}

Use src/aviso-editorial/editor.scss apenas quando a interface do editor precisar de um ajuste que não deve aparecer no front-end:

.wp-block-fellyph-aviso-editorial {
  min-height: 4rem;
}Code language: CSS (css)

Não dependa de seletores internos do editor ou de classes geradas por componentes. Prefira a classe estável do bloco e os estilos declarados nos metadados.

Os controles de cor e espaçamento foram ativados em supports. Como o elemento principal usa useBlockProps e useBlockProps.save(), o WordPress consegue aplicar esses valores tanto no editor quanto no site.

7. Criando um bloco que aceita outros blocos

Quando o conteúdo precisa combinar títulos, parágrafos, imagens ou botões, evite recriar todos esses recursos como atributos. Em vez disso, componha o bloco com InnerBlocks.

O exemplo abaixo representa um segundo bloco, chamado container-editorial. Ele começa com um título e um parágrafo, mas permite que o usuário reorganize o conteúdo.

Crie src/container-editorial/block.json com os metadados do novo bloco:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "fellyph/container-editorial",
  "title": "Container editorial",
  "category": "design",
  "icon": "layout",
  "description": "Agrupa conteúdo editorial em uma seção.",
  "supports": {
    "html": false
  },
  "textdomain": "aviso-editorial",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css"
}Code language: JSON / JSON with Comments (json)

Em src/container-editorial/index.js, registre a segunda definição:

import { registerBlockType } from '@wordpress/blocks';

import metadata from './block.json';
import Edit from './edit';
import save from './save';
import './style.scss';

registerBlockType( metadata.name, {
  edit: Edit,
  save,
} );Code language: JavaScript (javascript)

Em edit.js:

import {
  useBlockProps,
  useInnerBlocksProps,
} from '@wordpress/block-editor';

const TEMPLATE = [
  [ 'core/heading', { level: 3, placeholder: 'Título da seção' } ],
  [ 'core/paragraph', { placeholder: 'Escreva o conteúdo…' } ],
];

export default function Edit() {
  const blockProps = useBlockProps();
  const innerBlocksProps = useInnerBlocksProps( blockProps, {
    allowedBlocks: [ 'core/heading', 'core/paragraph', 'core/buttons' ],
    template: TEMPLATE,
  } );

  return <section { ...innerBlocksProps } />;
}Code language: JavaScript (javascript)

Em save.js:

import {
  useBlockProps,
  useInnerBlocksProps,
} from '@wordpress/block-editor';

export default function save() {
  const blockProps = useBlockProps.save();
  const innerBlocksProps = useInnerBlocksProps.save( blockProps );

  return <section { ...innerBlocksProps } />;
}Code language: JavaScript (javascript)

Cada bloco pode ter apenas uma área de InnerBlocks. Use allowedBlocks para limitar as opções ao propósito do componente e template para oferecer um ponto de partida útil. Adicione templateLock somente quando a estrutura realmente não puder ser alterada.

8. Criando um bloco dinâmico com PHP

Um bloco dinâmico gera o HTML no servidor a cada renderização. Essa opção é indicada quando a saída depende de dados que podem mudar sem que o post seja salvo novamente, como uma lista de posts recentes.

Crie outro projeto usando a variante dinâmica:

npx @wordpress/create-block@latest \
  ultimos-posts \
  --namespace="fellyph" \
  --variant="dynamic"Code language: CSS (css)

No block.json, a diferença central é a propriedade render:

{
  "$schema": "https://schemas.wp.org/trunk/block.json",
  "apiVersion": 3,
  "name": "fellyph/ultimos-posts",
  "title": "Últimos posts",
  "category": "widgets",
  "icon": "admin-post",
  "description": "Exibe uma lista atualizada de posts recentes.",
  "attributes": {
    "numberOfPosts": {
      "type": "number",
      "default": 3
    }
  },
  "supports": {
    "html": false
  },
  "textdomain": "ultimos-posts",
  "editorScript": "file:./index.js",
  "editorStyle": "file:./index.css",
  "style": "file:./style-index.css",
  "render": "file:./render.php"
}Code language: JSON / JSON with Comments (json)

Use InspectorControls para permitir a escolha da quantidade de posts e ServerSideRender para mostrar uma prévia simples no editor:

import { InspectorControls, useBlockProps } from '@wordpress/block-editor';
import { PanelBody, RangeControl } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import ServerSideRender from '@wordpress/server-side-render';

export default function Edit( { attributes, setAttributes } ) {
  const { numberOfPosts } = attributes;

  return (
    <div { ...useBlockProps() }>
      <InspectorControls>
        <PanelBody title={ __( 'Configurações', 'ultimos-posts' ) }>
          <RangeControl
            label={ __( 'Quantidade de posts', 'ultimos-posts' ) }
            value={ numberOfPosts }
            onChange={ ( value ) =>
              setAttributes( { numberOfPosts: value } )
            }
            min={ 1 }
            max={ 10 }
          />
        </PanelBody>
      </InspectorControls>

      <ServerSideRender
        block="fellyph/ultimos-posts"
        attributes={ attributes }
      />
    </div>
  );
}Code language: JavaScript (javascript)

Renderizando o bloco no servidor

O arquivo render.php consulta os posts e escapa os valores antes de gerar o HTML:

<?php
/**
 * Renderiza o bloco Últimos posts.
 *
 * @var array $attributes Atributos do bloco.
 */

$number_of_posts = isset( $attributes['numberOfPosts'] )
	? (int) $attributes['numberOfPosts']
	: 3;

$number_of_posts = min( 10, max( 1, $number_of_posts ) );

$latest_posts = get_posts(
	array(
		'numberposts' => $number_of_posts,
		'post_status' => 'publish',
	)
);
?>

<div <?php echo get_block_wrapper_attributes(); ?>>
	<?php if ( empty( $latest_posts ) ) : ?>
		<p><?php esc_html_e( 'Nenhum post encontrado.', 'ultimos-posts' ); ?></p>
	<?php else : ?>
		<ul>
			<?php foreach ( $latest_posts as $latest_post ) : ?>
				<li>
					<a href="<?php echo esc_url( get_permalink( $latest_post ) ); ?>">
						<?php echo esc_html( get_the_title( $latest_post ) ); ?>
					</a>
				</li>
			<?php endforeach; ?>
		</ul>
	<?php endif; ?>
</div>Code language: PHP (php)

Como a saída é dinâmica, o save.js retorna null:

export default function save() {
  return null;
}Code language: JavaScript (javascript)

get_block_wrapper_attributes() é o equivalente no servidor ao wrapper criado por useBlockProps.save(). Ele inclui classes, estilos e outros atributos gerados pelo WordPress.

Para uma prévia simples, ServerSideRender é suficiente. Em blocos mais interativos, prefira buscar os dados com os data stores e componentes do WordPress para evitar recarregar toda a prévia a cada alteração.

Bloco estático ou dinâmico: qual escolher

Use um bloco estático quando:

  • o HTML depende apenas do conteúdo configurado no bloco;
  • o resultado não precisa mudar até que o post seja editado novamente;
  • você quer que o conteúdo continue disponível mesmo se o plugin for desativado.

Use um bloco dinâmico quando:

  • o resultado depende de banco de dados, configurações ou APIs;
  • a informação precisa estar atualizada em cada acesso;
  • o mesmo bloco deve refletir mudanças sem salvar novamente todos os posts.

Uma lista de posts recentes é dinâmica. Uma caixa de aviso escrita pelo autor é estática. Essa decisão é mais importante do que escolher uma variante apenas pela quantidade de código gerado.

O que mudou em relação aos tutoriais antigos

Os primeiros tutoriais de Gutenberg registravam scripts manualmente em PHP, criavam dependências em arrays e chamavam wp.blocks.registerBlockType() dentro de funções JavaScript. Esse código ajuda a entender a evolução do editor, mas não deve ser o ponto de partida para um bloco novo.

No fluxo atual:

  • block.json é a fonte canônica dos metadados;
  • Block API versão 3 substitui exemplos presos à versão 2;
  • useBlockProps conecta o wrapper aos recursos do editor;
  • @wordpress/scripts cuida da compilação, lint e empacotamento;
  • scripts e estilos são declarados nos metadados do bloco;
  • o registro no servidor usa os metadados compilados;
  • render.php oferece uma estrutura direta para blocos dinâmicos.

Isso reduz configuração repetida e mantém o projeto alinhado às APIs oficiais.

Erros comuns ao criar blocos

O bloco não aparece no inseridor

Confirme se o plugin está ativo, se npm start ou npm run build terminou sem erro e se o name de block.json corresponde ao bloco registrado. Verifique também o console do navegador e o log do PHP.

O WordPress informa que o bloco contém conteúdo inesperado

Esse erro acontece quando o HTML salvo não corresponde ao resultado atual de save.js. Confira atributos, seletores e mudanças na marcação. Se uma versão publicada mudou, crie uma depreciação em vez de apagar a compatibilidade com o conteúdo antigo.

Os estilos aparecem no editor, mas não no site

Confirme se os arquivos foram importados e declarados corretamente em block.json. Use style para regras compartilhadas e editorStyle para regras exclusivas do editor.

As cores e o espaçamento não são aplicados

Ativar supports não basta se o wrapper ignorar os atributos gerados. Use useBlockProps() em edit.js, useBlockProps.save() em um bloco estático e get_block_wrapper_attributes() em um bloco dinâmico.

O código funciona durante o desenvolvimento, mas não no plugin instalado

Execute uma compilação de produção e teste o pacote gerado:

npm run build
npm run plugin-zip

Instale o ZIP em uma segunda instalação ou em um ambiente descartável. Esse teste encontra dependências ausentes e arquivos que existiam apenas na máquina de desenvolvimento.

Checklist antes de publicar um bloco

  • apiVersion está definido como 3;
  • namespace e slug são estáveis e exclusivos;
  • textos visíveis usam funções de internacionalização;
  • atributos possuem tipos, fontes e seletores coerentes;
  • save.js gera uma estrutura estável ou existe uma depreciação;
  • blocos dinâmicos validam e escapam a saída em PHP;
  • estilos usam a classe do bloco e evitam seletores internos do editor;
  • npm run build e os comandos de lint terminam sem erro;
  • o bloco foi testado no editor e no front-end;
  • o ZIP foi instalado em um ambiente limpo.

Próximos passos

Depois de dominar a estrutura básica, você pode evoluir o projeto com controles personalizados, componentes do WordPress, data stores, variações, transforms, padrões e a Interactivity API. Também vale preparar as strings do plugin seguindo o guia de internacionalização de blocos Gutenberg.

O caminho mais seguro é manter cada bloco pequeno, começar pela API pública mais simples e adicionar complexidade somente quando o caso de uso exigir.

Agora você tem a base necessária para criar blocos no WordPress, validar a saída no editor e escolher entre renderização estática ou dinâmica. Antes de adicionar mais recursos, gere o ZIP e instale o plugin em um ambiente limpo.

Referências oficiais


ENCODING: UTF-8

|

CHMOD: 644

// RELATED_ENTRIES

NEXT_READS

> cat ./comments.log

LOADING_ENTRIES…


> write ./comments.log –append

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *