Complete media upload and attachment management.
type Attachment struct {
ID ID
Type string
URL string
RemoteURL string
PreviewURL string
TextURL string
Description string
BlurHash string
Meta AttachmentMeta
}Attachment holds information for a media attachment.
Fields:
ID - Unique attachment identifierType - Media type ("image", "video", "gifv", "audio", "unknown")URL - URL of the media fileRemoteURL - Remote URL if media is from another instancePreviewURL - URL of the preview/thumbnailTextURL - Deprecated text URLDescription - Alt text description for accessibilityBlurHash - BlurHash string for placeholder generationMeta - Metadata about dimensions and focustype AttachmentMeta struct {
Original AttachmentSize
Small AttachmentSize
Focus AttachmentFocus
}AttachmentMeta holds metadata for attachment dimensions and focus point.
Fields:
Original - Original file dimensionsSmall - Thumbnail/preview dimensionsFocus - Focal point for croppingtype AttachmentSize struct {
Width int64
Height int64
Size string
Aspect float64
}AttachmentSize holds dimension information for an attachment.
Fields:
Width - Width in pixelsHeight - Height in pixelsSize - Size string (e.g., "1920x1080")Aspect - Aspect ratio (width/height)type AttachmentFocus struct {
X float64
Y float64
}AttachmentFocus holds focal point coordinates for smart cropping.
Fields:
X - Horizontal focal point (-1.0 to 1.0, 0 is center)Y - Vertical focal point (-1.0 to 1.0, 0 is center)type Media struct {
File io.Reader
Thumbnail io.Reader
Description string
Focus string
}Media is a struct for uploading media with additional metadata.
Fields:
File - Media file readerThumbnail - Optional custom thumbnail readerDescription - Alt text description for accessibilityFocus - Focal point string (e.g., "0.5,-0.3")func (c *Client) UploadMedia(ctx context.Context, file string) (*Attachment, error)Uploads media from a file path.
Parameters:
ctx - Context for cancellation/timeoutfile - Path to file to uploadReturns: Attachment with media ID or error
Example:
attachment, err := client.UploadMedia(ctx, "/path/to/image.jpg")
if err != nil {
log.Fatal(err)
}
fmt.Printf("Uploaded media ID: %s\n", attachment.ID)
// Use in status
toot := &mastodon.Toot{
Status: "Check out this image!",
MediaIDs: []mastodon.ID{attachment.ID},
}
status, err := client.PostStatus(ctx, toot)func (c *Client) UploadMediaFromBytes(ctx context.Context, b []byte) (*Attachment, error)Uploads media from a byte slice.
Parameters:
ctx - Context for cancellation/timeoutb - Byte slice containing media dataReturns: Attachment with media ID or error
Example:
imageData, err := os.ReadFile("image.png")
if err != nil {
log.Fatal(err)
}
attachment, err := client.UploadMediaFromBytes(ctx, imageData)
if err != nil {
log.Fatal(err)
}func (c *Client) UploadMediaFromReader(ctx context.Context, reader io.Reader) (*Attachment, error)Uploads media from an io.Reader.
Parameters:
ctx - Context for cancellation/timeoutreader - Reader providing media dataReturns: Attachment with media ID or error
Example:
file, err := os.Open("photo.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()
attachment, err := client.UploadMediaFromReader(ctx, file)
if err != nil {
log.Fatal(err)
}func (c *Client) UploadMediaFromMedia(ctx context.Context, media *Media) (*Attachment, error)Uploads media from a Media struct with additional metadata.
Parameters:
ctx - Context for cancellation/timeoutmedia - Media struct with file and metadataReturns: Attachment with media ID or error
Example:
file, err := os.Open("image.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()
media := &mastodon.Media{
File: file,
Description: "A beautiful sunset over the ocean",
Focus: "0.5,-0.3", // Focus slightly above center
}
attachment, err := client.UploadMediaFromMedia(ctx, media)
if err != nil {
log.Fatal(err)
}
// Use in status
toot := &mastodon.Toot{
Status: "Sunset photo",
MediaIDs: []mastodon.ID{attachment.ID},
}
status, err := client.PostStatus(ctx, toot)imagePaths := []string{
"/path/to/image1.jpg",
"/path/to/image2.jpg",
"/path/to/image3.jpg",
}
var mediaIDs []mastodon.ID
for _, path := range imagePaths {
attachment, err := client.UploadMedia(ctx, path)
if err != nil {
log.Fatal(err)
}
mediaIDs = append(mediaIDs, attachment.ID)
}
// Post with all images
toot := &mastodon.Toot{
Status: "Photo album!",
MediaIDs: mediaIDs,
}
status, err := client.PostStatus(ctx, toot)file, err := os.Open("chart.png")
if err != nil {
log.Fatal(err)
}
defer file.Close()
media := &mastodon.Media{
File: file,
Description: "Bar chart showing quarterly sales increase of 25%",
}
attachment, err := client.UploadMediaFromMedia(ctx, media)
if err != nil {
log.Fatal(err)
}
toot := &mastodon.Toot{
Status: "Q4 results are in!",
MediaIDs: []mastodon.ID{attachment.ID},
}
status, err := client.PostStatus(ctx, toot)resp, err := http.Get("https://example.com/image.jpg")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
attachment, err := client.UploadMediaFromReader(ctx, resp.Body)
if err != nil {
log.Fatal(err)
}videoFile, err := os.Open("video.mp4")
if err != nil {
log.Fatal(err)
}
defer videoFile.Close()
thumbnailFile, err := os.Open("thumbnail.jpg")
if err != nil {
log.Fatal(err)
}
defer thumbnailFile.Close()
media := &mastodon.Media{
File: videoFile,
Thumbnail: thumbnailFile,
Description: "Tutorial video: Getting started with Go",
}
attachment, err := client.UploadMediaFromMedia(ctx, media)
if err != nil {
log.Fatal(err)
}The focus point determines where the image is cropped in previews:
file, err := os.Open("portrait.jpg")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Focus on subject's face (slightly right, upper third)
media := &mastodon.Media{
File: file,
Description: "Portrait photo",
Focus: "0.2,0.3", // X: 20% right of center, Y: 30% above center
}
attachment, err := client.UploadMediaFromMedia(ctx, media)
if err != nil {
log.Fatal(err)
}Focus coordinates range from -1.0 to 1.0 on both axes:
import (
"bytes"
"image"
"image/png"
)
// Generate an image
img := image.NewRGBA(image.Rect(0, 0, 800, 600))
// ... draw on image ...
// Encode to PNG
var buf bytes.Buffer
err := png.Encode(&buf, img)
if err != nil {
log.Fatal(err)
}
// Upload
attachment, err := client.UploadMediaFromReader(ctx, &buf)
if err != nil {
log.Fatal(err)
}Typical Mastodon instance limits (may vary by instance):
Check instance configuration for specific limits:
instance, err := client.GetInstance(ctx)
if err != nil {
log.Fatal(err)
}
// Check media attachment limits
if config := instance.Configuration; config != nil {
if mediaConfig, ok := config.MediaAttachments["image_size_limit"]; ok {
fmt.Printf("Image size limit: %v\n", mediaConfig)
}
}Alt text is essential for accessibility:
media := &mastodon.Media{
File: file,
Description: "Detailed description of image content",
}For portraits and important subjects, set focus points to ensure proper cropping:
media := &mastodon.Media{
File: file,
Focus: "0.2,0.4", // Focus on subject's face
}Media uploads can fail due to size limits or format issues:
attachment, err := client.UploadMedia(ctx, path)
if err != nil {
if apiErr, ok := err.(*mastodon.APIError); ok {
if apiErr.StatusCode == 413 {
log.Printf("File too large")
} else if apiErr.StatusCode == 422 {
log.Printf("Invalid file format")
}
}
return err
}Consider optimizing media before upload to reduce file size:
// Resize image if too large
if fileInfo.Size() > 8*1024*1024 { // 8MB
// Resize image...
}Attachment IDs can be reused within a short time window (typically 24 hours):
// Upload once
attachment, err := client.UploadMedia(ctx, "image.jpg")
if err != nil {
log.Fatal(err)
}
// Use in multiple toots (if allowed by instance)
toot1 := &mastodon.Toot{
Status: "First post with image",
MediaIDs: []mastodon.ID{attachment.ID},
}
// Note: Not all instances allow reusing media IDsattachment, err := client.UploadMedia(ctx, path)
if err != nil {
if apiErr, ok := err.(*mastodon.APIError); ok {
switch apiErr.StatusCode {
case 413:
log.Fatal("File too large")
case 422:
log.Fatal("Invalid file format or corrupted file")
case 500:
log.Fatal("Server error processing media")
default:
log.Fatalf("Upload failed: %v", apiErr)
}
}
log.Fatal(err)
}See also: