Suas ideias em realidade digital!
>
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.
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.
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.
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.
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."
}
}
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.
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.
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.
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.
upload_max_filesize e
post_max_size no php.ini.
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.
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.
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.
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.
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.
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.'
]);
}
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.
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.
Upload de arquivos é uma área sensível. Alguns cuidados ajudam a reduzir riscos:
php, phtml, exe, sh, bat ou similares;
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.
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.
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.
Se o formulário não usa enctype="multipart/form-data", o arquivo não será enviado corretamente.
Um arquivo pode ser renomeado para parecer uma imagem. Por isso, valide também o MIME type.
Nomes originais podem causar conflito, conter caracteres problemáticos ou revelar dados desnecessários.
Sem limite de tamanho, o servidor pode receber arquivos grandes demais e consumir espaço rapidamente.
Nunca retorne caminhos como /var/www/site/uploads/arquivo.pdf.
Retorne apenas uma URL pública ou identificador do arquivo.
POST?$_FILES?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.
O envio deve ser feito usando multipart/form-data. No PHP, o arquivo será recebido em $_FILES.
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.
Não. A extensão pode ser alterada facilmente. Valide também o MIME type real usando finfo_file.
Depende do sistema. Para imagens simples, 2 MB a 5 MB costuma ser suficiente. Para documentos PDF, você pode ajustar conforme a necessidade.
Não é recomendado. O ideal é gerar um nome único e seguro para evitar conflitos e problemas com caracteres especiais.
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.
O envio deve ser feito usando multipart/form-data. No PHP, o arquivo será recebido em $_FILES.
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.
Não. A extensão pode ser alterada facilmente. Valide também o MIME type real usando finfo_file.
Depende do sistema. Para imagens simples, 2 MB a 5 MB costuma ser suficiente. Para documentos PDF, você pode ajustar conforme a necessidade.
Não é recomendado. O ideal é gerar um nome único e seguro para evitar conflitos e problemas com caracteres especiais.
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.