Francis was born in Poynton and came through the academy system at Manchester City, joining them in the under-6 year-group.[3] He captained the club's under-18 side in the final of the 2016–17 FA Youth Cup final against Chelsea, standing in for Joel Latibeaudiere, which City lost 6–2 on aggregate.[4][5][6]
Having graduated from the academy in summer 2018, Francis joined Dutch Eerste Divisie side Almere City on loan for the 2018–19 season.[7] During his time there he played predominantly for their development side, Jong Almere City, in the Tweede Divisie.
Francis was signed by Premier League side Wolverhampton Wanderers in January 2019 for an undisclosed fee. He signed a two-and-a-half-year with the option of a further year.[8] On 18 February 2020, he was loaned out to Swiss club Grasshopper Club Zürich until the end of the season.[9]
Following Notts County's promotion to the Football League at the end of the 2022–23 season, Francis was released by the club.[12] Having impressed the previous season on loan at Gateshead, he returned to the club on a permanent basis in June 2023, signing a two-year contract.[13]
In January 2024 he was hospitalised after fracturing his skull during a match.[14]
On 4 July 2024, Francis signed for League One side Exeter City for an undisclosed fee, signing a two-year deal with the option to extend for a third.[15]