spaces-storage
SKILL.md
Spaces Object Storage Skill
Overview
DigitalOcean Spaces provides S3-compatible object storage. Use it for:
- File uploads (images, documents, media)
- Static asset hosting
- Application backups
- Data lake storage
Setup
Create Space
doctl spaces create my-space --region nyc3
CDN (Built-in)
Enable CDN for faster global access to your assets.
# CDN endpoint
https://my-space.nyc3.cdn.digitaloceanspaces.com/file.jpg
# Direct endpoint
https://my-space.nyc3.digitaloceanspaces.com/file.jpg
SDK Integration
Node.js (@aws-sdk/client-s3)
import {
S3Client,
PutObjectCommand,
GetObjectCommand,
DeleteObjectCommand,
ListObjectsV2Command,
} from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
const s3 = new S3Client({
endpoint: 'https://nyc3.digitaloceanspaces.com',
region: 'nyc3',
credentials: {
accessKeyId: process.env.DIGITALOCEAN_SPACES_ACCESS_KEY!,
secretAccessKey: process.env.DIGITALOCEAN_SPACES_SECRET_KEY!,
},
});
const BUCKET = 'my-space';
// Upload file
async function uploadFile(key: string, body: Buffer, contentType: string) {
await s3.send(
new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: body,
ContentType: contentType,
ACL: 'public-read', // or "private"
})
);
return `https://${BUCKET}.nyc3.cdn.digitaloceanspaces.com/${key}`;
}
// Download file
async function downloadFile(key: string) {
const response = await s3.send(
new GetObjectCommand({
Bucket: BUCKET,
Key: key,
})
);
return response.Body;
}
// Generate presigned URL (for private uploads)
async function getPresignedUploadUrl(key: string, expiresIn = 3600) {
const command = new PutObjectCommand({
Bucket: BUCKET,
Key: key,
});
return getSignedUrl(s3, command, { expiresIn });
}
// Generate presigned download URL (for private files)
async function getPresignedDownloadUrl(key: string, expiresIn = 3600) {
const command = new GetObjectCommand({
Bucket: BUCKET,
Key: key,
});
return getSignedUrl(s3, command, { expiresIn });
}
// List files
async function listFiles(prefix = '') {
const response = await s3.send(
new ListObjectsV2Command({
Bucket: BUCKET,
Prefix: prefix,
})
);
return response.Contents || [];
}
// Delete file
async function deleteFile(key: string) {
await s3.send(
new DeleteObjectCommand({
Bucket: BUCKET,
Key: key,
})
);
}
Python (boto3)
import boto3
from botocore.config import Config
session = boto3.session.Session()
s3 = session.client(
's3',
region_name='nyc3',
endpoint_url='https://nyc3.digitaloceanspaces.com',
aws_access_key_id=os.environ['DIGITALOCEAN_SPACES_ACCESS_KEY'],
aws_secret_access_key=os.environ['DIGITALOCEAN_SPACES_SECRET_KEY']
)
BUCKET = 'my-space'
# Upload file
def upload_file(key: str, file_path: str, content_type: str = None):
extra_args = {'ACL': 'public-read'}
if content_type:
extra_args['ContentType'] = content_type
s3.upload_file(file_path, BUCKET, key, ExtraArgs=extra_args)
return f'https://{BUCKET}.nyc3.cdn.digitaloceanspaces.com/{key}'
# Upload from memory
def upload_bytes(key: str, data: bytes, content_type: str):
s3.put_object(
Bucket=BUCKET,
Key=key,
Body=data,
ContentType=content_type,
ACL='public-read'
)
return f'https://{BUCKET}.nyc3.cdn.digitaloceanspaces.com/{key}'
# Download file
def download_file(key: str, file_path: str):
s3.download_file(BUCKET, key, file_path)
# Generate presigned URL
def get_presigned_url(key: str, expires_in: int = 3600):
return s3.generate_presigned_url(
'get_object',
Params={'Bucket': BUCKET, 'Key': key},
ExpiresIn=expires_in
)
# List files
def list_files(prefix: str = ''):
response = s3.list_objects_v2(Bucket=BUCKET, Prefix=prefix)
return response.get('Contents', [])
# Delete file
def delete_file(key: str):
s3.delete_object(Bucket=BUCKET, Key=key)
Next.js Integration
API Route for File Upload
// app/api/upload/route.ts
import { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { nanoid } from 'nanoid';
const s3 = new S3Client({
endpoint: 'https://nyc3.digitaloceanspaces.com',
region: 'nyc3',
credentials: {
accessKeyId: process.env.DIGITALOCEAN_SPACES_ACCESS_KEY!,
secretAccessKey: process.env.DIGITALOCEAN_SPACES_SECRET_KEY!,
},
});
export async function POST(request: Request) {
const { filename, contentType } = await request.json();
const key = `uploads/${nanoid()}-${filename}`;
const command = new PutObjectCommand({
Bucket: process.env.DIGITALOCEAN_SPACES_BUCKET!,
Key: key,
ContentType: contentType,
ACL: 'public-read',
});
const uploadUrl = await getSignedUrl(s3, command, { expiresIn: 3600 });
const publicUrl = `https://${process.env.DIGITALOCEAN_SPACES_BUCKET}.nyc3.cdn.digitaloceanspaces.com/${key}`;
return Response.json({ uploadUrl, publicUrl, key });
}
Client Upload Component
"use client";
import { useState } from "react";
export function FileUpload({ onUpload }: { onUpload: (url: string) => void }) {
const [uploading, setUploading] = useState(false);
async function handleUpload(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
setUploading(true);
try {
// Get presigned URL
const response = await fetch("/api/upload", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
filename: file.name,
contentType: file.type,
}),
});
const { uploadUrl, publicUrl } = await response.json();
// Upload directly to Spaces
await fetch(uploadUrl, {
method: "PUT",
headers: { "Content-Type": file.type },
body: file,
});
onUpload(publicUrl);
} finally {
setUploading(false);
}
}
return (
<input
type="file"
onChange={handleUpload}
disabled={uploading}
/>
);
}
FastAPI Integration
from fastapi import FastAPI, UploadFile
import boto3
from nanoid import generate
app = FastAPI()
s3 = boto3.client(
's3',
region_name='nyc3',
endpoint_url='https://nyc3.digitaloceanspaces.com',
aws_access_key_id=os.environ['DIGITALOCEAN_SPACES_ACCESS_KEY'],
aws_secret_access_key=os.environ['DIGITALOCEAN_SPACES_SECRET_KEY']
)
BUCKET = os.environ['DIGITALOCEAN_SPACES_BUCKET']
@app.post("/upload")
async def upload_file(file: UploadFile):
key = f"uploads/{generate()}-{file.filename}"
s3.upload_fileobj(
file.file,
BUCKET,
key,
ExtraArgs={
'ACL': 'public-read',
'ContentType': file.content_type
}
)
return {
"url": f"https://{BUCKET}.nyc3.cdn.digitaloceanspaces.com/{key}",
"key": key
}
Environment Variables
# .env
DIGITALOCEAN_SPACES_ACCESS_KEY=your_access_key
DIGITALOCEAN_SPACES_SECRET_KEY=your_secret_key
DIGITALOCEAN_SPACES_BUCKET=my-space
DIGITALOCEAN_SPACES_REGION=nyc3
DIGITALOCEAN_SPACES_ENDPOINT=https://nyc3.digitaloceanspaces.com
CORS Configuration
Configure CORS via the DigitalOcean console or API:
{
"CORSRules": [
{
"AllowedOrigins": ["https://myapp.com"],
"AllowedMethods": ["GET", "PUT", "POST"],
"AllowedHeaders": ["*"],
"MaxAgeSeconds": 3000
}
]
}
Weekly Installs
4
Repository
vanman2024/ai-d…ketplaceGitHub Stars
3
First Seen
Feb 11, 2026
Security Audits
Installed on
opencode4
gemini-cli4
claude-code4
github-copilot4
codex4
amp4