Standard DNN menu has a range of limitation, such as:
- It is not SEO friendly, as it's a Javascript menu and is generated on the fly
- Doesn't support friendly URLs
- Is not XHTML compatible
So, a lot of time it makes sense to develop custom OWS solution instead.
Let's review an example of sub-navigation that looks similar to:

It's necessary to modify the skin to include 2 OWS skin objects - root navigation and sub navigation, which means you need to create 2 OWS configurations for each level of hierarchy. It will replace the standard menu, allowing you to customize the functionality (for instance, )
HTML query template:
<div class="topmainnav"><ul>
<li{IIF,"[pid]=[tid]"," id='active'",""}><a href="[NewURL]">[FORMAT,"[TABNAME]",{REPLACE:<br>, }]</a></li>
</ul></div>
The following SQl Server functions are required to be present in the database prior to implementing sub navigation.
--------- Function 1 ----------
Create FUNCTION [dbo].[ISNUMBER](@STRINGIN VARCHAR(50), @STRLEN BIGINT)
RETURNS SMALLINT
BEGIN
DECLARE @X INT,@RET SMALLINT,@CHR VARCHAR(1),@CH2 VARCHAR(1)
SET @X=1
SET @RET =1
WHILE (@X <= @STRLEN)
Begin
SET @CHR=''
SET @CH2=UPPER(SUBSTRING(@STRINGIN,@X,1))
IF @CH2<>'1' AND @CH2<>'2' AND @CH2<>'3' AND @CH2<>'4' AND @CH2<>'5' AND @CH2<>'6' AND @CH2<>'7' AND @CH2<>'8' AND @CH2<>'9' AND @CH2<>'0'
Begin
SET @RET = 0
Break
End
SET @X=@X+1
Continue
END
RETURN @RET
END
--------- Function 2 ----------
Create FUNCTION [dbo].[ReWriteURL](@STRINGIN VARCHAR(256),@TABID int)
RETURNS VARCHAR(256)
BEGIN
Declare @url varchar(256), @folder varchar(256), @file varchar(256), @pid int
if (dbo.ISNUMBER(@STRINGIN,len(@STRINGIN))=1 and @STRINGIN<>'')
set @url = '//[ALIAS,System]/tabid/'+@STRINGIN+'/Default.aspx'
else if (Substring(@STRINGIN,0,8)='FileiD=' and @STRINGIN<>'')
Begin
select @Folder = Folder from Files where FileID=SUBSTRING(@STRINGIN, 8, LEN(@STRINGIN))
select @file = fileName from Files where FileID=SUBSTRING(@STRINGIN, 8, LEN(@STRINGIN))
select @pid = PortalID from Files where FileID=SUBSTRING(@STRINGIN, 8, LEN(@STRINGIN))
set @url = '/Portals/'+convert(varchar(10),@pid)+'/'+@Folder+@file
End
else if (dbo.ISNUMBER(@STRINGIN,len(@STRINGIN))=0 and @STRINGIN<>'')
set @url = @STRINGIN
else
set @url = '//[ALIAS,System]/tabid/'+convert(varchar(10),@TABID)+'/Default.aspx'
return @url
END
Here's the query for a page root navigation. Permissions are taken in consideration.
declare @tid int, @pid int,@tpid int, @lvl int,@UserID int,@bIsSuper bit, @PortalID int
set @PortalID = [PortalID,system]
set @tid = [PortalSettings.ActiveTab.TABID,System]
set @userID = [UserID,System]
SELECT @bIsSuper = IsSuperUser FROM Users WHERE UserID = @UserID
select @lvl = level from tabs where tabid=@tid
while (@lvl > 0)
Begin
set @lvl = @lvl -1
select @pid = ParentID from Tabs where tabid = @tid
set @tid = @pid
if (@lvl = 0)
Break
ELSE
Continue
END
select distinct @tid as pid,
x.tabid as tid,
x.tabname,
dbo.ReWriteURL(x.url,x.tabid) as NewURL,
x.taborder,
x.HasChildren,
x.PortalID
FROM vw_tabs x INNER JOIN TabPermission TP ON x.TabID = TP.TabID
INNER JOIN Permission P ON TP.PermissionID = P.PermissionID
INNER JOIN (SELECT RoleID FROM UserRoles WHERE UserID = @UserID UNION SELECT -1 UNION SELECT -3 WHERE @userID = -1 UNION SELECT roleid from roles WHERE @bIsSuper = 1 ) as UR
ON TP.RoleID = UR.RoleID
WHERE x.IsVisible = 1 AND x.IsDeleted = 0 and x.Level=0 and (x.portalid = @PortalID or x.portalid is NULL)
AND P.PermissionCode = 'SYSTEM_TAB' AND P.PermissionKey = 'VIEW'
Order By x.PortalID DESC, x.TabOrder
-- -1 = All Users role
-- 1 = Registered Users role
-- -3 = Unauthenticated Users role
Here's the query for a page sub navigation. Permissions are taken in consideration.
DECLARE @TabId int, @LastTabId int
SET @TabId = [PortalSettings.ActiveTab.TabId,System]
WHILE (@TabId IS NOT NULL)
BEGIN
SET @LastTabId = @TabId
SELECT @TabId = ParentID FROM Tabs WHERE tabid = @TabId
END
declare @UserID int,@bIsSuper bit
set @userID = [UserID,System]
SELECT @bIsSuper = IsSuperUser FROM Users WHERE UserID = @UserID
SELECT ROW_NUMBER() OVER (ORDER BY TabOrder DESC) AS RowNumber, IsNull(@LastTabId, 0) as RootTabId, tabID, tabID as tid, TabOrder, TabName, [Level], dbo.ReWriteURL(url,tabid) as NewURL
FROM Tabs AS t where tabid IN
(
SELECT DISTINCT x.tabid from tabs x INNER JOIN TabPermission as TP ON x.TabID = TP.TabID
INNER JOIN Permission P ON TP.PermissionID = P.PermissionID
INNER JOIN (SELECT RoleID FROM UserRoles WHERE UserID = @UserID UNION SELECT -1 UNION SELECT -3 WHERE @userID = -1 UNION SELECT roleid from roles WHERE @bIsSuper = 1 ) as UR
ON TP.RoleID = UR.RoleID
WHERE x.IsVisible = 1 AND x.IsDeleted = 0 and x.ParentID=IsNull(@LastTabId, 0)
AND P.PermissionCode = 'SYSTEM_TAB' AND P.PermissionKey = 'VIEW'
)
ORDER BY TabOrder
-- -1 = All Users role
-- 1 = Registered Users role
-- -3 = Unauthenticated Users role