The Actual Way Portals Work in Roblox
Most people trying to build portal systems for their games hit the same wall within an hour. They try to make two parts connect by teleporting the player from one to the other and assume it will feel seamless. It doesn't. The player stutters, the camera snaps wrong, and movement feels broken. The real problem isn't teleportation itself. It's accounting for velocity, direction, and camera perspective across two separate coordinate systems. When you move a player through a portal, you're not just changing their position. You're changing their orientation in space relative to a new world coordinate. Get that wrong and the player launches into a wall or falls through the map.
How To Make Portal Roblox
Start by creating two Portal objects in your scene. Each portal needs a PrimaryPart set to a basepart, and you should tag them using CollectionService so you can reference them easily. Here is the basic structure most tutorials skip over because it seems obvious until you actually try it. The core script goes on each Portal part. When a player touches one portal, you find the matching portal, calculate the directional offset between them, teleport the player to the other side, then flip their velocity so they come out facing the right direction. The math here is where everything falls apart if you rush it. Input: Player enters Portal A moving forward at 50 studs per second.
Output: Portal B is rotated 90 degrees clockwise from Portal A. The player must exit facing forward relative to Portal B's orientation, not Portal A's. That means rotating the velocity vector by the same 90 degrees. The standard approach uses CFrame look vectors. You grab the LookVector of Portal A and Portal B, compute the delta, and apply it. It works for basic cases but breaks when portals face opposite directions or when the player enters at an angle. Here is the version that actually handles those cases. You take the player's velocity, rotate it by the angle between the two portals' forward vectors using CFrame multiplication, then add that rotated velocity back. The teleport itself uses SetPrimaryPartCFrame positioned at the exit portal's CFrame, offset slightly forward so the player doesn't clip into the wall on the other side. About 0.5 to 1 stud forward depending on how your portal models are built.
Get the Full Details

I spent three days debugging a portal puzzle game where players would occasionally spawn inside solid geometry after passing through. The issue was that the teleport position was calculated using the world-space center of the portal part, but some of my portal meshes were scaled unevenly on the X and Z axes. The center point drifted from where the actual opening was. The fix was measuring the spawn offset from the part's actual bounding box forward edge instead of its pivot point. That alone resolved maybe 80 percent of the edge cases I was dealing with. Another thing nobody mentions early enough is collision. When you teleport a player instantly, the physics engine doesn't clean up the old state properly. The character's RootPart can end up in a state where collision detection thinks it's still intersecting with geometry. The result is jittering, sliding, or getting stuck in place after exiting a portal. The workaround is to disable the character's collisions for about 0.15 seconds after teleporting, then re-enable them. You do this by setting PhysicsMaterial friction and bounce to zero temporarily or using CollisionGroup changes. Collision groups are cleaner. Put the player in a temporary collision group that ignores all static geometry for those 150 milliseconds, then switch them back. Camera handling is the second biggest source of broken portal feels. Roblox's default camera follows the character automatically, but after a portal teleport the camera can snap to the wrong angle because it was tracking the old orientation. You need to adjust the camera's CFrame to match where the player is now facing. Set workspace.CurrentCamera.CFrame to a position behind and slightly above the character, looking at their new forward direction. Doing this right after the teleport and then letting the camera controller take over again prevents the snap.
Here is a realistic performance concern you should be aware of. If you have more than five or six active portals in a single place, running touch detection scripts on all of them simultaneously starts to add up. Each touch event fires a chain of calculations. The solution is to use a region-based check instead of individual touch events. Use GetPartsInPart or OverlapParams with a small trigger volume around each portal, and only process players who enter that volume. This cuts down on unnecessary event firing and makes the system more predictable under load. Common pitfall: Don't use PromptTeleport for this. That function exists for limited-time events and lobby transfers. It is not designed for gameplay portal mechanics and will cause weird behavior with the character controller. Another pitfall: Using CFrame newPosition without accounting for the character's HumanoidRootPart size. If your character model has a large root part and you teleport it to exactly the portal's CFrame, half the model will be inside the wall. Always offset by the root part's radius or use the bounding sphere distance as your margin.
If you want a complete working example you can drop into a place file, search the Roblox Creator Hub for "portal system" or "portal gun." Several community scripts handle the velocity rotation and collision group toggling I described. The free ones vary in quality. The ones that work well tend to use RunService.RenderStepped for smooth camera interpolation after the teleport rather than snapping immediately. The whole process from blank place to functioning portal pair usually takes between 45 minutes and two hours depending on how polished you want it. Budget extra time for the edge cases around orientation and collision groups. Those are the parts that bite you. I also learned the hard way that portal pairs need to be consistent in scaling. If one portal part is scale 1,1,1 and the other is scale 2,1,1, the teleport math gets skewed because the look vector is still correct but the physical opening size doesn't match. Players will clip through or get stuck at the threshold. Keep both portal parts at identical scales or account for the scale ratio in your position offset calculation.

One more thing. If your game has gravity that changes or zero-g sections, the portal system needs to handle that. The velocity rotation assumes a consistent down vector. When gravity shifts, the player's "forward" relative to gravity might not align with their movement direction anymore. I built a version where portals existed in a space station segment with variable gravity and had to recalculate the exit velocity based on the local gravity normal rather than straight world Y-down. That added maybe twenty lines of code but saved the whole mechanic from breaking in those sections. Overall, building portals in Roblox is straightforward if you treat it as a coordinate transformation problem rather than a simple teleport. The velocity rotation, the collision group cleanup, the camera adjustment, and the scale consistency all matter. Ignore any one of them and the portal will feel wrong. Get all of them right and it just works.