Cookies are more appropriate for whole-site or whole-session data. There's no natural segregation of "this cookie belongs to this instance of this form". You could figure that out, but the additional moving parts cut down on the appeal.
So, when /upload is requested, the backend in response sets a cookie with a random uploadId (+ TTL). At the backend, we tie sessionId and uploadId.
With every step which is called, we verify sessionId and uploadId along with additional state which is stored.
This means even if the form is opened on a different tab, it will work well.
This does not have the benefit of being usable across different tabs or even closing and re-opening the page. Besides, (a minor point) shoving all the state in the cookie makes code simple i.e. don't have use URL params.