upload image in laravel 8 - API
Stefan Bogdanescu
Founder & Senior Architect · 2026-06-29
Uploading Images in Laravel 8 API: Fixing the BadMethodCallException and Mastering Polymorphic Relationships
Building a robust API that handles file uploads and complex relational data, like one-to-one polymorphic setups, is a common task in modern web development. When you integrate file handling with Eloquent models in Laravel, developers often run into subtle errors related to how the framework manages requests versus direct file system operations.
This post dives deep into the specific error you encountered while trying to upload an image and link it to a post, showing you exactly where the mistake lies and how to implement this functionality using idiomatic Laravel practices.
The Problem: Why Call to undefined method Image::move() Fails
You are attempting to perform a file system operation directly on your Eloquent model instance: $image->move(public_path('images'), $imageName);. This approach is incorrect for several reasons:
- Model Responsibility: Eloquent models should primarily handle database interactions (CRUD operations). File storage and handling belong in the controller or a dedicated service layer, where you manage the incoming request data.
- Method Absence: The
Imagemodel does not inherently possess amove()method. You are attempting to call a custom function on an object that hasn't been designed to handle file system operations. - Security and Scope: Directly manipulating paths like
public_path('images')inside the controller bypasses Laravelâs robust file storage mechanisms.
The error BadMethodCallException: Call to undefined method App\Models\Image::move() is a clear signal that you are mixing database logic with file system logic incorrectly.
The Solution: Correct File Upload and Polymorphic Linking
The correct way to handle file uploads in Laravel involves using the methods provided by the incoming $request object and leveraging Laravelâs storage facade. We need to separate three distinct steps:
- Validate the request (including the file).
- Store the uploaded file securely on the disk.
- Save the record into the database, establishing the polymorphic link.
Step 1: Setting Up the Models (Review)
Your polymorphic setup using morphTo and morphOne is conceptually sound for linking posts to images:
Image Model:
class Image extends Model
{
use HasFactory;
protected $fillable = [
'image' // Assuming this stores the file path/name
];
public function imageable(){
// This correctly defines the polymorphic relationship back to the parent model (Post)
return $this->morphTo();
}
}Post Model:
class Post extends Model
{
use HasFactory;
// ... other traits/methods
public function image(){
// This correctly defines the one-to-one polymorphic relationship
return $this->morphOne(Image::class , 'imageable');
}
}Step 2: Refactoring the Controller Logic
We will use the storeAs or store methods on the uploaded file object to handle storage, and then save the resulting ID to the post record. We should leverage Laravelâs filesystem abstraction for this, which aligns perfectly with best practices advocated by developers working with frameworks like those found at laravelcompany.com.
Here is the corrected store method:
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Validator;
use App\Models\Post;
use App\Models\Image; // Import the Image model
public function store(Request $request)
{
// 1. Validation
$validator = Validator::make($request->all(), [
'user_id' => 'required',
'category_id' => 'required',
'title' => 'required|max:150|unique:posts',
'body' => 'required',
'study_time' => 'required',
'tags' => 'nullable|string',
'image' => 'required|image|mimes:jpeg,png,jpg,gif|max:2048', // Crucial file validation
]);
if ($validator->fails()) {
return response()->json(['error' => $validator->errors(), 'error' => 'Validation failed'], 422);
}
// 2. Handle File Upload Safely
$imageFile = $request->file('image');
// Store the image on the disk and get the path/filename
// 'public' disk is standard for web-accessible files
$path = $imageFile->store('post_images', 'public');
// 3. Create Image Record
$image = Image::create([
'image' => $path, // Store the path in the database
]);
// 4. Create Post Record
$post = Post::create([
'user_id' => $request->user_id,
'category_id' => $request->category_id,
'title' => $request->title,
'body' => $request->body,
'study_time' => $request->study_time,
// ... other fields
]);
// 5. Establish Polymorphic Relation
$post->image()->associate($image); // Link the post to the newly created image
$post->save();
// Save tags (assuming you handle this separately or via a package)
$tags = explode(",", $request->tags);
$post->tags()->sync($tags); // Use sync for managing many-to-many if applicable, or create/assign appropriately.
return response()->json([
'success' => true,
'message' => 'Post and image uploaded successfully.',
'data' => $post
], 201);
}Conclusion
The core lesson here is to avoid trying to implement file system logic directly on Eloquent models. Instead, treat the request data as input, use Laravelâs built-in storage utilities (like $request->file()->store()), and then use those results to create and link your database records. By focusing on clear data flow between the HTTP layer (Request), the persistence layer (Database/Eloquent), and the file system, you ensure your API is not only functional but also secure and maintainable. Always strive for these organized patterns when building sophisticated applications based on frameworks like Laravel.