Escrito e revisado por Jefferson Oliveira. O código vem do Docsify Docs, o porte do Docsify pro WordPress que eu mantenho.
Todo projeto junta o mesmo tipo de informação: como subir o ambiente, o que o wp-config.php precisa ter, o que aquele endpoint devolve, como é o fluxo de aprovação com o cliente. Esse texto quase sempre existe em algum canto. A pergunta é em qual canto.
Onde a documentação costuma morar
Notion e Confluence ficam longe do código. Ninguém abre o Notion na hora de mudar uma função, então a documentação envelhece e você só percebe meses depois, quando alguém segue um passo que não existe mais.
README no repositório resolve pra quem tem acesso ao Git. O cliente não tem.
Site estático com MkDocs ou Docusaurus funciona bem, só que você passa a cuidar de mais um build, mais um deploy e mais um domínio. Se a documentação for restrita, ainda precisa montar um login só pra ela. E documentação de projeto quase nunca é pública.
Por que dentro do WordPress
O projeto já é um WordPress. Ele já tem usuário, papel, sessão, domínio e um painel que o cliente sabe usar. Deixar a documentação só pra administrador e editor vira uma configuração de dois cliques, não uma tarefa de infraestrutura.
Foi por isso que eu escrevi o Docsify Docs. O crédito da parte pesada é do Docsify (docsify.js.org), projeto open source sob licença MIT: é ele que lê os .md e desenha a documentação no navegador. O que eu fiz foi portar pro WordPress, cuidando da página, do controle de acesso e da proteção dos arquivos. A página sai do botão “Gerar página de documentação”, nas configurações do plugin, ou de qualquer página com o template Docsify Docs.
O Docsify não faz build
Escolhi o Docsify por um motivo bem prático: ele não tem etapa de build. É um script que carrega junto com a página, busca os arquivos .md por HTTP e monta tudo no navegador. Não gera HTML, não tem pasta dist, não tem pipeline.
Pra um plugin de WordPress isso muda tudo. Publicar uma página nova é copiar um arquivo na pasta. Quem escreve a documentação não precisa saber o que é build nem ter acesso a servidor nenhum.
Só que esse mesmo detalhe, buscar os arquivos por HTTP, complica o controle de acesso muito mais do que parece.
Onde eu errei na primeira versão
Restringir a página é a parte fácil. Um hook no template_redirect checa o papel do usuário e manda pro login. Umas vinte linhas.
Isso sozinho não protege nada. Quando o Docsify roda, ele pede /wp-content/uploads/docsify-docs/README.md direto pro servidor, e esse pedido não passa pelo WordPress. O Apache acha o arquivo no disco e entrega antes de rodar qualquer PHP. Quem souber o caminho lê a documentação inteira sem nunca ter logado.
A correção ficou em três partes, e nenhuma funciona sozinha. Na primeira, o WordPress passa a servir os arquivos: uma regra de rewrite cria a rota /docsify-docs-files/… e é ela que o Docsify consulta. Cada arquivo passa pela mesma checagem de papel que a página.
Na segunda, a pasta original para de responder. O plugin escreve um .htaccess ao lado dos arquivos negando acesso direto. Sem isso, a rota nova seria só mais um caminho pro mesmo conteúdo, que continuaria aberto.
# BEGIN Docsify Docs<IfModule mod_authz_core.c> Require all denied</IfModule><IfModule !mod_authz_core.c> Order allow,deny Deny from all</IfModule># END Docsify Docs
Na terceira, o endpoint desconfia do que chega. Caminho que vem de URL é entrada hostil, e ../../wp-config.php é o primeiro palpite de qualquer um.
$base = realpath( Docs::dir() );$path = realpath( $base . '/' . $requested );if ( $path === false || ! is_file( $path ) || ! is_readable( $path ) ) { return null;}// realpath() ja resolveu os ../ e seguiu os symlinks.// O resultado ainda precisa estar dentro da pasta.if ( strpos( $path, $base . DIRECTORY_SEPARATOR ) !== 0 ) { return null;}
O realpath() resolve os ../ e segue os links simbólicos, mas só resolve o caminho. Quem autoriza é a comparação de prefixo logo depois: um symlink apontando pra fora da pasta passa na primeira checagem e morre na segunda.
Depois disso entra uma lista fixa de extensões liberadas: markdown, texto, JSON, YAML, CSV, imagem e PDF. Arquivo .php nunca sai, mesmo que alguém consiga colocar um lá dentro. O SVG, que pode carregar script, sai com uma Content-Security-Policy que desliga a execução.
Dois problemas que só apareceram em produção
O primeiro foi o cache. O “delay JavaScript execution” do WP Rocket reescreve toda tag script pra um type que o navegador não executa e só devolve na primeira interação do usuário. Ajuda muito nas métricas de carregamento e mata o Docsify, que precisa rodar durante o load pra ler a configuração, buscar o markdown e desenhar a página. O visitante recebe uma página em branco.
Concatenar os scripts também quebra, porque bagunça a ordem em que os plugins do Docsify precisam carregar. E cachear é o pior dos três: entrega uma cópia da documentação restrita pro próximo que pedir, sem checagem nenhuma.
A saída foi um punhado de constantes que WP Rocket, W3 Total Cache, LiteSpeed e outros combinaram de respeitar: DONOTCACHEPAGE, DONOTROCKETOPTIMIZE, DONOTMINIFY, DONOTCACHEOBJECT e DONOTASYNCCSS. A rota dos arquivos também declara DONOTCACHEPAGE e responde com cabeçalhos de no-cache. Não é bonito, mas funciona.
O segundo foi o tema. O template imprime o documento inteiro, do <html> até o fechamento, e o CSS do tema e os estilos de bloco do WordPress só têm layout pra quebrar ali. Os estilos saem da fila na prioridade máxima, e quem quiser manter algum tem o filtro docsify_docs_kept_styles. A barra de administração, que aparecia sem estilo por cima da documentação, agora fica escondida só nessa página.
Documentação versionada junto do código
Sem configuração nenhuma, o plugin lê os arquivos da própria pasta src/docs. A constante DOCSIFYDOCS_DOCS_DIR aponta pra qualquer caminho absoluto:
define( 'DOCSIFYDOCS_DOCS_DIR', __DIR__ . '/wp-content/plugins/meu-projeto/docs');
Aí a documentação vive junto do código, versionada e revisada no mesmo pull request que muda o comportamento que ela descreve. Como quem serve os arquivos é o WordPress, a pasta nem precisa estar acessível pela web. Pode ficar fora da raiz do servidor.
Os padrões do plugin seguem a mesma ideia. Cor, papéis liberados e proteção de arquivo podem ser definidos no wp-config.php, o que mantém a configuração do projeto no repositório e não só no banco de um ambiente.
define( 'DOCSIFYDOCS_DEFAULT_THEME_COLOR', '#004f9f' );define( 'DOCSIFYDOCS_DEFAULT_IS_RESTRICTED', true );define( 'DOCSIFYDOCS_DEFAULT_PROTECT_FILES', true );define( 'DOCSIFYDOCS_DEFAULT_ALLOWED_ROLES', [ 'administrator', 'editor' ]);
A API dentro da documentação
Quase todo projeto esbarra numa API em algum momento, e o Swagger UI já vem junto no plugin. Um link chamado swagger em qualquer página markdown vira a referência interativa:
[swagger](api/openapi.json)
O caminho é resolvido a partir da pasta de documentação, então a especificação fica atrás da mesma proteção que o resto. JSON, YAML e YML funcionam, e uma URL absoluta também, quando a API publica o próprio arquivo. A referência usa a cor de tema da documentação e não parece um corpo estranho colado na página.
O “Try it out” manda a requisição a partir do navegador de quem está lendo, então a API precisa liberar a origem da documentação no CORS.
E não tem CDN em lugar nenhum. Docsify, Swagger UI, Mermaid e, desde a 3.6.1, as fontes da documentação saem do próprio site. Isso importa quando a documentação é interna e o navegador do cliente não deveria ficar avisando terceiros sobre o que está sendo lido. De quebra, uma Content Security Policy não bloqueia mais as fontes, que antes vinham do Google Fonts.
Guias passo a passo
Muita documentação de painel é uma sequência de cliques. Desde a 3.4, uma lista numerada precedida de <!– docsify-guide –> vira um guia: um passo por vez, com Voltar e Próximo, as setas do teclado e um Play que avança sozinho.
# Criar um autor> Painel → Usuários → Adicionar novo<!-- docsify-guide -->1. Na barra lateral, clique em **Usuários**. 2. Clique em **Adicionar novo usuário**. 
O marcador é um comentário HTML e os passos continuam sendo uma lista comum. Sem o script, a página ainda é uma lista numerada com imagens, e a busca indexa cada passo. As imagens seguem a mesma regra de acesso do resto.
O Play espera 2 segundos por passo, e a barra de progresso tem uma parte por etapa, que enche durante a espera. Com prefers-reduced-motion, ela pula direto. O print de cada passo abre ampliado com um clique, e “Ver todos os passos” mostra o guia inteiro de uma vez.
Quando não usar
Se a sua documentação é pública e o projeto já tem CI, um site estático é mais simples, mais rápido e não depende do WordPress estar no ar. Não force.
O plugin compensa quando pelo menos duas destas coisas são verdade: a documentação é restrita, quem vai ler já tem conta no WordPress, e você não quer manter mais uma infraestrutura só pra hospedar texto.
Antes de instalar, vale conhecer duas limitações. A rota dos arquivos é uma regra de rewrite, então precisa de links permanentes amigáveis: no modo “Simples”, a proteção avisa que está inativa e os arquivos voltam pras URLs diretas. E no nginx, ou em qualquer servidor que ignore .htaccess, a regra que nega a pasta tem que ir na configuração do servidor.
O plugin é open source, GPL-2.0, e funciona no PHP 8.2 ou superior. A cada pull request roda uma esteira de validação: WordPress Coding Standards, análise estática e checagem de consistência de versão. O código está em github.com/jeffersonrucu/wp-docsify.
Perguntas frequentes
Restringir a página já não protege a documentação?
Funciona com WP Rocket e outros plugins de cache?
Funciona no nginx?
Referências
- [1] Documentação oficial · PHP Manual: realpath (abre em nova aba) php.net/manual/pt_BR/function.realpath.php
- [2] Documentação oficial · Docsify (abre em nova aba) docsify.js.org
- [3] Código-fonte · Docsify Docs no GitHub (abre em nova aba) github.com/jeffersonrucu/wp-docsify
Fontes conferidas em out 2026.