Makrosites

Suas ideias em realidade digital!

Upload de arquivos em PHP API: como enviar imagem, PDF e validar com segurança

Upload de arquivos em PHP API: como enviar imagem, PDF e validar com segurança >

Fazer upload de arquivos em uma API PHP é uma necessidade comum em muitos sistemas. Pode ser o envio de uma imagem de perfil, um comprovante em PDF, uma foto de produto, um documento anexado ou qualquer arquivo enviado por um formulário, aplicativo mobile ou painel administrativo.

Porém, upload de arquivos exige cuidado. Se a API aceitar qualquer arquivo sem validação, o sistema pode ficar vulnerável a arquivos maliciosos, consumo excessivo de espaço, nomes duplicados, extensões perigosas e falhas de segurança.

Neste artigo, você vai aprender como criar um upload de arquivos em PHP API usando multipart/form-data, validando imagem, PDF, tamanho máximo, extensão, MIME type e retornando respostas JSON padronizadas.

Resumo prático: nunca salve um arquivo enviado pela API sem validar tamanho, extensão, MIME type, erro de upload, nome seguro e pasta de destino.

Como funciona o upload de arquivos em PHP?

Quando um arquivo é enviado para o PHP usando um formulário ou requisição multipart/form-data, ele fica disponível na variável global $_FILES.

Diferente de uma API JSON comum, o upload de arquivos não é recebido com php://input e json_decode. Para arquivos, o correto é usar $_FILES e, se necessário, enviar campos extras em $_POST.

Exemplo de campos recebidos:

$_FILES['arquivo']
$_POST['titulo']
$_POST['descricao']

O arquivo temporário enviado pelo usuário fica armazenado em uma pasta temporária do servidor até que você mova esse arquivo para o destino final usando move_uploaded_file.

Exemplo de envio via formulário HTML

Um formulário simples para enviar arquivo para uma API PHP pode ser feito assim:

<form action="/api/upload" method="post" enctype="multipart/form-data">
    <label>Arquivo</label>
    <input type="file" name="arquivo">

    <label>Título</label>
    <input type="text" name="titulo">

    <button type="submit">Enviar</button>
</form>

O ponto mais importante é o atributo enctype="multipart/form-data". Sem ele, o arquivo não será enviado corretamente.

Exemplo de envio usando JavaScript fetch

Em aplicações modernas, é comum enviar o arquivo via JavaScript usando FormData.

<input type="file" id="arquivo">
<input type="text" id="titulo" placeholder="Título">
<button onclick="enviarArquivo()">Enviar</button>

<script>
async function enviarArquivo() {
    const arquivo = document.getElementById('arquivo').files[0];
    const titulo = document.getElementById('titulo').value;

    if (!arquivo) {
        alert('Selecione um arquivo.');
        return;
    }

    const formData = new FormData();
    formData.append('arquivo', arquivo);
    formData.append('titulo', titulo);

    const response = await fetch('/api/upload', {
        method: 'POST',
        body: formData
    });

    const result = await response.json();

    if (!result.success) {
        alert(result.message || 'Erro ao enviar arquivo.');
        return;
    }

    console.log(result.data);
}
</script>

Quando usamos FormData, não é necessário definir manualmente o cabeçalho Content-Type. O navegador monta o multipart/form-data automaticamente.

Estrutura básica da resposta JSON

Mesmo sendo upload de arquivo, a API deve responder em JSON.

{
  "success": true,
  "message": "Arquivo enviado com sucesso.",
  "data": {
    "filename": "arquivo_20260504_103000.webp",
    "url": "/uploads/arquivo_20260504_103000.webp"
  }
}

Em caso de erro:

{
  "success": false,
  "message": "Arquivo inválido.",
  "errors": {
    "arquivo": "O tipo de arquivo não é permitido."
  }
}

Criando uma função para retornar JSON

Para manter o padrão da API, vamos criar uma função simples para resposta JSON.

<?php

function jsonResponse(int $statusCode, array $data): void
{
    http_response_code($statusCode);
    header('Content-Type: application/json; charset=utf-8');

    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

Essa função será usada para sucesso e erro.

Verificando se o arquivo foi enviado

O primeiro passo no backend é verificar se o arquivo existe em $_FILES.

<?php

if (!isset($_FILES['arquivo'])) {
    jsonResponse(400, [
        'success' => false,
        'message' => 'Nenhum arquivo foi enviado.',
        'errors' => [
            'arquivo' => 'Envie um arquivo no campo arquivo.'
        ]
    ]);
}

Se o campo não existir, a API retorna erro 400 Bad Request, pois a requisição está incompleta.

Validando erro de upload

O PHP possui códigos internos para indicar erros durante o envio do arquivo. Antes de validar extensão ou tamanho, é importante verificar se o upload foi concluído corretamente.

<?php

$arquivo = $_FILES['arquivo'];

if ($arquivo['error'] !== UPLOAD_ERR_OK) {
    jsonResponse(400, [
        'success' => false,
        'message' => 'Erro ao enviar o arquivo.',
        'errors' => [
            'arquivo' => 'O upload não foi concluído corretamente.'
        ]
    ]);
}

Isso evita tentar mover ou validar um arquivo que nem chegou corretamente ao servidor.

Validando tamanho máximo do arquivo

Uma das validações mais importantes é limitar o tamanho máximo do arquivo. Isso evita abuso e ajuda a controlar o espaço em disco.

<?php

$tamanhoMaximo = 5 * 1024 * 1024; // 5 MB

if ($arquivo['size'] > $tamanhoMaximo) {
    jsonResponse(422, [
        'success' => false,
        'message' => 'Arquivo muito grande.',
        'errors' => [
            'arquivo' => 'O arquivo deve ter no máximo 5 MB.'
        ]
    ]);
}

Você pode ajustar o limite conforme o tipo de arquivo e a necessidade do sistema.

Atenção: além da validação no código, verifique também as configurações upload_max_filesize e post_max_size no php.ini.

Validando extensão do arquivo

A extensão ajuda a identificar o tipo do arquivo, mas não deve ser a única validação.

<?php

$nomeOriginal = $arquivo['name'];
$extensao = strtolower(pathinfo($nomeOriginal, PATHINFO_EXTENSION));

$extensoesPermitidas = ['jpg', 'jpeg', 'png', 'webp', 'pdf'];

if (!in_array($extensao, $extensoesPermitidas, true)) {
    jsonResponse(422, [
        'success' => false,
        'message' => 'Tipo de arquivo não permitido.',
        'errors' => [
            'arquivo' => 'Envie apenas arquivos JPG, PNG, WEBP ou PDF.'
        ]
    ]);
}

Essa validação bloqueia extensões não permitidas, como arquivos executáveis ou scripts.

Validando MIME type com finfo_file

Apenas validar extensão não é suficiente, porque um arquivo malicioso pode ser renomeado. Por isso, também devemos verificar o MIME type real do arquivo.

<?php

$finfo = finfo_open(FILEINFO_MIME_TYPE);
$mimeType = finfo_file($finfo, $arquivo['tmp_name']);
finfo_close($finfo);

$mimesPermitidos = [
    'image/jpeg',
    'image/png',
    'image/webp',
    'application/pdf'
];

if (!in_array($mimeType, $mimesPermitidos, true)) {
    jsonResponse(422, [
        'success' => false,
        'message' => 'Arquivo inválido.',
        'errors' => [
            'arquivo' => 'O conteúdo do arquivo não corresponde a um tipo permitido.'
        ]
    ]);
}

Essa checagem é mais confiável que verificar apenas o nome do arquivo.

Gerando um nome seguro para o arquivo

Nunca é recomendado salvar o arquivo com o nome original enviado pelo usuário. O nome pode conter espaços, caracteres especiais ou até causar conflito com arquivos existentes.

Uma alternativa simples é gerar um nome único:

<?php

$novoNome = bin2hex(random_bytes(16)) . '.' . $extensao;

Também é possível usar data e hora:

<?php

$novoNome = 'arquivo_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $extensao;

Assim, você evita sobrescrever arquivos e reduz problemas com nomes inválidos.

Criando a pasta de upload se não existir

Antes de mover o arquivo, verifique se a pasta de destino existe.

<?php

$pastaUpload = __DIR__ . '/uploads';

if (!is_dir($pastaUpload)) {
    mkdir($pastaUpload, 0755, true);
}

Em servidores Linux, também verifique permissões e proprietário da pasta para garantir que o PHP consiga gravar os arquivos.

Movendo o arquivo para a pasta final

Para salvar o arquivo enviado, use move_uploaded_file.

<?php

$caminhoFinal = $pastaUpload . '/' . $novoNome;

if (!move_uploaded_file($arquivo['tmp_name'], $caminhoFinal)) {
    jsonResponse(500, [
        'success' => false,
        'message' => 'Erro ao salvar o arquivo no servidor.'
    ]);
}

Se o arquivo for movido com sucesso, você pode retornar o nome e a URL pública.

Exemplo completo de upload em API PHP

Agora veja o exemplo completo com validação de tamanho, extensão, MIME type, nome seguro e resposta JSON.

<?php

header('Content-Type: application/json; charset=utf-8');

function jsonResponse(int $statusCode, array $data): void
{
    http_response_code($statusCode);
    echo json_encode($data, JSON_UNESCAPED_UNICODE);
    exit;
}

try {
    if (!isset($_FILES['arquivo'])) {
        jsonResponse(400, [
            'success' => false,
            'message' => 'Nenhum arquivo foi enviado.',
            'errors' => [
                'arquivo' => 'Envie um arquivo no campo arquivo.'
            ]
        ]);
    }

    $arquivo = $_FILES['arquivo'];

    if ($arquivo['error'] !== UPLOAD_ERR_OK) {
        jsonResponse(400, [
            'success' => false,
            'message' => 'Erro ao enviar o arquivo.',
            'errors' => [
                'arquivo' => 'O upload não foi concluído corretamente.'
            ]
        ]);
    }

    $tamanhoMaximo = 5 * 1024 * 1024; // 5 MB

    if ($arquivo['size'] > $tamanhoMaximo) {
        jsonResponse(422, [
            'success' => false,
            'message' => 'Arquivo muito grande.',
            'errors' => [
                'arquivo' => 'O arquivo deve ter no máximo 5 MB.'
            ]
        ]);
    }

    $nomeOriginal = $arquivo['name'];
    $extensao = strtolower(pathinfo($nomeOriginal, PATHINFO_EXTENSION));

    $extensoesPermitidas = ['jpg', 'jpeg', 'png', 'webp', 'pdf'];

    if (!in_array($extensao, $extensoesPermitidas, true)) {
        jsonResponse(422, [
            'success' => false,
            'message' => 'Tipo de arquivo não permitido.',
            'errors' => [
                'arquivo' => 'Envie apenas arquivos JPG, PNG, WEBP ou PDF.'
            ]
        ]);
    }

    $finfo = finfo_open(FILEINFO_MIME_TYPE);
    $mimeType = finfo_file($finfo, $arquivo['tmp_name']);
    finfo_close($finfo);

    $mimesPermitidos = [
        'image/jpeg',
        'image/png',
        'image/webp',
        'application/pdf'
    ];

    if (!in_array($mimeType, $mimesPermitidos, true)) {
        jsonResponse(422, [
            'success' => false,
            'message' => 'Arquivo inválido.',
            'errors' => [
                'arquivo' => 'O conteúdo do arquivo não corresponde a um tipo permitido.'
            ]
        ]);
    }

    $pastaUpload = __DIR__ . '/uploads';

    if (!is_dir($pastaUpload)) {
        mkdir($pastaUpload, 0755, true);
    }

    $novoNome = 'arquivo_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $extensao;
    $caminhoFinal = $pastaUpload . '/' . $novoNome;

    if (!move_uploaded_file($arquivo['tmp_name'], $caminhoFinal)) {
        jsonResponse(500, [
            'success' => false,
            'message' => 'Erro ao salvar o arquivo no servidor.'
        ]);
    }

    jsonResponse(201, [
        'success' => true,
        'message' => 'Arquivo enviado com sucesso.',
        'data' => [
            'filename' => $novoNome,
            'original_name' => $nomeOriginal,
            'mime_type' => $mimeType,
            'size' => $arquivo['size'],
            'url' => '/uploads/' . $novoNome
        ]
    ]);

} catch (Throwable $e) {
    error_log('Erro no upload: ' . $e->getMessage());

    jsonResponse(500, [
        'success' => false,
        'message' => 'Erro interno no servidor.'
    ]);
}

Salvando informações do arquivo no banco de dados

Em muitos sistemas, além de salvar o arquivo na pasta, também é necessário gravar informações no banco.

Exemplo de tabela:

CREATE TABLE arquivos (
    id INT AUTO_INCREMENT PRIMARY KEY,
    titulo VARCHAR(150) NULL,
    nome_original VARCHAR(255) NOT NULL,
    nome_arquivo VARCHAR(255) NOT NULL,
    mime_type VARCHAR(100) NOT NULL,
    tamanho BIGINT NOT NULL,
    url VARCHAR(255) NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

Depois do upload, você pode inserir os dados:

<?php

$titulo = isset($_POST['titulo']) ? trim($_POST['titulo']) : null;
$url = '/uploads/' . $novoNome;

$stmt = $pdo->prepare("
    INSERT INTO arquivos
    (titulo, nome_original, nome_arquivo, mime_type, tamanho, url)
    VALUES
    (:titulo, :nome_original, :nome_arquivo, :mime_type, :tamanho, :url)
");

$stmt->execute([
    ':titulo' => $titulo,
    ':nome_original' => $nomeOriginal,
    ':nome_arquivo' => $novoNome,
    ':mime_type' => $mimeType,
    ':tamanho' => $arquivo['size'],
    ':url' => $url
]);

Isso facilita listar os arquivos enviados depois, exibir histórico, associar anexos a usuários ou vincular documentos a pedidos.

Upload com autenticação Bearer Token

Se o upload fizer parte de uma área protegida, o ideal é validar o token antes de aceitar o arquivo.

<?php

$headers = getallheaders();
$authorization = $headers['Authorization'] ?? $headers['authorization'] ?? '';

if (empty($authorization) || !str_starts_with($authorization, 'Bearer ')) {
    jsonResponse(401, [
        'success' => false,
        'message' => 'Token de autenticação não informado ou inválido.'
    ]);
}

Assim, você evita que qualquer pessoa envie arquivos para o seu servidor.

Cuidados importantes de segurança

Upload de arquivos é uma área sensível. Alguns cuidados ajudam a reduzir riscos:

Protegendo a pasta de upload no Apache

Se a pasta de upload for pública, você pode bloquear execução de scripts PHP dentro dela usando um arquivo .htaccess.

php_flag engine off

<FilesMatch "\.(php|phtml|php3|php4|php5|php7|php8)$">
    Require all denied
</FilesMatch>

Isso ajuda a impedir que um arquivo PHP enviado por engano seja executado dentro da pasta.

Exemplo de configuração no php.ini

O PHP também possui limites próprios para upload.

file_uploads = On
upload_max_filesize = 5M
post_max_size = 8M
max_file_uploads = 20

Se sua API rejeitar arquivos grandes mesmo com a validação correta no código, verifique esses valores no servidor.

Upload de imagem: devo converter para WebP?

Em sites e sistemas web, pode ser interessante converter imagens para WebP para reduzir o tamanho dos arquivos e melhorar carregamento. Porém, isso depende da necessidade do projeto.

Para uma API simples, primeiro garanta validação, nome seguro e armazenamento correto. Depois, você pode adicionar otimização de imagem, redimensionamento e conversão para WebP.

Erros comuns em upload de arquivos com PHP

1. Esquecer o multipart/form-data

Se o formulário não usa enctype="multipart/form-data", o arquivo não será enviado corretamente.

2. Validar apenas a extensão

Um arquivo pode ser renomeado para parecer uma imagem. Por isso, valide também o MIME type.

3. Salvar com o nome original

Nomes originais podem causar conflito, conter caracteres problemáticos ou revelar dados desnecessários.

4. Não limitar tamanho

Sem limite de tamanho, o servidor pode receber arquivos grandes demais e consumir espaço rapidamente.

5. Expor caminho interno do servidor

Nunca retorne caminhos como /var/www/site/uploads/arquivo.pdf. Retorne apenas uma URL pública ou identificador do arquivo.

Checklist para upload seguro em API PHP

Conclusão

Criar upload de arquivos em uma API PHP puro é simples, mas precisa ser feito com cuidado. O mais importante é não confiar no arquivo enviado pelo usuário sem validação.

Antes de salvar, verifique se o upload foi concluído, limite o tamanho, valide extensão, confira o MIME type, gere um nome seguro, mova o arquivo corretamente e retorne uma resposta JSON padronizada.

Com essa estrutura, sua API fica mais segura para receber imagens, PDFs e anexos em sistemas administrativos, sites, aplicativos mobile e integrações externas.

Perguntas frequentes sobre upload de arquivos em PHP API

Como enviar arquivo para uma API PHP?

O envio deve ser feito usando multipart/form-data. No PHP, o arquivo será recebido em $_FILES.

Posso enviar arquivo junto com JSON?

Em uploads comuns, o mais prático é usar FormData, enviando o arquivo em $_FILES e os campos adicionais em $_POST. Para JSON puro, seria necessário enviar o arquivo em Base64, mas isso geralmente aumenta o tamanho da requisição.

É seguro validar apenas a extensão do arquivo?

Não. A extensão pode ser alterada facilmente. Valide também o MIME type real usando finfo_file.

Qual tamanho máximo devo permitir?

Depende do sistema. Para imagens simples, 2 MB a 5 MB costuma ser suficiente. Para documentos PDF, você pode ajustar conforme a necessidade.

Devo salvar o arquivo com o nome original?

Não é recomendado. O ideal é gerar um nome único e seguro para evitar conflitos e problemas com caracteres especiais.

Onde salvar arquivos enviados pela API?

Você pode salvar em uma pasta de uploads no servidor ou em um serviço externo de armazenamento. Em ambos os casos, valide o arquivo antes de salvar.


Perguntas frequentes sobre upload de arquivos em PHP API

Como enviar arquivo para uma API PHP?

O envio deve ser feito usando multipart/form-data. No PHP, o arquivo será recebido em $_FILES.

Posso enviar arquivo junto com JSON?

Em uploads comuns, o mais prático é usar FormData, enviando o arquivo em $_FILES e os campos adicionais em $_POST. Para JSON puro, seria necessário enviar o arquivo em Base64, mas isso geralmente aumenta o tamanho da requisição.

É seguro validar apenas a extensão do arquivo?

Não. A extensão pode ser alterada facilmente. Valide também o MIME type real usando finfo_file.

Qual tamanho máximo devo permitir?

Depende do sistema. Para imagens simples, 2 MB a 5 MB costuma ser suficiente. Para documentos PDF, você pode ajustar conforme a necessidade.

Devo salvar o arquivo com o nome original?

Não é recomendado. O ideal é gerar um nome único e seguro para evitar conflitos e problemas com caracteres especiais.

Onde salvar arquivos enviados pela API?

Você pode salvar em uma pasta de uploads no servidor ou em um serviço externo de armazenamento. Em ambos os casos, valide o arquivo antes de salvar.