Custom binary PUT
Engine: Binary PUT / POST · Preset: Blank · Custom binary PUT
This engine puts the file's bytes in the request body, with nothing wrapped
around them. No form, no boundary, no fields — just the file and a
Content-Type derived from its extension.
It is the right engine whenever the URL decides where the file goes: WebDAV,
nginx's DAV module, a Caddy file_server, a small Worker or a one-file PHP
receiver.
What Dropline sends
PUT /files/images/2026/08/19/a1f3c9.png HTTP/1.1
Host: files.example.com
Authorization: Basic dXNlcjpwYXNz
Content-Type: image/png
<the file's bytes>
Configuration
Settings → Hosts → + → Blank · Custom binary PUT.
| Field | Value |
|---|---|
| Upload URL | https://files.example.com/{{key}} |
| Method | PUT |
| Custom headers | Anything the server needs |
| Authentication | Whichever kind the server uses |
| JSON path | empty — these servers return no body |
| Final URL template | https://files.example.com/{{key}} |
| Object key template | images/{{date}}/{{uuid}}.{{ext}} |
The shape is always the same: {{key}} appears twice, once in the URL you
write to and once in the URL you read from. If the two hostnames differ — you
write to an origin and read from a CDN — that is exactly where the difference
goes.
Leaving the JSON path empty makes Dropline treat the response body as the
result, which for an empty 201 Created is an empty string; the final URL
template then supplies the real link.
Server recipes
nginx with the DAV module
server {
server_name files.example.com;
root /var/www/files;
location / {
# Reads are public
autoindex off;
}
location /images/ {
dav_methods PUT;
create_full_put_path on;
dav_access user:rw group:r all:r;
client_max_body_size 512m;
auth_basic "upload";
auth_basic_user_file /etc/nginx/.htpasswd;
limit_except GET HEAD { }
}
}
create_full_put_path on is required — without it, a PUT to a path whose
directories do not yet exist fails with 409 Conflict, and a date-based key
template creates a new directory every day.
In Dropline: authentication Basic, username in the name field, password as the secret.
Caddy
files.example.com {
root * /var/www/files
file_server
@upload method PUT
handle @upload {
basicauth {
uploader $2a$14$…hash…
}
rewrite * /upload{path}
reverse_proxy localhost:8080
}
}
Caddy has no built-in PUT receiver, so pair it with a tiny upstream — or use
the webdav plugin.
A Cloudflare Worker writing to R2
Useful when you want a custom auth scheme in front of R2 rather than S3 credentials on the client.
export default {
async fetch(request: Request, env: Env) {
if (request.method !== 'PUT') return new Response('Method not allowed', { status: 405 })
if (request.headers.get('Authorization') !== `Bearer ${env.UPLOAD_TOKEN}`) {
return new Response('Unauthorized', { status: 401 })
}
const key = new URL(request.url).pathname.slice(1)
await env.BUCKET.put(key, request.body, {
httpMetadata: { contentType: request.headers.get('Content-Type') ?? 'application/octet-stream' },
})
return new Response(null, { status: 201 })
},
}
In Dropline: upload URL https://upload.example.com/{{key}}, authentication
Bearer Token, JSON path empty, final URL template
https://cdn.example.com/{{key}}.
Generic WebDAV
Most WebDAV servers — Nextcloud, rclone serve webdav, Synology — accept a
plain PUT:
| Field | Value |
|---|---|
| Upload URL | https://dav.example.com/remote.php/dav/files/user/{{key}} |
| Method | PUT |
| Authentication | Basic |
Note that WebDAV storage is usually not publicly readable, so the final URL template needs to point at whatever share link the server exposes — for Nextcloud, a public share of the folder.
When the server does return a body
Some receivers reply with the URL as plain text:
https://files.example.com/images/2026/08/19/a1f3c9.png
Leave the JSON path empty — Dropline trims the body and uses it — and leave the final URL template empty too, so the server's answer wins.
If it returns JSON, set the JSON path as usual; see Reading the response.
Common problems
| Symptom | Cause |
|---|---|
HTTP 409 |
Parent directory does not exist — create_full_put_path on in nginx |
HTTP 405 |
The server does not allow PUT on that path |
HTTP 401 |
Wrong auth kind; check whether the server wants Basic or a token |
HTTP 411 |
The server requires Content-Length on a chunked body — rare; try POST |
| Upload succeeds, link 404s | The read hostname or path differs from the write one — fix the final URL template |
| The clipboard gets an empty string | JSON path empty and final URL template empty, with an empty response body |